Skip to content

Commit c48ee1c

Browse files
nikomatsakisMark-Simulacrum
authored andcommitted
optimization: use a single DepthFirstSearch instead of hashsets
Extend the `DepthFirstSearch` iterator so that it can be re-used and extended with add'l start nodes. Then replace the FxHashSets of nodes we were using in the fallback analysis with a single iterator. This way we won't re-walk portions of the graph that are reached more than once, and we also do less allocation etc.
1 parent 141246c commit c48ee1c

File tree

4 files changed

+78
-13
lines changed

4 files changed

+78
-13
lines changed

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

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,58 @@ impl<G> DepthFirstSearch<'graph, G>
8383
where
8484
G: ?Sized + DirectedGraph + WithNumNodes + WithSuccessors,
8585
{
86-
pub fn new(graph: &'graph G, start_node: G::Node) -> Self {
87-
let mut visited = BitSet::new_empty(graph.num_nodes());
88-
visited.insert(start_node);
89-
Self { graph, stack: vec![start_node], visited }
86+
pub fn new(graph: &'graph G) -> Self {
87+
Self { graph, stack: vec![], visited: BitSet::new_empty(graph.num_nodes()) }
88+
}
89+
90+
/// Version of `push_start_node` that is convenient for chained
91+
/// use.
92+
pub fn with_start_node(mut self, start_node: G::Node) -> Self {
93+
self.push_start_node(start_node);
94+
self
95+
}
96+
97+
/// Pushes another start node onto the stack. If the node
98+
/// has not already been visited, then you will be able to
99+
/// walk its successors (and so forth) after the current
100+
/// contents of the stack are drained. If multiple start nodes
101+
/// are added into the walk, then their mutual successors
102+
/// will all be walked. You can use this method once the
103+
/// iterator has been completely drained to add additional
104+
/// start nodes.
105+
pub fn push_start_node(&mut self, start_node: G::Node) {
106+
if self.visited.insert(start_node) {
107+
self.stack.push(start_node);
108+
}
109+
}
110+
111+
/// Searches all nodes reachable from the current start nodes.
112+
/// This is equivalent to just invoke `next` repeatedly until
113+
/// you get a `None` result.
114+
pub fn complete_search(&mut self) {
115+
while let Some(_) = self.next() {}
116+
}
117+
118+
/// Returns true if node has been visited thus far.
119+
/// A node is considered "visited" once it is pushed
120+
/// onto the internal stack; it may not yet have been yielded
121+
/// from the iterator. This method is best used after
122+
/// the iterator is completely drained.
123+
pub fn visited(&self, node: G::Node) -> bool {
124+
self.visited.contains(node)
125+
}
126+
}
127+
128+
impl<G> std::fmt::Debug for DepthFirstSearch<'_, G>
129+
where
130+
G: ?Sized + DirectedGraph + WithNumNodes + WithSuccessors,
131+
{
132+
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133+
let mut f = fmt.debug_set();
134+
for n in self.visited.iter() {
135+
f.entry(&n);
136+
}
137+
f.finish()
90138
}
91139
}
92140

compiler/rustc_data_structures/src/graph/iterate/tests.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ fn is_cyclic() {
2525
fn dfs() {
2626
let graph = TestGraph::new(0, &[(0, 1), (0, 2), (1, 3), (2, 3), (3, 0)]);
2727

28-
let result: Vec<usize> = DepthFirstSearch::new(&graph, 0).collect();
28+
let result: Vec<usize> = DepthFirstSearch::new(&graph).with_start_node(0).collect();
2929
assert_eq!(result, vec![0, 2, 3, 1]);
3030
}
31+
32+
#[test]
33+
fn dfs_debug() {
34+
let graph = TestGraph::new(0, &[(0, 1), (0, 2), (1, 3), (2, 3), (3, 0)]);
35+
let mut dfs = DepthFirstSearch::new(&graph).with_start_node(0);
36+
while let Some(_) = dfs.next() {}
37+
assert_eq!(format!("{{0, 1, 2, 3}}"), format!("{:?}", dfs));
38+
}

compiler/rustc_data_structures/src/graph/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ where
3232
where
3333
Self: WithNumNodes,
3434
{
35-
iterate::DepthFirstSearch::new(self, from)
35+
iterate::DepthFirstSearch::new(self).with_start_node(from)
3636
}
3737
}
3838

compiler/rustc_typeck/src/check/fallback.rs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use crate::check::FnCtxt;
22
use rustc_data_structures::{
3-
fx::FxHashMap, graph::vec_graph::VecGraph, graph::WithSuccessors, stable_set::FxHashSet,
3+
fx::FxHashMap,
4+
graph::WithSuccessors,
5+
graph::{iterate::DepthFirstSearch, vec_graph::VecGraph},
6+
stable_set::FxHashSet,
47
};
58
use rustc_middle::ty::{self, Ty};
69

@@ -280,7 +283,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
280283
// type variable. These will typically default to `!`, unless
281284
// we find later that they are *also* reachable from some
282285
// other type variable outside this set.
283-
let mut roots_reachable_from_diverging = FxHashSet::default();
286+
let mut roots_reachable_from_diverging = DepthFirstSearch::new(&coercion_graph);
284287
let mut diverging_vids = vec![];
285288
let mut non_diverging_vids = vec![];
286289
for unsolved_vid in unsolved_vids {
@@ -293,16 +296,21 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
293296
);
294297
if diverging_roots.contains(&root_vid) {
295298
diverging_vids.push(unsolved_vid);
299+
roots_reachable_from_diverging.push_start_node(root_vid);
300+
296301
debug!(
297302
"calculate_diverging_fallback: root_vid={:?} reaches {:?}",
298303
root_vid,
299304
coercion_graph.depth_first_search(root_vid).collect::<Vec<_>>()
300305
);
301-
roots_reachable_from_diverging.extend(coercion_graph.depth_first_search(root_vid));
306+
307+
// drain the iterator to visit all nodes reachable from this node
308+
roots_reachable_from_diverging.complete_search();
302309
} else {
303310
non_diverging_vids.push(unsolved_vid);
304311
}
305312
}
313+
306314
debug!(
307315
"calculate_diverging_fallback: roots_reachable_from_diverging={:?}",
308316
roots_reachable_from_diverging,
@@ -312,13 +320,14 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
312320
// diverging variable, and then compute the set reachable from
313321
// N0, which we call N. These are the *non-diverging* type
314322
// variables. (Note that this set consists of "root variables".)
315-
let mut roots_reachable_from_non_diverging = FxHashSet::default();
323+
let mut roots_reachable_from_non_diverging = DepthFirstSearch::new(&coercion_graph);
316324
for &non_diverging_vid in &non_diverging_vids {
317325
let root_vid = self.infcx.root_var(non_diverging_vid);
318-
if roots_reachable_from_diverging.contains(&root_vid) {
326+
if roots_reachable_from_diverging.visited(root_vid) {
319327
continue;
320328
}
321-
roots_reachable_from_non_diverging.extend(coercion_graph.depth_first_search(root_vid));
329+
roots_reachable_from_non_diverging.push_start_node(root_vid);
330+
roots_reachable_from_non_diverging.complete_search();
322331
}
323332
debug!(
324333
"calculate_diverging_fallback: roots_reachable_from_non_diverging={:?}",
@@ -334,7 +343,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
334343
let root_vid = self.infcx.root_var(diverging_vid);
335344
let can_reach_non_diverging = coercion_graph
336345
.depth_first_search(root_vid)
337-
.any(|n| roots_reachable_from_non_diverging.contains(&n));
346+
.any(|n| roots_reachable_from_non_diverging.visited(n));
338347
if can_reach_non_diverging {
339348
debug!("fallback to (): {:?}", diverging_vid);
340349
diverging_fallback.insert(diverging_ty, self.tcx.types.unit);

0 commit comments

Comments
 (0)