@@ -274,30 +274,118 @@ where
274
274
/// of `r2` (and updates `r` to reflect current result). This is
275
275
/// basically the "find" part of a standard union-find algorithm
276
276
/// (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
+ } ;
291
314
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
+ }
297
365
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)
301
389
}
302
390
}
303
391
}
0 commit comments