[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 13:45:01 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()) {
----------------
MaskRay wrote:
> In summary, this is a subset of a classical (Tarjan's, I believe?) loop algorithm -- when finding a (candidate) loop header, walk backwards through the CFG to mark all blocks on paths that reach the header again. Instead of walking the dominator tree to find candidate loop headers (=all blocks), this takes knowledge of the previously constructed irreducible loop analysis to restrict the set of candidate headers. Is this accurate?
Accurate. We utilize the fact that loop nest already exists to simplify `discoverAndMapSubloop`. We only check the irreducible headers.
https://github.com/llvm/llvm-project/pull/212000
More information about the llvm-commits
mailing list