]>
Commit | Line | Data |
---|---|---|
532ac7d7 | 1 | use proc_macro::TokenStream; |
dc9dc135 | 2 | use proc_macro2::{TokenTree, Delimiter}; |
532ac7d7 XL |
3 | use syn::{ |
4 | Token, Ident, Type, Attribute, ReturnType, Expr, Block, Error, | |
5 | braced, parenthesized, parse_macro_input, | |
6 | }; | |
7 | use syn::spanned::Spanned; | |
8 | use syn::parse::{Result, Parse, ParseStream}; | |
9 | use syn::punctuated::Punctuated; | |
10 | use syn; | |
11 | use quote::quote; | |
12 | use itertools::Itertools; | |
13 | ||
14 | #[allow(non_camel_case_types)] | |
15 | mod kw { | |
16 | syn::custom_keyword!(query); | |
17 | } | |
18 | ||
19 | /// Ident or a wildcard `_`. | |
20 | struct IdentOrWild(Ident); | |
21 | ||
22 | impl Parse for IdentOrWild { | |
23 | fn parse(input: ParseStream<'_>) -> Result<Self> { | |
24 | Ok(if input.peek(Token![_]) { | |
25 | let underscore = input.parse::<Token![_]>()?; | |
26 | IdentOrWild(Ident::new("_", underscore.span())) | |
27 | } else { | |
28 | IdentOrWild(input.parse()?) | |
29 | }) | |
30 | } | |
31 | } | |
32 | ||
33 | /// A modifier for a query | |
34 | enum QueryModifier { | |
35 | /// The description of the query. | |
36 | Desc(Option<Ident>, Punctuated<Expr, Token![,]>), | |
37 | ||
38 | /// Cache the query to disk if the `Expr` returns true. | |
dc9dc135 | 39 | Cache(Option<(IdentOrWild, IdentOrWild)>, Block), |
532ac7d7 XL |
40 | |
41 | /// Custom code to load the query from disk. | |
42 | LoadCached(Ident, Ident, Block), | |
43 | ||
44 | /// A cycle error for this query aborting the compilation with a fatal error. | |
45 | FatalCycle, | |
46 | ||
47 | /// A cycle error results in a delay_bug call | |
48 | CycleDelayBug, | |
49 | ||
50 | /// Don't hash the result, instead just mark a query red if it runs | |
51 | NoHash, | |
52 | ||
53 | /// Don't force the query | |
54 | NoForce, | |
55 | ||
56 | /// Generate a dep node based on the dependencies of the query | |
57 | Anon, | |
58 | ||
48663c56 | 59 | /// Always evaluate the query, ignoring its depdendencies |
532ac7d7 XL |
60 | EvalAlways, |
61 | } | |
62 | ||
63 | impl Parse for QueryModifier { | |
64 | fn parse(input: ParseStream<'_>) -> Result<Self> { | |
65 | let modifier: Ident = input.parse()?; | |
66 | if modifier == "desc" { | |
67 | // Parse a description modifier like: | |
68 | // `desc { |tcx| "foo {}", tcx.item_path(key) }` | |
69 | let attr_content; | |
70 | braced!(attr_content in input); | |
71 | let tcx = if attr_content.peek(Token![|]) { | |
72 | attr_content.parse::<Token![|]>()?; | |
73 | let tcx = attr_content.parse()?; | |
74 | attr_content.parse::<Token![|]>()?; | |
75 | Some(tcx) | |
76 | } else { | |
77 | None | |
78 | }; | |
79 | let desc = attr_content.parse_terminated(Expr::parse)?; | |
80 | Ok(QueryModifier::Desc(tcx, desc)) | |
dc9dc135 | 81 | } else if modifier == "cache_on_disk_if" { |
532ac7d7 | 82 | // Parse a cache modifier like: |
dc9dc135 XL |
83 | // `cache(tcx, value) { |tcx| key.is_local() }` |
84 | let has_args = if let TokenTree::Group(group) = input.fork().parse()? { | |
85 | group.delimiter() == Delimiter::Parenthesis | |
86 | } else { | |
87 | false | |
88 | }; | |
89 | let args = if has_args { | |
90 | let args; | |
91 | parenthesized!(args in input); | |
92 | let tcx = args.parse()?; | |
93 | args.parse::<Token![,]>()?; | |
94 | let value = args.parse()?; | |
95 | Some((tcx, value)) | |
532ac7d7 XL |
96 | } else { |
97 | None | |
98 | }; | |
dc9dc135 XL |
99 | let block = input.parse()?; |
100 | Ok(QueryModifier::Cache(args, block)) | |
532ac7d7 XL |
101 | } else if modifier == "load_cached" { |
102 | // Parse a load_cached modifier like: | |
103 | // `load_cached(tcx, id) { tcx.queries.on_disk_cache.try_load_query_result(tcx, id) }` | |
104 | let args; | |
105 | parenthesized!(args in input); | |
106 | let tcx = args.parse()?; | |
107 | args.parse::<Token![,]>()?; | |
108 | let id = args.parse()?; | |
109 | let block = input.parse()?; | |
110 | Ok(QueryModifier::LoadCached(tcx, id, block)) | |
111 | } else if modifier == "fatal_cycle" { | |
112 | Ok(QueryModifier::FatalCycle) | |
113 | } else if modifier == "cycle_delay_bug" { | |
114 | Ok(QueryModifier::CycleDelayBug) | |
115 | } else if modifier == "no_hash" { | |
116 | Ok(QueryModifier::NoHash) | |
117 | } else if modifier == "no_force" { | |
118 | Ok(QueryModifier::NoForce) | |
119 | } else if modifier == "anon" { | |
120 | Ok(QueryModifier::Anon) | |
121 | } else if modifier == "eval_always" { | |
122 | Ok(QueryModifier::EvalAlways) | |
123 | } else { | |
124 | Err(Error::new(modifier.span(), "unknown query modifier")) | |
125 | } | |
126 | } | |
127 | } | |
128 | ||
129 | /// Ensures only doc comment attributes are used | |
130 | fn check_attributes(attrs: Vec<Attribute>) -> Result<()> { | |
131 | for attr in attrs { | |
132 | if !attr.path.is_ident("doc") { | |
133 | return Err(Error::new(attr.span(), "attributes not supported on queries")); | |
134 | } | |
135 | } | |
136 | Ok(()) | |
137 | } | |
138 | ||
139 | /// A compiler query. `query ... { ... }` | |
140 | struct Query { | |
141 | modifiers: List<QueryModifier>, | |
142 | name: Ident, | |
143 | key: IdentOrWild, | |
144 | arg: Type, | |
145 | result: ReturnType, | |
146 | } | |
147 | ||
148 | impl Parse for Query { | |
149 | fn parse(input: ParseStream<'_>) -> Result<Self> { | |
150 | check_attributes(input.call(Attribute::parse_outer)?)?; | |
151 | ||
152 | // Parse the query declaration. Like `query type_of(key: DefId) -> Ty<'tcx>` | |
153 | input.parse::<kw::query>()?; | |
154 | let name: Ident = input.parse()?; | |
155 | let arg_content; | |
156 | parenthesized!(arg_content in input); | |
157 | let key = arg_content.parse()?; | |
158 | arg_content.parse::<Token![:]>()?; | |
159 | let arg = arg_content.parse()?; | |
160 | let result = input.parse()?; | |
161 | ||
162 | // Parse the query modifiers | |
163 | let content; | |
164 | braced!(content in input); | |
165 | let modifiers = content.parse()?; | |
166 | ||
167 | Ok(Query { | |
168 | modifiers, | |
169 | name, | |
170 | key, | |
171 | arg, | |
172 | result, | |
173 | }) | |
174 | } | |
175 | } | |
176 | ||
177 | /// A type used to greedily parse another type until the input is empty. | |
178 | struct List<T>(Vec<T>); | |
179 | ||
180 | impl<T: Parse> Parse for List<T> { | |
181 | fn parse(input: ParseStream<'_>) -> Result<Self> { | |
182 | let mut list = Vec::new(); | |
183 | while !input.is_empty() { | |
184 | list.push(input.parse()?); | |
185 | } | |
186 | Ok(List(list)) | |
187 | } | |
188 | } | |
189 | ||
190 | /// A named group containing queries. | |
191 | struct Group { | |
192 | name: Ident, | |
193 | queries: List<Query>, | |
194 | } | |
195 | ||
196 | impl Parse for Group { | |
197 | fn parse(input: ParseStream<'_>) -> Result<Self> { | |
198 | let name: Ident = input.parse()?; | |
199 | let content; | |
200 | braced!(content in input); | |
201 | Ok(Group { | |
202 | name, | |
203 | queries: content.parse()?, | |
204 | }) | |
205 | } | |
206 | } | |
207 | ||
208 | struct QueryModifiers { | |
209 | /// The description of the query. | |
210 | desc: Option<(Option<Ident>, Punctuated<Expr, Token![,]>)>, | |
211 | ||
dc9dc135 XL |
212 | /// Cache the query to disk if the `Block` returns true. |
213 | cache: Option<(Option<(IdentOrWild, IdentOrWild)>, Block)>, | |
532ac7d7 XL |
214 | |
215 | /// Custom code to load the query from disk. | |
216 | load_cached: Option<(Ident, Ident, Block)>, | |
217 | ||
218 | /// A cycle error for this query aborting the compilation with a fatal error. | |
219 | fatal_cycle: bool, | |
220 | ||
221 | /// A cycle error results in a delay_bug call | |
222 | cycle_delay_bug: bool, | |
223 | ||
224 | /// Don't hash the result, instead just mark a query red if it runs | |
225 | no_hash: bool, | |
226 | ||
227 | /// Don't force the query | |
228 | no_force: bool, | |
229 | ||
230 | /// Generate a dep node based on the dependencies of the query | |
231 | anon: bool, | |
232 | ||
233 | // Always evaluate the query, ignoring its depdendencies | |
234 | eval_always: bool, | |
235 | } | |
236 | ||
237 | /// Process query modifiers into a struct, erroring on duplicates | |
238 | fn process_modifiers(query: &mut Query) -> QueryModifiers { | |
239 | let mut load_cached = None; | |
240 | let mut cache = None; | |
241 | let mut desc = None; | |
242 | let mut fatal_cycle = false; | |
243 | let mut cycle_delay_bug = false; | |
244 | let mut no_hash = false; | |
245 | let mut no_force = false; | |
246 | let mut anon = false; | |
247 | let mut eval_always = false; | |
248 | for modifier in query.modifiers.0.drain(..) { | |
249 | match modifier { | |
250 | QueryModifier::LoadCached(tcx, id, block) => { | |
251 | if load_cached.is_some() { | |
252 | panic!("duplicate modifier `load_cached` for query `{}`", query.name); | |
253 | } | |
254 | load_cached = Some((tcx, id, block)); | |
255 | } | |
dc9dc135 | 256 | QueryModifier::Cache(args, expr) => { |
532ac7d7 XL |
257 | if cache.is_some() { |
258 | panic!("duplicate modifier `cache` for query `{}`", query.name); | |
259 | } | |
dc9dc135 | 260 | cache = Some((args, expr)); |
532ac7d7 XL |
261 | } |
262 | QueryModifier::Desc(tcx, list) => { | |
263 | if desc.is_some() { | |
264 | panic!("duplicate modifier `desc` for query `{}`", query.name); | |
265 | } | |
266 | desc = Some((tcx, list)); | |
267 | } | |
268 | QueryModifier::FatalCycle => { | |
269 | if fatal_cycle { | |
270 | panic!("duplicate modifier `fatal_cycle` for query `{}`", query.name); | |
271 | } | |
272 | fatal_cycle = true; | |
273 | } | |
274 | QueryModifier::CycleDelayBug => { | |
275 | if cycle_delay_bug { | |
276 | panic!("duplicate modifier `cycle_delay_bug` for query `{}`", query.name); | |
277 | } | |
278 | cycle_delay_bug = true; | |
279 | } | |
280 | QueryModifier::NoHash => { | |
281 | if no_hash { | |
282 | panic!("duplicate modifier `no_hash` for query `{}`", query.name); | |
283 | } | |
284 | no_hash = true; | |
285 | } | |
286 | QueryModifier::NoForce => { | |
287 | if no_force { | |
288 | panic!("duplicate modifier `no_force` for query `{}`", query.name); | |
289 | } | |
290 | no_force = true; | |
291 | } | |
292 | QueryModifier::Anon => { | |
293 | if anon { | |
294 | panic!("duplicate modifier `anon` for query `{}`", query.name); | |
295 | } | |
296 | anon = true; | |
297 | } | |
298 | QueryModifier::EvalAlways => { | |
299 | if eval_always { | |
300 | panic!("duplicate modifier `eval_always` for query `{}`", query.name); | |
301 | } | |
302 | eval_always = true; | |
303 | } | |
304 | } | |
305 | } | |
306 | QueryModifiers { | |
307 | load_cached, | |
308 | cache, | |
309 | desc, | |
310 | fatal_cycle, | |
311 | cycle_delay_bug, | |
312 | no_hash, | |
313 | no_force, | |
314 | anon, | |
315 | eval_always, | |
316 | } | |
317 | } | |
318 | ||
319 | /// Add the impl of QueryDescription for the query to `impls` if one is requested | |
320 | fn add_query_description_impl( | |
321 | query: &Query, | |
322 | modifiers: QueryModifiers, | |
dc9dc135 | 323 | impls: &mut proc_macro2::TokenStream, |
532ac7d7 XL |
324 | ) { |
325 | let name = &query.name; | |
326 | let arg = &query.arg; | |
327 | let key = &query.key.0; | |
328 | ||
329 | // Find out if we should cache the query on disk | |
dc9dc135 | 330 | let cache = modifiers.cache.as_ref().map(|(args, expr)| { |
532ac7d7 XL |
331 | let try_load_from_disk = if let Some((tcx, id, block)) = modifiers.load_cached.as_ref() { |
332 | // Use custom code to load the query from disk | |
333 | quote! { | |
334 | #[inline] | |
335 | fn try_load_from_disk( | |
dc9dc135 | 336 | #tcx: TyCtxt<'tcx>, |
532ac7d7 XL |
337 | #id: SerializedDepNodeIndex |
338 | ) -> Option<Self::Value> { | |
339 | #block | |
340 | } | |
341 | } | |
342 | } else { | |
343 | // Use the default code to load the query from disk | |
344 | quote! { | |
345 | #[inline] | |
346 | fn try_load_from_disk( | |
dc9dc135 | 347 | tcx: TyCtxt<'tcx>, |
532ac7d7 XL |
348 | id: SerializedDepNodeIndex |
349 | ) -> Option<Self::Value> { | |
350 | tcx.queries.on_disk_cache.try_load_query_result(tcx, id) | |
351 | } | |
352 | } | |
353 | }; | |
354 | ||
dc9dc135 XL |
355 | let tcx = args.as_ref().map(|t| { |
356 | let t = &(t.0).0; | |
357 | quote! { #t } | |
358 | }).unwrap_or(quote! { _ }); | |
359 | let value = args.as_ref().map(|t| { | |
360 | let t = &(t.1).0; | |
361 | quote! { #t } | |
362 | }).unwrap_or(quote! { _ }); | |
532ac7d7 XL |
363 | quote! { |
364 | #[inline] | |
365 | #[allow(unused_variables)] | |
dc9dc135 XL |
366 | fn cache_on_disk( |
367 | #tcx: TyCtxt<'tcx>, | |
368 | #key: Self::Key, | |
369 | #value: Option<&Self::Value> | |
370 | ) -> bool { | |
532ac7d7 XL |
371 | #expr |
372 | } | |
373 | ||
374 | #try_load_from_disk | |
375 | } | |
376 | }); | |
377 | ||
378 | if cache.is_none() && modifiers.load_cached.is_some() { | |
379 | panic!("load_cached modifier on query `{}` without a cache modifier", name); | |
380 | } | |
381 | ||
382 | let desc = modifiers.desc.as_ref().map(|(tcx, desc)| { | |
383 | let tcx = tcx.as_ref().map(|t| quote! { #t }).unwrap_or(quote! { _ }); | |
384 | quote! { | |
385 | #[allow(unused_variables)] | |
386 | fn describe( | |
dc9dc135 | 387 | #tcx: TyCtxt<'_>, |
532ac7d7 XL |
388 | #key: #arg, |
389 | ) -> Cow<'static, str> { | |
390 | format!(#desc).into() | |
391 | } | |
392 | } | |
393 | }); | |
394 | ||
395 | if desc.is_some() || cache.is_some() { | |
396 | let cache = cache.unwrap_or(quote! {}); | |
397 | let desc = desc.unwrap_or(quote! {}); | |
398 | ||
399 | impls.extend(quote! { | |
400 | impl<'tcx> QueryDescription<'tcx> for queries::#name<'tcx> { | |
401 | #desc | |
402 | #cache | |
403 | } | |
404 | }); | |
405 | } | |
406 | } | |
407 | ||
408 | pub fn rustc_queries(input: TokenStream) -> TokenStream { | |
409 | let groups = parse_macro_input!(input as List<Group>); | |
410 | ||
411 | let mut query_stream = quote! {}; | |
412 | let mut query_description_stream = quote! {}; | |
413 | let mut dep_node_def_stream = quote! {}; | |
414 | let mut dep_node_force_stream = quote! {}; | |
dc9dc135 | 415 | let mut try_load_from_on_disk_cache_stream = quote! {}; |
532ac7d7 | 416 | let mut no_force_queries = Vec::new(); |
416331ca | 417 | let mut cached_queries = quote! {}; |
532ac7d7 XL |
418 | |
419 | for group in groups.0 { | |
420 | let mut group_stream = quote! {}; | |
421 | for mut query in group.queries.0 { | |
422 | let modifiers = process_modifiers(&mut query); | |
423 | let name = &query.name; | |
424 | let arg = &query.arg; | |
425 | let result_full = &query.result; | |
426 | let result = match query.result { | |
427 | ReturnType::Default => quote! { -> () }, | |
428 | _ => quote! { #result_full }, | |
429 | }; | |
430 | ||
416331ca XL |
431 | if modifiers.cache.is_some() { |
432 | cached_queries.extend(quote! { | |
433 | #name, | |
434 | }); | |
435 | } | |
436 | ||
dc9dc135 XL |
437 | if modifiers.cache.is_some() && !modifiers.no_force { |
438 | try_load_from_on_disk_cache_stream.extend(quote! { | |
439 | DepKind::#name => { | |
440 | debug_assert!(tcx.dep_graph | |
441 | .node_color(self) | |
442 | .map(|c| c.is_green()) | |
443 | .unwrap_or(false)); | |
444 | ||
e74abb32 XL |
445 | let key = RecoverKey::recover(tcx, self).unwrap(); |
446 | if queries::#name::cache_on_disk(tcx, key, None) { | |
dc9dc135 XL |
447 | let _ = tcx.#name(key); |
448 | } | |
449 | } | |
450 | }); | |
451 | } | |
452 | ||
532ac7d7 XL |
453 | let mut attributes = Vec::new(); |
454 | ||
455 | // Pass on the fatal_cycle modifier | |
456 | if modifiers.fatal_cycle { | |
457 | attributes.push(quote! { fatal_cycle }); | |
458 | }; | |
459 | // Pass on the cycle_delay_bug modifier | |
460 | if modifiers.cycle_delay_bug { | |
461 | attributes.push(quote! { cycle_delay_bug }); | |
462 | }; | |
463 | // Pass on the no_hash modifier | |
464 | if modifiers.no_hash { | |
465 | attributes.push(quote! { no_hash }); | |
466 | }; | |
532ac7d7 XL |
467 | // Pass on the anon modifier |
468 | if modifiers.anon { | |
469 | attributes.push(quote! { anon }); | |
470 | }; | |
471 | // Pass on the eval_always modifier | |
472 | if modifiers.eval_always { | |
473 | attributes.push(quote! { eval_always }); | |
474 | }; | |
475 | ||
476 | let mut attribute_stream = quote! {}; | |
477 | for e in attributes.into_iter().intersperse(quote! {,}) { | |
478 | attribute_stream.extend(e); | |
479 | } | |
dc9dc135 XL |
480 | |
481 | // Add the query to the group | |
482 | group_stream.extend(quote! { | |
483 | [#attribute_stream] fn #name: #name(#arg) #result, | |
484 | }); | |
485 | ||
532ac7d7 XL |
486 | // Create a dep node for the query |
487 | dep_node_def_stream.extend(quote! { | |
488 | [#attribute_stream] #name(#arg), | |
489 | }); | |
490 | ||
491 | if modifiers.no_force { | |
492 | no_force_queries.push(name.clone()); | |
493 | } else { | |
494 | // Add a match arm to force the query given the dep node | |
495 | dep_node_force_stream.extend(quote! { | |
496 | DepKind::#name => { | |
497 | if let Some(key) = RecoverKey::recover($tcx, $dep_node) { | |
e74abb32 XL |
498 | $tcx.force_query::<crate::ty::query::queries::#name<'_>>( |
499 | key, | |
500 | DUMMY_SP, | |
501 | *$dep_node | |
502 | ); | |
532ac7d7 XL |
503 | } else { |
504 | return false; | |
505 | } | |
506 | } | |
507 | }); | |
508 | } | |
509 | ||
dc9dc135 XL |
510 | add_query_description_impl( |
511 | &query, | |
512 | modifiers, | |
513 | &mut query_description_stream, | |
514 | ); | |
532ac7d7 XL |
515 | } |
516 | let name = &group.name; | |
517 | query_stream.extend(quote! { | |
518 | #name { #group_stream }, | |
519 | }); | |
520 | } | |
521 | ||
522 | // Add an arm for the no force queries to panic when trying to force them | |
523 | for query in no_force_queries { | |
524 | dep_node_force_stream.extend(quote! { | |
525 | DepKind::#query | | |
526 | }); | |
527 | } | |
528 | dep_node_force_stream.extend(quote! { | |
529 | DepKind::Null => { | |
530 | bug!("Cannot force dep node: {:?}", $dep_node) | |
531 | } | |
532 | }); | |
533 | ||
534 | TokenStream::from(quote! { | |
535 | macro_rules! rustc_query_append { | |
536 | ([$($macro:tt)*][$($other:tt)*]) => { | |
537 | $($macro)* { | |
538 | $($other)* | |
539 | ||
540 | #query_stream | |
541 | ||
542 | } | |
543 | } | |
544 | } | |
545 | macro_rules! rustc_dep_node_append { | |
546 | ([$($macro:tt)*][$($other:tt)*]) => { | |
547 | $($macro)*( | |
548 | $($other)* | |
549 | ||
550 | #dep_node_def_stream | |
551 | ); | |
552 | } | |
553 | } | |
554 | macro_rules! rustc_dep_node_force { | |
555 | ([$dep_node:expr, $tcx:expr] $($other:tt)*) => { | |
556 | match $dep_node.kind { | |
557 | $($other)* | |
558 | ||
559 | #dep_node_force_stream | |
560 | } | |
561 | } | |
562 | } | |
416331ca XL |
563 | macro_rules! rustc_cached_queries { |
564 | ($($macro:tt)*) => { | |
565 | $($macro)*(#cached_queries); | |
566 | } | |
567 | } | |
568 | ||
532ac7d7 | 569 | #query_description_stream |
dc9dc135 XL |
570 | |
571 | impl DepNode { | |
572 | /// Check whether the query invocation corresponding to the given | |
573 | /// DepNode is eligible for on-disk-caching. If so, this is method | |
574 | /// will execute the query corresponding to the given DepNode. | |
575 | /// Also, as a sanity check, it expects that the corresponding query | |
576 | /// invocation has been marked as green already. | |
577 | pub fn try_load_from_on_disk_cache(&self, tcx: TyCtxt<'_>) { | |
578 | match self.kind { | |
579 | #try_load_from_on_disk_cache_stream | |
580 | _ => (), | |
581 | } | |
582 | } | |
583 | } | |
532ac7d7 XL |
584 | }) |
585 | } |