[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