Skip to content

Commit c4c8ab2

Browse files
committed
Auto merge of rust-lang#138629 - Zoxc:graph-anon-hashmap, r=<try>
Only use the new node hashmap for anonymous nodes This is a rebase of rust-lang#112469. cc `@cjgillot`
2 parents a7fc463 + 6737653 commit c4c8ab2

File tree

5 files changed

+115
-53
lines changed

5 files changed

+115
-53
lines changed

compiler/rustc_codegen_ssa/src/base.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -1104,11 +1104,12 @@ pub fn determine_cgu_reuse<'tcx>(tcx: TyCtxt<'tcx>, cgu: &CodegenUnit<'tcx>) ->
11041104
// know that later). If we are not doing LTO, there is only one optimized
11051105
// version of each module, so we re-use that.
11061106
let dep_node = cgu.codegen_dep_node(tcx);
1107-
assert!(
1108-
!tcx.dep_graph.dep_node_exists(&dep_node),
1109-
"CompileCodegenUnit dep-node for CGU `{}` already exists before marking.",
1110-
cgu.name()
1111-
);
1107+
tcx.dep_graph.assert_dep_node_not_yet_allocated_in_current_session(&dep_node, || {
1108+
format!(
1109+
"CompileCodegenUnit dep-node for CGU `{}` already exists before marking.",
1110+
cgu.name()
1111+
)
1112+
});
11121113

11131114
if tcx.try_mark_green(&dep_node) {
11141115
// We can re-use either the pre- or the post-thinlto state. If no LTO is

compiler/rustc_incremental/src/persist/save.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ pub(crate) fn build_dep_graph(
173173
sess.opts.dep_tracking_hash(false).encode(&mut encoder);
174174

175175
Some(DepGraph::new(
176-
&sess.prof,
176+
sess,
177177
prev_graph,
178178
prev_work_products,
179179
encoder,

compiler/rustc_query_system/src/dep_graph/graph.rs

+98-45
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ use std::sync::atomic::{AtomicU32, Ordering};
77

88
use rustc_data_structures::fingerprint::Fingerprint;
99
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
10-
use rustc_data_structures::profiling::{QueryInvocationId, SelfProfilerRef};
10+
use rustc_data_structures::outline;
11+
use rustc_data_structures::profiling::QueryInvocationId;
1112
use rustc_data_structures::sharded::{self, ShardedHashMap};
1213
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
1314
use rustc_data_structures::sync::{AtomicU64, Lock};
1415
use rustc_data_structures::unord::UnordMap;
1516
use rustc_index::IndexVec;
1617
use rustc_macros::{Decodable, Encodable};
1718
use rustc_serialize::opaque::{FileEncodeResult, FileEncoder};
19+
use rustc_session::Session;
1820
use tracing::{debug, instrument};
1921
#[cfg(debug_assertions)]
2022
use {super::debug::EdgeFilter, std::env};
@@ -118,7 +120,7 @@ where
118120

119121
impl<D: Deps> DepGraph<D> {
120122
pub fn new(
121-
profiler: &SelfProfilerRef,
123+
session: &Session,
122124
prev_graph: Arc<SerializedDepGraph>,
123125
prev_work_products: WorkProductMap,
124126
encoder: FileEncoder,
@@ -128,7 +130,7 @@ impl<D: Deps> DepGraph<D> {
128130
let prev_graph_node_count = prev_graph.node_count();
129131

130132
let current = CurrentDepGraph::new(
131-
profiler,
133+
session,
132134
prev_graph_node_count,
133135
encoder,
134136
record_graph,
@@ -139,7 +141,7 @@ impl<D: Deps> DepGraph<D> {
139141
let colors = DepNodeColorMap::new(prev_graph_node_count);
140142

141143
// Instantiate a dependy-less node only once for anonymous queries.
142-
let _green_node_index = current.intern_new_node(
144+
let _green_node_index = current.alloc_new_node(
143145
DepNode { kind: D::DEP_KIND_NULL, hash: current.anon_id_seed.into() },
144146
EdgesVec::new(),
145147
Fingerprint::ZERO,
@@ -353,12 +355,13 @@ impl<D: Deps> DepGraphData<D> {
353355
// in `DepGraph::try_mark_green()`.
354356
// 2. Two distinct query keys get mapped to the same `DepNode`
355357
// (see for example #48923).
356-
assert!(
357-
!self.dep_node_exists(&key),
358-
"forcing query with already existing `DepNode`\n\
358+
self.assert_dep_node_not_yet_allocated_in_current_session(&key, || {
359+
format!(
360+
"forcing query with already existing `DepNode`\n\
359361
- query-key: {arg:?}\n\
360362
- dep-node: {key:?}"
361-
);
363+
)
364+
});
362365

363366
let with_deps = |task_deps| D::with_deps(task_deps, || task(cx, arg));
364367
let (result, edges) = if cx.dep_context().is_eval_always(key.kind) {
@@ -438,7 +441,16 @@ impl<D: Deps> DepGraphData<D> {
438441
hash: self.current.anon_id_seed.combine(hasher.finish()).into(),
439442
};
440443

441-
self.current.intern_new_node(target_dep_node, task_deps, Fingerprint::ZERO)
444+
// The DepNodes generated by the process above are not unique. 2 queries could
445+
// have exactly the same dependencies. However, deserialization does not handle
446+
// duplicated nodes, so we do the deduplication here directly.
447+
//
448+
// As anonymous nodes are a small quantity compared to the full dep-graph, the
449+
// memory impact of this `anon_node_to_index` map remains tolerable, and helps
450+
// us avoid useless growth of the graph with almost-equivalent nodes.
451+
self.current.anon_node_to_index.get_or_insert_with(target_dep_node, || {
452+
self.current.alloc_new_node(target_dep_node, task_deps, Fingerprint::ZERO)
453+
})
442454
}
443455
};
444456

@@ -613,20 +625,24 @@ impl<D: Deps> DepGraph<D> {
613625
}
614626

615627
impl<D: Deps> DepGraphData<D> {
616-
#[inline]
617-
fn dep_node_index_of_opt(&self, dep_node: &DepNode) -> Option<DepNodeIndex> {
628+
fn assert_dep_node_not_yet_allocated_in_current_session<S: std::fmt::Display>(
629+
&self,
630+
dep_node: &DepNode,
631+
msg: impl FnOnce() -> S,
632+
) {
618633
if let Some(prev_index) = self.previous.node_to_index_opt(dep_node) {
619-
self.current.prev_index_to_index.lock()[prev_index]
620-
} else {
621-
self.current.new_node_to_index.get(dep_node)
634+
let current = self.current.prev_index_to_index.lock()[prev_index];
635+
assert!(current.is_none(), "{}", msg())
636+
} else if let Some(nodes_newly_allocated_in_current_session) =
637+
&self.current.nodes_newly_allocated_in_current_session
638+
{
639+
outline(|| {
640+
let seen = nodes_newly_allocated_in_current_session.lock().contains_key(dep_node);
641+
assert!(!seen, "{}", msg());
642+
});
622643
}
623644
}
624645

625-
#[inline]
626-
fn dep_node_exists(&self, dep_node: &DepNode) -> bool {
627-
self.dep_node_index_of_opt(dep_node).is_some()
628-
}
629-
630646
fn node_color(&self, dep_node: &DepNode) -> Option<DepNodeColor> {
631647
if let Some(prev_index) = self.previous.node_to_index_opt(dep_node) {
632648
self.colors.get(prev_index)
@@ -659,11 +675,6 @@ impl<D: Deps> DepGraphData<D> {
659675
}
660676

661677
impl<D: Deps> DepGraph<D> {
662-
#[inline]
663-
pub fn dep_node_exists(&self, dep_node: &DepNode) -> bool {
664-
self.data.as_ref().is_some_and(|data| data.dep_node_exists(dep_node))
665-
}
666-
667678
/// Checks whether a previous work product exists for `v` and, if
668679
/// so, return the path that leads to it. Used to skip doing work.
669680
pub fn previous_work_product(&self, v: &WorkProductId) -> Option<WorkProduct> {
@@ -926,6 +937,16 @@ impl<D: Deps> DepGraph<D> {
926937
self.node_color(dep_node).is_some_and(|c| c.is_green())
927938
}
928939

940+
pub fn assert_dep_node_not_yet_allocated_in_current_session<S: std::fmt::Display>(
941+
&self,
942+
dep_node: &DepNode,
943+
msg: impl FnOnce() -> S,
944+
) {
945+
if let Some(data) = &self.data {
946+
data.assert_dep_node_not_yet_allocated_in_current_session(dep_node, msg)
947+
}
948+
}
949+
929950
/// This method loads all on-disk cacheable query results into memory, so
930951
/// they can be written out to the new cache file again. Most query results
931952
/// will already be in memory but in the case where we marked something as
@@ -1031,24 +1052,24 @@ rustc_index::newtype_index! {
10311052
/// largest in the compiler.
10321053
///
10331054
/// For this reason, we avoid storing `DepNode`s more than once as map
1034-
/// keys. The `new_node_to_index` map only contains nodes not in the previous
1055+
/// keys. The `anon_node_to_index` map only contains nodes of anonymous queries not in the previous
10351056
/// graph, and we map nodes in the previous graph to indices via a two-step
10361057
/// mapping. `SerializedDepGraph` maps from `DepNode` to `SerializedDepNodeIndex`,
10371058
/// and the `prev_index_to_index` vector (which is more compact and faster than
10381059
/// using a map) maps from `SerializedDepNodeIndex` to `DepNodeIndex`.
10391060
///
1040-
/// This struct uses three locks internally. The `data`, `new_node_to_index`,
1061+
/// This struct uses three locks internally. The `data`, `anon_node_to_index`,
10411062
/// and `prev_index_to_index` fields are locked separately. Operations that take
10421063
/// a `DepNodeIndex` typically just access the `data` field.
10431064
///
10441065
/// We only need to manipulate at most two locks simultaneously:
1045-
/// `new_node_to_index` and `data`, or `prev_index_to_index` and `data`. When
1046-
/// manipulating both, we acquire `new_node_to_index` or `prev_index_to_index`
1066+
/// `anon_node_to_index` and `data`, or `prev_index_to_index` and `data`. When
1067+
/// manipulating both, we acquire `anon_node_to_index` or `prev_index_to_index`
10471068
/// first, and `data` second.
10481069
pub(super) struct CurrentDepGraph<D: Deps> {
10491070
encoder: GraphEncoder<D>,
1050-
new_node_to_index: ShardedHashMap<DepNode, DepNodeIndex>,
10511071
prev_index_to_index: Lock<IndexVec<SerializedDepNodeIndex, Option<DepNodeIndex>>>,
1072+
anon_node_to_index: ShardedHashMap<DepNode, DepNodeIndex>,
10521073

10531074
/// This is used to verify that fingerprints do not change between the creation of a node
10541075
/// and its recomputation.
@@ -1060,6 +1081,14 @@ pub(super) struct CurrentDepGraph<D: Deps> {
10601081
#[cfg(debug_assertions)]
10611082
forbidden_edge: Option<EdgeFilter>,
10621083

1084+
/// Used to verify the absence of hash collisions among DepNodes.
1085+
/// This field is only `Some` if the `-Z incremental_verify_ich` option is present
1086+
/// or if `debug_assertions` are enabled.
1087+
///
1088+
/// The map contains all DepNodes that have been allocated in the current session so far and
1089+
/// for which there is no equivalent in the previous session.
1090+
nodes_newly_allocated_in_current_session: Option<Lock<FxHashMap<DepNode, DepNodeIndex>>>,
1091+
10631092
/// Anonymous `DepNode`s are nodes whose IDs we compute from the list of
10641093
/// their edges. This has the beneficial side-effect that multiple anonymous
10651094
/// nodes can be coalesced into one without changing the semantics of the
@@ -1081,7 +1110,7 @@ pub(super) struct CurrentDepGraph<D: Deps> {
10811110

10821111
impl<D: Deps> CurrentDepGraph<D> {
10831112
fn new(
1084-
profiler: &SelfProfilerRef,
1113+
session: &Session,
10851114
prev_graph_node_count: usize,
10861115
encoder: FileEncoder,
10871116
record_graph: bool,
@@ -1107,16 +1136,20 @@ impl<D: Deps> CurrentDepGraph<D> {
11071136

11081137
let new_node_count_estimate = 102 * prev_graph_node_count / 100 + 200;
11091138

1139+
let new_node_dbg =
1140+
session.opts.unstable_opts.incremental_verify_ich || cfg!(debug_assertions);
1141+
11101142
CurrentDepGraph {
11111143
encoder: GraphEncoder::new(
11121144
encoder,
11131145
prev_graph_node_count,
11141146
record_graph,
11151147
record_stats,
1116-
profiler,
1148+
&session.prof,
11171149
previous,
11181150
),
1119-
new_node_to_index: ShardedHashMap::with_capacity(
1151+
anon_node_to_index: ShardedHashMap::with_capacity(
1152+
// FIXME: The count estimate is off as anon nodes are only a portion of the nodes.
11201153
new_node_count_estimate / sharded::shards(),
11211154
),
11221155
prev_index_to_index: Lock::new(IndexVec::from_elem_n(None, prev_graph_node_count)),
@@ -1125,6 +1158,12 @@ impl<D: Deps> CurrentDepGraph<D> {
11251158
forbidden_edge,
11261159
#[cfg(debug_assertions)]
11271160
fingerprints: Lock::new(IndexVec::from_elem_n(None, new_node_count_estimate)),
1161+
nodes_newly_allocated_in_current_session: new_node_dbg.then(|| {
1162+
Lock::new(FxHashMap::with_capacity_and_hasher(
1163+
new_node_count_estimate,
1164+
Default::default(),
1165+
))
1166+
}),
11281167
total_read_count: AtomicU64::new(0),
11291168
total_duplicate_read_count: AtomicU64::new(0),
11301169
}
@@ -1142,19 +1181,31 @@ impl<D: Deps> CurrentDepGraph<D> {
11421181
/// Writes the node to the current dep-graph and allocates a `DepNodeIndex` for it.
11431182
/// Assumes that this is a node that has no equivalent in the previous dep-graph.
11441183
#[inline(always)]
1145-
fn intern_new_node(
1184+
fn alloc_new_node(
11461185
&self,
11471186
key: DepNode,
11481187
edges: EdgesVec,
11491188
current_fingerprint: Fingerprint,
11501189
) -> DepNodeIndex {
1151-
let dep_node_index = self
1152-
.new_node_to_index
1153-
.get_or_insert_with(key, || self.encoder.send(key, current_fingerprint, edges));
1190+
let dep_node_index = self.encoder.send(key, current_fingerprint, edges);
11541191

11551192
#[cfg(debug_assertions)]
11561193
self.record_edge(dep_node_index, key, current_fingerprint);
11571194

1195+
if let Some(ref nodes_newly_allocated_in_current_session) =
1196+
self.nodes_newly_allocated_in_current_session
1197+
{
1198+
outline(|| {
1199+
if nodes_newly_allocated_in_current_session
1200+
.lock()
1201+
.insert(key, dep_node_index)
1202+
.is_some()
1203+
{
1204+
panic!("Found duplicate dep-node {key:?}");
1205+
}
1206+
});
1207+
}
1208+
11581209
dep_node_index
11591210
}
11601211

@@ -1209,7 +1260,7 @@ impl<D: Deps> CurrentDepGraph<D> {
12091260
let fingerprint = fingerprint.unwrap_or(Fingerprint::ZERO);
12101261

12111262
// This is a new node: it didn't exist in the previous compilation session.
1212-
let dep_node_index = self.intern_new_node(key, edges, fingerprint);
1263+
let dep_node_index = self.alloc_new_node(key, edges, fingerprint);
12131264

12141265
(dep_node_index, None)
12151266
}
@@ -1248,7 +1299,10 @@ impl<D: Deps> CurrentDepGraph<D> {
12481299
) {
12491300
let node = &prev_graph.index_to_node(prev_index);
12501301
debug_assert!(
1251-
!self.new_node_to_index.get(node).is_some(),
1302+
!self
1303+
.nodes_newly_allocated_in_current_session
1304+
.as_ref()
1305+
.map_or(false, |set| set.lock().contains_key(node)),
12521306
"node from previous graph present in new node collection"
12531307
);
12541308
}
@@ -1370,13 +1424,12 @@ fn panic_on_forbidden_read<D: Deps>(data: &DepGraphData<D>, dep_node_index: DepN
13701424
}
13711425
}
13721426

1373-
if dep_node.is_none() {
1374-
// Try to find it among the new nodes
1375-
for shard in data.current.new_node_to_index.lock_shards() {
1376-
if let Some((node, _)) = shard.iter().find(|(_, index)| *index == dep_node_index) {
1377-
dep_node = Some(*node);
1378-
break;
1379-
}
1427+
if dep_node.is_none()
1428+
&& let Some(nodes) = &data.current.nodes_newly_allocated_in_current_session
1429+
{
1430+
// Try to find it among the nodes allocated so far in this session
1431+
if let Some((node, _)) = nodes.lock().iter().find(|&(_, index)| *index == dep_node_index) {
1432+
dep_node = Some(*node);
13801433
}
13811434
}
13821435

compiler/rustc_query_system/src/dep_graph/serialized.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,14 @@ impl SerializedDepGraph {
252252
.collect();
253253

254254
for (idx, node) in nodes.iter_enumerated() {
255-
index[node.kind.as_usize()].insert(node.hash, idx);
255+
if index[node.kind.as_usize()].insert(node.hash, idx).is_some() {
256+
panic!(
257+
"Error: A dep graph node does not have an unique index. \
258+
Running a clean build on a nightly compiler with `-Z incremental-verify-ich` \
259+
can help narrow down the issue for reporting. A clean build may also work around the issue.\n
260+
DepNode: {node:?}"
261+
)
262+
}
256263
}
257264

258265
Arc::new(SerializedDepGraph {

compiler/rustc_session/src/options.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -2227,7 +2227,8 @@ options! {
22272227
incremental_verify_ich: bool = (false, parse_bool, [UNTRACKED],
22282228
"verify extended properties for incr. comp. (default: no):
22292229
- hashes of green query instances
2230-
- hash collisions of query keys"),
2230+
- hash collisions of query keys
2231+
- hash collisions when creating dep-nodes"),
22312232
inline_llvm: bool = (true, parse_bool, [TRACKED],
22322233
"enable LLVM inlining (default: yes)"),
22332234
inline_mir: Option<bool> = (None, parse_opt_bool, [TRACKED],

0 commit comments

Comments
 (0)