Skip to content

Commit eb597f5

Browse files
committed
Remove recursion from sccc walking
This allows constructing the sccc for large that visit many nodes before finding a single cycle of sccc, for example lists. When used to find dependencies in borrow checking the list case is what occurs in very long functions.
1 parent 355904d commit eb597f5

File tree

1 file changed

+182
-73
lines changed
  • compiler/rustc_data_structures/src/graph/scc

1 file changed

+182
-73
lines changed

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

+182-73
Original file line numberDiff line numberDiff line change
@@ -231,20 +231,30 @@ where
231231

232232
let scc_indices = (0..num_nodes)
233233
.map(G::Node::new)
234-
.map(|node| match this.walk_node(0, node) {
234+
.map(|node| match this.start_walk_from(node) {
235235
WalkReturn::Complete { scc_index } => scc_index,
236-
WalkReturn::Cycle { min_depth } => {
237-
panic!("`walk_node(0, {:?})` returned cycle with depth {:?}", node, min_depth)
238-
}
236+
WalkReturn::Cycle { min_depth } => panic!(
237+
"`start_walk_node({:?})` returned cycle with depth {:?}",
238+
node, min_depth
239+
),
239240
})
240241
.collect();
241242

242243
Sccs { scc_indices, scc_data: this.scc_data }
243244
}
244245

245-
/// Visits a node during the DFS. We first examine its current
246-
/// state -- if it is not yet visited (`NotVisited`), we can push
247-
/// it onto the stack and start walking its successors.
246+
fn start_walk_from(&mut self, node: G::Node) -> WalkReturn<S> {
247+
if let Some(result) = self.inspect_node(node) {
248+
result
249+
} else {
250+
self.walk_unvisited_node(node)
251+
}
252+
}
253+
254+
/// Inspect a node during the DFS. We first examine its current
255+
/// state -- if it is not yet visited (`NotVisited`), return `None` so
256+
/// that the caller might push it onto the stack and start walking its
257+
/// successors.
248258
///
249259
/// If it is already on the DFS stack it will be in the state
250260
/// `BeingVisited`. In that case, we have found a cycle and we
@@ -253,20 +263,19 @@ where
253263
/// Otherwise, we are looking at a node that has already been
254264
/// completely visited. We therefore return `WalkReturn::Complete`
255265
/// with its associated SCC index.
256-
fn walk_node(&mut self, depth: usize, node: G::Node) -> WalkReturn<S> {
257-
debug!("walk_node(depth = {:?}, node = {:?})", depth, node);
258-
match self.find_state(node) {
266+
fn inspect_node(&mut self, node: G::Node) -> Option<WalkReturn<S>> {
267+
Some(match self.find_state(node) {
259268
NodeState::InCycle { scc_index } => WalkReturn::Complete { scc_index },
260269

261270
NodeState::BeingVisited { depth: min_depth } => WalkReturn::Cycle { min_depth },
262271

263-
NodeState::NotVisited => self.walk_unvisited_node(depth, node),
272+
NodeState::NotVisited => return None,
264273

265274
NodeState::InCycleWith { parent } => panic!(
266275
"`find_state` returned `InCycleWith({:?})`, which ought to be impossible",
267276
parent
268277
),
269-
}
278+
})
270279
}
271280

272281
/// Fetches the state of the node `r`. If `r` is recorded as being
@@ -392,74 +401,174 @@ where
392401
}
393402

394403
/// Walks a node that has never been visited before.
395-
fn walk_unvisited_node(&mut self, depth: usize, node: G::Node) -> WalkReturn<S> {
396-
debug!("walk_unvisited_node(depth = {:?}, node = {:?})", depth, node);
397-
398-
debug_assert!(matches!(self.node_states[node], NodeState::NotVisited));
399-
400-
// Push `node` onto the stack.
401-
self.node_states[node] = NodeState::BeingVisited { depth };
402-
self.node_stack.push(node);
403-
404-
// Walk each successor of the node, looking to see if any of
405-
// them can reach a node that is presently on the stack. If
406-
// so, that means they can also reach us.
407-
let mut min_depth = depth;
408-
let mut min_cycle_root = node;
409-
let successors_len = self.successors_stack.len();
410-
for successor_node in self.graph.successors(node) {
411-
debug!("walk_unvisited_node: node = {:?} successor_ode = {:?}", node, successor_node);
412-
match self.walk_node(depth + 1, successor_node) {
413-
WalkReturn::Cycle { min_depth: successor_min_depth } => {
414-
// Track the minimum depth we can reach.
415-
assert!(successor_min_depth <= depth);
416-
if successor_min_depth < min_depth {
404+
///
405+
/// Call this method when `inspect_node` has returned `None`. Having the
406+
/// caller decide avoids mutual recursion between the two methods and allows
407+
/// us to maintain an allocated stack for nodes on the path between calls.
408+
fn walk_unvisited_node(&mut self, initial: G::Node) -> WalkReturn<S> {
409+
struct VisitingNodeFrame<G: DirectedGraph, Successors> {
410+
node: G::Node,
411+
iter: Option<Successors>,
412+
depth: usize,
413+
min_depth: usize,
414+
successors_len: usize,
415+
min_cycle_root: G::Node,
416+
successor_node: G::Node,
417+
}
418+
419+
// Move the stack to a local variable. We want to utilize the existing allocation and
420+
// mutably borrow it without borrowing self at the same time.
421+
let mut successors_stack = core::mem::take(&mut self.successors_stack);
422+
debug_assert_eq!(successors_stack.len(), 0);
423+
424+
let mut stack: Vec<VisitingNodeFrame<G, _>> = vec![VisitingNodeFrame {
425+
node: initial,
426+
depth: 0,
427+
min_depth: 0,
428+
iter: None,
429+
successors_len: 0,
430+
min_cycle_root: initial,
431+
successor_node: initial,
432+
}];
433+
434+
let mut return_value = None;
435+
436+
'recurse: while let Some(frame) = stack.last_mut() {
437+
let VisitingNodeFrame {
438+
node,
439+
depth,
440+
iter,
441+
successors_len,
442+
min_depth,
443+
min_cycle_root,
444+
successor_node,
445+
} = frame;
446+
447+
let node = *node;
448+
let depth = *depth;
449+
450+
let successors = match iter {
451+
Some(iter) => iter,
452+
None => {
453+
// This None marks that we still have the initialize this node's frame.
454+
debug!("walk_unvisited_node(depth = {:?}, node = {:?})", depth, node);
455+
456+
debug_assert!(matches!(self.node_states[node], NodeState::NotVisited));
457+
458+
// Push `node` onto the stack.
459+
self.node_states[node] = NodeState::BeingVisited { depth };
460+
self.node_stack.push(node);
461+
462+
// Walk each successor of the node, looking to see if any of
463+
// them can reach a node that is presently on the stack. If
464+
// so, that means they can also reach us.
465+
*successors_len = successors_stack.len();
466+
// Set and return a reference, this is currently empty.
467+
iter.get_or_insert(self.graph.successors(node))
468+
}
469+
};
470+
471+
// Now that iter is initialized, this is a constant for this frame.
472+
let successors_len = *successors_len;
473+
474+
// Construct iterators for the nodes and walk results. There are two cases:
475+
// * The walk of a successor node returned.
476+
// * The remaining successor nodes.
477+
let returned_walk =
478+
return_value.take().into_iter().map(|walk| (*successor_node, Some(walk)));
479+
480+
let successor_walk = successors.by_ref().map(|successor_node| {
481+
debug!(
482+
"walk_unvisited_node: node = {:?} successor_ode = {:?}",
483+
node, successor_node
484+
);
485+
(successor_node, self.inspect_node(successor_node))
486+
});
487+
488+
for (successor_node, walk) in returned_walk.chain(successor_walk) {
489+
match walk {
490+
Some(WalkReturn::Cycle { min_depth: successor_min_depth }) => {
491+
// Track the minimum depth we can reach.
492+
assert!(successor_min_depth <= depth);
493+
if successor_min_depth < *min_depth {
494+
debug!(
495+
"walk_unvisited_node: node = {:?} successor_min_depth = {:?}",
496+
node, successor_min_depth
497+
);
498+
*min_depth = successor_min_depth;
499+
*min_cycle_root = successor_node;
500+
}
501+
}
502+
503+
Some(WalkReturn::Complete { scc_index: successor_scc_index }) => {
504+
// Push the completed SCC indices onto
505+
// the `successors_stack` for later.
417506
debug!(
418-
"walk_unvisited_node: node = {:?} successor_min_depth = {:?}",
419-
node, successor_min_depth
507+
"walk_unvisited_node: node = {:?} successor_scc_index = {:?}",
508+
node, successor_scc_index
420509
);
421-
min_depth = successor_min_depth;
422-
min_cycle_root = successor_node;
510+
successors_stack.push(successor_scc_index);
423511
}
424-
}
425512

426-
WalkReturn::Complete { scc_index: successor_scc_index } => {
427-
// Push the completed SCC indices onto
428-
// the `successors_stack` for later.
429-
debug!(
430-
"walk_unvisited_node: node = {:?} successor_scc_index = {:?}",
431-
node, successor_scc_index
432-
);
433-
self.successors_stack.push(successor_scc_index);
513+
None => {
514+
let depth = depth + 1;
515+
debug!("walk_node(depth = {:?}, node = {:?})", depth, successor_node);
516+
// Remember which node the return value will come from.
517+
frame.successor_node = successor_node;
518+
// Start a new stack frame the step into it.
519+
stack.push(VisitingNodeFrame {
520+
node: successor_node,
521+
depth,
522+
iter: None,
523+
successors_len: 0,
524+
min_depth: depth,
525+
min_cycle_root: successor_node,
526+
successor_node: successor_node,
527+
});
528+
continue 'recurse;
529+
}
434530
}
435531
}
436-
}
437532

438-
// Completed walk, remove `node` from the stack.
439-
let r = self.node_stack.pop();
440-
debug_assert_eq!(r, Some(node));
441-
442-
// If `min_depth == depth`, then we are the root of the
443-
// cycle: we can't reach anyone further down the stack.
444-
if min_depth == depth {
445-
// Note that successor stack may have duplicates, so we
446-
// want to remove those:
447-
let deduplicated_successors = {
448-
let duplicate_set = &mut self.duplicate_set;
449-
duplicate_set.clear();
450-
self.successors_stack
451-
.drain(successors_len..)
452-
.filter(move |&i| duplicate_set.insert(i))
453-
};
454-
let scc_index = self.scc_data.create_scc(deduplicated_successors);
455-
self.node_states[node] = NodeState::InCycle { scc_index };
456-
WalkReturn::Complete { scc_index }
457-
} else {
458-
// We are not the head of the cycle. Return back to our
459-
// caller. They will take ownership of the
460-
// `self.successors` data that we pushed.
461-
self.node_states[node] = NodeState::InCycleWith { parent: min_cycle_root };
462-
WalkReturn::Cycle { min_depth }
533+
// Completed walk, remove `node` from the stack.
534+
let r = self.node_stack.pop();
535+
debug_assert_eq!(r, Some(node));
536+
537+
// Remove the frame, it's done.
538+
let frame = stack.pop().unwrap();
539+
540+
// If `min_depth == depth`, then we are the root of the
541+
// cycle: we can't reach anyone further down the stack.
542+
543+
// Pass the 'return value' down the stack.
544+
// We return one frame at a time so there can't be another return value.
545+
debug_assert!(return_value.is_none());
546+
return_value = Some(if frame.min_depth == depth {
547+
// Note that successor stack may have duplicates, so we
548+
// want to remove those:
549+
let deduplicated_successors = {
550+
let duplicate_set = &mut self.duplicate_set;
551+
duplicate_set.clear();
552+
successors_stack
553+
.drain(successors_len..)
554+
.filter(move |&i| duplicate_set.insert(i))
555+
};
556+
let scc_index = self.scc_data.create_scc(deduplicated_successors);
557+
self.node_states[node] = NodeState::InCycle { scc_index };
558+
WalkReturn::Complete { scc_index }
559+
} else {
560+
// We are not the head of the cycle. Return back to our
561+
// caller. They will take ownership of the
562+
// `self.successors` data that we pushed.
563+
self.node_states[node] = NodeState::InCycleWith { parent: frame.min_cycle_root };
564+
WalkReturn::Cycle { min_depth: frame.min_depth }
565+
});
463566
}
567+
568+
// Keep the allocation we used for successors_stack.
569+
self.successors_stack = successors_stack;
570+
debug_assert_eq!(self.successors_stack.len(), 0);
571+
572+
return_value.unwrap()
464573
}
465574
}

0 commit comments

Comments
 (0)