[llvm] [LoopInfo] Identify loops with a single-pass DFS algorithm. NFC (PR #212000)
via llvm-commits
llvm-commits at lists.llvm.org
Sat Jul 25 01:57:17 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-support
Author: Fangrui Song (MaskRay)
<details>
<summary>Changes</summary>
analyze() numbers the dominator tree, scans it in reverse preorder for header
candidates, floods backward through the CFG from each header's latches, then
walks the CFG again to order the blocks.
Do all of it in one forward search, with the algorithm of Wei et al., "A New
Algorithm for Identifying Loops in Decompilation", which GenericCycleInfo
already implements.
On an irreducible CFG that algorithm computes different loops from the legacy
discoverAndMapSubloop: a cycle may have more than one entry, while a natural
loop has one, as LoopSimplify and verifyLoop require. Reduce each such loop to
the natural loop of its header's backedges instead: the latches the header
dominates, and the blocks reaching them without passing the header.
discoverAndMapSubloop then has no callers.
A function whose loops are all reducible needs no dominance query.
1000x require<loops> on sqlite3.bc: instructions -24%, cycles -14%.
Aided by Claude Opus 5
---
Full diff: https://github.com/llvm/llvm-project/pull/212000.diff
2 Files Affected:
- (modified) llvm/include/llvm/Support/GenericLoopInfo.h (-10)
- (modified) llvm/include/llvm/Support/GenericLoopInfoImpl.h (+211-88)
``````````diff
diff --git a/llvm/include/llvm/Support/GenericLoopInfo.h b/llvm/include/llvm/Support/GenericLoopInfo.h
index e2819a21fe0ce..f3ebef97c9e8b 100644
--- a/llvm/include/llvm/Support/GenericLoopInfo.h
+++ b/llvm/include/llvm/Support/GenericLoopInfo.h
@@ -427,12 +427,6 @@ template <class BlockT, class LoopT> class LoopBase {
LI->reallocBlocks(*static_cast<LoopT *>(this), Size);
}
- /// interface to do reserve() for SubLoops
- void reserveSubLoops(unsigned Size) {
- assert(!isInvalid() && "Loop not in a valid state!");
- SubLoops.reserve(Size);
- }
-
/// This method is used to move BB (which must be part of this loop) to be the
/// loop header of the loop (the block that dominates all others).
void moveToHeader(BlockT *BB) {
@@ -668,10 +662,6 @@ template <class BlockT, class LoopT> class LoopInfoBase {
/// builds the block list.
static BlockT *pendingHeader(const LoopT *L) { return L->PendingHeader; }
- void discoverAndMapSubloop(LoopT *L, BlockT *Header,
- ArrayRef<BlockT *> Backedges,
- const DominatorTreeBase<BlockT, false> &DomTree);
-
/// True if \p L borrows its block list from BlockLayout.
static bool hasBorrowedBlocks(const LoopT &L) {
return L.BlockCapacity == LoopT::BorrowedCapacity;
diff --git a/llvm/include/llvm/Support/GenericLoopInfoImpl.h b/llvm/include/llvm/Support/GenericLoopInfoImpl.h
index 5ea9f440dc28b..e46174baa1ca8 100644
--- a/llvm/include/llvm/Support/GenericLoopInfoImpl.h
+++ b/llvm/include/llvm/Support/GenericLoopInfoImpl.h
@@ -451,65 +451,8 @@ 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
@@ -517,48 +460,228 @@ void LoopInfoBase<BlockT, LoopT>::discoverAndMapSubloop(
/// 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);
+ // Without a backedge the header forms no loop at all.
+ Info[H].Pos = HasBackedge ? IsHeader : OffPath;
+ // Partition the header's blocks: the loop keeps the ones the traversal
+ // reached, and the enclosing header takes the rest, which its own turn
+ // then tests. Both arms relink the block, so step first.
+ unsigned Parent = Info[H].LoopHeader;
+ unsigned Kept = NoBlock;
+ for (unsigned B = FirstChild[H], Next; B != NoBlock; B = Next) {
+ Next = NextSibling[B];
+ if (Mark[B] == H) {
+ NextSibling[B] = Kept;
+ Kept = B;
+ } else {
+ // Leaving the loop; the block is top level if it had no other header.
+ Info[B].LoopHeader = Parent;
+ if (Parent != NoBlock) {
+ NextSibling[B] = FirstChild[Parent];
+ FirstChild[Parent] = B;
+ }
+ }
+ }
+ FirstChild[H] = Kept;
+ }
+ if (none_of(Headers, [&](unsigned H) { return Info[H].Pos == IsHeader; }))
+ return;
+ }
+
+ // Resolve the chains in reverse postorder: a block's innermost header is
+ // one of its search tree ancestors, so it is mapped to its loop first.
+ BBMap.resize(MaxNumber);
+ for (BlockT *BB : llvm::reverse(Postorder)) {
+ unsigned B = num(BB);
+ unsigned H = Info[B].LoopHeader;
+ LoopT *Enclosing = H == NoBlock ? nullptr : BBMap[H];
+ LoopT *L = Enclosing;
+ if (Info[B].Pos == IsHeader) {
+ L = allocateLoop(BB);
+ L->setParentLoop(Enclosing);
+ }
+ BBMap[B] = L;
+ }
// Record each in-loop block with its innermost loop in forward CFG postorder,
// and build the loop list in PO.
SmallVector<std::pair<BlockT *, LoopT *>, 32> PO;
SmallVector<LoopT *, 4> LoopsPO;
- PO.reserve(BBMap.size());
- for (BlockT *BB : post_order(ParentPtr)) {
+ PO.reserve(Postorder.size());
+ for (BlockT *BB : Postorder) {
LoopT *L = lookupLoopFor(BB);
if (!L)
continue;
``````````
</details>
https://github.com/llvm/llvm-project/pull/212000
More information about the llvm-commits
mailing list