[llvm] [CycleInfo] Identify cycles with a single-pass DFS algorithm (PR #210491)

Fangrui Song via llvm-commits llvm-commits at lists.llvm.org
Sat Jul 18 11:10:22 PDT 2026


================
@@ -329,257 +323,204 @@ void GenericCycleInfo<ContextT>::addBlockToCycle(BlockT *Block, CycleRef C) {
       ++X.IdxEnd;
     }
   }
-  addToBlockMap(Block, &Cyc);
+  addToBlockMap(Block, C);
   // Cyc and its ancestors gain the new block: extend each one's slice and
   // invalidate its exit-block cache in a single walk up the tree.
-  for (unsigned I = getCycleIndex(Cyc); I != NoCycle;
-       I = Cycles[I].ParentIndex) {
-    ++Cycles[I].IdxEnd;
+  for (CycleRef I = C; I; I = deref(I).Parent) {
+    ++deref(I).IdxEnd;
     if (!ExitBlocksCaches.empty())
-      ExitBlocksCaches[I].clear();
+      ExitBlocksCaches[I.Index].clear();
   }
 }
 
-/// Move the discovered forest into Info's flat preorder array. Assigns preorder
-/// IDs, depths and descendant counts, remaps BlockMap from creation-order to
-/// preorder indices, and lays out every cycle's blocks in BlockLayout.
+/// Lay the discovered cycle forest out into Info's flat preorder array: number
+/// the cycles in Euler-tour order, set each one's parent, depth and descendant
+/// count, place every block into its innermost cycle's region of BlockLayout,
+/// and fill BlockMap. Arrays are keyed by header-preorder rank.
 template <typename ContextT>
-void GenericCycleInfoCompute<ContextT>::flatten(ArrayRef<BlockT *> Order) {
-  unsigned N = AllCycles.size();
+void GenericCycleInfoCompute<ContextT>::flatten(ArrayRef<BlockT *> Headers,
+                                                ArrayRef<unsigned> ChildHead,
+                                                ArrayRef<unsigned> NextSibling,
+                                                ArrayRef<unsigned> OwnCount,
+                                                unsigned TopHead) {
+  unsigned N = Headers.size();
   Info.NumCycles = N;
   if (!N)
     return;
   Info.Cycles = std::make_unique<CycleT[]>(N);
 
-  // Walk the cycle forest as an Euler tour. On entry, a cycle's IdxEnd still
-  // holds its own-block count (accumulated during run()); reserve that many
-  // slots for its own region [Cursor, Cursor + count). Its children take the
-  // following slots, so on leaving, Cursor is the cycle's real range end, which
-  // overwrites the now-consumed count in IdxEnd.
+  // Walk the cycle forest as an Euler tour. On entry a cycle reserves [Cursor,
+  // Cursor + OwnCount) for its own blocks (IdxBegin temporarily holds that
+  // region's end; the fill loop below walks it back down); its descendants take
+  // the following slots, so on exit Cursor is its IdxEnd.
+  SmallVector<unsigned, 8> FlatIdx(N);
   struct Frame {
-    CycleT *Flat;
-    unsigned ChildCur, ChildEnd;
-    unsigned ID;
+    unsigned Flat;
+    unsigned Child; // Next child to enter, NoCycle once exhausted.
   };
   SmallVector<Frame, 8> Stack;
-  unsigned Cursor = 0;
-  unsigned NextID = 0;
-  auto enter = [&](CycleT *Temp, CycleT *Parent) {
+  unsigned Cursor = 0, NextID = 0;
+  auto enter = [&](unsigned C, CycleRef Parent) {
     unsigned ID = NextID++;
+    FlatIdx[C] = ID;
     CycleT &Flat = Info.Cycles[ID];
-    Flat.ParentIndex =
-        Parent ? Info.getCycleIndex(*Parent) : CycleInfoT::NoCycle;
-    Flat.Depth = Parent ? Parent->Depth + 1 : 1;
-    Flat.Entries = std::move(Temp->Entries);
-    Cursor += Temp->IdxEnd; // IdxEnd currently holds Temp's own-block count.
-    Flat.IdxBegin = Cursor; // Real begin restored by the fill loop below.
-    Stack.push_back({&Flat, Temp->IdxBegin, Temp->Depth, ID});
-    // Temp's IdxBegin now holds the flat index, for the BlockMap remap below.
-    Temp->IdxBegin = ID;
+    Flat.Parent = Parent;
+    Flat.Depth = Parent ? Info.deref(Parent).Depth + 1 : 1;
+    Flat.appendEntry(Headers[C]);
+    Cursor += OwnCount[C];
+    Flat.IdxBegin = Cursor;
+    Stack.push_back({ID, ChildHead[C]});
   };
-  for (CycleT *TLC : TopLevelCycles) {
-    enter(TLC, nullptr);
+  for (auto TLC = TopHead; TLC != NoCycle; TLC = NextSibling[TLC]) {
+    enter(TLC, CycleRef());
     while (!Stack.empty()) {
       Frame &F = Stack.back();
-      if (F.ChildCur != F.ChildEnd) {
-        enter(AttachedChildren[F.ChildCur++], F.Flat);
+      if (F.Child != NoCycle) {
+        unsigned C = F.Child;
+        F.Child = NextSibling[C];
+        enter(C, CycleRef(F.Flat));
       } else {
-        F.Flat->IdxEnd = Cursor;
-        F.Flat->NumDescendants = NextID - F.ID - 1;
+        CycleT &Flat = Info.Cycles[F.Flat];
+        Flat.IdxEnd = Cursor;
+        Flat.NumDescendants = NextID - F.Flat - 1;
         Stack.pop_back();
       }
     }
   }
 
-  // Place every block into its innermost cycle's own region, remapping its
-  // BlockMap entry from a creation-order index to the flat preorder index.
+  // Place every block into its innermost cycle's own region.
   Info.BlockLayout.resize_for_overwrite(Cursor);
-  for (BlockT *B : llvm::reverse(Order)) {
-    unsigned Number = GraphTraits<const BlockT *>::getNumber(B);
-    unsigned Created = Info.BlockMap[Number];
-    if (Created != CycleInfoT::NoCycle) {
-      // Created indexes AllCycles; enter() stashed the flat preorder index in
-      // that temporary node's IdxBegin.
-      unsigned Flat = AllCycles[Created].IdxBegin;
-      Info.BlockMap[Number] = Flat;
-      CycleT &FlatCycle = Info.Cycles[Flat];
-      Info.BlockLayout[--FlatCycle.IdxBegin] = B;
-    }
+  for (unsigned N : llvm::reverse(Preorder)) {
+    BlockInfo &BI = info(N);
+    if (BI.CycleIdx == NoCycle)
+      continue;
+    unsigned Flat = FlatIdx[BI.CycleIdx];
+    Info.BlockMap[N] = CycleRef(Flat);
+    Info.BlockLayout[--Info.Cycles[Flat].IdxBegin] = BI.Block;
   }
 }
 
 /// \brief Main function of the cycle info computations.
 template <typename ContextT>
 void GenericCycleInfoCompute<ContextT>::run(FunctionT *F) {
   BlockT *EntryBlock = GraphTraits<FunctionT *>::getEntryNode(F);
-  LLVM_DEBUG(errs() << "Entry block: " << Info.Context.print(EntryBlock)
-                    << "\n");
-  dfs(F, EntryBlock);
-
-  SmallVector<BlockT *, 8> Worklist;
-
-  for (BlockT *HeaderCandidate : llvm::reverse(BlockPreorder)) {
-    const DFSInfo CandidateInfo = getDFSInfo(HeaderCandidate);
-
-    for (BlockT *Pred : predecessors(HeaderCandidate)) {
-      const DFSInfo PredDFSInfo = getDFSInfo(Pred);
-      // This automatically ignores unreachable predecessors since they have
-      // zeros in their DFSInfo.
-      if (CandidateInfo.isAncestorOf(PredDFSInfo))
-        Worklist.push_back(Pred);
-    }
-    if (Worklist.empty()) {
-      continue;
+  BlockInfos.assign(GraphTraits<FunctionT *>::getMaxNumber(F), BlockInfo{});
+
+  dfs(EntryBlock);
+
+  // Number the cycles by their header's preorder rank and resolve every
+  // block's innermost cycle in one pass: a block's LoopHeader is a DFS
+  // ancestor and so already numbered, and parents get smaller ranks than
+  // their children.
+  SmallVector<BlockT *, 8> Headers;
+  SmallVector<unsigned, 8> ChildHead, NextSibling, OwnCount;
+  unsigned TopHead = NoCycle;
+  for (unsigned N : Preorder) {
+    BlockInfo &BI = info(N);
+    if (BI.IsHeader) {
+      unsigned I = Headers.size();
+      BI.CycleIdx = I;
+      Headers.push_back(BI.Block);
+      ChildHead.push_back(NoCycle);
+      OwnCount.push_back(1); // The header itself.
+      unsigned &Head = BI.LoopHeader != NoBlock
+                           ? ChildHead[info(BI.LoopHeader).CycleIdx]
+                           : TopHead;
+      NextSibling.push_back(Head);
+      Head = I;
+      LLVM_DEBUG(errs() << "Found cycle for header: "
+                        << Info.Context.print(BI.Block) << "\n");
+    } else if (BI.LoopHeader != NoBlock) {
+      BI.CycleIdx = info(BI.LoopHeader).CycleIdx;
+      ++OwnCount[BI.CycleIdx];
     }
-
-    // Found a cycle with the candidate as its header.
-    LLVM_DEBUG(errs() << "Found cycle for header: "
-                      << Info.Context.print(HeaderCandidate) << "\n");
-    CycleT *NewCycle = &AllCycles.emplace_back();
-    NewCycle->IdxBegin = AttachedChildren.size(); // Attach-log slice start.
-    NewCycle->appendEntry(HeaderCandidate);
-    recordInnermostCycle(HeaderCandidate);
-    // The header is this cycle's first own block. Until flatten() runs,
-    // IdxEnd accumulates this cycle's own-block count (see the IdxBegin/
-    // IdxEnd doc comment), so flatten() needs no separate counting pass.
-    ++NewCycle->IdxEnd;
-
-    // Helper function to process (non-back-edge) predecessors of a discovered
-    // block and either add them to the worklist or recognize that the given
-    // block is an additional cycle entry.
-    auto ProcessPredecessors = [&](BlockT *Block) {
-      LLVM_DEBUG(errs() << "  block " << Info.Context.print(Block) << ": ");
-
-      bool IsEntry = false;
-      for (BlockT *Pred : predecessors(Block)) {
-        const DFSInfo PredDFSInfo = getDFSInfo(Pred);
-        if (CandidateInfo.isAncestorOf(PredDFSInfo)) {
-          Worklist.push_back(Pred);
-        } else if (!PredDFSInfo) {
-          // Ignore an unreachable predecessor. It will will incorrectly cause
-          // Block to be treated as a cycle entry.
-          LLVM_DEBUG(errs() << " skipped unreachable predecessor.\n");
-        } else {
-          IsEntry = true;
-        }
-      }
-      if (IsEntry) {
-        assert(!is_contained(NewCycle->Entries, Block));
-        LLVM_DEBUG(errs() << "append as entry\n");
-        NewCycle->appendEntry(Block);
-      } else {
-        LLVM_DEBUG(errs() << "append as child\n");
-      }
-    };
-
-    do {
-      BlockT *Block = Worklist.pop_back_val();
-      if (Block == HeaderCandidate)
-        continue;
-
-      // If the block has already been discovered by some cycle
-      // (possibly by ourself), then the outermost cycle containing it
-      // should become our child. Walk the temporary forest directly:
-      // handles are not meaningful until flatten() builds the flat array, so
-      // BlockMap still holds creation-order indices into AllCycles.
-      unsigned Created = Info.BlockMap[GraphTraits<BlockT *>::getNumber(Block)];
-      CycleT *BlockParent =
-          Created == CycleInfoT::NoCycle ? nullptr : &AllCycles[Created];
-      while (BlockParent && BlockParent->hasParent())
-        BlockParent = &AllCycles[BlockParent->ParentIndex];
-      if (BlockParent) {
-        LLVM_DEBUG(errs() << "  block " << Info.Context.print(Block) << ": ");
-
-        if (BlockParent != NewCycle) {
-          LLVM_DEBUG(errs()
-                     << "discovered child cycle "
-                     << Info.Context.print(BlockParent->Entries[0]) << "\n");
-          // Make BlockParent the child of NewCycle.
-          moveTopLevelCycleToNewParent(NewCycle, BlockParent);
-
-          for (auto *ChildEntry : BlockParent->Entries)
-            ProcessPredecessors(ChildEntry);
-        } else {
-          LLVM_DEBUG(errs()
-                     << "known child cycle "
-                     << Info.Context.print(BlockParent->Entries[0]) << "\n");
-        }
-      } else {
-        recordInnermostCycle(Block);
-        ++NewCycle->IdxEnd; // Block's innermost cycle is NewCycle.
-        ProcessPredecessors(Block);
-      }
-    } while (!Worklist.empty());
-
-    NewCycle->Depth = AttachedChildren.size(); // Attach-log slice end.
-    TopLevelCycles.push_back(NewCycle);
   }
+  flatten(Headers, ChildHead, NextSibling, OwnCount, TopHead);
+  if (Reentries.empty())
+    return;
 
-  // The cycle forest and the block-to-innermost-cycle map are complete; move
-  // the forest into Info's flat preorder array and lay out every cycle's
-  // blocks into the shared contiguous BlockLayout.
-  flatten(BlockPreorder);
+  // Add the non-header entries recorded during the DFS. Sorting by preorder
+  // rank appends each cycle's entries in block preorder; several edges may
+  // re-enter a cycle at the same block, so drop duplicates.
+  SmallVector<unsigned, 8> Rank(BlockInfos.size());
+  for (auto [R, N] : enumerate(Preorder))
+    Rank[N] = R;
+  for (auto &[B, H] : Reentries)
+    B = Rank[B];
+  llvm::sort(Reentries);
+  Reentries.erase(llvm::unique(Reentries), Reentries.end());
+  for (auto [R, H] : Reentries)
+    Info.deref(Info.BlockMap[H]).appendEntry(info(Preorder[R]).Block);
 }
 
-/// \brief Compute a DFS of basic blocks starting at the function entry.
-///
-/// Fills BlockDFSInfo with start/end counters and BlockPreorder.
+/// Identify (possibly irreducible) loops using a single-pass DFS algorithm of
+/// "A New Algorithm for Identifying Loops in Decompilation" (SAS 2007). The
+/// cycle forest is then reconstructed from the per-block header tags.
 template <typename ContextT>
-void GenericCycleInfoCompute<ContextT>::dfs(FunctionT *F, BlockT *EntryBlock) {
-  BlockDFSInfo.resize(GraphTraits<FunctionT *>::getMaxNumber(F));
-
+void GenericCycleInfoCompute<ContextT>::dfs(BlockT *EntryBlock) {
   // Successors are visited in reverse order to match the legacy
   // single-LIFO-stack traversal, keeping cycle identification and block order
   // unchanged.
   using SuccIt = decltype(successors(EntryBlock).begin());
   struct Frame {
-    BlockT *Block;
+    unsigned Block;
     std::reverse_iterator<SuccIt> Cur, End;
   };
   SmallVector<Frame, 8> Stack;
   unsigned Counter = 0;
+  Preorder.reserve(BlockInfos.size());
 
   auto open = [&](BlockT *Block) {
-    getOrInsertDFSInfo(Block).Start = ++Counter;
-    BlockPreorder.push_back(Block);
-    LLVM_DEBUG(errs() << "DFS visiting block: " << Info.Context.print(Block)
-                      << ", preorder number: " << Counter << "\n");
+    unsigned N = num(Block);
+    BlockInfo &BI = info(N);
+    BI.Block = Block;
+    BI.DFSPPos = ++Counter;
+    Preorder.push_back(N);
     auto Succs = successors(Block);
-    Stack.push_back({Block, std::make_reverse_iterator(Succs.end()),
+    Stack.push_back({N, std::make_reverse_iterator(Succs.end()),
                      std::make_reverse_iterator(Succs.begin())});
   };
 
   open(EntryBlock);
   while (!Stack.empty()) {
     Frame &Top = Stack.back();
-    BlockT *Next = nullptr;
-    while (Top.Cur != Top.End) {
-      BlockT *Succ = *Top.Cur++;
-      if (getOrInsertDFSInfo(Succ).Start == 0) {
-        Next = Succ;
-        break;
+    if (Top.Cur != Top.End) {
+      unsigned B0 = Top.Block;
+      BlockT *B1P = *Top.Cur++;
+      unsigned B1 = num(B1P);
+      BlockInfo &B1Info = info(B1);
+      if (!B1Info.Block) {
+        // Tree edge; the weaving happens when B1's frame is popped.
+        open(B1P);
+      } else if (B1Info.DFSPPos > 0) {
+        // B1 is a loop header (including self-edge).
+        B1Info.IsHeader = true;
----------------
MaskRay wrote:

Incremented `NumHeaders` here

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


More information about the llvm-commits mailing list