diff options
Diffstat (limited to 'compiler/rustc_macros/src/query.rs')
| -rw-r--r-- | compiler/rustc_macros/src/query.rs | 567 | 
1 files changed, 567 insertions, 0 deletions
| diff --git a/compiler/rustc_macros/src/query.rs b/compiler/rustc_macros/src/query.rs new file mode 100644 index 00000000000..c17d5311e8f --- /dev/null +++ b/compiler/rustc_macros/src/query.rs @@ -0,0 +1,567 @@ +use proc_macro::TokenStream; +use proc_macro2::{Delimiter, TokenTree}; +use quote::quote; +use syn::parse::{Parse, ParseStream, Result}; +use syn::punctuated::Punctuated; +use syn::spanned::Spanned; +use syn::{ + braced, parenthesized, parse_macro_input, Attribute, Block, Error, Expr, Ident, ReturnType, + Token, Type, +}; + +#[allow(non_camel_case_types)] +mod kw { + syn::custom_keyword!(query); +} + +/// Ident or a wildcard `_`. +struct IdentOrWild(Ident); + +impl Parse for IdentOrWild { + fn parse(input: ParseStream<'_>) -> Result<Self> { + Ok(if input.peek(Token![_]) { + let underscore = input.parse::<Token![_]>()?; + IdentOrWild(Ident::new("_", underscore.span())) + } else { + IdentOrWild(input.parse()?) + }) + } +} + +/// A modifier for a query +enum QueryModifier { + /// The description of the query. + Desc(Option<Ident>, Punctuated<Expr, Token![,]>), + + /// Use this type for the in-memory cache. + Storage(Type), + + /// Cache the query to disk if the `Expr` returns true. + Cache(Option<(IdentOrWild, IdentOrWild)>, Block), + + /// Custom code to load the query from disk. + LoadCached(Ident, Ident, Block), + + /// A cycle error for this query aborting the compilation with a fatal error. + FatalCycle, + + /// A cycle error results in a delay_bug call + CycleDelayBug, + + /// Don't hash the result, instead just mark a query red if it runs + NoHash, + + /// Generate a dep node based on the dependencies of the query + Anon, + + /// Always evaluate the query, ignoring its dependencies + EvalAlways, +} + +impl Parse for QueryModifier { + fn parse(input: ParseStream<'_>) -> Result<Self> { + let modifier: Ident = input.parse()?; + if modifier == "desc" { + // Parse a description modifier like: + // `desc { |tcx| "foo {}", tcx.item_path(key) }` + let attr_content; + braced!(attr_content in input); + let tcx = if attr_content.peek(Token![|]) { + attr_content.parse::<Token![|]>()?; + let tcx = attr_content.parse()?; + attr_content.parse::<Token![|]>()?; + Some(tcx) + } else { + None + }; + let desc = attr_content.parse_terminated(Expr::parse)?; + Ok(QueryModifier::Desc(tcx, desc)) + } else if modifier == "cache_on_disk_if" { + // Parse a cache modifier like: + // `cache(tcx, value) { |tcx| key.is_local() }` + let has_args = if let TokenTree::Group(group) = input.fork().parse()? { + group.delimiter() == Delimiter::Parenthesis + } else { + false + }; + let args = if has_args { + let args; + parenthesized!(args in input); + let tcx = args.parse()?; + args.parse::<Token![,]>()?; + let value = args.parse()?; + Some((tcx, value)) + } else { + None + }; + let block = input.parse()?; + Ok(QueryModifier::Cache(args, block)) + } else if modifier == "load_cached" { + // Parse a load_cached modifier like: + // `load_cached(tcx, id) { tcx.queries.on_disk_cache.try_load_query_result(tcx, id) }` + let args; + parenthesized!(args in input); + let tcx = args.parse()?; + args.parse::<Token![,]>()?; + let id = args.parse()?; + let block = input.parse()?; + Ok(QueryModifier::LoadCached(tcx, id, block)) + } else if modifier == "storage" { + let args; + parenthesized!(args in input); + let ty = args.parse()?; + Ok(QueryModifier::Storage(ty)) + } else if modifier == "fatal_cycle" { + Ok(QueryModifier::FatalCycle) + } else if modifier == "cycle_delay_bug" { + Ok(QueryModifier::CycleDelayBug) + } else if modifier == "no_hash" { + Ok(QueryModifier::NoHash) + } else if modifier == "anon" { + Ok(QueryModifier::Anon) + } else if modifier == "eval_always" { + Ok(QueryModifier::EvalAlways) + } else { + Err(Error::new(modifier.span(), "unknown query modifier")) + } + } +} + +/// Ensures only doc comment attributes are used +fn check_attributes(attrs: Vec<Attribute>) -> Result<()> { + for attr in attrs { + if !attr.path.is_ident("doc") { + return Err(Error::new(attr.span(), "attributes not supported on queries")); + } + } + Ok(()) +} + +/// A compiler query. `query ... { ... }` +struct Query { + modifiers: List<QueryModifier>, + name: Ident, + key: IdentOrWild, + arg: Type, + result: ReturnType, +} + +impl Parse for Query { + fn parse(input: ParseStream<'_>) -> Result<Self> { + check_attributes(input.call(Attribute::parse_outer)?)?; + + // Parse the query declaration. Like `query type_of(key: DefId) -> Ty<'tcx>` + input.parse::<kw::query>()?; + let name: Ident = input.parse()?; + let arg_content; + parenthesized!(arg_content in input); + let key = arg_content.parse()?; + arg_content.parse::<Token![:]>()?; + let arg = arg_content.parse()?; + let result = input.parse()?; + + // Parse the query modifiers + let content; + braced!(content in input); + let modifiers = content.parse()?; + + Ok(Query { modifiers, name, key, arg, result }) + } +} + +/// A type used to greedily parse another type until the input is empty. +struct List<T>(Vec<T>); + +impl<T: Parse> Parse for List<T> { + fn parse(input: ParseStream<'_>) -> Result<Self> { + let mut list = Vec::new(); + while !input.is_empty() { + list.push(input.parse()?); + } + Ok(List(list)) + } +} + +/// A named group containing queries. +struct Group { + name: Ident, + queries: List<Query>, +} + +impl Parse for Group { + fn parse(input: ParseStream<'_>) -> Result<Self> { + let name: Ident = input.parse()?; + let content; + braced!(content in input); + Ok(Group { name, queries: content.parse()? }) + } +} + +struct QueryModifiers { + /// The description of the query. + desc: (Option<Ident>, Punctuated<Expr, Token![,]>), + + /// Use this type for the in-memory cache. + storage: Option<Type>, + + /// Cache the query to disk if the `Block` returns true. + cache: Option<(Option<(IdentOrWild, IdentOrWild)>, Block)>, + + /// Custom code to load the query from disk. + load_cached: Option<(Ident, Ident, Block)>, + + /// A cycle error for this query aborting the compilation with a fatal error. + fatal_cycle: bool, + + /// A cycle error results in a delay_bug call + cycle_delay_bug: bool, + + /// Don't hash the result, instead just mark a query red if it runs + no_hash: bool, + + /// Generate a dep node based on the dependencies of the query + anon: bool, + + // Always evaluate the query, ignoring its dependencies + eval_always: bool, +} + +/// Process query modifiers into a struct, erroring on duplicates +fn process_modifiers(query: &mut Query) -> QueryModifiers { + let mut load_cached = None; + let mut storage = None; + let mut cache = None; + let mut desc = None; + let mut fatal_cycle = false; + let mut cycle_delay_bug = false; + let mut no_hash = false; + let mut anon = false; + let mut eval_always = false; + for modifier in query.modifiers.0.drain(..) { + match modifier { + QueryModifier::LoadCached(tcx, id, block) => { + if load_cached.is_some() { + panic!("duplicate modifier `load_cached` for query `{}`", query.name); + } + load_cached = Some((tcx, id, block)); + } + QueryModifier::Storage(ty) => { + if storage.is_some() { + panic!("duplicate modifier `storage` for query `{}`", query.name); + } + storage = Some(ty); + } + QueryModifier::Cache(args, expr) => { + if cache.is_some() { + panic!("duplicate modifier `cache` for query `{}`", query.name); + } + cache = Some((args, expr)); + } + QueryModifier::Desc(tcx, list) => { + if desc.is_some() { + panic!("duplicate modifier `desc` for query `{}`", query.name); + } + desc = Some((tcx, list)); + } + QueryModifier::FatalCycle => { + if fatal_cycle { + panic!("duplicate modifier `fatal_cycle` for query `{}`", query.name); + } + fatal_cycle = true; + } + QueryModifier::CycleDelayBug => { + if cycle_delay_bug { + panic!("duplicate modifier `cycle_delay_bug` for query `{}`", query.name); + } + cycle_delay_bug = true; + } + QueryModifier::NoHash => { + if no_hash { + panic!("duplicate modifier `no_hash` for query `{}`", query.name); + } + no_hash = true; + } + QueryModifier::Anon => { + if anon { + panic!("duplicate modifier `anon` for query `{}`", query.name); + } + anon = true; + } + QueryModifier::EvalAlways => { + if eval_always { + panic!("duplicate modifier `eval_always` for query `{}`", query.name); + } + eval_always = true; + } + } + } + let desc = desc.unwrap_or_else(|| { + panic!("no description provided for query `{}`", query.name); + }); + QueryModifiers { + load_cached, + storage, + cache, + desc, + fatal_cycle, + cycle_delay_bug, + no_hash, + anon, + eval_always, + } +} + +/// Add the impl of QueryDescription for the query to `impls` if one is requested +fn add_query_description_impl( + query: &Query, + modifiers: QueryModifiers, + impls: &mut proc_macro2::TokenStream, +) { + let name = &query.name; + let arg = &query.arg; + let key = &query.key.0; + + // Find out if we should cache the query on disk + let cache = if let Some((args, expr)) = modifiers.cache.as_ref() { + let try_load_from_disk = if let Some((tcx, id, block)) = modifiers.load_cached.as_ref() { + // Use custom code to load the query from disk + quote! { + #[inline] + fn try_load_from_disk( + #tcx: TyCtxt<'tcx>, + #id: SerializedDepNodeIndex + ) -> Option<Self::Value> { + #block + } + } + } else { + // Use the default code to load the query from disk + quote! { + #[inline] + fn try_load_from_disk( + tcx: TyCtxt<'tcx>, + id: SerializedDepNodeIndex + ) -> Option<Self::Value> { + tcx.queries.on_disk_cache.try_load_query_result(tcx, id) + } + } + }; + + let tcx = args + .as_ref() + .map(|t| { + let t = &(t.0).0; + quote! { #t } + }) + .unwrap_or(quote! { _ }); + let value = args + .as_ref() + .map(|t| { + let t = &(t.1).0; + quote! { #t } + }) + .unwrap_or(quote! { _ }); + // expr is a `Block`, meaning that `{ #expr }` gets expanded + // to `{ { stmts... } }`, which triggers the `unused_braces` lint. + quote! { + #[inline] + #[allow(unused_variables, unused_braces)] + fn cache_on_disk( + #tcx: TyCtxt<'tcx>, + #key: &Self::Key, + #value: Option<&Self::Value> + ) -> bool { + #expr + } + + #try_load_from_disk + } + } else { + if modifiers.load_cached.is_some() { + panic!("load_cached modifier on query `{}` without a cache modifier", name); + } + quote! {} + }; + + let (tcx, desc) = modifiers.desc; + let tcx = tcx.as_ref().map(|t| quote! { #t }).unwrap_or(quote! { _ }); + + let desc = quote! { + #[allow(unused_variables)] + fn describe( + #tcx: TyCtxt<'tcx>, + #key: #arg, + ) -> Cow<'static, str> { + format!(#desc).into() + } + }; + + impls.extend(quote! { + impl<'tcx> QueryDescription<TyCtxt<'tcx>> for queries::#name<'tcx> { + #desc + #cache + } + }); +} + +pub fn rustc_queries(input: TokenStream) -> TokenStream { + let groups = parse_macro_input!(input as List<Group>); + + let mut query_stream = quote! {}; + let mut query_description_stream = quote! {}; + let mut dep_node_def_stream = quote! {}; + let mut dep_node_force_stream = quote! {}; + let mut try_load_from_on_disk_cache_stream = quote! {}; + let mut cached_queries = quote! {}; + + for group in groups.0 { + let mut group_stream = quote! {}; + for mut query in group.queries.0 { + let modifiers = process_modifiers(&mut query); + let name = &query.name; + let arg = &query.arg; + let result_full = &query.result; + let result = match query.result { + ReturnType::Default => quote! { -> () }, + _ => quote! { #result_full }, + }; + + if modifiers.cache.is_some() { + cached_queries.extend(quote! { + #name, + }); + + try_load_from_on_disk_cache_stream.extend(quote! { + ::rustc_middle::dep_graph::DepKind::#name => { + if <#arg as DepNodeParams<TyCtxt<'_>>>::can_reconstruct_query_key() { + debug_assert!($tcx.dep_graph + .node_color($dep_node) + .map(|c| c.is_green()) + .unwrap_or(false)); + + let key = <#arg as DepNodeParams<TyCtxt<'_>>>::recover($tcx, $dep_node).unwrap(); + if queries::#name::cache_on_disk($tcx, &key, None) { + let _ = $tcx.#name(key); + } + } + } + }); + } + + let mut attributes = Vec::new(); + + // Pass on the fatal_cycle modifier + if modifiers.fatal_cycle { + attributes.push(quote! { fatal_cycle }); + }; + // Pass on the storage modifier + if let Some(ref ty) = modifiers.storage { + attributes.push(quote! { storage(#ty) }); + }; + // Pass on the cycle_delay_bug modifier + if modifiers.cycle_delay_bug { + attributes.push(quote! { cycle_delay_bug }); + }; + // Pass on the no_hash modifier + if modifiers.no_hash { + attributes.push(quote! { no_hash }); + }; + // Pass on the anon modifier + if modifiers.anon { + attributes.push(quote! { anon }); + }; + // Pass on the eval_always modifier + if modifiers.eval_always { + attributes.push(quote! { eval_always }); + }; + + let attribute_stream = quote! {#(#attributes),*}; + + // Add the query to the group + group_stream.extend(quote! { + [#attribute_stream] fn #name: #name(#arg) #result, + }); + + // Create a dep node for the query + dep_node_def_stream.extend(quote! { + [#attribute_stream] #name(#arg), + }); + + // Add a match arm to force the query given the dep node + dep_node_force_stream.extend(quote! { + ::rustc_middle::dep_graph::DepKind::#name => { + if <#arg as DepNodeParams<TyCtxt<'_>>>::can_reconstruct_query_key() { + if let Some(key) = <#arg as DepNodeParams<TyCtxt<'_>>>::recover($tcx, $dep_node) { + force_query::<crate::ty::query::queries::#name<'_>, _>( + $tcx, + key, + DUMMY_SP, + *$dep_node + ); + return true; + } + } + } + }); + + add_query_description_impl(&query, modifiers, &mut query_description_stream); + } + let name = &group.name; + query_stream.extend(quote! { + #name { #group_stream }, + }); + } + + dep_node_force_stream.extend(quote! { + ::rustc_middle::dep_graph::DepKind::Null => { + bug!("Cannot force dep node: {:?}", $dep_node) + } + }); + + TokenStream::from(quote! { + macro_rules! rustc_query_append { + ([$($macro:tt)*][$($other:tt)*]) => { + $($macro)* { + $($other)* + + #query_stream + + } + } + } + macro_rules! rustc_dep_node_append { + ([$($macro:tt)*][$($other:tt)*]) => { + $($macro)*( + $($other)* + + #dep_node_def_stream + ); + } + } + macro_rules! rustc_dep_node_force { + ([$dep_node:expr, $tcx:expr] $($other:tt)*) => { + match $dep_node.kind { + $($other)* + + #dep_node_force_stream + } + } + } + macro_rules! rustc_cached_queries { + ($($macro:tt)*) => { + $($macro)*(#cached_queries); + } + } + + #query_description_stream + + macro_rules! rustc_dep_node_try_load_from_on_disk_cache { + ($dep_node:expr, $tcx:expr) => { + match $dep_node.kind { + #try_load_from_on_disk_cache_stream + _ => (), + } + } + } + }) +} | 
