Allow to set a query's result as a side effect.
This commit is contained in:
parent
ca42dd6716
commit
5471381349
@ -114,6 +114,9 @@ struct QueryModifiers {
|
||||
|
||||
/// Always remap the ParamEnv's constness before hashing.
|
||||
remap_env_constness: Option<Ident>,
|
||||
|
||||
/// Generate a `feed` method to set the query's value from another query.
|
||||
feedable: Option<Ident>,
|
||||
}
|
||||
|
||||
fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> {
|
||||
@ -128,6 +131,7 @@ fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> {
|
||||
let mut depth_limit = None;
|
||||
let mut separate_provide_extern = None;
|
||||
let mut remap_env_constness = None;
|
||||
let mut feedable = None;
|
||||
|
||||
while !input.is_empty() {
|
||||
let modifier: Ident = input.parse()?;
|
||||
@ -187,6 +191,8 @@ macro_rules! try_insert {
|
||||
try_insert!(separate_provide_extern = modifier);
|
||||
} else if modifier == "remap_env_constness" {
|
||||
try_insert!(remap_env_constness = modifier);
|
||||
} else if modifier == "feedable" {
|
||||
try_insert!(feedable = modifier);
|
||||
} else {
|
||||
return Err(Error::new(modifier.span(), "unknown query modifier"));
|
||||
}
|
||||
@ -206,6 +212,7 @@ macro_rules! try_insert {
|
||||
depth_limit,
|
||||
separate_provide_extern,
|
||||
remap_env_constness,
|
||||
feedable,
|
||||
})
|
||||
}
|
||||
|
||||
@ -296,6 +303,7 @@ pub fn rustc_queries(input: TokenStream) -> TokenStream {
|
||||
let mut query_stream = quote! {};
|
||||
let mut query_description_stream = quote! {};
|
||||
let mut query_cached_stream = quote! {};
|
||||
let mut feedable_queries = quote! {};
|
||||
|
||||
for query in queries.0 {
|
||||
let Query { name, arg, modifiers, .. } = &query;
|
||||
@ -350,6 +358,13 @@ macro_rules! passthrough {
|
||||
[#attribute_stream] fn #name(#arg) #result,
|
||||
});
|
||||
|
||||
if modifiers.feedable.is_some() {
|
||||
feedable_queries.extend(quote! {
|
||||
#(#doc_comments)*
|
||||
[#attribute_stream] fn #name(#arg) #result,
|
||||
});
|
||||
}
|
||||
|
||||
add_query_desc_cached_impl(&query, &mut query_description_stream, &mut query_cached_stream);
|
||||
}
|
||||
|
||||
@ -363,7 +378,11 @@ macro_rules! rustc_query_append {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! rustc_feedable_queries {
|
||||
( $macro:ident! ) => {
|
||||
$macro!(#feedable_queries);
|
||||
}
|
||||
}
|
||||
pub mod descs {
|
||||
use super::*;
|
||||
#query_description_stream
|
||||
|
@ -165,6 +165,7 @@
|
||||
}
|
||||
cache_on_disk_if { key.is_local() }
|
||||
separate_provide_extern
|
||||
feedable
|
||||
}
|
||||
|
||||
query collect_trait_impl_trait_tys(key: DefId)
|
||||
|
@ -85,6 +85,11 @@ pub struct TyCtxtEnsure<'tcx> {
|
||||
pub tcx: TyCtxt<'tcx>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct TyCtxtFeed<'tcx> {
|
||||
pub tcx: TyCtxt<'tcx>,
|
||||
}
|
||||
|
||||
impl<'tcx> TyCtxt<'tcx> {
|
||||
/// Returns a transparent wrapper for `TyCtxt`, which ensures queries
|
||||
/// are executed instead of just returning their results.
|
||||
@ -93,6 +98,12 @@ pub fn ensure(self) -> TyCtxtEnsure<'tcx> {
|
||||
TyCtxtEnsure { tcx: self }
|
||||
}
|
||||
|
||||
/// Returns a transparent wrapper for `TyCtxt`, for setting a result into a query.
|
||||
#[inline(always)]
|
||||
pub fn feed(self) -> TyCtxtFeed<'tcx> {
|
||||
TyCtxtFeed { tcx: self }
|
||||
}
|
||||
|
||||
/// Returns a transparent wrapper for `TyCtxt` which uses
|
||||
/// `span` as the location of queries performed through it.
|
||||
#[inline(always)]
|
||||
@ -175,6 +186,18 @@ macro_rules! opt_remap_env_constness {
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! hash_result {
|
||||
([]) => {{
|
||||
Some(dep_graph::hash_result)
|
||||
}};
|
||||
([(no_hash) $($rest:tt)*]) => {{
|
||||
None
|
||||
}};
|
||||
([$other:tt $($modifiers:tt)*]) => {
|
||||
hash_result!([$($modifiers)*])
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! define_callbacks {
|
||||
(
|
||||
$($(#[$attr:meta])*
|
||||
@ -327,6 +350,50 @@ fn $name(
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! define_feedable {
|
||||
($($(#[$attr:meta])* [$($modifiers:tt)*] fn $name:ident($($K:tt)*) -> $V:ty,)*) => {
|
||||
impl<'tcx> TyCtxtFeed<'tcx> {
|
||||
$($(#[$attr])*
|
||||
#[inline(always)]
|
||||
pub fn $name(
|
||||
self,
|
||||
key: query_helper_param_ty!($($K)*),
|
||||
value: $V,
|
||||
) -> query_stored::$name<'tcx> {
|
||||
let key = key.into_query_param();
|
||||
opt_remap_env_constness!([$($modifiers)*][key]);
|
||||
|
||||
let tcx = self.tcx;
|
||||
let cache = &tcx.query_caches.$name;
|
||||
|
||||
let cached = try_get_cached(tcx, cache, &key, copy);
|
||||
|
||||
match cached {
|
||||
Ok(old) => {
|
||||
assert_eq!(
|
||||
value, old,
|
||||
"Trying to feed an already recorded value for query {} key={key:?}",
|
||||
stringify!($name),
|
||||
);
|
||||
return old;
|
||||
}
|
||||
Err(()) => (),
|
||||
}
|
||||
|
||||
let dep_node = dep_graph::DepNode::construct(tcx, dep_graph::DepKind::$name, &key);
|
||||
let dep_node_index = tcx.dep_graph.with_feed_task(
|
||||
dep_node,
|
||||
tcx,
|
||||
key,
|
||||
&value,
|
||||
hash_result!([$($modifiers)*]).unwrap(),
|
||||
);
|
||||
cache.complete(key, value, dep_node_index)
|
||||
})*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Each of these queries corresponds to a function pointer field in the
|
||||
// `Providers` struct for requesting a value of that type, and a method
|
||||
// on `tcx: TyCtxt` (and `tcx.at(span)`) for doing that request in a way
|
||||
@ -340,6 +407,7 @@ fn $name(
|
||||
// as they will raise an fatal error on query cycles instead.
|
||||
|
||||
rustc_query_append! { define_callbacks! }
|
||||
rustc_feedable_queries! { define_feedable! }
|
||||
|
||||
mod sealed {
|
||||
use super::{DefId, LocalDefId, OwnerId};
|
||||
|
@ -489,6 +489,88 @@ pub fn read_index(&self, dep_node_index: DepNodeIndex) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a node when we force-feed a value into the query cache.
|
||||
/// This is used to remove cycles during type-checking const generic parameters.
|
||||
///
|
||||
/// As usual in the query system, we consider the current state of the calling query
|
||||
/// only depends on the list of dependencies up to now. As a consequence, the value
|
||||
/// that this query gives us can only depend on those dependencies too. Therefore,
|
||||
/// it is sound to use the current dependency set for the created node.
|
||||
///
|
||||
/// During replay, the order of the nodes is relevant in the dependency graph.
|
||||
/// So the unchanged replay will mark the caller query before trying to mark this one.
|
||||
/// If there is a change to report, the caller query will be re-executed before this one.
|
||||
///
|
||||
/// FIXME: If the code is changed enough for this node to be marked before requiring the
|
||||
/// caller's node, we suppose that those changes will be enough to mark this node red and
|
||||
/// force a recomputation using the "normal" way.
|
||||
pub fn with_feed_task<Ctxt: DepContext<DepKind = K>, A: Debug, R: Debug>(
|
||||
&self,
|
||||
node: DepNode<K>,
|
||||
cx: Ctxt,
|
||||
key: A,
|
||||
result: &R,
|
||||
hash_result: fn(&mut StableHashingContext<'_>, &R) -> Fingerprint,
|
||||
) -> DepNodeIndex {
|
||||
if let Some(data) = self.data.as_ref() {
|
||||
if let Some(dep_node_index) = self.dep_node_index_of_opt(&node) {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let hashing_timer = cx.profiler().incr_result_hashing();
|
||||
let current_fingerprint =
|
||||
cx.with_stable_hashing_context(|mut hcx| hash_result(&mut hcx, result));
|
||||
hashing_timer.finish_with_query_invocation_id(dep_node_index.into());
|
||||
data.current.record_edge(dep_node_index, node, current_fingerprint);
|
||||
}
|
||||
|
||||
return dep_node_index;
|
||||
}
|
||||
|
||||
let mut edges = SmallVec::new();
|
||||
K::read_deps(|task_deps| match task_deps {
|
||||
TaskDepsRef::Allow(deps) => edges.extend(deps.lock().reads.iter().copied()),
|
||||
TaskDepsRef::Ignore | TaskDepsRef::Forbid => {
|
||||
panic!("Cannot summarize when dependencies are not recorded.")
|
||||
}
|
||||
});
|
||||
|
||||
let hashing_timer = cx.profiler().incr_result_hashing();
|
||||
let current_fingerprint =
|
||||
cx.with_stable_hashing_context(|mut hcx| hash_result(&mut hcx, result));
|
||||
|
||||
let print_status = cfg!(debug_assertions) && cx.sess().opts.unstable_opts.dep_tasks;
|
||||
|
||||
// Intern the new `DepNode` with the dependencies up-to-now.
|
||||
let (dep_node_index, prev_and_color) = data.current.intern_node(
|
||||
cx.profiler(),
|
||||
&data.previous,
|
||||
node,
|
||||
edges,
|
||||
Some(current_fingerprint),
|
||||
print_status,
|
||||
);
|
||||
|
||||
hashing_timer.finish_with_query_invocation_id(dep_node_index.into());
|
||||
|
||||
if let Some((prev_index, color)) = prev_and_color {
|
||||
debug_assert!(
|
||||
data.colors.get(prev_index).is_none(),
|
||||
"DepGraph::with_task() - Duplicate DepNodeColor insertion for {key:?}",
|
||||
);
|
||||
|
||||
data.colors.insert(prev_index, color);
|
||||
}
|
||||
|
||||
dep_node_index
|
||||
} else {
|
||||
// Incremental compilation is turned off. We just execute the task
|
||||
// without tracking. We still provide a dep-node index that uniquely
|
||||
// identifies the task so that we have a cheap way of referring to
|
||||
// the query for self-profiling.
|
||||
self.next_virtual_depnode_index()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn dep_node_index_of(&self, dep_node: &DepNode<K>) -> DepNodeIndex {
|
||||
self.dep_node_index_of_opt(dep_node).unwrap()
|
||||
|
Loading…
Reference in New Issue
Block a user