| use std::num::NonZero; | |
| use rustc_data_structures::unord::UnordMap; | |
| use rustc_hir::limit::Limit; | |
| use rustc_middle::bug; | |
| #[expect(unused_imports, reason = "used by doc comments")] | |
| use rustc_middle::dep_graph::DepKindVTable; | |
| use rustc_middle::dep_graph::{DepNode, DepNodeKey, SerializedDepNodeIndex}; | |
| use rustc_middle::query::erase::{Erasable, Erased}; | |
| use rustc_middle::query::on_disk_cache::{CacheDecoder, CacheEncoder}; | |
| use rustc_middle::query::{QueryCache, QueryJobId, QueryMode, QueryVTable, erase}; | |
| use rustc_middle::ty::TyCtxt; | |
| use rustc_middle::ty::tls::{self, ImplicitCtxt}; | |
| 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; | |
| use crate::job::find_dep_kind_root; | |
| use crate::query_impl::for_each_query_vtable; | |
| use crate::{CollectActiveJobsKind, collect_active_query_jobs}; | |
| fn depth_limit_error<'tcx>(tcx: TyCtxt<'tcx>, job: QueryJobId) { | |
| let job_map = collect_active_query_jobs(tcx, CollectActiveJobsKind::Full); | |
| let (span, desc, depth) = find_dep_kind_root(tcx, job, job_map); | |
| let suggested_limit = match tcx.recursion_limit() { | |
| Limit(0) => Limit(2), | |
| limit => limit * 2, | |
| }; | |
| tcx.dcx().emit_fatal(QueryOverflow { | |
| span, | |
| note: QueryOverflowNote { desc, 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) | |
| }) | |
| } | |
| pub(crate) fn encode_query_values<'tcx>(tcx: TyCtxt<'tcx>, encoder: &mut CacheEncoder<'_, 'tcx>) { | |
| for_each_query_vtable!(CACHE_ON_DISK, tcx, |query| { | |
| encode_query_values_inner(tcx, query, encoder) | |
| }); | |
| } | |
| fn encode_query_values_inner<'a, 'tcx, C, V>( | |
| tcx: TyCtxt<'tcx>, | |
| query: &'tcx QueryVTable<'tcx, C>, | |
| encoder: &mut CacheEncoder<'a, 'tcx>, | |
| ) 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)(*key) { | |
| encoder.encode_query_value::<V>(dep_node, &erase::restore_val::<V>(*value)); | |
| } | |
| }); | |
| } | |
| pub(crate) fn verify_query_key_hashes<'tcx>(tcx: TyCtxt<'tcx>) { | |
| if tcx.sess.opts.unstable_opts.incremental_verify_ich || cfg!(debug_assertions) { | |
| tcx.sess.time("verify_query_key_hashes", || { | |
| for_each_query_vtable!(ALL, tcx, |query| { | |
| verify_query_key_hashes_inner(query, tcx); | |
| }); | |
| }); | |
| } | |
| } | |
| fn verify_query_key_hashes_inner<'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 | |
| ); | |
| } | |
| }); | |
| } | |
| /// Inner implementation of [`DepKindVTable::promote_from_disk_fn`] for queries. | |
| pub(crate) fn promote_from_disk_inner<'tcx, C: QueryCache>( | |
| tcx: TyCtxt<'tcx>, | |
| query: &'tcx QueryVTable<'tcx, C>, | |
| dep_node: DepNode, | |
| ) { | |
| debug_assert!(tcx.dep_graph.is_green(&dep_node)); | |
| let key = C::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)(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, | |
| ) -> Option<V> | |
| where | |
| V: for<'a> Decodable<CacheDecoder<'a, 'tcx>>, | |
| { | |
| let on_disk_cache = tcx.query_system.on_disk_cache.as_ref()?; | |
| // The call to `with_query_deserialization` enforces that no new `DepNodes` | |
| // are created during deserialization. See the docs of that method for more | |
| // details. | |
| tcx.dep_graph.with_query_deserialization(|| on_disk_cache.try_load_query_value(tcx, prev_index)) | |
| } |