Skip to main content

rustc_query_impl/
execution.rs

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