Skip to content

Commit e9b2658

Browse files
authored
Rollup merge of #157352 - xmakro:fix/parallel-dep-graph-determinism, r=petrochenkov
Make the retained dep graph deterministic under the parallel frontend Part of #154314. The in-memory dep graph kept for `-Zquery-dep-graph` is built by `GraphEncoder::record`, which pushed each node using `try_lock` and silently dropped the node when the lock was contended. Single-threaded the only contention is a query forced re-entrantly from within `with_retained_dep_graph`, so dropping was harmless. Under the parallel frontend several encoder threads record nodes at the same time, so a contended `try_lock` dropped nodes and edges at random, leaving the retained graph nondeterministic. The dep-graph ui tests, which assert on its contents, then failed intermittently. The reentrancy came from `check_paths` emitting its diagnostics while holding the retained graph: the error path calls `def_path_str`, which can create new dep nodes and re-enter the encoder. This reads the path results into owned values while the lock is held and emits the diagnostics afterwards, so `record` never re-enters while the lock is held. `record` can then block on the lock unconditionally instead of using `try_lock`, so concurrent encoders never drop a node or edge.
2 parents 0831b34 + d00722c commit e9b2658

12 files changed

Lines changed: 52 additions & 38 deletions

File tree

compiler/rustc_data_structures/src/graph/linked_graph/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,19 @@ mod tests;
4545
/// This graph implementation predates the later [graph traits](crate::graph),
4646
/// and does not implement those traits, so it has its own implementations of a
4747
/// few basic graph algorithms.
48+
#[derive(Clone)]
4849
pub struct LinkedGraph<N, E> {
4950
nodes: IndexVec<NodeIndex, Node<N>>,
5051
edges: Vec<Edge<E>>,
5152
}
5253

54+
#[derive(Clone)]
5355
pub struct Node<N> {
5456
first_edge: [EdgeIndex; 2], // see module comment
5557
pub data: Option<N>,
5658
}
5759

58-
#[derive(Debug)]
60+
#[derive(Clone, Debug)]
5961
pub struct Edge<E> {
6062
next_edge: [EdgeIndex; 2], // see module comment
6163
source: NodeIndex,

compiler/rustc_incremental/src/assert_dep_graph.rs

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,14 @@ use crate::errors;
5757
#[allow(missing_docs)]
5858
pub(crate) fn assert_dep_graph(tcx: TyCtxt<'_>) {
5959
tcx.dep_graph.with_ignore(|| {
60+
// Clone the retained dep graph once and share it between the graph dump and the path
61+
// checks below, rather than locking and cloning it separately for each.
62+
let retained_dep_graph = tcx.dep_graph.retained_dep_graph();
63+
6064
if tcx.sess.opts.unstable_opts.dump_dep_graph {
61-
tcx.dep_graph.with_retained_dep_graph(dump_graph);
65+
if let Some(graph) = &retained_dep_graph {
66+
dump_graph(graph);
67+
}
6268
}
6369

6470
if !tcx.sess.opts.unstable_opts.query_dep_graph {
@@ -92,7 +98,7 @@ pub(crate) fn assert_dep_graph(tcx: TyCtxt<'_>) {
9298
}
9399

94100
// Check paths.
95-
check_paths(tcx, &if_this_changed, &then_this_would_need);
101+
check_paths(tcx, retained_dep_graph.as_ref(), &if_this_changed, &then_this_would_need);
96102
})
97103
}
98104

@@ -172,30 +178,33 @@ impl<'tcx> Visitor<'tcx> for IfThisChanged<'tcx> {
172178
}
173179
}
174180

175-
fn check_paths<'tcx>(tcx: TyCtxt<'tcx>, if_this_changed: &Sources, then_this_would_need: &Targets) {
176-
// Return early here so as not to construct the query, which is not cheap.
181+
fn check_paths<'tcx>(
182+
tcx: TyCtxt<'tcx>,
183+
retained_dep_graph: Option<&RetainedDepGraph>,
184+
if_this_changed: &Sources,
185+
then_this_would_need: &Targets,
186+
) {
177187
if if_this_changed.is_empty() {
178188
for &(target_span, _, _, _) in then_this_would_need {
179189
tcx.dcx().emit_err(errors::MissingIfThisChanged { span: target_span });
180190
}
181191
return;
182192
}
183-
tcx.dep_graph.with_retained_dep_graph(|query| {
184-
for &(_, source_def_id, ref source_dep_node) in if_this_changed {
185-
let dependents = query.transitive_predecessors(source_dep_node);
186-
for &(target_span, ref target_pass, _, ref target_dep_node) in then_this_would_need {
187-
if !dependents.contains(&target_dep_node) {
188-
tcx.dcx().emit_err(errors::NoPath {
189-
span: target_span,
190-
source: tcx.def_path_str(source_def_id),
191-
target: *target_pass,
192-
});
193-
} else {
194-
tcx.dcx().emit_err(errors::Ok { span: target_span });
195-
}
193+
let Some(query) = retained_dep_graph else { return };
194+
for &(_, source_def_id, ref source_dep_node) in if_this_changed {
195+
let dependents = query.transitive_predecessors(source_dep_node);
196+
for &(target_span, ref target_pass, _, ref target_dep_node) in then_this_would_need {
197+
if !dependents.contains(&target_dep_node) {
198+
tcx.dcx().emit_err(errors::NoPath {
199+
span: target_span,
200+
source: tcx.def_path_str(source_def_id),
201+
target: *target_pass,
202+
});
203+
} else {
204+
tcx.dcx().emit_err(errors::Ok { span: target_span });
196205
}
197206
}
198-
});
207+
}
199208
}
200209

201210
fn dump_graph(graph: &RetainedDepGraph) {

compiler/rustc_middle/src/dep_graph/graph.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,12 @@ impl DepGraph {
195195
self.data.is_some()
196196
}
197197

198-
pub fn with_retained_dep_graph(&self, f: impl Fn(&RetainedDepGraph)) {
199-
if let Some(data) = &self.data {
200-
data.current.encoder.with_retained_dep_graph(f)
201-
}
198+
/// Returns a clone of the in-memory retained dep graph, if it is being built
199+
/// (i.e. `-Zquery-dep-graph` is set). Cloning rather than exposing the lock keeps
200+
/// callers from holding it while forcing queries, which would deadlock against a
201+
/// reentrant `record` under the parallel frontend.
202+
pub fn retained_dep_graph(&self) -> Option<RetainedDepGraph> {
203+
self.data.as_ref().and_then(|data| data.current.encoder.retained_dep_graph())
202204
}
203205

204206
pub fn assert_ignored(&self) {

compiler/rustc_middle/src/dep_graph/retained.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use super::{DepNode, DepNodeIndex};
99
/// Normally, dependencies recorded during the current session are written to
1010
/// disk and then forgotten, to avoid wasting memory on information that is
1111
/// not needed when the compiler is working correctly.
12+
#[derive(Clone)]
1213
pub struct RetainedDepGraph {
1314
pub inner: LinkedGraph<DepNode, ()>,
1415
pub indices: FxHashMap<DepNode, NodeIndex>,

compiler/rustc_middle/src/dep_graph/serialized.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -636,10 +636,12 @@ impl EncoderState {
636636

637637
// Outline the build of the full dep graph as it's typically disabled and cold.
638638
outline(move || {
639-
// Do not ICE when a query is called from within `with_query`.
640-
if let Some(retained_graph) = &mut retained_graph.try_lock() {
641-
retained_graph.push(index, *node, &edges);
642-
}
639+
// Block on the lock rather than using `try_lock`: under the parallel frontend
640+
// several threads record nodes concurrently, and dropping a node on lock
641+
// contention would make the retained graph nondeterministic. Readers take a
642+
// clone of the graph (`retained_dep_graph`) rather than holding the lock, so
643+
// this never deadlocks against a reentrant `record`.
644+
retained_graph.lock().push(index, *node, &edges);
643645
});
644646
}
645647

@@ -874,10 +876,8 @@ impl GraphEncoder {
874876
GraphEncoder { status, retained_graph, profiler: sess.prof.clone() }
875877
}
876878

877-
pub(crate) fn with_retained_dep_graph(&self, f: impl Fn(&RetainedDepGraph)) {
878-
if let Some(retained_graph) = &self.retained_graph {
879-
f(&retained_graph.lock())
880-
}
879+
pub(crate) fn retained_dep_graph(&self) -> Option<RetainedDepGraph> {
880+
self.retained_graph.as_ref().map(|retained_graph| retained_graph.lock().clone())
881881
}
882882

883883
/// Encodes a node that does not exists in the previous graph.

tests/ui/dep-graph/dep-graph-assoc-type-codegen.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Test that when a trait impl changes, fns whose body uses that trait
22
// must also be recompiled.
3-
//@ ignore-parallel-frontend dep graph
3+
44
//@ incremental
55
//@ compile-flags: -Z query-dep-graph
66

tests/ui/dep-graph/dep-graph-caller-callee.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
//@ incremental
55
//@ compile-flags: -Z query-dep-graph
6-
//@ ignore-parallel-frontend dep graph
6+
77
#![feature(rustc_attrs)]
88
#![allow(dead_code)]
99

tests/ui/dep-graph/dep-graph-struct-signature.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Test cases where a changing struct appears in the signature of fns
22
// and methods.
3-
//@ ignore-parallel-frontend dep graph
3+
44
//@ incremental
55
//@ compile-flags: -Z query-dep-graph
66

tests/ui/dep-graph/dep-graph-trait-impl-two-traits-same-method.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Test that adding an impl to a trait `Foo` DOES affect functions
22
// that only use `Bar` if they have methods in common.
3-
//@ ignore-parallel-frontend dep graph
3+
44
//@ incremental
55
//@ compile-flags: -Z query-dep-graph
66

tests/ui/dep-graph/dep-graph-trait-impl.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Test that when a trait impl changes, fns whose body uses that trait
22
// must also be recompiled.
3-
//@ ignore-parallel-frontend dep graph
3+
44
//@ incremental
55
//@ compile-flags: -Z query-dep-graph
66

0 commit comments

Comments
 (0)