blob: f4649b2403a6b9eeae709c8d40b32c1109e0de59 [file] [log] [blame]
//! The implementation of the query system itself. This defines the macros that
//! generate the actual methods on tcx which find and execute the provider,
//! manage the caches, and so forth.
use std::num::NonZero;
use rustc_data_structures::sync::{DynSend, DynSync};
use rustc_data_structures::unord::UnordMap;
use rustc_hir::def_id::DefId;
use rustc_hir::limit::Limit;
use rustc_index::Idx;
use rustc_middle::bug;
#[expect(unused_imports, reason = "used by doc comments")]
use rustc_middle::dep_graph::DepKindVTable;
use rustc_middle::dep_graph::{DepKind, DepNode, DepNodeIndex, DepNodeKey, SerializedDepNodeIndex};
use rustc_middle::query::erase::{Erasable, Erased};
use rustc_middle::query::on_disk_cache::{
AbsoluteBytePos, CacheDecoder, CacheEncoder, EncodedDepNodeIndex,
};
use rustc_middle::query::plumbing::QueryVTable;
use rustc_middle::query::{
QueryCache, QueryJobId, QueryKey, QueryMode, QueryStackDeferred, QueryStackFrame,
QueryStackFrameExtra, erase,
};
use rustc_middle::ty::codec::TyEncoder;
use rustc_middle::ty::print::with_reduced_queries;
use rustc_middle::ty::tls::{self, ImplicitCtxt};
use rustc_middle::ty::{self, TyCtxt};
use rustc_serialize::{Decodable, Encodable};
use rustc_span::DUMMY_SP;
use rustc_span::def_id::LOCAL_CRATE;
use crate::error::{QueryOverflow, QueryOverflowNote};
use crate::execution::{all_inactive, force_query};
use crate::job::find_dep_kind_root;
use crate::{GetQueryVTable, collect_active_jobs_from_all_queries};
fn depth_limit_error<'tcx>(tcx: TyCtxt<'tcx>, job: QueryJobId) {
let job_map =
collect_active_jobs_from_all_queries(tcx, true).expect("failed to collect active queries");
let (info, depth) = find_dep_kind_root(job, job_map);
let suggested_limit = match tcx.recursion_limit() {
Limit(0) => Limit(2),
limit => limit * 2,
};
tcx.sess.dcx().emit_fatal(QueryOverflow {
span: info.job.span,
note: QueryOverflowNote { desc: info.frame.info.extract().description, depth },
suggested_limit,
crate_name: tcx.crate_name(LOCAL_CRATE),
});
}
#[inline]
pub(crate) fn next_job_id<'tcx>(tcx: TyCtxt<'tcx>) -> QueryJobId {
QueryJobId(
NonZero::new(tcx.query_system.jobs.fetch_add(1, std::sync::atomic::Ordering::Relaxed))
.unwrap(),
)
}
#[inline]
pub(crate) fn current_query_job() -> Option<QueryJobId> {
tls::with_context(|icx| icx.query)
}
/// Executes a job by changing the `ImplicitCtxt` to point to the new query job while it executes.
#[inline(always)]
pub(crate) fn start_query<R>(
job_id: QueryJobId,
depth_limit: bool,
compute: impl FnOnce() -> R,
) -> R {
tls::with_context(move |icx| {
if depth_limit && !icx.tcx.recursion_limit().value_within_limit(icx.query_depth) {
depth_limit_error(icx.tcx, job_id);
}
// Update the `ImplicitCtxt` to point to our new query job.
let icx = ImplicitCtxt {
query: Some(job_id),
query_depth: icx.query_depth + if depth_limit { 1 } else { 0 },
..*icx
};
// Use the `ImplicitCtxt` while we execute the query.
tls::enter_context(&icx, compute)
})
}
/// The deferred part of a deferred query stack frame.
fn mk_query_stack_frame_extra<'tcx, Cache>(
(tcx, vtable, key): (TyCtxt<'tcx>, &'tcx QueryVTable<'tcx, Cache>, Cache::Key),
) -> QueryStackFrameExtra
where
Cache: QueryCache,
Cache::Key: QueryKey,
{
let def_id = key.key_as_def_id();
// If reduced queries are requested, we may be printing a query stack due
// to a panic. Avoid using `default_span` and `def_kind` in that case.
let reduce_queries = with_reduced_queries();
// Avoid calling queries while formatting the description
let description = ty::print::with_no_queries!((vtable.description_fn)(tcx, key));
let description = if tcx.sess.verbose_internals() {
format!("{description} [{name:?}]", name = vtable.name)
} else {
description
};
let span = if vtable.dep_kind == DepKind::def_span || reduce_queries {
// The `def_span` query is used to calculate `default_span`,
// so exit to avoid infinite recursion.
None
} else {
Some(key.default_span(tcx))
};
let def_kind = if vtable.dep_kind == DepKind::def_kind || reduce_queries {
// Try to avoid infinite recursion.
None
} else {
def_id.and_then(|def_id| def_id.as_local()).map(|def_id| tcx.def_kind(def_id))
};
QueryStackFrameExtra::new(description, span, def_kind)
}
pub(crate) fn create_deferred_query_stack_frame<'tcx, C>(
tcx: TyCtxt<'tcx>,
vtable: &'tcx QueryVTable<'tcx, C>,
key: C::Key,
) -> QueryStackFrame<QueryStackDeferred<'tcx>>
where
C: QueryCache<Key: QueryKey + DynSend + DynSync>,
QueryVTable<'tcx, C>: DynSync,
{
let kind = vtable.dep_kind;
let def_id: Option<DefId> = key.key_as_def_id();
let def_id_for_ty_in_cycle: Option<DefId> = key.def_id_for_ty_in_cycle();
let info = QueryStackDeferred::new((tcx, vtable, key), mk_query_stack_frame_extra);
QueryStackFrame::new(info, kind, def_id, def_id_for_ty_in_cycle)
}
pub(crate) fn encode_query_results<'a, 'tcx, C, V>(
tcx: TyCtxt<'tcx>,
query: &'tcx QueryVTable<'tcx, C>,
encoder: &mut CacheEncoder<'a, 'tcx>,
query_result_index: &mut EncodedDepNodeIndex,
) where
C: QueryCache<Value = Erased<V>>,
V: Erasable + Encodable<CacheEncoder<'a, 'tcx>>,
{
let _timer = tcx.prof.generic_activity_with_arg("encode_query_results_for", query.name);
assert!(all_inactive(&query.state));
query.cache.for_each(&mut |key, value, dep_node| {
if (query.will_cache_on_disk_for_key_fn)(tcx, *key) {
let dep_node = SerializedDepNodeIndex::new(dep_node.index());
// Record position of the cache entry.
query_result_index.push((dep_node, AbsoluteBytePos::new(encoder.position())));
// Encode the type check tables with the `SerializedDepNodeIndex`
// as tag.
encoder.encode_tagged(dep_node, &erase::restore_val::<V>(*value));
}
});
}
pub(crate) fn query_key_hash_verify<'tcx, C: QueryCache>(
query: &'tcx QueryVTable<'tcx, C>,
tcx: TyCtxt<'tcx>,
) {
let _timer = tcx.prof.generic_activity_with_arg("query_key_hash_verify_for", query.name);
let cache = &query.cache;
let mut map = UnordMap::with_capacity(cache.len());
cache.for_each(&mut |key, _, _| {
let node = DepNode::construct(tcx, query.dep_kind, key);
if let Some(other_key) = map.insert(node, *key) {
bug!(
"query key:\n\
`{:?}`\n\
and key:\n\
`{:?}`\n\
mapped to the same dep node:\n\
{:?}",
key,
other_key,
node
);
}
});
}
/// Implementation of [`DepKindVTable::promote_from_disk_fn`] for queries.
pub(crate) fn promote_from_disk_inner<'tcx, Q: GetQueryVTable<'tcx>>(
tcx: TyCtxt<'tcx>,
dep_node: DepNode,
) {
let query = Q::query_vtable(tcx);
debug_assert!(tcx.dep_graph.is_green(&dep_node));
let key = <Q::Cache as QueryCache>::Key::try_recover_key(tcx, &dep_node).unwrap_or_else(|| {
panic!(
"Failed to recover key for {dep_node:?} with key fingerprint {}",
dep_node.key_fingerprint
)
});
// If the recovered key isn't eligible for cache-on-disk, then there's no
// value on disk to promote.
if !(query.will_cache_on_disk_for_key_fn)(tcx, key) {
return;
}
match query.cache.lookup(&key) {
// If the value is already in memory, then promotion isn't needed.
Some(_) => {}
// "Execute" the query to load its disk-cached value into memory.
//
// We know that the key is cache-on-disk and its node is green,
// so there _must_ be a value on disk to load.
//
// FIXME(Zalathar): Is there a reasonable way to skip more of the
// query bookkeeping when doing this?
None => {
(query.execute_query_fn)(tcx, DUMMY_SP, key, QueryMode::Get);
}
}
}
pub(crate) fn loadable_from_disk<'tcx>(tcx: TyCtxt<'tcx>, id: SerializedDepNodeIndex) -> bool {
if let Some(cache) = tcx.query_system.on_disk_cache.as_ref() {
cache.loadable_from_disk(id)
} else {
false
}
}
pub(crate) fn try_load_from_disk<'tcx, V>(
tcx: TyCtxt<'tcx>,
prev_index: SerializedDepNodeIndex,
index: DepNodeIndex,
) -> Option<V>
where
V: for<'a> Decodable<CacheDecoder<'a, 'tcx>>,
{
let on_disk_cache = tcx.query_system.on_disk_cache.as_ref()?;
let prof_timer = tcx.prof.incr_cache_loading();
// The call to `with_query_deserialization` enforces that no new `DepNodes`
// are created during deserialization. See the docs of that method for more
// details.
let value = tcx
.dep_graph
.with_query_deserialization(|| on_disk_cache.try_load_query_result(tcx, prev_index));
prof_timer.finish_with_query_invocation_id(index.into());
value
}
/// Implementation of [`DepKindVTable::force_from_dep_node_fn`] for queries.
pub(crate) fn force_from_dep_node_inner<'tcx, Q: GetQueryVTable<'tcx>>(
tcx: TyCtxt<'tcx>,
dep_node: DepNode,
// Needed by the vtable function signature, but not used when forcing queries.
_prev_index: SerializedDepNodeIndex,
) -> bool {
let query = Q::query_vtable(tcx);
// We must avoid ever having to call `force_from_dep_node()` for a
// `DepNode::codegen_unit`:
// Since we cannot reconstruct the query key of a `DepNode::codegen_unit`, we
// would always end up having to evaluate the first caller of the
// `codegen_unit` query that *is* reconstructible. This might very well be
// the `compile_codegen_unit` query, thus re-codegenning the whole CGU just
// to re-trigger calling the `codegen_unit` query with the right key. At
// that point we would already have re-done all the work we are trying to
// avoid doing in the first place.
// The solution is simple: Just explicitly call the `codegen_unit` query for
// each CGU, right after partitioning. This way `try_mark_green` will always
// hit the cache instead of having to go through `force_from_dep_node`.
// This assertion makes sure, we actually keep applying the solution above.
debug_assert!(
dep_node.kind != DepKind::codegen_unit,
"calling force_from_dep_node() on dep_kinds::codegen_unit"
);
if let Some(key) = <Q::Cache as QueryCache>::Key::try_recover_key(tcx, &dep_node) {
force_query(query, tcx, key, dep_node);
true
} else {
false
}
}
macro_rules! define_queries {
(
// Note: `$K` and `$V` are unused but present so this can be called by
// `rustc_with_all_queries`.
queries {
$(
$(#[$attr:meta])*
fn $name:ident($K:ty) -> $V:ty
{
// Search for (QMODLIST) to find all occurrences of this query modifier list.
anon: $anon:literal,
arena_cache: $arena_cache:literal,
cache_on_disk: $cache_on_disk:literal,
cycle_error_handling: $cycle_error_handling:ident,
depth_limit: $depth_limit:literal,
eval_always: $eval_always:literal,
feedable: $feedable:literal,
no_hash: $no_hash:literal,
returns_error_guaranteed: $returns_error_guaranteed:literal,
separate_provide_extern: $separate_provide_extern:literal,
}
)*
}
// Non-queries are unused here.
non_queries { $($_:tt)* }
) => {
pub(crate) mod query_impl { $(pub(crate) mod $name {
use super::super::*;
use ::rustc_middle::query::erase::{self, Erased};
// It seems to be important that every query has its own monomorphic
// copy of `execute_query_incr` and `execute_query_non_incr`.
// Trying to inline these wrapper functions into their generic
// "inner" helpers tends to break `tests/run-make/short-ice`.
pub(crate) mod execute_query_incr {
use super::*;
// Adding `__rust_end_short_backtrace` marker to backtraces so that we emit the frames
// when `RUST_BACKTRACE=1`, add a new mod with `$name` here is to allow duplicate naming
#[inline(never)]
pub(crate) fn __rust_end_short_backtrace<'tcx>(
tcx: TyCtxt<'tcx>,
span: Span,
key: queries::$name::Key<'tcx>,
mode: QueryMode,
) -> Option<Erased<queries::$name::Value<'tcx>>> {
#[cfg(debug_assertions)]
let _guard = tracing::span!(tracing::Level::TRACE, stringify!($name), ?key).entered();
execution::execute_query_incr_inner(
&tcx.query_system.query_vtables.$name,
tcx,
span,
key,
mode
)
}
}
pub(crate) mod execute_query_non_incr {
use super::*;
#[inline(never)]
pub(crate) fn __rust_end_short_backtrace<'tcx>(
tcx: TyCtxt<'tcx>,
span: Span,
key: queries::$name::Key<'tcx>,
__mode: QueryMode,
) -> Option<Erased<queries::$name::Value<'tcx>>> {
Some(execution::execute_query_non_incr_inner(
&tcx.query_system.query_vtables.$name,
tcx,
span,
key,
))
}
}
/// Defines an `invoke_provider` function that calls the query's provider,
/// to be used as a function pointer in the query's vtable.
///
/// To mark a short-backtrace boundary, the function's actual name
/// (after demangling) must be `__rust_begin_short_backtrace`.
mod invoke_provider_fn {
use super::*;
use ::rustc_middle::queries::$name::{Key, Value, provided_to_erased};
#[inline(never)]
pub(crate) fn __rust_begin_short_backtrace<'tcx>(
tcx: TyCtxt<'tcx>,
key: Key<'tcx>,
) -> Erased<Value<'tcx>> {
#[cfg(debug_assertions)]
let _guard = tracing::span!(tracing::Level::TRACE, stringify!($name), ?key).entered();
// Call the actual provider function for this query.
#[cfg($separate_provide_extern)]
let provided_value = if let Some(local_key) = key.as_local_key() {
(tcx.query_system.local_providers.$name)(tcx, local_key)
} else {
(tcx.query_system.extern_providers.$name)(tcx, key)
};
#[cfg(not($separate_provide_extern))]
let provided_value = (tcx.query_system.local_providers.$name)(tcx, key);
rustc_middle::ty::print::with_reduced_queries!({
tracing::trace!(?provided_value);
});
// Erase the returned value, because `QueryVTable` uses erased values.
// For queries with `arena_cache`, this also arena-allocates the value.
provided_to_erased(tcx, provided_value)
}
}
pub(crate) fn make_query_vtable<'tcx>(incremental: bool)
-> QueryVTable<'tcx, queries::$name::Cache<'tcx>>
{
QueryVTable {
name: stringify!($name),
anon: $anon,
eval_always: $eval_always,
depth_limit: $depth_limit,
feedable: $feedable,
dep_kind: dep_graph::DepKind::$name,
cycle_error_handling:
rustc_middle::query::CycleErrorHandling::$cycle_error_handling,
state: Default::default(),
cache: Default::default(),
invoke_provider_fn: self::invoke_provider_fn::__rust_begin_short_backtrace,
#[cfg($cache_on_disk)]
will_cache_on_disk_for_key_fn:
rustc_middle::queries::_cache_on_disk_if_fns::$name,
#[cfg(not($cache_on_disk))]
will_cache_on_disk_for_key_fn: |_, _| false,
#[cfg($cache_on_disk)]
try_load_from_disk_fn: |tcx, key, prev_index, index| {
// Check the `cache_on_disk_if` condition for this key.
if !rustc_middle::queries::_cache_on_disk_if_fns::$name(tcx, key) {
return None;
}
let value: queries::$name::ProvidedValue<'tcx> =
$crate::plumbing::try_load_from_disk(tcx, prev_index, index)?;
// Arena-alloc the value if appropriate, and erase it.
Some(queries::$name::provided_to_erased(tcx, value))
},
#[cfg(not($cache_on_disk))]
try_load_from_disk_fn: |_tcx, _key, _prev_index, _index| None,
#[cfg($cache_on_disk)]
is_loadable_from_disk_fn: |tcx, key, index| -> bool {
rustc_middle::queries::_cache_on_disk_if_fns::$name(tcx, key) &&
$crate::plumbing::loadable_from_disk(tcx, index)
},
#[cfg(not($cache_on_disk))]
is_loadable_from_disk_fn: |_tcx, _key, _index| false,
value_from_cycle_error: |tcx, cycle, guar| {
let result: queries::$name::Value<'tcx> =
FromCycleError::from_cycle_error(tcx, cycle, guar);
erase::erase_val(result)
},
#[cfg($no_hash)]
hash_value_fn: None,
#[cfg(not($no_hash))]
hash_value_fn: Some(|hcx, erased_value: &erase::Erased<queries::$name::Value<'tcx>>| {
let value = erase::restore_val(*erased_value);
rustc_middle::dep_graph::hash_result(hcx, &value)
}),
format_value: |value| format!("{:?}", erase::restore_val::<queries::$name::Value<'tcx>>(*value)),
description_fn: $crate::queries::_description_fns::$name,
execute_query_fn: if incremental {
query_impl::$name::execute_query_incr::__rust_end_short_backtrace
} else {
query_impl::$name::execute_query_non_incr::__rust_end_short_backtrace
},
}
}
/// Marker type that implements [`GetQueryVTable`] for this query.
pub(crate) enum VTableGetter {}
impl<'tcx> GetQueryVTable<'tcx> for VTableGetter {
type Cache = rustc_middle::queries::$name::Cache<'tcx>;
#[inline(always)]
fn query_vtable(tcx: TyCtxt<'tcx>) -> &'tcx QueryVTable<'tcx, Self::Cache> {
&tcx.query_system.query_vtables.$name
}
}
})*}
pub fn make_query_vtables<'tcx>(incremental: bool) -> queries::QueryVTables<'tcx> {
queries::QueryVTables {
$(
$name: query_impl::$name::make_query_vtable(incremental),
)*
}
}
/// Returns a map of currently active query jobs, collected from all queries.
///
/// If `require_complete` is `true`, this function locks all shards of the
/// query results to produce a complete map, which always returns `Ok`.
/// Otherwise, it may return an incomplete map as an error if any shard
/// lock cannot be acquired.
///
/// Prefer passing `false` to `require_complete` to avoid potential deadlocks,
/// especially when called from within a deadlock handler, unless a
/// complete map is needed and no deadlock is possible at this call site.
pub fn collect_active_jobs_from_all_queries<'tcx>(
tcx: TyCtxt<'tcx>,
require_complete: bool,
) -> Result<QueryJobMap<'tcx>, QueryJobMap<'tcx>> {
let mut job_map_out = QueryJobMap::default();
let mut complete = true;
$(
let res = crate::execution::gather_active_jobs(
&tcx.query_system.query_vtables.$name,
tcx,
require_complete,
&mut job_map_out,
);
if res.is_none() {
complete = false;
}
)*
if complete { Ok(job_map_out) } else { Err(job_map_out) }
}
/// All self-profiling events generated by the query engine use
/// virtual `StringId`s for their `event_id`. This method makes all
/// those virtual `StringId`s point to actual strings.
///
/// If we are recording only summary data, the ids will point to
/// just the query names. If we are recording query keys too, we
/// allocate the corresponding strings here.
pub fn alloc_self_profile_query_strings(tcx: TyCtxt<'_>) {
if !tcx.prof.enabled() {
return;
}
let _prof_timer = tcx.sess.prof.generic_activity("self_profile_alloc_query_strings");
let mut string_cache = QueryKeyStringCache::new();
$(
$crate::profiling_support::alloc_self_profile_query_strings_for_query_cache(
tcx,
stringify!($name),
&tcx.query_system.query_vtables.$name.cache,
&mut string_cache,
);
)*
tcx.sess.prof.store_query_cache_hits();
}
fn encode_all_query_results<'tcx>(
tcx: TyCtxt<'tcx>,
encoder: &mut CacheEncoder<'_, 'tcx>,
query_result_index: &mut EncodedDepNodeIndex,
) {
$(
#[cfg($cache_on_disk)]
{
$crate::plumbing::encode_query_results(
tcx,
&tcx.query_system.query_vtables.$name,
encoder,
query_result_index,
)
}
)*
}
pub fn query_key_hash_verify_all<'tcx>(tcx: TyCtxt<'tcx>) {
if tcx.sess.opts.unstable_opts.incremental_verify_ich || cfg!(debug_assertions) {
tcx.sess.time("query_key_hash_verify_all", || {
$(
$crate::plumbing::query_key_hash_verify(
&tcx.query_system.query_vtables.$name,
tcx
);
)*
})
}
}
}
}