[llvm] [LoopInfo] Identify loops with a single-pass DFS algorithm. NFC (PR #212000)

Fangrui Song via llvm-commits llvm-commits at lists.llvm.org
Sat Jul 25 15:05:10 PDT 2026


================
@@ -451,114 +451,237 @@ void LoopBase<BlockT, LoopT>::print(raw_ostream &OS, bool Verbose,
 /// result does / not depend on use list (block predecessor) order.
 ///
 
-/// Discover a subloop with the specified backedges such that: All blocks within
-/// this loop are mapped to this loop or a subloop. And all subloops within this
-/// loop have their parent loop set to this loop or a subloop.
-template <class BlockT, class LoopT>
-void LoopInfoBase<BlockT, LoopT>::discoverAndMapSubloop(
-    LoopT *L, BlockT *Header, ArrayRef<BlockT *> Backedges,
-    const DominatorTreeBase<BlockT, false> &DomTree) {
-  using InvBlockTraits = GraphTraits<Inverse<BlockT *>>;
-
-  unsigned NumSubloops = 0;
-
-  // Perform a backward CFG traversal using a worklist.
-  std::vector<BlockT *> ReverseCFGWorklist(Backedges.begin(), Backedges.end());
-  while (!ReverseCFGWorklist.empty()) {
-    BlockT *PredBB = ReverseCFGWorklist.back();
-    ReverseCFGWorklist.pop_back();
-
-    LoopT *Subloop = getLoopFor(PredBB);
-    if (!Subloop) {
-      if (!DomTree.isReachableFromEntry(PredBB))
-        continue;
-
-      // This is an undiscovered block. Map it to the current loop.
-      changeLoopFor(PredBB, L);
-      if (PredBB == Header)
-        continue;
-      // Push all block predecessors on the worklist.
-      ReverseCFGWorklist.insert(ReverseCFGWorklist.end(),
-                                InvBlockTraits::child_begin(PredBB),
-                                InvBlockTraits::child_end(PredBB));
-    } else {
-      // This is a discovered block. Find its outermost discovered loop.
-      Subloop = Subloop->getOutermostLoop();
-
-      // If it is already discovered to be a subloop of this loop, continue.
-      if (Subloop == L)
-        continue;
-
-      // Discover a subloop of this loop.
-      Subloop->setParentLoop(L);
-      ++NumSubloops;
-      PredBB = pendingHeader(Subloop);
-      // Continue traversal along predecessors that are not loop-back edges from
-      // within this subloop tree itself. Note that a predecessor may directly
-      // reach another subloop that is not yet discovered to be a subloop of
-      // this loop, which we must traverse.
-      for (const auto Pred : inverse_children<BlockT *>(PredBB)) {
-        if (getLoopFor(Pred) != Subloop)
-          ReverseCFGWorklist.push_back(Pred);
-      }
-    }
-  }
-  L->reserveSubLoops(NumSubloops);
-}
-
-/// Analyze LoopInfo discovers loops during a reverse preorder DominatorTree
-/// traversal interleaved with backward CFG traversals within each subloop
-/// (discoverAndMapSubloop). The backward traversal skips inner subloops, so
-/// this part of the algorithm is linear in the number of CFG edges.
+/// Analyze LoopInfo identifies the loops during a single forward depth-first
+/// search of the CFG.
 ///
 /// Then build a loop-contiguous reverse postorder for in-loops blocks. Lists
 /// are header-first with each subloop's blocks contiguous, ordered by first
 /// appearance in RPO; SubLoops keep program order, TopLevelLoops reverse
 /// program order.
 template <class BlockT, class LoopT>
 void LoopInfoBase<BlockT, LoopT>::analyze(const DomTreeBase<BlockT> &DomTree) {
+  using BlockTraits = GraphTraits<BlockT *>;
+  auto num = [](const BlockT *BB) {
+    return GraphTraits<const BlockT *>::getNumber(BB);
+  };
+
   const DomTreeNodeBase<BlockT> *DomRoot = DomTree.getRootNode();
   ParentPtr = DomRoot->getBlock()->getParent();
   BlockNumberEpoch = GraphTraits<ParentT>::getNumberEpoch(ParentPtr);
-  BBMap.resize(GraphTraits<ParentT>::getMaxNumber(ParentPtr));
-
-  // Visit dominator tree nodes in reverse preorder: like postorder, this
-  // guarantees a sub-loop is discovered before the outer loop.
-  DomTree.updateDFSNumbers();
-  SmallVector<const DomTreeNodeBase<BlockT> *, 32> PreorderNodes(
-      DomRoot->getDFSNumOut());
-  for (const DomTreeNodeBase<BlockT> *Node : DomTree.nodes())
-    PreorderNodes[Node->getDFSNumIn()] = Node;
-
-  bool HasLoops = false;
-  for (const DomTreeNodeBase<BlockT> *DomNode : llvm::reverse(PreorderNodes)) {
-    BlockT *Header = DomNode->getBlock();
-    SmallVector<BlockT *, 4> Backedges;
-
-    // Check each predecessor of the potential loop header.
-    for (const auto Backedge : inverse_children<BlockT *>(Header)) {
-      // If Header dominates predBB, this is a new loop. Collect the backedges.
-      const DomTreeNodeBase<BlockT> *BackedgeNode = DomTree.getNode(Backedge);
-      if (BackedgeNode && DomTree.dominates(DomNode, BackedgeNode))
-        Backedges.push_back(Backedge);
+  unsigned MaxNumber = GraphTraits<ParentT>::getMaxNumber(ParentPtr);
+
+  // Sentinel block number meaning "no block".
+  constexpr unsigned NoBlock = ~0u;
+  // States during DFS (Unvisited, OffPath, >=FirstOnPath) and post-DFS
+  // (IsHeader, IsReentered).
+  constexpr unsigned Unvisited = 0;
+  constexpr unsigned OffPath = 1;
+  constexpr unsigned IsHeader = 2;
+  constexpr unsigned IsReentered = 3;
+  constexpr unsigned FirstOnPath = IsReentered + 1;
+
+  // Per-block search state, indexed by block number.
+  struct BlockInfo {
+    // Unvisited. Spelled 0 to work around GCC 11 ICE.
+    unsigned Pos = 0;
+    // Block number of the innermost enclosing header; NoBlock if none. Set to
+    // NoBlock when the block is visited, then woven by tagLoopHeader.
+    unsigned LoopHeader = 0;
+  };
+  SmallVector<BlockInfo, 32> Info(MaxNumber);
+  // The loop headers, repeated once per backedge.
+  SmallVector<unsigned, 4> Headers;
+  // The headers of the loops that an edge re-enters, likewise repeated.
+  SmallVector<unsigned, 0> Reentries;
+
+  // Weave loop header \p H (and its own header chain) into the loop header
+  // chain of \p B, keeping the chain ordered from innermost to outermost by
+  // search path position. Building this chain on the fly is why the algorithm
+  // needs no union-find (used in the Havlak algorithm) at all.
+  auto tagLoopHeader = [&](unsigned B, unsigned H) {
+    assert(H != NoBlock);
+    // Invariant: Info[B].Pos >= Info[H].Pos.
+    while (B != H) {
+      unsigned IH = Info[B].LoopHeader;
+      if (IH == NoBlock) {
+        // B's chain ended: append the rest of H's chain.
+        Info[B].LoopHeader = H;
+        return;
+      }
+      // Keep whichever candidate header is inner (larger search path position).
+      if (Info[IH].Pos >= Info[H].Pos) {
+        B = IH;
+      } else {
+        Info[B].LoopHeader = H;
+        B = H;
+        H = IH;
+      }
     }
-    // Perform a backward CFG traversal to discover and map blocks in this loop.
-    if (!Backedges.empty()) {
-      HasLoops = true;
-      LoopT *L = allocateLoop(Header);
-      discoverAndMapSubloop(L, Header, Backedges, DomTree);
+  };
+
+  // Identify loops with the algorithm of Wei et al., "A New Algorithm for
+  // Identifying Loops in Decompilation" (SAS 2007): tag each block with its
+  // innermost enclosing header. It also records the postorder the layout below
+  // needs.
+  SmallVector<BlockT *, 32> Postorder;
+  Postorder.reserve(MaxNumber);
+  struct Frame {
+    BlockT *Block;
+    typename BlockTraits::ChildIteratorType Cur, End;
+  };
+  SmallVector<Frame, 8> Stack;
+  unsigned Counter = FirstOnPath;
+  auto push = [&](BlockT *BB) {
+    unsigned B = num(BB);
+    Info[B].Pos = Counter++;
+    Info[B].LoopHeader = NoBlock;
+    Stack.push_back(
+        {BB, BlockTraits::child_begin(BB), BlockTraits::child_end(BB)});
+  };
+
+  push(DomRoot->getBlock());
+  while (!Stack.empty()) {
+    Frame &Top = Stack.back();
+    if (Top.Cur == Top.End) {
+      // Leave the search path, and weave into the parent's chain.
+      unsigned B0 = num(Top.Block);
+      Info[B0].Pos = OffPath;
+      Postorder.push_back(Top.Block);
+      Stack.pop_back();
+      if (!Stack.empty() && Info[B0].LoopHeader != NoBlock)
+        tagLoopHeader(num(Stack.back().Block), Info[B0].LoopHeader);
+      continue;
+    }
+    BlockT *B0P = Top.Block;
+    BlockT *B1P = *Top.Cur++;
+    unsigned B1 = num(B1P);
+    if (Info[B1].Pos == Unvisited) {
+      // Tree edge; the weaving happens when B1's frame is popped.
+      push(B1P);
+    } else if (Info[B1].Pos >= FirstOnPath) {
+      // Retreating edge, including a self edge: B1 heads a loop.
+      Headers.push_back(B1);
+      tagLoopHeader(num(B0P), B1);
+    } else {
+      // Cross or forward edge. Tagging B1's innermost header adds B0 to that
+      // loop and, through its chain, to the ones enclosing it. A header that
+      // has left the search path heads a loop this edge re-enters at a block
+      // other than its header, so record it and keep looking outwards.
+      for (unsigned H = Info[B1].LoopHeader; H != NoBlock;
+           H = Info[H].LoopHeader) {
+        if (Info[H].Pos >= FirstOnPath) {
+          tagLoopHeader(num(B0P), H);
+          break;
+        }
+        Reentries.push_back(H);
+      }
     }
   }
   // Most functions have no loops; skip the layout construction.
-  if (!HasLoops)
+  if (Headers.empty())
     return;
+  // Every block is off the search path now, so marking the headers cannot be
+  // mistaken for a position on it.
+  for (unsigned H : Headers)
+    Info[H].Pos = IsHeader;
+
+  if (!Reentries.empty()) {
+    // A re-entered loop has more than one entry, so it is not a natural loop.
+    // Reduce it, innermost first, to the natural loop of its header's
+    // backedges by splicing the header out of the chain of every block that
+    // loop excludes.
+    for (unsigned H : Reentries)
+      Info[H].Pos = IsReentered;
+    DomTree.updateDFSNumbers();
+    SmallVector<unsigned, 0> Mark(MaxNumber, NoBlock);
+    SmallVector<BlockT *, 8> Worklist;
+    // The blocks of each chain, so that a header visits only its own instead
+    // of searching every block for them.
+    SmallVector<unsigned, 0> FirstChild(MaxNumber, NoBlock);
+    SmallVector<unsigned, 0> NextSibling(MaxNumber, NoBlock);
+    for (BlockT *BB : Postorder) {
+      unsigned B = num(BB);
+      if (unsigned P = Info[B].LoopHeader; P != NoBlock) {
+        NextSibling[B] = FirstChild[P];
+        FirstChild[P] = B;
+      }
+    }
+    for (BlockT *Header : Postorder) {
+      unsigned H = num(Header);
+      if (Info[H].Pos != IsReentered)
+        continue;
+      Mark[H] = H;
+      Worklist.clear();
+      auto enqueue = [&](BlockT *Pred) {
+        if (Mark[num(Pred)] == H)
+          return;
+        Mark[num(Pred)] = H;
+        Worklist.push_back(Pred);
+      };
+      // Place the latches, the predecessors the header dominates, into a
+      // worklist.
+      const DomTreeNodeBase<BlockT> *DomNode = DomTree.getNode(Header);
+      assert(DomNode && "header missing from the dominator tree");
+      bool HasBackedge = false;
+      for (BlockT *Pred : inverse_children<BlockT *>(Header)) {
+        const DomTreeNodeBase<BlockT> *PredNode = DomTree.getNode(Pred);
+        if (PredNode && DomTree.dominates(DomNode, PredNode)) {
+          HasBackedge = true;
+          enqueue(Pred);
+        }
+      }
+      // Whatever reaches a latch without passing the header is in the loop.
+      for (unsigned I = 0; I != Worklist.size(); ++I)
+        for (BlockT *Pred : inverse_children<BlockT *>(Worklist[I]))
+          enqueue(Pred);
----------------
MaskRay wrote:

OK. Ported the skip logic from discoverAndMapSubloop to protect against the pathologic cases.

https://github.com/llvm/llvm-project/pull/212000


More information about the llvm-commits mailing list