| //! Routine to compute the strongly connected components (SCCs) of a graph. | |
| //! | |
| //! Also computes as the resulting DAG if each SCC is replaced with a | |
| //! node in the graph. This uses [Tarjan's algorithm]( | |
| //! https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm) | |
| //! that completes in *O*(*n*) time. | |
| //! Optionally, also annotate the SCC nodes with some commutative data. | |
| //! Typical examples would include: minimum element in SCC, maximum element | |
| //! reachable from it, etc. | |
| use std::debug_assert_matches; | |
| use std::fmt::Debug; | |
| use std::marker::PhantomData; | |
| use std::ops::Range; | |
| use rustc_index::{Idx, IndexSlice, IndexVec}; | |
| use tracing::{debug, instrument, trace}; | |
| use crate::fx::FxHashSet; | |
| use crate::graph::vec_graph::VecGraph; | |
| use crate::graph::{DirectedGraph, NumEdges, Successors}; | |
| #[cfg(test)] | |
| mod tests; | |
| /// An annotation for an SCC. This can be a representative, | |
| /// the max/min element of the SCC, or all of the above. | |
| /// | |
| /// Concretely, the both merge operations must commute, e.g. where `merge` | |
| /// is `update_scc` and `update_reachable`: `a.merge(b) == b.merge(a)` | |
| /// | |
| /// In general, what you want is probably always min/max according | |
| /// to some ordering, potentially with side constraints (min x such | |
| /// that P holds). | |
| pub trait Annotation: Debug + Copy { | |
| /// Merge two existing annotations into one during | |
| /// path compression. | |
| fn update_scc(&mut self, other: &Self); | |
| /// Merge a successor into this annotation. | |
| fn update_reachable(&mut self, other: &Self); | |
| } | |
| /// An accumulator for annotations. | |
| pub trait Annotations<N: Idx> { | |
| type Ann: Annotation; | |
| type SccIdx: Idx + Ord; | |
| fn new(&self, element: N) -> Self::Ann; | |
| fn annotate_scc(&mut self, scc: Self::SccIdx, annotation: Self::Ann); | |
| } | |
| /// The nil annotation accumulator, which does nothing. | |
| struct NoAnnotations<S: Idx + Ord>(PhantomData<S>); | |
| impl<N: Idx, S: Idx + Ord> Annotations<N> for NoAnnotations<S> { | |
| type SccIdx = S; | |
| type Ann = (); | |
| fn new(&self, _element: N) {} | |
| fn annotate_scc(&mut self, _scc: S, _annotation: ()) {} | |
| } | |
| /// The empty annotation, which does nothing. | |
| impl Annotation for () { | |
| fn update_reachable(&mut self, _other: &Self) {} | |
| fn update_scc(&mut self, _other: &Self) {} | |
| } | |
| /// Strongly connected components (SCC) of a graph. The type `N` is | |
| /// the index type for the graph nodes and `S` is the index type for | |
| /// the SCCs. We can map from each node to the SCC that it | |
| /// participates in, and we also have the successors of each SCC. | |
| pub struct Sccs<N: Idx, S: Idx> { | |
| /// For each node, what is the SCC index of the SCC to which it | |
| /// belongs. | |
| scc_indices: IndexVec<N, S>, | |
| /// Data about all the SCCs. | |
| scc_data: SccData<S>, | |
| } | |
| /// Information about an invidividual SCC node. | |
| struct SccDetails { | |
| /// For this SCC, the range of `all_successors` where its | |
| /// successors can be found. | |
| range: Range<usize>, | |
| } | |
| // The name of this struct should discourage you from making it public and leaking | |
| // its representation. This message was left here by one who came before you, | |
| // who learnt the hard way that making even small changes in representation | |
| // is difficult when it's publicly inspectable. | |
| // | |
| // Obey the law of Demeter! | |
| struct SccData<S: Idx> { | |
| /// Maps SCC indices to their metadata, including | |
| /// offsets into `all_successors`. | |
| scc_details: IndexVec<S, SccDetails>, | |
| /// Contains the successors for all the Sccs, concatenated. The | |
| /// range of indices corresponding to a given SCC is found in its | |
| /// `scc_details.range`. | |
| all_successors: Vec<S>, | |
| } | |
| impl<N: Idx, S: Idx + Ord> Sccs<N, S> { | |
| /// Compute SCCs without annotations. | |
| pub fn new(graph: &impl Successors<Node = N>) -> Self { | |
| Self::new_with_annotation(graph, &mut NoAnnotations(PhantomData::<S>)) | |
| } | |
| /// Compute SCCs and annotate them with a user-supplied annotation | |
| pub fn new_with_annotation<A: Annotations<N, SccIdx = S>>( | |
| graph: &impl Successors<Node = N>, | |
| annotations: &mut A, | |
| ) -> Self { | |
| SccsConstruction::construct(graph, annotations) | |
| } | |
| pub fn scc_indices(&self) -> &IndexSlice<N, S> { | |
| &self.scc_indices | |
| } | |
| /// Returns the number of SCCs in the graph. | |
| pub fn num_sccs(&self) -> usize { | |
| self.scc_data.len() | |
| } | |
| /// Returns an iterator over the SCCs in the graph. | |
| /// | |
| /// The SCCs will be iterated in **dependency order** (or **post order**), | |
| /// meaning that if `S1 -> S2`, we will visit `S2` first and `S1` after. | |
| /// This is convenient when the edges represent dependencies: when you visit | |
| /// `S1`, the value for `S2` will already have been computed. | |
| pub fn all_sccs(&self) -> impl Iterator<Item = S> + 'static { | |
| (0..self.scc_data.len()).map(S::new) | |
| } | |
| /// Returns the SCC to which a node `r` belongs. | |
| pub fn scc(&self, r: N) -> S { | |
| self.scc_indices[r] | |
| } | |
| /// Returns the successors of the given SCC. | |
| pub fn successors(&self, scc: S) -> &[S] { | |
| self.scc_data.successors(scc) | |
| } | |
| /// Construct the reverse graph of the SCC graph. | |
| pub fn reverse(&self) -> VecGraph<S> { | |
| VecGraph::new( | |
| self.num_sccs(), | |
| self.all_sccs() | |
| .flat_map(|source| { | |
| self.successors(source).iter().map(move |&target| (target, source)) | |
| }) | |
| .collect(), | |
| ) | |
| } | |
| } | |
| impl<N: Idx, S: Idx + Ord> DirectedGraph for Sccs<N, S> { | |
| type Node = S; | |
| fn num_nodes(&self) -> usize { | |
| self.num_sccs() | |
| } | |
| } | |
| impl<N: Idx, S: Idx + Ord> NumEdges for Sccs<N, S> { | |
| fn num_edges(&self) -> usize { | |
| self.scc_data.all_successors.len() | |
| } | |
| } | |
| impl<N: Idx, S: Idx + Ord> Successors for Sccs<N, S> { | |
| fn successors(&self, node: S) -> impl Iterator<Item = Self::Node> { | |
| self.successors(node).iter().cloned() | |
| } | |
| } | |
| impl<S: Idx> SccData<S> { | |
| /// Number of SCCs, | |
| fn len(&self) -> usize { | |
| self.scc_details.len() | |
| } | |
| /// Returns the successors of the given SCC. | |
| fn successors(&self, scc: S) -> &[S] { | |
| &self.all_successors[self.scc_details[scc].range.clone()] | |
| } | |
| /// Creates a new SCC with `successors` as its successors and | |
| /// returns the resulting index. | |
| fn create_scc(&mut self, successors: impl IntoIterator<Item = S>) -> S { | |
| // Store the successors on `scc_successors_vec`, remembering | |
| // the range of indices. | |
| let all_successors_start = self.all_successors.len(); | |
| self.all_successors.extend(successors); | |
| let all_successors_end = self.all_successors.len(); | |
| debug!( | |
| "create_scc({:?}) successors={:?}", | |
| self.len(), | |
| &self.all_successors[all_successors_start..all_successors_end], | |
| ); | |
| let range = all_successors_start..all_successors_end; | |
| let metadata = SccDetails { range }; | |
| self.scc_details.push(metadata) | |
| } | |
| } | |
| struct SccsConstruction<'c, 'a, G, A> | |
| where | |
| G: DirectedGraph + Successors, | |
| A: Annotations<G::Node>, | |
| { | |
| graph: &'c G, | |
| /// The state of each node; used during walk to record the stack | |
| /// and after walk to record what cycle each node ended up being | |
| /// in. | |
| node_states: IndexVec<G::Node, NodeState<G::Node, A::SccIdx, A::Ann>>, | |
| /// The stack of nodes that we are visiting as part of the DFS. | |
| node_stack: Vec<G::Node>, | |
| /// The stack of successors: as we visit a node, we mark our | |
| /// position in this stack, and when we encounter a successor SCC, | |
| /// we push it on the stack. When we complete an SCC, we can pop | |
| /// everything off the stack that was found along the way. | |
| successors_stack: Vec<A::SccIdx>, | |
| /// A set used to strip duplicates. As we accumulate successors | |
| /// into the successors_stack, we sometimes get duplicate entries. | |
| /// We use this set to remove those -- we also keep its storage | |
| /// around between successors to amortize memory allocation costs. | |
| duplicate_set: FxHashSet<A::SccIdx>, | |
| scc_data: SccData<A::SccIdx>, | |
| annotations: &'a mut A, | |
| } | |
| #[derive(Copy, Clone, Debug)] | |
| enum NodeState<N, S, A: Annotation> { | |
| /// This node has not yet been visited as part of the DFS. | |
| /// | |
| /// After SCC construction is complete, this state ought to be | |
| /// impossible. | |
| NotVisited, | |
| /// This node is currently being walked as part of our DFS. It is on | |
| /// the stack at the depth `depth` and its current annotation is | |
| /// `annotation`. | |
| /// | |
| /// After SCC construction is complete, this state ought to be | |
| /// impossible. | |
| BeingVisited { depth: usize, annotation: A }, | |
| /// Indicates that this node is a member of the given cycle where | |
| /// the merged annotation is `annotation`. | |
| /// Note that an SCC can have several cycles, so its final annotation | |
| /// is the merged value of all its member annotations. | |
| InCycle { scc_index: S, annotation: A }, | |
| /// Indicates that this node is a member of whatever cycle | |
| /// `parent` is a member of. This state is transient: whenever we | |
| /// see it, we try to overwrite it with the current state of | |
| /// `parent` (this is the "path compression" step of a union-find | |
| /// algorithm). | |
| InCycleWith { parent: N }, | |
| } | |
| /// The state of walking a given node. | |
| #[derive(Copy, Clone, Debug)] | |
| enum WalkReturn<S, A: Annotation> { | |
| /// The walk found a cycle, but the entire component is not known to have | |
| /// been fully walked yet. We only know the minimum depth of this | |
| /// component in a minimum spanning tree of the graph. This component | |
| /// is tentatively represented by the state of the first node of this | |
| /// cycle we met, which is at `min_depth`. | |
| Cycle { min_depth: usize, annotation: A }, | |
| /// The SCC and everything reachable from it have been fully walked. | |
| /// At this point we know what is inside the SCC as we have visited every | |
| /// node reachable from it. The SCC can now be fully represented by its ID. | |
| Complete { scc_index: S, annotation: A }, | |
| } | |
| impl<'c, 'a, G, A> SccsConstruction<'c, 'a, G, A> | |
| where | |
| G: DirectedGraph + Successors, | |
| A: Annotations<G::Node>, | |
| { | |
| /// Identifies SCCs in the graph `G` and computes the resulting | |
| /// DAG. This uses a variant of [Tarjan's | |
| /// algorithm][wikipedia]. The high-level summary of the algorithm | |
| /// is that we do a depth-first search. Along the way, we keep a | |
| /// stack of each node whose successors are being visited. We | |
| /// track the depth of each node on this stack (there is no depth | |
| /// if the node is not on the stack). When we find that some node | |
| /// N with depth D can reach some other node N' with lower depth | |
| /// D' (i.e., D' < D), we know that N, N', and all nodes in | |
| /// between them on the stack are part of an SCC. | |
| /// | |
| /// Additionally, we keep track of a current annotation of the SCC. | |
| /// | |
| /// [wikipedia]: https://bit.ly/2EZIx84 | |
| fn construct(graph: &'c G, annotations: &'a mut A) -> Sccs<G::Node, A::SccIdx> { | |
| let num_nodes = graph.num_nodes(); | |
| let mut this = Self { | |
| graph, | |
| node_states: IndexVec::from_elem_n(NodeState::NotVisited, num_nodes), | |
| node_stack: Vec::with_capacity(num_nodes), | |
| successors_stack: Vec::new(), | |
| scc_data: SccData { scc_details: IndexVec::new(), all_successors: Vec::new() }, | |
| duplicate_set: FxHashSet::default(), | |
| annotations, | |
| }; | |
| let scc_indices = graph | |
| .iter_nodes() | |
| .map(|node| match this.start_walk_from(node) { | |
| WalkReturn::Complete { scc_index, .. } => scc_index, | |
| WalkReturn::Cycle { min_depth, .. } => { | |
| panic!("`start_walk_node({node:?})` returned cycle with depth {min_depth:?}") | |
| } | |
| }) | |
| .collect(); | |
| Sccs { scc_indices, scc_data: this.scc_data } | |
| } | |
| fn start_walk_from(&mut self, node: G::Node) -> WalkReturn<A::SccIdx, A::Ann> { | |
| self.inspect_node(node).unwrap_or_else(|| self.walk_unvisited_node(node)) | |
| } | |
| /// Inspect a node during the DFS. We first examine its current | |
| /// state -- if it is not yet visited (`NotVisited`), return `None` so | |
| /// that the caller might push it onto the stack and start walking its | |
| /// successors. | |
| /// | |
| /// If it is already on the DFS stack it will be in the state | |
| /// `BeingVisited`. In that case, we have found a cycle and we | |
| /// return the depth from the stack. | |
| /// | |
| /// Otherwise, we are looking at a node that has already been | |
| /// completely visited. We therefore return `WalkReturn::Complete` | |
| /// with its associated SCC index. | |
| fn inspect_node(&mut self, node: G::Node) -> Option<WalkReturn<A::SccIdx, A::Ann>> { | |
| Some(match self.find_state(node) { | |
| NodeState::InCycle { scc_index, annotation } => { | |
| WalkReturn::Complete { scc_index, annotation } | |
| } | |
| NodeState::BeingVisited { depth: min_depth, annotation } => { | |
| WalkReturn::Cycle { min_depth, annotation } | |
| } | |
| NodeState::NotVisited => return None, | |
| NodeState::InCycleWith { parent } => panic!( | |
| "`find_state` returned `InCycleWith({parent:?})`, which ought to be impossible" | |
| ), | |
| }) | |
| } | |
| /// Fetches the state of the node `r`. If `r` is recorded as being | |
| /// in a cycle with some other node `r2`, then fetches the state | |
| /// of `r2` (and updates `r` to reflect current result). This is | |
| /// basically the "find" part of a standard union-find algorithm | |
| /// (with path compression). | |
| fn find_state(&mut self, mut node: G::Node) -> NodeState<G::Node, A::SccIdx, A::Ann> { | |
| // To avoid recursion we temporarily reuse the `parent` of each | |
| // InCycleWith link to encode a downwards link while compressing | |
| // the path. After we have found the root or deepest node being | |
| // visited, we traverse the reverse links and correct the node | |
| // states on the way. | |
| // | |
| // **Note**: This mutation requires that this is a leaf function | |
| // or at least that none of the called functions inspects the | |
| // current node states. Luckily, we are a leaf. | |
| // Remember one previous link. The termination condition when | |
| // following links downwards is then simply as soon as we have | |
| // found the initial self-loop. | |
| let mut previous_node = node; | |
| // Ultimately propagated to all the transitive parents when following | |
| // `InCycleWith` upwards. | |
| // This loop performs the downward link encoding mentioned above. Details below! | |
| // Note that there are two different states being assigned: the root state, and | |
| // a potentially derived version of the root state for non-root nodes in the chain. | |
| let (root_state, assigned_state) = { | |
| loop { | |
| trace!("find_state(r = {node:?} in state {:?})", self.node_states[node]); | |
| match self.node_states[node] { | |
| // This must have been the first and only state since it is unexplored*; | |
| // no update needed! * Unless there is a bug :') | |
| s @ NodeState::NotVisited => return s, | |
| // We are in a completely discovered SCC; every node on our path is in that SCC: | |
| s @ NodeState::InCycle { .. } => break (s, s), | |
| // The Interesting Third Base Case: we are a path back to a root node | |
| // still being explored. Now we need that node to keep its state and | |
| // every other node to be recorded as being in whatever component that | |
| // ends up in. | |
| s @ NodeState::BeingVisited { depth, .. } => { | |
| break (s, NodeState::InCycleWith { parent: self.node_stack[depth] }); | |
| } | |
| // We are not at the head of a path; keep compressing it! | |
| NodeState::InCycleWith { parent } => { | |
| // We test this, to be extremely sure that we never | |
| // ever break our termination condition for the | |
| // reverse iteration loop. | |
| assert!(node != parent, "Node can not be in cycle with itself"); | |
| // Store the previous node as an inverted list link | |
| self.node_states[node] = NodeState::InCycleWith { parent: previous_node }; | |
| // Update to parent node. | |
| previous_node = node; | |
| node = parent; | |
| } | |
| } | |
| } | |
| }; | |
| // The states form a graph where up to one outgoing link is stored at | |
| // each node. Initially in general, | |
| // | |
| // E | |
| // ^ | |
| // | | |
| // InCycleWith/BeingVisited/NotVisited | |
| // | | |
| // A-InCycleWith->B-InCycleWith…>C-InCycleWith->D-+ | |
| // | | |
| // = node, previous_node | |
| // | |
| // After the first loop, this will look like | |
| // E | |
| // ^ | |
| // | | |
| // InCycleWith/BeingVisited/NotVisited | |
| // | | |
| // +>A<-InCycleWith-B<…InCycleWith-C<-InCycleWith-D-+ | |
| // | | | | | |
| // | InCycleWith | = node | |
| // +-+ =previous_node | |
| // | |
| // Note in particular that A will be linked to itself in a self-cycle | |
| // and no other self-cycles occur due to how InCycleWith is assigned in | |
| // the find phase implemented by `walk_unvisited_node`. | |
| // | |
| // We now want to compress the path, that is assign the state of the | |
| // link D-E to all other links. | |
| // | |
| // We can then walk backwards, starting from `previous_node`, and assign | |
| // each node in the list with the updated state. The loop terminates | |
| // when we reach the self-cycle. | |
| // Move backwards until we found the node where we started. We | |
| // will know when we hit the state where previous_node == node. | |
| loop { | |
| // Back at the beginning, we can return. Note that we return the root state. | |
| // This is because for components being explored, we would otherwise get a | |
| // `node_state[n] = InCycleWith{ parent: n }` and that's wrong. | |
| if previous_node == node { | |
| return root_state; | |
| } | |
| trace!("Compressing {node:?} down to {previous_node:?} with state {assigned_state:?}"); | |
| // Update to previous node in the link. | |
| match self.node_states[previous_node] { | |
| NodeState::InCycleWith { parent: previous } => { | |
| node = previous_node; | |
| previous_node = previous; | |
| } | |
| // Only InCycleWith nodes were added to the reverse linked list. | |
| other => unreachable!("Invalid previous link while compressing cycle: {other:?}"), | |
| } | |
| // Update the node state to the (potentially derived) state. | |
| // If the root is still being explored, this is | |
| // `InCycleWith{ parent: <root node>}`, otherwise | |
| // `assigned_state == root_state`. | |
| self.node_states[node] = assigned_state; | |
| } | |
| } | |
| /// Walks a node that has never been visited before. | |
| /// | |
| /// Call this method when `inspect_node` has returned `None`. Having the | |
| /// caller decide avoids mutual recursion between the two methods and allows | |
| /// us to maintain an allocated stack for nodes on the path between calls. | |
| #[instrument(skip(self, initial), level = "trace")] | |
| fn walk_unvisited_node(&mut self, initial: G::Node) -> WalkReturn<A::SccIdx, A::Ann> { | |
| trace!("Walk unvisited node: {initial:?}"); | |
| struct VisitingNodeFrame<G: DirectedGraph, Successors, A> { | |
| node: G::Node, | |
| successors: Option<Successors>, | |
| depth: usize, | |
| min_depth: usize, | |
| successors_len: usize, | |
| min_cycle_root: G::Node, | |
| successor_node: G::Node, | |
| /// The annotation for the SCC starting in `node`. It may or may | |
| /// not contain other nodes. | |
| current_component_annotation: A, | |
| } | |
| // Move the stack to a local variable. We want to utilize the existing allocation and | |
| // mutably borrow it without borrowing self at the same time. | |
| let mut successors_stack = core::mem::take(&mut self.successors_stack); | |
| debug_assert_eq!(successors_stack.len(), 0); | |
| let mut stack: Vec<VisitingNodeFrame<G, _, _>> = vec![VisitingNodeFrame { | |
| node: initial, | |
| depth: 0, | |
| min_depth: 0, | |
| successors: None, | |
| successors_len: 0, | |
| min_cycle_root: initial, | |
| successor_node: initial, | |
| current_component_annotation: self.annotations.new(initial), | |
| }]; | |
| let mut return_value = None; | |
| 'recurse: while let Some(frame) = stack.last_mut() { | |
| let VisitingNodeFrame { | |
| node, | |
| depth, | |
| successors, | |
| successors_len, | |
| min_depth, | |
| min_cycle_root, | |
| successor_node, | |
| current_component_annotation, | |
| } = frame; | |
| let node = *node; | |
| let depth = *depth; | |
| trace!( | |
| "Visiting {node:?} at depth {depth:?}, annotation: {current_component_annotation:?}" | |
| ); | |
| let successors = match successors { | |
| Some(successors) => successors, | |
| None => { | |
| // This None marks that we still have the initialize this node's frame. | |
| trace!(?depth, ?node); | |
| debug_assert_matches!(self.node_states[node], NodeState::NotVisited); | |
| // Push `node` onto the stack. | |
| self.node_states[node] = NodeState::BeingVisited { | |
| depth, | |
| annotation: *current_component_annotation, | |
| }; | |
| self.node_stack.push(node); | |
| // Walk each successor of the node, looking to see if any of | |
| // them can reach a node that is presently on the stack. If | |
| // so, that means they can also reach us. | |
| *successors_len = successors_stack.len(); | |
| // Set and return a reference, this is currently empty. | |
| successors.get_or_insert(self.graph.successors(node)) | |
| } | |
| }; | |
| // Now that the successors iterator is initialized, this is a constant for this frame. | |
| let successors_len = *successors_len; | |
| // Construct iterators for the nodes and walk results. There are two cases: | |
| // * The walk of a successor node returned. | |
| // * The remaining successor nodes. | |
| let returned_walk = | |
| return_value.take().into_iter().map(|walk| (*successor_node, Some(walk))); | |
| let successor_walk = successors.map(|successor_node| { | |
| trace!(?node, ?successor_node); | |
| (successor_node, self.inspect_node(successor_node)) | |
| }); | |
| for (successor_node, walk) in returned_walk.chain(successor_walk) { | |
| match walk { | |
| // The starting node `node` leads to a cycle whose earliest node, | |
| // `successor_node`, is at `min_depth`. There may be more cycles. | |
| Some(WalkReturn::Cycle { | |
| min_depth: successor_min_depth, | |
| annotation: successor_annotation, | |
| }) => { | |
| trace!( | |
| "Cycle found from {node:?}, minimum depth: {successor_min_depth:?}, annotation: {successor_annotation:?}" | |
| ); | |
| // Track the minimum depth we can reach. | |
| assert!(successor_min_depth <= depth); | |
| if successor_min_depth < *min_depth { | |
| trace!(?node, ?successor_min_depth); | |
| *min_depth = successor_min_depth; | |
| *min_cycle_root = successor_node; | |
| } | |
| current_component_annotation.update_scc(&successor_annotation); | |
| } | |
| // The starting node `node` is succeeded by a fully identified SCC | |
| // which is now added to the set under `scc_index`. | |
| Some(WalkReturn::Complete { | |
| scc_index: successor_scc_index, | |
| annotation: successor_annotation, | |
| }) => { | |
| trace!( | |
| "Complete; {node:?} is root of complete-visited SCC idx {successor_scc_index:?} with annotation {successor_annotation:?}" | |
| ); | |
| // Push the completed SCC indices onto | |
| // the `successors_stack` for later. | |
| trace!(?node, ?successor_scc_index); | |
| successors_stack.push(successor_scc_index); | |
| current_component_annotation.update_reachable(&successor_annotation); | |
| } | |
| // `node` has no more (direct) successors; search recursively. | |
| None => { | |
| let depth = depth + 1; | |
| trace!("Recursing down into {successor_node:?} at depth {depth:?}"); | |
| trace!(?depth, ?successor_node); | |
| // Remember which node the return value will come from. | |
| frame.successor_node = successor_node; | |
| // Start a new stack frame, then step into it. | |
| stack.push(VisitingNodeFrame { | |
| node: successor_node, | |
| depth, | |
| successors: None, | |
| successors_len: 0, | |
| min_depth: depth, | |
| min_cycle_root: successor_node, | |
| successor_node, | |
| current_component_annotation: self.annotations.new(successor_node), | |
| }); | |
| continue 'recurse; | |
| } | |
| } | |
| } | |
| trace!("Finished walk from {node:?} with annotation: {current_component_annotation:?}"); | |
| // Completed walk, remove `node` from the stack. | |
| let r = self.node_stack.pop(); | |
| debug_assert_eq!(r, Some(node)); | |
| // Remove the frame, it's done. | |
| let frame = stack.pop().unwrap(); | |
| let current_component_annotation = frame.current_component_annotation; | |
| debug_assert_eq!(frame.node, node); | |
| // If `min_depth == depth`, then we are the root of the | |
| // cycle: we can't reach anyone further down the stack. | |
| // Pass the 'return value' down the stack. | |
| // We return one frame at a time so there can't be another return value. | |
| debug_assert!(return_value.is_none()); | |
| return_value = Some(if frame.min_depth == depth { | |
| // We are at the head of the component. | |
| // Note that successor stack may have duplicates, so we | |
| // want to remove those: | |
| let deduplicated_successors = { | |
| let duplicate_set = &mut self.duplicate_set; | |
| duplicate_set.clear(); | |
| successors_stack | |
| .drain(successors_len..) | |
| .filter(move |&i| duplicate_set.insert(i)) | |
| }; | |
| debug!("Creating SCC rooted in {node:?} with successor {:?}", frame.successor_node); | |
| let scc_index = self.scc_data.create_scc(deduplicated_successors); | |
| self.annotations.annotate_scc(scc_index, current_component_annotation); | |
| self.node_states[node] = | |
| NodeState::InCycle { scc_index, annotation: current_component_annotation }; | |
| WalkReturn::Complete { scc_index, annotation: current_component_annotation } | |
| } else { | |
| // We are not the head of the cycle. Return back to our | |
| // caller. They will take ownership of the | |
| // `self.successors` data that we pushed. | |
| self.node_states[node] = NodeState::InCycleWith { parent: frame.min_cycle_root }; | |
| WalkReturn::Cycle { | |
| min_depth: frame.min_depth, | |
| annotation: current_component_annotation, | |
| } | |
| }); | |
| } | |
| // Keep the allocation we used for successors_stack. | |
| self.successors_stack = successors_stack; | |
| debug_assert_eq!(self.successors_stack.len(), 0); | |
| return_value.unwrap() | |
| } | |
| } |