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