Skip to content

Commit a7e2e33

Browse files
committed
Auto merge of #91919 - Aaron1011:query-recursive-read, r=michaelwoerister
Don't perform any new queries while reading a query result on disk In addition to being very confusing, this can cause us to add dep node edges between two queries that would not otherwise have an edge. We now panic if any new dep node edges are created during the deserialization of a query result. This requires serializing the full `AdtDef` to disk, instead of just serializing the `DefId` and invoking the `adt_def` query during deserialization. I'll probably split this up into several smaller PRs for perf runs.
2 parents 488acf8 + 27ed52c commit a7e2e33

File tree

2 files changed

+76
-2
lines changed

2 files changed

+76
-2
lines changed

compiler/rustc_query_system/src/dep_graph/graph.rs

+69
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,62 @@ impl<K: DepKind> DepGraph<K> {
177177
K::with_deps(None, op)
178178
}
179179

180+
/// Used to wrap the deserialization of a query result from disk,
181+
/// This method enforces that no new `DepNodes` are created during
182+
/// query result deserialization.
183+
///
184+
/// Enforcing this makes the query dep graph simpler - all nodes
185+
/// must be created during the query execution, and should be
186+
/// created from inside the 'body' of a query (the implementation
187+
/// provided by a particular compiler crate).
188+
///
189+
/// Consider the case of three queries `A`, `B`, and `C`, where
190+
/// `A` invokes `B` and `B` invokes `C`:
191+
///
192+
/// `A -> B -> C`
193+
///
194+
/// Suppose that decoding the result of query `B` required re-computing
195+
/// the query `C`. If we did not create a fresh `TaskDeps` when
196+
/// decoding `B`, we would still be using the `TaskDeps` for query `A`
197+
/// (if we needed to re-execute `A`). This would cause us to create
198+
/// a new edge `A -> C`. If this edge did not previously
199+
/// exist in the `DepGraph`, then we could end up with a different
200+
/// `DepGraph` at the end of compilation, even if there were no
201+
/// meaningful changes to the overall program (e.g. a newline was added).
202+
/// In addition, this edge might cause a subsequent compilation run
203+
/// to try to force `C` before marking other necessary nodes green. If
204+
/// `C` did not exist in the new compilation session, then we could
205+
/// get an ICE. Normally, we would have tried (and failed) to mark
206+
/// some other query green (e.g. `item_children`) which was used
207+
/// to obtain `C`, which would prevent us from ever trying to force
208+
/// a non-existent `D`.
209+
///
210+
/// It might be possible to enforce that all `DepNode`s read during
211+
/// deserialization already exist in the previous `DepGraph`. In
212+
/// the above example, we would invoke `D` during the deserialization
213+
/// of `B`. Since we correctly create a new `TaskDeps` from the decoding
214+
/// of `B`, this would result in an edge `B -> D`. If that edge already
215+
/// existed (with the same `DepPathHash`es), then it should be correct
216+
/// to allow the invocation of the query to proceed during deserialization
217+
/// of a query result. We would merely assert that the dep-graph fragment
218+
/// that would have been added by invoking `C` while decoding `B`
219+
/// is equivalent to the dep-graph fragment that we already instantiated for B
220+
/// (at the point where we successfully marked B as green).
221+
///
222+
/// However, this would require additional complexity
223+
/// in the query infrastructure, and is not currently needed by the
224+
/// decoding of any query results. Should the need arise in the future,
225+
/// we should consider extending the query system with this functionality.
226+
pub fn with_query_deserialization<OP, R>(&self, op: OP) -> R
227+
where
228+
OP: FnOnce() -> R,
229+
{
230+
let mut deps = TaskDeps::default();
231+
deps.read_allowed = false;
232+
let deps = Lock::new(deps);
233+
K::with_deps(Some(&deps), op)
234+
}
235+
180236
/// Starts a new dep-graph task. Dep-graph tasks are specified
181237
/// using a free function (`task`) and **not** a closure -- this
182238
/// is intentional because we want to exercise tight control over
@@ -257,6 +313,7 @@ impl<K: DepKind> DepGraph<K> {
257313
reads: SmallVec::new(),
258314
read_set: Default::default(),
259315
phantom_data: PhantomData,
316+
read_allowed: true,
260317
}))
261318
};
262319
let result = K::with_deps(task_deps.as_ref(), || task(cx, arg));
@@ -368,6 +425,11 @@ impl<K: DepKind> DepGraph<K> {
368425
if let Some(task_deps) = task_deps {
369426
let mut task_deps = task_deps.lock();
370427
let task_deps = &mut *task_deps;
428+
429+
if !task_deps.read_allowed {
430+
panic!("Illegal read of: {:?}", dep_node_index);
431+
}
432+
371433
if cfg!(debug_assertions) {
372434
data.current.total_read_count.fetch_add(1, Relaxed);
373435
}
@@ -1129,6 +1191,12 @@ pub struct TaskDeps<K> {
11291191
reads: EdgesVec,
11301192
read_set: FxHashSet<DepNodeIndex>,
11311193
phantom_data: PhantomData<DepNode<K>>,
1194+
/// Whether or not we allow `DepGraph::read_index` to run.
1195+
/// This is normally true, except inside `with_query_deserialization`,
1196+
/// where it set to `false` to enforce that no new `DepNode` edges are
1197+
/// created. See the documentation of `with_query_deserialization` for
1198+
/// more details.
1199+
read_allowed: bool,
11321200
}
11331201

11341202
impl<K> Default for TaskDeps<K> {
@@ -1139,6 +1207,7 @@ impl<K> Default for TaskDeps<K> {
11391207
reads: EdgesVec::new(),
11401208
read_set: FxHashSet::default(),
11411209
phantom_data: PhantomData,
1210+
read_allowed: true,
11421211
}
11431212
}
11441213
}

compiler/rustc_query_system/src/query/plumbing.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ use crate::query::job::{
99
report_cycle, QueryInfo, QueryJob, QueryJobId, QueryJobInfo, QueryShardJobId,
1010
};
1111
use crate::query::{QueryContext, QueryMap, QuerySideEffects, QueryStackFrame};
12-
1312
use rustc_data_structures::fingerprint::Fingerprint;
1413
use rustc_data_structures::fx::{FxHashMap, FxHasher};
1514
#[cfg(parallel_compiler)]
@@ -515,7 +514,13 @@ where
515514
// Some things are never cached on disk.
516515
if query.cache_on_disk {
517516
let prof_timer = tcx.dep_context().profiler().incr_cache_loading();
518-
let result = query.try_load_from_disk(tcx, prev_dep_node_index);
517+
518+
// The call to `with_query_deserialization` enforces that no new `DepNodes`
519+
// are created during deserialization. See the docs of that method for more
520+
// details.
521+
let result = dep_graph
522+
.with_query_deserialization(|| query.try_load_from_disk(tcx, prev_dep_node_index));
523+
519524
prof_timer.finish_with_query_invocation_id(dep_node_index.into());
520525

521526
if let Some(result) = result {

0 commit comments

Comments
 (0)