@@ -274,30 +274,118 @@ where
274274 /// of `r2` (and updates `r` to reflect current result). This is
275275 /// basically the "find" part of a standard union-find algorithm
276276 /// (with path compression).
277- fn find_state ( & mut self , r : G :: Node ) -> NodeState < G :: Node , S > {
278- debug ! ( "find_state(r = {:?} in state {:?})" , r, self . node_states[ r] ) ;
279- match self . node_states [ r] {
280- NodeState :: InCycle { scc_index } => NodeState :: InCycle { scc_index } ,
281- NodeState :: BeingVisited { depth } => NodeState :: BeingVisited { depth } ,
282- NodeState :: NotVisited => NodeState :: NotVisited ,
283- NodeState :: InCycleWith { parent } => {
284- let parent_state = self . find_state ( parent) ;
285- debug ! ( "find_state: parent_state = {:?}" , parent_state) ;
286- match parent_state {
287- NodeState :: InCycle { .. } => {
288- self . node_states [ r] = parent_state;
289- parent_state
290- }
277+ fn find_state ( & mut self , mut node : G :: Node ) -> NodeState < G :: Node , S > {
278+ // To avoid recursion we temporarily reuse the `parent` of each
279+ // InCycleWith link to encode a downwards link while compressing
280+ // the path. After we have found the root or deepest node being
281+ // visited, we traverse the reverse links and correct the node
282+ // states on the way.
283+ //
284+ // **Note**: This mutation requires that this is a leaf function
285+ // or at least that none of the called functions inspects the
286+ // current node states. Luckily, we are a leaf.
287+
288+ // Remember one previous link. The termination condition when
289+ // following links downwards is then simply as soon as we have
290+ // found the initial self-loop.
291+ let mut previous_node = node;
292+
293+ // Ultimately assigned by the parent when following
294+ // `InCycleWith` upwards.
295+ let node_state = loop {
296+ debug ! ( "find_state(r = {:?} in state {:?})" , node, self . node_states[ node] ) ;
297+ match self . node_states [ node] {
298+ NodeState :: InCycle { scc_index } => break NodeState :: InCycle { scc_index } ,
299+ NodeState :: BeingVisited { depth } => break NodeState :: BeingVisited { depth } ,
300+ NodeState :: NotVisited => break NodeState :: NotVisited ,
301+ NodeState :: InCycleWith { parent } => {
302+ // We test this, to be extremely sure that we never
303+ // ever break our termination condition for the
304+ // reverse iteration loop.
305+ assert ! ( node != parent, "Node can not be in cycle with itself" ) ;
306+ // Store the previous node as an inverted list link
307+ self . node_states [ node] = NodeState :: InCycleWith { parent : previous_node } ;
308+ // Update to parent node.
309+ previous_node = node;
310+ node = parent;
311+ }
312+ }
313+ } ;
291314
292- NodeState :: BeingVisited { depth } => {
293- self . node_states [ r] =
294- NodeState :: InCycleWith { parent : self . node_stack [ depth] } ;
295- parent_state
296- }
315+ // The states form a graph where up to one outgoing link is stored at
316+ // each node. Initially in general,
317+ //
318+ // E
319+ // ^
320+ // |
321+ // InCycleWith/BeingVisited/NotVisited
322+ // |
323+ // A-InCycleWith->B-InCycleWith…>C-InCycleWith->D-+
324+ // |
325+ // = node, previous_node
326+ //
327+ // After the first loop, this will look like
328+ // E
329+ // ^
330+ // |
331+ // InCycleWith/BeingVisited/NotVisited
332+ // |
333+ // +>A<-InCycleWith-B<…InCycleWith-C<-InCycleWith-D-+
334+ // | | | |
335+ // | InCycleWith | = node
336+ // +-+ =previous_node
337+ //
338+ // Note in particular that A will be linked to itself in a self-cycle
339+ // and no other self-cycles occur due to how InCycleWith is assigned in
340+ // the find phase implemented by `walk_unvisited_node`.
341+ //
342+ // We now want to compress the path, that is assign the state of the
343+ // link D-E to all other links.
344+ //
345+ // We can then walk backwards, starting from `previous_node`, and assign
346+ // each node in the list with the updated state. The loop terminates
347+ // when we reach the self-cycle.
348+
349+ // Move backwards until we found the node where we started. We
350+ // will know when we hit the state where previous_node == node.
351+ loop {
352+ // Back at the beginning, we can return.
353+ if previous_node == node {
354+ return node_state;
355+ }
356+ // Update to previous node in the link.
357+ match self . node_states [ previous_node] {
358+ NodeState :: InCycleWith { parent : previous } => {
359+ node = previous_node;
360+ previous_node = previous;
361+ }
362+ // Only InCycleWith nodes were added to the reverse linked list.
363+ other => panic ! ( "Invalid previous link while compressing cycle: {:?}" , other) ,
364+ }
297365
298- NodeState :: NotVisited | NodeState :: InCycleWith { .. } => {
299- panic ! ( "invalid parent state: {:?}" , parent_state)
300- }
366+ debug ! ( "find_state: parent_state = {:?}" , node_state) ;
367+
368+ // Update the node state from the parent state. The assigned
369+ // state is actually a loop invariant but it will only be
370+ // evaluated if there is at least one backlink to follow.
371+ // Fully trusting llvm here to find this loop optimization.
372+ match node_state {
373+ // Path compression, make current node point to the same root.
374+ NodeState :: InCycle { .. } => {
375+ self . node_states [ node] = node_state;
376+ }
377+ // Still visiting nodes, compress to cycle to the node
378+ // at that depth.
379+ NodeState :: BeingVisited { depth } => {
380+ self . node_states [ node] =
381+ NodeState :: InCycleWith { parent : self . node_stack [ depth] } ;
382+ }
383+ // These are never allowed as parent nodes. InCycleWith
384+ // should have been followed to a real parent and
385+ // NotVisited can not be part of a cycle since it should
386+ // have instead gotten explored.
387+ NodeState :: NotVisited | NodeState :: InCycleWith { .. } => {
388+ panic ! ( "invalid parent state: {:?}" , node_state)
301389 }
302390 }
303391 }
0 commit comments