Skip to content

Commit 3382fdd

Browse files
Rollup merge of rust-lang#153033 - Zalathar:ensure, r=nnethercote
Clarify how "ensure" queries check whether they can skip execution The current `check_cache: bool` field in QueryMode is confusing for a few reasons: - It actually refers to checking the *disk cache*, not the in-memory cache. - It gives no indication of what condition is being checked, or why. - It obscures the link between `tcx.ensure_ok()` and `tcx.ensure_done()`, and the actual check. This PR replaces that field with an `EnsureMode` enum that distinguishes between ensure-ok and ensure-done, and leaves the actual check as an implementation detail of the ensure-done mode. This PR also renames `ensure_must_run` → `check_if_ensure_can_skip_execution`, and uses a return struct to more clearly indicate how this helper function gives permission for its caller to skip execution of a query that would otherwise be executed. This also inverts the sense of the returned boolean, which was previously “must run” but is now ”skip execution”. r? nnethercote (or compiler)
2 parents 6d09787 + bf0f511 commit 3382fdd

File tree

4 files changed

+79
-37
lines changed

4 files changed

+79
-37
lines changed

compiler/rustc_middle/src/query/inner.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
//! Helper functions that serve as the immediate implementation of
22
//! `tcx.$query(..)` and its variations.
33
4+
use rustc_data_structures::assert_matches;
45
use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span};
56

67
use crate::dep_graph;
78
use crate::dep_graph::{DepKind, DepNodeKey};
89
use crate::query::erase::{self, Erasable, Erased};
910
use crate::query::plumbing::QueryVTable;
10-
use crate::query::{QueryCache, QueryMode};
11+
use crate::query::{EnsureMode, QueryCache, QueryMode};
1112
use crate::ty::TyCtxt;
1213

1314
/// Checks whether there is already a value for this key in the in-memory
@@ -56,12 +57,12 @@ pub(crate) fn query_ensure<'tcx, Cache>(
5657
execute_query: fn(TyCtxt<'tcx>, Span, Cache::Key, QueryMode) -> Option<Cache::Value>,
5758
query_cache: &Cache,
5859
key: Cache::Key,
59-
check_cache: bool,
60+
ensure_mode: EnsureMode,
6061
) where
6162
Cache: QueryCache,
6263
{
6364
if try_get_cached(tcx, query_cache, &key).is_none() {
64-
execute_query(tcx, DUMMY_SP, key, QueryMode::Ensure { check_cache });
65+
execute_query(tcx, DUMMY_SP, key, QueryMode::Ensure { ensure_mode });
6566
}
6667
}
6768

@@ -73,16 +74,20 @@ pub(crate) fn query_ensure_error_guaranteed<'tcx, Cache, T>(
7374
execute_query: fn(TyCtxt<'tcx>, Span, Cache::Key, QueryMode) -> Option<Cache::Value>,
7475
query_cache: &Cache,
7576
key: Cache::Key,
76-
check_cache: bool,
77+
// This arg is needed to match the signature of `query_ensure`,
78+
// but should always be `EnsureMode::Ok`.
79+
ensure_mode: EnsureMode,
7780
) -> Result<(), ErrorGuaranteed>
7881
where
7982
Cache: QueryCache<Value = Erased<Result<T, ErrorGuaranteed>>>,
8083
Result<T, ErrorGuaranteed>: Erasable,
8184
{
85+
assert_matches!(ensure_mode, EnsureMode::Ok);
86+
8287
if let Some(res) = try_get_cached(tcx, query_cache, &key) {
8388
erase::restore_val(res).map(drop)
8489
} else {
85-
execute_query(tcx, DUMMY_SP, key, QueryMode::Ensure { check_cache })
90+
execute_query(tcx, DUMMY_SP, key, QueryMode::Ensure { ensure_mode })
8691
.map(erase::restore_val)
8792
.map(|res| res.map(drop))
8893
// Either we actually executed the query, which means we got a full `Result`,

compiler/rustc_middle/src/query/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ pub use self::caches::{
66
pub use self::job::{QueryInfo, QueryJob, QueryJobId, QueryLatch, QueryWaiter};
77
pub use self::keys::{AsLocalKey, Key, LocalCrate};
88
pub use self::plumbing::{
9-
ActiveKeyStatus, CycleError, CycleErrorHandling, IntoQueryParam, QueryMode, QueryState,
10-
TyCtxtAt, TyCtxtEnsureDone, TyCtxtEnsureOk,
9+
ActiveKeyStatus, CycleError, CycleErrorHandling, EnsureMode, IntoQueryParam, QueryMode,
10+
QueryState, TyCtxtAt, TyCtxtEnsureDone, TyCtxtEnsureOk,
1111
};
1212
pub use self::stack::{QueryStackDeferred, QueryStackFrame, QueryStackFrameExtra};
1313
pub use crate::queries::Providers;

compiler/rustc_middle/src/query/plumbing.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,20 @@ impl<'tcx> CycleError<QueryStackDeferred<'tcx>> {
9797

9898
#[derive(Debug)]
9999
pub enum QueryMode {
100+
/// This is a normal query call to `tcx.$query(..)` or `tcx.at(span).$query(..)`.
100101
Get,
101-
Ensure { check_cache: bool },
102+
/// This is a call to `tcx.ensure_ok().$query(..)` or `tcx.ensure_done().$query(..)`.
103+
Ensure { ensure_mode: EnsureMode },
104+
}
105+
106+
/// Distinguishes between `tcx.ensure_ok()` and `tcx.ensure_done()` in shared
107+
/// code paths that handle both modes.
108+
#[derive(Debug)]
109+
pub enum EnsureMode {
110+
/// Corresponds to [`TyCtxt::ensure_ok`].
111+
Ok,
112+
/// Corresponds to [`TyCtxt::ensure_done`].
113+
Done,
102114
}
103115

104116
/// Stores function pointers and other metadata for a particular query.
@@ -537,7 +549,7 @@ macro_rules! define_callbacks {
537549
self.tcx.query_system.fns.engine.$name,
538550
&self.tcx.query_system.caches.$name,
539551
$crate::query::IntoQueryParam::into_query_param(key),
540-
false,
552+
$crate::query::EnsureMode::Ok,
541553
)
542554
}
543555
)*
@@ -553,7 +565,7 @@ macro_rules! define_callbacks {
553565
self.tcx.query_system.fns.engine.$name,
554566
&self.tcx.query_system.caches.$name,
555567
$crate::query::IntoQueryParam::into_query_param(key),
556-
true,
568+
$crate::query::EnsureMode::Done,
557569
);
558570
}
559571
)*

compiler/rustc_query_impl/src/execution.rs

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use rustc_errors::{Diag, FatalError, StashKey};
88
use rustc_middle::dep_graph::{DepGraphData, DepNodeKey};
99
use rustc_middle::query::plumbing::QueryVTable;
1010
use rustc_middle::query::{
11-
ActiveKeyStatus, CycleError, CycleErrorHandling, QueryCache, QueryJob, QueryJobId, QueryLatch,
12-
QueryMode, QueryStackDeferred, QueryStackFrame, QueryState,
11+
ActiveKeyStatus, CycleError, CycleErrorHandling, EnsureMode, QueryCache, QueryJob, QueryJobId,
12+
QueryLatch, QueryMode, QueryStackDeferred, QueryStackFrame, QueryState,
1313
};
1414
use rustc_middle::ty::TyCtxt;
1515
use rustc_middle::verify_ich::incremental_verify_ich;
@@ -276,6 +276,8 @@ fn try_execute_query<'tcx, C: QueryCache, const INCR: bool>(
276276
tcx: TyCtxt<'tcx>,
277277
span: Span,
278278
key: C::Key,
279+
// If present, some previous step has already created a `DepNode` for this
280+
// query+key, which we should reuse instead of creating a new one.
279281
dep_node: Option<DepNode>,
280282
) -> (C::Value, Option<DepNodeIndex>) {
281283
let state = query.query_state(tcx);
@@ -582,23 +584,32 @@ fn try_load_from_disk_and_cache_in_memory<'tcx, C: QueryCache>(
582584
Some((result, dep_node_index))
583585
}
584586

585-
/// Ensure that either this query has all green inputs or been executed.
586-
/// Executing `query::ensure(D)` is considered a read of the dep-node `D`.
587-
/// Returns true if the query should still run.
588-
///
589-
/// This function is particularly useful when executing passes for their
590-
/// side-effects -- e.g., in order to report errors for erroneous programs.
587+
/// Return value struct for [`check_if_ensure_can_skip_execution`].
588+
struct EnsureCanSkip {
589+
/// If true, the current `tcx.ensure_ok()` or `tcx.ensure_done()` query
590+
/// can return early without actually trying to execute.
591+
skip_execution: bool,
592+
/// A dep node that was prepared while checking whether execution can be
593+
/// skipped, to be reused by execution itself if _not_ skipped.
594+
dep_node: Option<DepNode>,
595+
}
596+
597+
/// Checks whether a `tcx.ensure_ok()` or `tcx.ensure_done()` query call can
598+
/// return early without actually trying to execute.
591599
///
592-
/// Note: The optimization is only available during incr. comp.
600+
/// This only makes sense during incremental compilation, because it relies
601+
/// on having the dependency graph (and in some cases a disk-cached value)
602+
/// from the previous incr-comp session.
593603
#[inline(never)]
594-
fn ensure_must_run<'tcx, C: QueryCache>(
604+
fn check_if_ensure_can_skip_execution<'tcx, C: QueryCache>(
595605
query: &'tcx QueryVTable<'tcx, C>,
596606
tcx: TyCtxt<'tcx>,
597607
key: &C::Key,
598-
check_cache: bool,
599-
) -> (bool, Option<DepNode>) {
608+
ensure_mode: EnsureMode,
609+
) -> EnsureCanSkip {
610+
// Queries with `eval_always` should never skip execution.
600611
if query.eval_always {
601-
return (true, None);
612+
return EnsureCanSkip { skip_execution: false, dep_node: None };
602613
}
603614

604615
// Ensuring an anonymous query makes no sense
@@ -615,7 +626,7 @@ fn ensure_must_run<'tcx, C: QueryCache>(
615626
// DepNodeIndex. We must invoke the query itself. The performance cost
616627
// this introduces should be negligible as we'll immediately hit the
617628
// in-memory cache, or another query down the line will.
618-
return (true, Some(dep_node));
629+
return EnsureCanSkip { skip_execution: false, dep_node: Some(dep_node) };
619630
}
620631
Some((serialized_dep_node_index, dep_node_index)) => {
621632
dep_graph.read_index(dep_node_index);
@@ -624,13 +635,21 @@ fn ensure_must_run<'tcx, C: QueryCache>(
624635
}
625636
};
626637

627-
// We do not need the value at all, so do not check the cache.
628-
if !check_cache {
629-
return (false, None);
638+
match ensure_mode {
639+
EnsureMode::Ok => {
640+
// In ensure-ok mode, we can skip execution for this key if the node
641+
// is green. It must have succeeded in the previous session, and
642+
// therefore would succeed in the current session if executed.
643+
EnsureCanSkip { skip_execution: true, dep_node: None }
644+
}
645+
EnsureMode::Done => {
646+
// In ensure-done mode, we can only skip execution for this key if
647+
// there's a disk-cached value available to load later if needed,
648+
// which guarantees the query provider will never run for this key.
649+
let is_loadable = query.is_loadable_from_disk(tcx, key, serialized_dep_node_index);
650+
EnsureCanSkip { skip_execution: is_loadable, dep_node: Some(dep_node) }
651+
}
630652
}
631-
632-
let loadable = query.is_loadable_from_disk(tcx, key, serialized_dep_node_index);
633-
(!loadable, Some(dep_node))
634653
}
635654

636655
#[inline(always)]
@@ -655,14 +674,20 @@ pub(super) fn get_query_incr<'tcx, C: QueryCache>(
655674
) -> Option<C::Value> {
656675
debug_assert!(tcx.dep_graph.is_fully_enabled());
657676

658-
let dep_node = if let QueryMode::Ensure { check_cache } = mode {
659-
let (must_run, dep_node) = ensure_must_run(query, tcx, &key, check_cache);
660-
if !must_run {
661-
return None;
677+
// Check if query execution can be skipped, for `ensure_ok` or `ensure_done`.
678+
// This might have the side-effect of creating a suitable DepNode, which
679+
// we should reuse for execution instead of creating a new one.
680+
let dep_node: Option<DepNode> = match mode {
681+
QueryMode::Ensure { ensure_mode } => {
682+
let EnsureCanSkip { skip_execution, dep_node } =
683+
check_if_ensure_can_skip_execution(query, tcx, &key, ensure_mode);
684+
if skip_execution {
685+
// Return early to skip execution.
686+
return None;
687+
}
688+
dep_node
662689
}
663-
dep_node
664-
} else {
665-
None
690+
QueryMode::Get => None,
666691
};
667692

668693
let (result, dep_node_index) =

0 commit comments

Comments
 (0)