Skip to content

Commit 4157667

Browse files
committed
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 128de40 commit 4157667

File tree

4 files changed

+77
-13
lines changed

4 files changed

+77
-13
lines changed

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

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +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() {}
90116
}
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()
91138
}
92139
}
93140

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

@@ -275,7 +278,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
275278
// type variable. These will typically default to `!`, unless
276279
// we find later that they are *also* reachable from some
277280
// other type variable outside this set.
278-
let mut roots_reachable_from_diverging = FxHashSet::default();
281+
let mut roots_reachable_from_diverging = DepthFirstSearch::new(&coercion_graph);
279282
let mut diverging_vids = vec![];
280283
let mut non_diverging_vids = vec![];
281284
for unsolved_vid in unsolved_vids {
@@ -288,16 +291,21 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
288291
);
289292
if diverging_roots.contains(&root_vid) {
290293
diverging_vids.push(unsolved_vid);
294+
roots_reachable_from_diverging.push_start_node(root_vid);
295+
291296
debug!(
292297
"calculate_diverging_fallback: root_vid={:?} reaches {:?}",
293298
root_vid,
294299
coercion_graph.depth_first_search(root_vid).collect::<Vec<_>>()
295300
);
296-
roots_reachable_from_diverging.extend(coercion_graph.depth_first_search(root_vid));
301+
302+
// drain the iterator to visit all nodes reachable from this node
303+
roots_reachable_from_diverging.complete_search();
297304
} else {
298305
non_diverging_vids.push(unsolved_vid);
299306
}
300307
}
308+
301309
debug!(
302310
"calculate_diverging_fallback: roots_reachable_from_diverging={:?}",
303311
roots_reachable_from_diverging,
@@ -307,13 +315,14 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
307315
// diverging variable, and then compute the set reachable from
308316
// N0, which we call N. These are the *non-diverging* type
309317
// variables. (Note that this set consists of "root variables".)
310-
let mut roots_reachable_from_non_diverging = FxHashSet::default();
318+
let mut roots_reachable_from_non_diverging = DepthFirstSearch::new(&coercion_graph);
311319
for &non_diverging_vid in &non_diverging_vids {
312320
let root_vid = self.infcx.root_var(non_diverging_vid);
313-
if roots_reachable_from_diverging.contains(&root_vid) {
321+
if roots_reachable_from_diverging.visited(root_vid) {
314322
continue;
315323
}
316-
roots_reachable_from_non_diverging.extend(coercion_graph.depth_first_search(root_vid));
324+
roots_reachable_from_non_diverging.push_start_node(root_vid);
325+
roots_reachable_from_non_diverging.complete_search();
317326
}
318327
debug!(
319328
"calculate_diverging_fallback: roots_reachable_from_non_diverging={:?}",
@@ -329,7 +338,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
329338
let root_vid = self.infcx.root_var(diverging_vid);
330339
let can_reach_non_diverging = coercion_graph
331340
.depth_first_search(root_vid)
332-
.any(|n| roots_reachable_from_non_diverging.contains(&n));
341+
.any(|n| roots_reachable_from_non_diverging.visited(n));
333342
if can_reach_non_diverging {
334343
debug!("fallback to (): {:?}", diverging_vid);
335344
diverging_fallback.insert(diverging_ty, self.tcx.types.unit);

0 commit comments

Comments
 (0)