Skip to main content

rustc_query_impl/
execution.rs

1use std::hash::Hash;
2use std::mem::ManuallyDrop;
3use std::num::NonZero;
4
5use rustc_data_structures::hash_table::Entry;
6use rustc_data_structures::{Limit, defer, outline, sharded, sync};
7use rustc_errors::FatalError;
8use rustc_middle::dep_graph::{
9    DepGraphData, DepNode, DepNodeIndex, DepNodeKey, SerializedDepNodeIndex,
10};
11use rustc_middle::query::{
12    ActiveKeyStatus, Cycle, QueryCache, QueryJob, QueryJobId, QueryLatch, QueryMode, QueryState,
13    QueryVTable,
14};
15use rustc_middle::ty::TyCtxt;
16use rustc_middle::ty::tls::{self, ImplicitCtxt};
17use rustc_middle::verify_ich::incremental_verify_ich;
18use rustc_span::def_id::LOCAL_CRATE;
19use rustc_span::{DUMMY_SP, Span};
20
21use crate::diagnostics::{QueryOverflow, QueryOverflowNote};
22use crate::handle_cycle_error;
23use crate::incremental::should_verify_loaded_value;
24use crate::job::{
25    CollectActiveJobsKind, collect_active_query_jobs, find_cycle_in_stack, find_dep_kind_root,
26};
27
28#[inline]
29fn equivalent_key<K: Eq, V>(k: K) -> impl Fn(&(K, V)) -> bool {
30    move |x| x.0 == k
31}
32
33#[cold]
34#[inline(never)]
35fn handle_cycle<'tcx, C: QueryCache>(
36    query: &'tcx QueryVTable<'tcx, C>,
37    tcx: TyCtxt<'tcx>,
38    key: C::Key,
39    cycle: Cycle<'tcx>,
40) -> C::Value {
41    let nested;
42    {
43        let mut nesting = tcx.query_system.cycle_handler_nesting.lock();
44        nested = match *nesting {
45            0 => false,
46            1 => true,
47            _ => {
48                // Don't print further nested errors to avoid cases of infinite recursion
49                tcx.dcx().delayed_bug("doubly nested cycle error").raise_fatal()
50            }
51        };
52        *nesting += 1;
53    }
54    let _guard = defer(|| *tcx.query_system.cycle_handler_nesting.lock() -= 1);
55
56    let error = handle_cycle_error::create_cycle_error(tcx, &cycle, nested);
57
58    if nested {
59        // Avoid custom handlers and only use the robust `create_cycle_error` for nested cycle errors
60        handle_cycle_error::default(error)
61    } else {
62        (query.handle_cycle_error_fn)(tcx, key, cycle, error)
63    }
64}
65
66/// Guard object representing the responsibility to execute a query job and
67/// mark it as completed.
68///
69/// This will poison the relevant query key if it is dropped without calling
70/// [`Self::complete`].
71struct ActiveJobGuard<'tcx, K>
72where
73    K: Eq + Hash + Copy,
74{
75    state: &'tcx QueryState<'tcx, K>,
76    key: K,
77    key_hash: u64,
78}
79
80impl<'tcx, K> ActiveJobGuard<'tcx, K>
81where
82    K: Eq + Hash + Copy,
83{
84    /// Completes the query by updating the query cache with the `result`,
85    /// signals the waiter, and forgets the guard so it won't poison the query.
86    fn complete<C>(self, cache: &C, value: C::Value, dep_node_index: DepNodeIndex)
87    where
88        C: QueryCache<Key = K>,
89    {
90        // Mark as complete before we remove the job from the active state
91        // so no other thread can re-execute this query.
92        cache.complete(self.key, value, dep_node_index);
93
94        let mut this = ManuallyDrop::new(self);
95
96        // Drop everything without poisoning the query.
97        this.drop_and_maybe_poison(/* poison */ false);
98    }
99
100    fn drop_and_maybe_poison(&mut self, poison: bool) {
101        let status = {
102            let mut shard = self.state.active.lock_shard_by_hash(self.key_hash);
103            match shard.find_entry(self.key_hash, equivalent_key(self.key)) {
104                Err(_) => {
105                    // Note: we must not panic while holding the lock, because unwinding also looks
106                    // at this map, which can result in a double panic. So drop it first.
107                    drop(shard);
108                    ::core::panicking::panic("explicit panic");panic!();
109                }
110                Ok(occupied) => {
111                    let ((key, status), vacant) = occupied.remove();
112                    if poison {
113                        vacant.insert((key, ActiveKeyStatus::Poisoned));
114                    }
115                    status
116                }
117            }
118        };
119
120        // Also signal the completion of the job, so waiters will continue execution.
121        match status {
122            ActiveKeyStatus::Started(job) => job.signal_complete(),
123            ActiveKeyStatus::Poisoned => ::core::panicking::panic("explicit panic")panic!(),
124        }
125    }
126}
127
128impl<'tcx, K> Drop for ActiveJobGuard<'tcx, K>
129where
130    K: Eq + Hash + Copy,
131{
132    #[inline(never)]
133    #[cold]
134    fn drop(&mut self) {
135        // Poison the query so jobs waiting on it panic.
136        self.drop_and_maybe_poison(/* poison */ true);
137    }
138}
139
140#[cold]
141#[inline(never)]
142fn find_and_handle_cycle<'tcx, C: QueryCache>(
143    query: &'tcx QueryVTable<'tcx, C>,
144    tcx: TyCtxt<'tcx>,
145    key: C::Key,
146    try_execute: QueryJobId,
147    span: Span,
148) -> (C::Value, Option<DepNodeIndex>) {
149    // Ensure there were no errors collecting all active jobs.
150    // We need the complete map to ensure we find a cycle to break.
151    let job_map = collect_active_query_jobs(tcx, CollectActiveJobsKind::FullNoContention);
152
153    let cycle = find_cycle_in_stack(try_execute, job_map, &current_query_job(), span);
154    (handle_cycle(query, tcx, key, cycle), None)
155}
156
157#[inline(always)]
158fn wait_for_query<'tcx, C: QueryCache>(
159    query: &'tcx QueryVTable<'tcx, C>,
160    tcx: TyCtxt<'tcx>,
161    span: Span,
162    key: C::Key,
163    key_hash: u64,
164    latch: QueryLatch<'tcx>,
165    current: Option<QueryJobId>,
166) -> (C::Value, Option<DepNodeIndex>) {
167    // For parallel queries, we'll block and wait until the query running
168    // in another thread has completed. Record how long we wait in the
169    // self-profiler.
170    let query_blocked_prof_timer = tcx.prof.query_blocked();
171
172    // With parallel queries we might just have to wait on some other thread.
173    let result = latch.wait_on(current, span);
174
175    match result {
176        Ok(()) => {
177            let Some((v, index)) = query.cache.lookup(&key) else {
178                outline(|| {
179                    // We didn't find the query result in the query cache. Check if it was
180                    // poisoned due to a panic instead.
181                    let shard = query.state.active.lock_shard_by_hash(key_hash);
182                    match shard.find(key_hash, equivalent_key(key)) {
183                        // The query we waited on panicked. Continue unwinding here.
184                        Some((_, ActiveKeyStatus::Poisoned)) => FatalError.raise(),
185                        _ => {
    ::core::panicking::panic_fmt(format_args!("query \'{0}\' result must be in the cache or the query must be poisoned after a wait",
            query.name));
}panic!(
186                            "query '{}' result must be in the cache or the query must be poisoned after a wait",
187                            query.name
188                        ),
189                    }
190                })
191            };
192
193            tcx.prof.query_cache_hit(index.into());
194            query_blocked_prof_timer.finish_with_query_invocation_id(index.into());
195
196            (v, Some(index))
197        }
198        Err(cycle) => (handle_cycle(query, tcx, key, cycle), None),
199    }
200}
201
202#[inline]
203fn next_job_id<'tcx>(tcx: TyCtxt<'tcx>) -> QueryJobId {
204    QueryJobId(
205        NonZero::new(tcx.query_system.jobs.fetch_add(1, std::sync::atomic::Ordering::Relaxed))
206            .unwrap(),
207    )
208}
209
210#[inline]
211fn current_query_job() -> Option<QueryJobId> {
212    tls::with_context(|icx| icx.query)
213}
214
215/// Shared main part of both [`execute_query_incr_inner`] and [`execute_query_non_incr_inner`].
216#[inline(never)]
217fn try_execute_query<'tcx, C: QueryCache, const INCR: bool>(
218    query: &'tcx QueryVTable<'tcx, C>,
219    tcx: TyCtxt<'tcx>,
220    span: Span,
221    key: C::Key,
222    dep_node: Option<DepNode>, // `None` for non-incremental, `Some` for incremental
223) -> (C::Value, Option<DepNodeIndex>) {
224    let key_hash = sharded::make_hash(&key);
225    let mut state_lock = query.state.active.lock_shard_by_hash(key_hash);
226
227    // For the parallel compiler we need to check both the query cache and query state structures
228    // while holding the state lock to ensure that 1) the query has not yet completed and 2) the
229    // query is not still executing. Without checking the query cache here, we can end up
230    // re-executing the query since `try_start` only checks that the query is not currently
231    // executing, but another thread may have already completed the query and stores it result
232    // in the query cache.
233    if tcx.sess.opts.jobs.frontend.is_some() {
234        if let Some((value, index)) = query.cache.lookup(&key) {
235            tcx.prof.query_cache_hit(index.into());
236            return (value, Some(index));
237        }
238    }
239
240    let current_job_id = current_query_job();
241
242    match state_lock.entry(key_hash, equivalent_key(key), |(k, _)| sharded::make_hash(k)) {
243        Entry::Vacant(entry) => {
244            // Nothing has computed or is computing the query, so we start a new job and insert it
245            // in the state map.
246            let id = next_job_id(tcx);
247            let job = QueryJob::new(id, span, current_job_id);
248            entry.insert((key, ActiveKeyStatus::Started(job)));
249
250            // Drop the lock before we start executing the query.
251            drop(state_lock);
252
253            // Set up a guard object that will automatically poison the query if a
254            // panic occurs while executing the query (or any intermediate plumbing).
255            let job_guard = ActiveJobGuard { state: &query.state, key, key_hash };
256
257            // Delegate to another function to actually execute the query job.
258            let (value, dep_node_index) = if INCR {
259                execute_job_incr(query, tcx, key, dep_node.unwrap(), id)
260            } else {
261                execute_job_non_incr(query, tcx, key, id)
262            };
263
264            if query.feedable {
265                check_feedable_consistency(tcx, query, key, &value);
266            }
267
268            // Tell the guard to insert `value` in the cache and remove the status entry from
269            // `query.state`.
270            job_guard.complete(&query.cache, value, dep_node_index);
271
272            (value, Some(dep_node_index))
273        }
274        Entry::Occupied(mut entry) => {
275            match &mut entry.get_mut().1 {
276                ActiveKeyStatus::Started(job) => {
277                    if sync::is_dyn_thread_safe() {
278                        // Get the latch out
279                        let latch = job.latch();
280                        drop(state_lock);
281
282                        // Only call `wait_for_query` if we're using a Rayon thread pool
283                        // as it will attempt to mark the worker thread as blocked.
284                        wait_for_query(query, tcx, span, key, key_hash, latch, current_job_id)
285                    } else {
286                        let id = job.id;
287                        drop(state_lock);
288
289                        // If we are single-threaded we know that we have cycle error,
290                        // so we just return the error.
291                        find_and_handle_cycle(query, tcx, key, id, span)
292                    }
293                }
294                ActiveKeyStatus::Poisoned => FatalError.raise(),
295            }
296        }
297    }
298}
299
300#[inline(always)]
301fn check_feedable_consistency<'tcx, C: QueryCache>(
302    tcx: TyCtxt<'tcx>,
303    query: &'tcx QueryVTable<'tcx, C>,
304    key: C::Key,
305    value: &C::Value,
306) {
307    // We should not compute queries that also got a value via feeding.
308    // This can't happen, as query feeding adds the very dependencies to the fed query
309    // as its feeding query had. So if the fed query is red, so is its feeder, which will
310    // get evaluated first, and re-feed the query.
311    let Some((cached_value, _)) = query.cache.lookup(&key) else { return };
312
313    let Some(hash_value_fn) = query.hash_value_fn else {
314        {
    ::core::panicking::panic_fmt(format_args!("no_hash fed query later has its value computed.\nRemove `no_hash` modifier to allow recomputation.\nThe already cached value: {0}",
            (query.format_value)(&cached_value)));
};panic!(
315            "no_hash fed query later has its value computed.\n\
316            Remove `no_hash` modifier to allow recomputation.\n\
317            The already cached value: {}",
318            (query.format_value)(&cached_value)
319        );
320    };
321
322    let (old_hash, new_hash) = tcx.with_stable_hashing_context(|mut hcx| {
323        (hash_value_fn(&mut hcx, &cached_value), hash_value_fn(&mut hcx, value))
324    });
325    let formatter = query.format_value;
326    if old_hash != new_hash {
327        // We have an inconsistency. This can happen if one of the two
328        // results is tainted by errors.
329        if !tcx.dcx().has_errors().is_some() {
    {
        ::core::panicking::panic_fmt(format_args!("Computed query value for {0:?}({1:?}) is inconsistent with fed value,\ncomputed={2:#?}\nfed={3:#?}",
                query.dep_kind, key, formatter(value),
                formatter(&cached_value)));
    }
};assert!(
330            tcx.dcx().has_errors().is_some(),
331            "Computed query value for {:?}({:?}) is inconsistent with fed value,\n\
332                computed={:#?}\nfed={:#?}",
333            query.dep_kind,
334            key,
335            formatter(value),
336            formatter(&cached_value),
337        );
338    }
339}
340
341fn depth_limit_error<'tcx>(tcx: TyCtxt<'tcx>, job: QueryJobId) {
342    let job_map = collect_active_query_jobs(tcx, CollectActiveJobsKind::Full);
343    let (span, desc, depth) = find_dep_kind_root(tcx, job, job_map);
344
345    let suggested_limit = match tcx.recursion_limit() {
346        Limit(0) => Limit(2),
347        limit => limit * 2,
348    };
349
350    tcx.dcx().emit_fatal(QueryOverflow {
351        span,
352        note: QueryOverflowNote { desc, depth },
353        suggested_limit,
354        crate_name: tcx.crate_name(LOCAL_CRATE),
355    });
356}
357
358/// Executes a job by changing the `ImplicitCtxt` to point to the new query job while it executes.
359#[inline(always)]
360fn start_query<R>(job_id: QueryJobId, depth_limit: bool, compute: impl FnOnce() -> R) -> R {
361    tls::with_context(move |icx| {
362        if depth_limit && !icx.tcx.recursion_limit().value_within_limit(icx.query_depth) {
363            depth_limit_error(icx.tcx, job_id);
364        }
365
366        // Update the `ImplicitCtxt` to point to our new query job.
367        let icx = ImplicitCtxt {
368            query: Some(job_id),
369            query_depth: icx.query_depth + if depth_limit { 1 } else { 0 },
370            ..*icx
371        };
372
373        // Use the `ImplicitCtxt` while we execute the query.
374        tls::enter_context(&icx, compute)
375    })
376}
377
378// Fast path for when incr. comp. is off.
379#[inline(always)]
380fn execute_job_non_incr<'tcx, C: QueryCache>(
381    query: &'tcx QueryVTable<'tcx, C>,
382    tcx: TyCtxt<'tcx>,
383    key: C::Key,
384    job_id: QueryJobId,
385) -> (C::Value, DepNodeIndex) {
386    if true {
    if !!tcx.dep_graph.is_fully_enabled() {
        ::core::panicking::panic("assertion failed: !tcx.dep_graph.is_fully_enabled()")
    };
};debug_assert!(!tcx.dep_graph.is_fully_enabled());
387
388    let prof_timer = tcx.prof.query_provider();
389    // Call the query provider.
390    let value = start_query(job_id, query.depth_limit, || (query.invoke_provider_fn)(tcx, key));
391    let dep_node_index = tcx.dep_graph.next_virtual_depnode_index();
392    prof_timer.finish_with_query_invocation_id(dep_node_index.into());
393
394    // Sanity: Fingerprint the key and the result to assert they don't contain anything unhashable.
395    if truecfg!(debug_assertions) {
396        let _ = key.to_fingerprint(tcx);
397        if let Some(hash_value_fn) = query.hash_value_fn {
398            tcx.with_stable_hashing_context(|mut hcx| {
399                hash_value_fn(&mut hcx, &value);
400            });
401        }
402    }
403
404    (value, dep_node_index)
405}
406
407#[inline(always)]
408fn execute_job_incr<'tcx, C: QueryCache>(
409    query: &'tcx QueryVTable<'tcx, C>,
410    tcx: TyCtxt<'tcx>,
411    key: C::Key,
412    dep_node: DepNode,
413    job_id: QueryJobId,
414) -> (C::Value, DepNodeIndex) {
415    let dep_graph_data =
416        tcx.dep_graph.data().expect("should always be present in incremental mode");
417
418    if !query.eval_always {
419        // The diagnostics for this query will be promoted to the current session during
420        // `try_mark_green()`, so we can ignore them here.
421        if let Some(ret) = start_query(job_id, false, || try {
422            let (prev_index, dep_node_index) = dep_graph_data.try_mark_green(tcx, &dep_node)?;
423            let value = load_from_disk_or_invoke_provider_green(
424                tcx,
425                dep_graph_data,
426                query,
427                key,
428                &dep_node,
429                prev_index,
430                dep_node_index,
431            );
432            (value, dep_node_index)
433        }) {
434            return ret;
435        }
436    }
437
438    let prof_timer = tcx.prof.query_provider();
439
440    let (result, dep_node_index) = start_query(job_id, query.depth_limit, || {
441        // Call the query provider.
442        dep_graph_data.with_task(
443            dep_node,
444            tcx,
445            || (query.invoke_provider_fn)(tcx, key),
446            query.hash_value_fn,
447        )
448    });
449
450    prof_timer.finish_with_query_invocation_id(dep_node_index.into());
451
452    (result, dep_node_index)
453}
454
455/// Given that the dep node for this query+key is green, obtain a value for it
456/// by loading one from disk if possible, or by invoking its query provider if
457/// necessary.
458#[inline(always)]
459fn load_from_disk_or_invoke_provider_green<'tcx, C: QueryCache>(
460    tcx: TyCtxt<'tcx>,
461    dep_graph_data: &DepGraphData,
462    query: &'tcx QueryVTable<'tcx, C>,
463    key: C::Key,
464    dep_node: &DepNode,
465    prev_index: SerializedDepNodeIndex,
466    dep_node_index: DepNodeIndex,
467) -> C::Value {
468    // Note this function can be called concurrently from the same query
469    // We must ensure that this is handled correctly.
470
471    if true {
    if !dep_graph_data.is_index_green(prev_index) {
        ::core::panicking::panic("assertion failed: dep_graph_data.is_index_green(prev_index)")
    };
};debug_assert!(dep_graph_data.is_index_green(prev_index));
472
473    // First try to load the result from the on-disk cache. Some things are never cached on disk.
474    let try_value = if query.will_cache_on_disk_for_key(key) {
475        let prof_timer = tcx.prof.incr_cache_loading();
476        let value = (query.try_load_from_disk_fn)(tcx, prev_index);
477        prof_timer.finish_with_query_invocation_id(dep_node_index.into());
478        value
479    } else {
480        None
481    };
482    let (value, verify) = match try_value {
483        Some(value) => {
484            if std::intrinsics::unlikely(tcx.sess.opts.unstable_opts.query_dep_graph) {
485                dep_graph_data.mark_debug_loaded_from_disk(*dep_node)
486            }
487
488            let verify = should_verify_loaded_value(tcx, dep_graph_data, dep_node.key_fingerprint);
489
490            (value, verify)
491        }
492        None => {
493            // We could not load a result from the on-disk cache, so recompute. The dep-graph for
494            // this computation is already in-place, so we can just call the query provider.
495            let prof_timer = tcx.prof.query_provider();
496            let value = tcx.dep_graph.with_ignore(|| (query.invoke_provider_fn)(tcx, key));
497            prof_timer.finish_with_query_invocation_id(dep_node_index.into());
498
499            (value, true)
500        }
501    };
502
503    if verify {
504        // Verify that re-running the query produced a result with the expected hash.
505        // This catches bugs in query implementations, turning them into ICEs.
506        // For example, a query might sort its result by `DefId` - since `DefId`s are
507        // not stable across compilation sessions, the result could get up getting sorted
508        // in a different order when the query is re-run, even though all of the inputs
509        // (e.g. `DefPathHash` values) were green.
510        //
511        // See issue #82920 for an example of a miscompilation that would get turned into
512        // an ICE by this check
513        incremental_verify_ich(
514            tcx,
515            dep_graph_data,
516            &value,
517            prev_index,
518            query.hash_value_fn,
519            query.format_value,
520        );
521    }
522
523    value
524}
525
526/// Checks whether a `tcx.ensure_ok()` query call can
527/// return early without actually trying to execute.
528///
529/// This only makes sense during incremental compilation, because it relies
530/// on having the dependency graph (and in some cases a disk-cached value)
531/// from the previous incr-comp session.
532#[inline(never)]
533fn ensure_can_skip_execution<'tcx, C: QueryCache>(
534    query: &'tcx QueryVTable<'tcx, C>,
535    tcx: TyCtxt<'tcx>,
536    dep_node: DepNode,
537) -> bool {
538    // Queries with `eval_always` should never skip execution.
539    if query.eval_always {
540        return false;
541    }
542
543    match tcx.dep_graph.try_mark_green(tcx, &dep_node) {
544        None => {
545            // A None return from `try_mark_green` means that this is either
546            // a new dep node or that the dep node has already been marked red.
547            // Either way, we can't call `dep_graph.read()` as we don't have the
548            // DepNodeIndex. We must invoke the query itself. The performance cost
549            // this introduces should be negligible as we'll immediately hit the
550            // in-memory cache, or another query down the line will.
551            false
552        }
553        Some((_, dep_node_index)) => {
554            tcx.dep_graph.read_index(dep_node_index);
555            tcx.prof.query_cache_hit(dep_node_index.into());
556
557            // We can skip execution for this key if the
558            // node is green. It must have succeeded in the previous
559            // session, and therefore would succeed in the current session
560            // if executed.
561            true
562        }
563    }
564}
565
566/// Called by a macro-generated impl of [`QueryVTable::execute_query_fn`],
567/// in non-incremental mode.
568#[inline(always)]
569pub(super) fn execute_query_non_incr_inner<'tcx, C: QueryCache>(
570    query: &'tcx QueryVTable<'tcx, C>,
571    tcx: TyCtxt<'tcx>,
572    span: Span,
573    key: C::Key,
574) -> C::Value {
575    try_execute_query::<C, false>(query, tcx, span, key, None).0
576}
577
578/// Called by a macro-generated impl of [`QueryVTable::execute_query_fn`],
579/// in incremental mode.
580#[inline(always)]
581pub(super) fn execute_query_incr_inner<'tcx, C: QueryCache>(
582    query: &'tcx QueryVTable<'tcx, C>,
583    tcx: TyCtxt<'tcx>,
584    span: Span,
585    key: C::Key,
586    mode: QueryMode,
587) -> Option<C::Value> {
588    let dep_node = DepNode::construct(tcx, query.dep_kind, &key);
589
590    // Check if query execution can be skipped, for `ensure_ok`.
591    if let QueryMode::EnsureOk = mode
592        && ensure_can_skip_execution(query, tcx, dep_node)
593    {
594        return None;
595    }
596
597    let (result, dep_node_index) =
598        try_execute_query::<C, true>(query, tcx, span, key, Some(dep_node));
599    if let Some(dep_node_index) = dep_node_index {
600        tcx.dep_graph.read_index(dep_node_index)
601    }
602    Some(result)
603}
604
605/// Inner implementation of [`DepKindVTable::force_from_dep_node_fn`][force_fn]
606/// for query nodes.
607///
608/// [force_fn]: rustc_middle::dep_graph::DepKindVTable::force_from_dep_node_fn
609pub(crate) fn force_query_dep_node<'tcx, C: QueryCache>(
610    tcx: TyCtxt<'tcx>,
611    query: &'tcx QueryVTable<'tcx, C>,
612    dep_node: DepNode,
613) -> bool {
614    let Some(key) = C::Key::try_recover_key(tcx, &dep_node) else {
615        // We couldn't recover a key from the node's key fingerprint.
616        // Tell the caller that we couldn't force the node.
617        return false;
618    };
619
620    try_execute_query::<C, true>(query, tcx, DUMMY_SP, key, Some(dep_node));
621
622    // We did manage to recover a key and force the node, though it's up to
623    // the caller to check whether the node ended up marked red or green.
624    true
625}