1
+ use std:: cell:: Cell ;
2
+ use std:: ptr:: NonNull ;
3
+
1
4
use rustc_ast:: tokenstream:: TokenStream ;
5
+ use rustc_data_structures:: svh:: Svh ;
2
6
use rustc_errors:: ErrorGuaranteed ;
7
+ use rustc_middle:: ty:: { self , TyCtxt } ;
3
8
use rustc_parse:: parser:: { ForceCollect , Parser } ;
9
+ use rustc_session:: Session ;
4
10
use rustc_session:: config:: ProcMacroExecutionStrategy ;
5
- use rustc_span:: Span ;
6
11
use rustc_span:: profiling:: SpannedEventArgRecorder ;
12
+ use rustc_span:: { LocalExpnId , Span } ;
7
13
use { rustc_ast as ast, rustc_proc_macro as pm} ;
8
14
9
15
use crate :: base:: { self , * } ;
@@ -30,9 +36,9 @@ impl<T> pm::bridge::server::MessagePipe<T> for MessagePipe<T> {
30
36
}
31
37
}
32
38
33
- fn exec_strategy ( ecx : & ExtCtxt < ' _ > ) -> impl pm:: bridge:: server:: ExecutionStrategy + ' static {
39
+ pub fn exec_strategy ( sess : & Session ) -> impl pm:: bridge:: server:: ExecutionStrategy + ' static {
34
40
pm:: bridge:: server:: MaybeCrossThread :: < MessagePipe < _ > > :: new (
35
- ecx . sess . opts . unstable_opts . proc_macro_execution_strategy
41
+ sess. opts . unstable_opts . proc_macro_execution_strategy
36
42
== ProcMacroExecutionStrategy :: CrossThread ,
37
43
)
38
44
}
@@ -54,7 +60,7 @@ impl base::BangProcMacro for BangProcMacro {
54
60
} ) ;
55
61
56
62
let proc_macro_backtrace = ecx. ecfg . proc_macro_backtrace ;
57
- let strategy = exec_strategy ( ecx) ;
63
+ let strategy = exec_strategy ( ecx. sess ) ;
58
64
let server = proc_macro_server:: Rustc :: new ( ecx) ;
59
65
self . client . run ( & strategy, server, input, proc_macro_backtrace) . map_err ( |e| {
60
66
ecx. dcx ( ) . emit_err ( errors:: ProcMacroPanicked {
@@ -85,7 +91,7 @@ impl base::AttrProcMacro for AttrProcMacro {
85
91
} ) ;
86
92
87
93
let proc_macro_backtrace = ecx. ecfg . proc_macro_backtrace ;
88
- let strategy = exec_strategy ( ecx) ;
94
+ let strategy = exec_strategy ( ecx. sess ) ;
89
95
let server = proc_macro_server:: Rustc :: new ( ecx) ;
90
96
self . client . run ( & strategy, server, annotation, annotated, proc_macro_backtrace) . map_err (
91
97
|e| {
@@ -113,6 +119,13 @@ impl MultiItemModifier for DeriveProcMacro {
113
119
item : Annotatable ,
114
120
_is_derive_const : bool ,
115
121
) -> ExpandResult < Vec < Annotatable > , Annotatable > {
122
+ let _timer = ecx. sess . prof . generic_activity_with_arg_recorder (
123
+ "expand_derive_proc_macro_outer" ,
124
+ |recorder| {
125
+ recorder. record_arg_with_span ( ecx. sess . source_map ( ) , ecx. expansion_descr ( ) , span) ;
126
+ } ,
127
+ ) ;
128
+
116
129
// We need special handling for statement items
117
130
// (e.g. `fn foo() { #[derive(Debug)] struct Bar; }`)
118
131
let is_stmt = matches ! ( item, Annotatable :: Stmt ( ..) ) ;
@@ -123,36 +136,39 @@ impl MultiItemModifier for DeriveProcMacro {
123
136
// altogether. See #73345.
124
137
crate :: base:: ann_pretty_printing_compatibility_hack ( & item, & ecx. sess . psess ) ;
125
138
let input = item. to_tokens ( ) ;
126
- let stream = {
127
- let _timer =
128
- ecx. sess . prof . generic_activity_with_arg_recorder ( "expand_proc_macro" , |recorder| {
129
- recorder. record_arg_with_span (
130
- ecx. sess . source_map ( ) ,
131
- ecx. expansion_descr ( ) ,
132
- span,
133
- ) ;
134
- } ) ;
135
- let proc_macro_backtrace = ecx. ecfg . proc_macro_backtrace ;
136
- let strategy = exec_strategy ( ecx) ;
137
- let server = proc_macro_server:: Rustc :: new ( ecx) ;
138
- match self . client . run ( & strategy, server, input, proc_macro_backtrace) {
139
- Ok ( stream) => stream,
140
- Err ( e) => {
141
- ecx. dcx ( ) . emit_err ( {
142
- errors:: ProcMacroDerivePanicked {
143
- span,
144
- message : e. as_str ( ) . map ( |message| {
145
- errors:: ProcMacroDerivePanickedHelp { message : message. into ( ) }
146
- } ) ,
147
- }
148
- } ) ;
149
- return ExpandResult :: Ready ( vec ! [ ] ) ;
150
- }
139
+ let res = ty:: tls:: with ( |tcx| {
140
+ let input = tcx. arena . alloc ( input) as & TokenStream ;
141
+ let invoc_id = ecx. current_expansion . id ;
142
+ let invoc_expn_data = invoc_id. expn_data ( ) ;
143
+
144
+ assert_eq ! ( invoc_expn_data. call_site, span) ;
145
+
146
+ // FIXME(pr-time): Is this the correct way to check for incremental compilation (as
147
+ // well as for `cache_proc_macros`)?
148
+ if tcx. sess . opts . incremental . is_some ( ) && tcx. sess . opts . unstable_opts . cache_proc_macros
149
+ {
150
+ // FIXME(pr-time): Just using the crate hash to notice when the proc-macro code has
151
+ // changed. How to *correctly* depend on exactly the macro definition?
152
+ // I.e., depending on the crate hash is just a HACK, and ideally the dependency would be
153
+ // more narrow.
154
+ let macro_def_id = invoc_expn_data. macro_def_id . unwrap ( ) ;
155
+ let proc_macro_crate_hash = tcx. crate_hash ( macro_def_id. krate ) ;
156
+
157
+ let key = ( invoc_id, proc_macro_crate_hash, input) ;
158
+
159
+ enter_context ( ( ecx, self . client ) , move || tcx. derive_macro_expansion ( key) . cloned ( ) )
160
+ } else {
161
+ expand_derive_macro ( tcx, invoc_id, input, ecx, self . client ) . cloned ( )
151
162
}
163
+ } ) ;
164
+
165
+ let Ok ( output) = res else {
166
+ // error will already have been emitted
167
+ return ExpandResult :: Ready ( vec ! [ ] ) ;
152
168
} ;
153
169
154
170
let error_count_before = ecx. dcx ( ) . err_count ( ) ;
155
- let mut parser = Parser :: new ( & ecx. sess . psess , stream , Some ( "proc-macro derive" ) ) ;
171
+ let mut parser = Parser :: new ( & ecx. sess . psess , output , Some ( "proc-macro derive" ) ) ;
156
172
let mut items = vec ! [ ] ;
157
173
158
174
loop {
@@ -180,3 +196,102 @@ impl MultiItemModifier for DeriveProcMacro {
180
196
ExpandResult :: Ready ( items)
181
197
}
182
198
}
199
+
200
+ pub ( super ) fn provide_derive_macro_expansion < ' tcx > (
201
+ tcx : TyCtxt < ' tcx > ,
202
+ key : ( LocalExpnId , Svh , & ' tcx TokenStream ) ,
203
+ ) -> Result < & ' tcx TokenStream , ( ) > {
204
+ let ( invoc_id, _macro_crate_hash, input) = key;
205
+
206
+ with_context ( |( ecx, client) | expand_derive_macro ( tcx, invoc_id, input, ecx, * client) )
207
+ }
208
+
209
+ type CLIENT = pm:: bridge:: client:: Client < pm:: TokenStream , pm:: TokenStream > ;
210
+
211
+ fn expand_derive_macro < ' tcx > (
212
+ tcx : TyCtxt < ' tcx > ,
213
+ invoc_id : LocalExpnId ,
214
+ input : & ' tcx TokenStream ,
215
+ ecx : & mut ExtCtxt < ' _ > ,
216
+ client : CLIENT ,
217
+ ) -> Result < & ' tcx TokenStream , ( ) > {
218
+ let invoc_expn_data = invoc_id. expn_data ( ) ;
219
+ let span = invoc_expn_data. call_site ;
220
+ let event_arg = invoc_expn_data. kind . descr ( ) ;
221
+ let _timer = tcx. sess . prof . generic_activity_with_arg_recorder (
222
+ "expand_derive_proc_macro_inner" ,
223
+ |recorder| {
224
+ recorder. record_arg_with_span ( tcx. sess . source_map ( ) , event_arg. clone ( ) , span) ;
225
+ } ,
226
+ ) ;
227
+
228
+ let proc_macro_backtrace = ecx. ecfg . proc_macro_backtrace ;
229
+ let strategy = crate :: proc_macro:: exec_strategy ( tcx. sess ) ;
230
+ let server = crate :: proc_macro_server:: Rustc :: new ( ecx) ;
231
+
232
+ match client. run ( & strategy, server, input. clone ( ) , proc_macro_backtrace) {
233
+ Ok ( stream) => Ok ( tcx. arena . alloc ( stream) as & TokenStream ) ,
234
+ Err ( e) => {
235
+ tcx. dcx ( ) . emit_err ( {
236
+ errors:: ProcMacroDerivePanicked {
237
+ span,
238
+ message : e. as_str ( ) . map ( |message| errors:: ProcMacroDerivePanickedHelp {
239
+ message : message. into ( ) ,
240
+ } ) ,
241
+ }
242
+ } ) ;
243
+ Err ( ( ) )
244
+ }
245
+ }
246
+ }
247
+
248
+ // based on rust/compiler/rustc_middle/src/ty/context/tls.rs
249
+ thread_local ! {
250
+ /// A thread local variable that stores a pointer to the current `CONTEXT`.
251
+ static TLV : Cell <( * mut ( ) , Option <CLIENT >) > = const { Cell :: new( ( std:: ptr:: null_mut( ) , None ) ) } ;
252
+ }
253
+
254
+ /// Sets `context` as the new current `CONTEXT` for the duration of the function `f`.
255
+ #[ inline]
256
+ pub ( crate ) fn enter_context < ' a , F , R > ( context : ( & mut ExtCtxt < ' a > , CLIENT ) , f : F ) -> R
257
+ where
258
+ F : FnOnce ( ) -> R ,
259
+ {
260
+ let ( ectx, client) = context;
261
+ let erased = ( ectx as * mut _ as * mut ( ) , Some ( client) ) ;
262
+ TLV . with ( |tlv| {
263
+ let old = tlv. replace ( erased) ;
264
+ let _reset = rustc_data_structures:: defer ( move || tlv. set ( old) ) ;
265
+ f ( )
266
+ } )
267
+ }
268
+
269
+ /// Allows access to the current `CONTEXT`.
270
+ /// Panics if there is no `CONTEXT` available.
271
+ #[ inline]
272
+ #[ track_caller]
273
+ fn with_context < F , R > ( f : F ) -> R
274
+ where
275
+ F : for <' a , ' b > FnOnce ( & ' b mut ( & mut ExtCtxt < ' a > , CLIENT ) ) -> R ,
276
+ {
277
+ let ( ectx, client_opt) = TLV . get ( ) ;
278
+ let ectx = NonNull :: new ( ectx) . expect ( "no CONTEXT stored in tls" ) ;
279
+
280
+ // We could get an `CONTEXT` pointer from another thread.
281
+ // Ensure that `CONTEXT` is `DynSync`.
282
+ // FIXME(pr-time): we should not be able to?
283
+ // sync::assert_dyn_sync::<CONTEXT<'_>>();
284
+
285
+ // prevent double entering, as that would allow creating two `&mut ExtCtxt`s
286
+ // FIXME(pr-time): probably use a RefCell instead (which checks this properly)?
287
+ TLV . with ( |tlv| {
288
+ let old = tlv. replace ( ( std:: ptr:: null_mut ( ) , None ) ) ;
289
+ let _reset = rustc_data_structures:: defer ( move || tlv. set ( old) ) ;
290
+ let ectx = {
291
+ let mut casted = ectx. cast :: < ExtCtxt < ' _ > > ( ) ;
292
+ unsafe { casted. as_mut ( ) }
293
+ } ;
294
+
295
+ f ( & mut ( ectx, client_opt. unwrap ( ) ) )
296
+ } )
297
+ }
0 commit comments