[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 12:07:09 PDT 2026
https://github.com/MaskRay updated https://github.com/llvm/llvm-project/pull/210491
>From 0c235be238813d7f5dc925ced840e1dd0a5e9c5b Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Fri, 17 Jul 2026 09:43:39 -0700
Subject: [PATCH 1/4] [CycleInfo] Identify cycles with a single-pass DFS
algorithm
Replace the Havlak-Tarjan construction in GenericCycleInfoCompute, a DFS
followed by a reverse-preorder scan, with the single-pass algorithm of
Wei, Mao, Zou and Chen, "A New Algorithm for Identifying Loops in
Decompilation" (SAS 2007). One depth-first traversal tags every block
with its innermost loop header on the fly; tagLoopHeader weaves the
per-block header chains, replacing UNION-FIND.
The flat forest is reconstructed from the tags, dropping the temporary
cycle objects and the per-block worklist passes. An edge re-entering an
already-closed cycle records non-header entries, so entries need no
predecessor scan.
The cycle sets, headers, reducibility and nesting are identical for the
given DFS order, cross-checked against the old construction on random
reducible and irreducible CFGs. Two implementation-defined orders change
(with minor test churn): sibling cycles are laid out in decreasing
header preorder, and non-header entries in block preorder.
Construction cost (51x require+invalidate of <cycles> minus a 1x run,
median of interleaved rounds, x86-64): sqlite3.bc executes 14% fewer
instructions and 21% fewer cycles; a 1500-deep loop nest executes 45%
fewer instructions and 44% fewer cycles.
Aided by Claude Fable 5
---
llvm/include/llvm/ADT/GenericCycleImpl.h | 453 ++++++++++-------------
llvm/include/llvm/ADT/GenericCycleInfo.h | 36 +-
llvm/test/Analysis/CycleInfo/basic.ll | 2 +-
llvm/test/CodeGen/X86/cycle-info.mir | 6 +-
4 files changed, 212 insertions(+), 285 deletions(-)
diff --git a/llvm/include/llvm/ADT/GenericCycleImpl.h b/llvm/include/llvm/ADT/GenericCycleImpl.h
index e2da17af20826..dc45ee19bb8a4 100644
--- a/llvm/include/llvm/ADT/GenericCycleImpl.h
+++ b/llvm/include/llvm/ADT/GenericCycleImpl.h
@@ -26,8 +26,8 @@
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/DepthFirstIterator.h"
#include "llvm/ADT/GenericCycleInfo.h"
+#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringExtras.h"
-#include <deque>
#include <iterator>
#define DEBUG_TYPE "generic-cycle-impl"
@@ -207,7 +207,7 @@ void GenericCycleInfo<ContextT>::verifyCycleNest(CycleRef C) const {
// Check the parent cycle.
if (Cyc.hasParent()) {
- assert(is_contained(children(CycleRef(Cyc.ParentIndex)), C) &&
+ assert(is_contained(children(Cyc.Parent), C) &&
"Cycle is not a subcycle of its parent!");
}
#endif
@@ -222,90 +222,84 @@ template <typename ContextT> class GenericCycleInfoCompute {
CycleInfoT &Info;
- struct DFSInfo {
- unsigned Start = 0; // DFS start; positive if block is found
- unsigned End = 0; // DFS end
-
- DFSInfo() = default;
- explicit DFSInfo(unsigned Start) : Start(Start) {}
-
- explicit operator bool() const { return Start; }
-
- /// Whether this node is an ancestor (or equal to) the node \p Other
- /// in the DFS tree.
- bool isAncestorOf(const DFSInfo &Other) const {
- return Start <= Other.Start && Other.End <= End;
- }
+ /// Sentinel header-preorder rank meaning "no cycle".
+ static constexpr unsigned NoCycle = ~0u;
+ /// Sentinel block number meaning "no block".
+ static constexpr unsigned NoBlock = ~0u;
+
+ /// Per-block state indexed by block number.
+ struct BlockInfo {
+ /// The block this entry describes; null for an unreachable block.
+ BlockT *Block = nullptr;
+ /// Block number of the innermost loop header found so far; NoBlock if none.
+ unsigned LoopHeader = NoBlock;
+ /// 1-based position on the current DFS path; 0 once the block leaves it.
+ unsigned DFSPPos = 0;
+ /// Header-preorder rank of the innermost cycle containing this block (the
+ /// cycle it heads if IsHeader); NoCycle if none, or unreachable.
+ unsigned CycleIdx = NoCycle;
+ bool IsHeader = false;
};
-
- // Indexed by block number.
- SmallVector<DFSInfo, 8> BlockDFSInfo;
- SmallVector<BlockT *, 8> BlockPreorder;
-
- /// Append-only cycles discovered so far, in creation order.
- std::deque<CycleT> AllCycles;
-
- /// Flat log of child attachments, in attach order. Cycles are only ever
- /// attached to the newest cycle, so each cycle's children occupy the
- /// contiguous slice [IdxBegin, Depth) of this log.
- SmallVector<CycleT *, 8> AttachedChildren;
-
- SmallVector<CycleT *, 8> TopLevelCycles;
+ SmallVector<BlockInfo, 8> BlockInfos;
+ /// Reachable block numbers in DFS preorder.
+ SmallVector<unsigned, 8> Preorder;
+ /// Records (block B, header H): an edge from outside re-enters the closed
+ /// cycle headed by H at B, making B a non-header entry of it.
+ SmallVector<std::pair<unsigned, unsigned>, 8> Reentries;
GenericCycleInfoCompute(const GenericCycleInfoCompute &) = delete;
GenericCycleInfoCompute &operator=(const GenericCycleInfoCompute &) = delete;
- DFSInfo getDFSInfo(BlockT *B) const {
- unsigned Number = GraphTraits<BlockT *>::getNumber(B);
- return BlockDFSInfo[Number];
+ static unsigned num(const BlockT *B) {
+ return GraphTraits<const BlockT *>::getNumber(B);
}
- DFSInfo &getOrInsertDFSInfo(BlockT *B) {
- unsigned Number = GraphTraits<BlockT *>::getNumber(B);
- return BlockDFSInfo[Number];
- }
-
- /// Make top-level cycle \p Child a child of \p NewParent.
- void moveTopLevelCycleToNewParent(CycleT *NewParent, CycleT *Child) {
- assert((!Child->hasParent() && !NewParent->hasParent()) &&
- "NewParent and Child must be both top level cycle!\n");
- auto Pos = llvm::find(TopLevelCycles, Child);
- assert(Pos != TopLevelCycles.end());
- *Pos = TopLevelCycles.back();
- TopLevelCycles.pop_back();
- AttachedChildren.push_back(Child);
- // NewParent is the newest cycle and its creation-order index is
- // AllCycles.size() - 1.
- assert(NewParent == &AllCycles.back() &&
- "attach slices in AttachedChildren must stay contiguous");
- Child->ParentIndex = AllCycles.size() - 1;
+ BlockInfo &info(unsigned Number) { return BlockInfos[Number]; }
+
+ /// 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
+ /// DFS-path position. Building this chain on the fly is why the algorithm
+ /// needs no union-find (used in the Havlak algorithm) at all.
+ void tagLoopHeader(unsigned B, unsigned H) {
+ if (H == NoBlock)
+ return;
+ // Invariant: info(B).DFSPPos >= info(H).DFSPPos.
+ 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 DFS-path position).
+ if (info(IH).DFSPPos >= info(H).DFSPPos)
+ B = IH;
+ else {
+ info(B).LoopHeader = H;
+ B = H;
+ H = IH;
+ }
+ }
}
- /// Record that \p Block's innermost cycle is the one currently being built
- /// (always AllCycles.back()), storing its creation-order index. flatten()
- /// remaps it to the preorder index.
- void recordInnermostCycle(BlockT *Block) {
- Info.BlockMap[GraphTraits<BlockT *>::getNumber(Block)] =
- AllCycles.size() - 1;
- }
+ void dfs(BlockT *EntryBlock);
+ void flatten(ArrayRef<BlockT *> Headers, ArrayRef<unsigned> ChildHead,
+ ArrayRef<unsigned> NextSibling, ArrayRef<unsigned> OwnCount,
+ unsigned TopHead);
public:
GenericCycleInfoCompute(CycleInfoT &Info) : Info(Info) {}
void run(FunctionT *F);
-
-private:
- void dfs(FunctionT *F, BlockT *EntryBlock);
- void flatten(ArrayRef<BlockT *> Order);
};
template <typename ContextT>
-void GenericCycleInfo<ContextT>::addToBlockMap(BlockT *Block, CycleT *Cycle) {
- // The caller should ensure that BlockMap is large enough. \p Cycle is a flat
+void GenericCycleInfo<ContextT>::addToBlockMap(BlockT *Block, CycleRef C) {
+ // The caller should ensure that BlockMap is large enough. C is a flat
// cycle, so its preorder index is well-defined.
verifyBlockNumberEpoch(Block->getParent());
unsigned Number = GraphTraits<BlockT *>::getNumber(Block);
- BlockMap[Number] = getCycleIndex(*Cycle);
+ BlockMap[Number] = C;
}
template <typename ContextT>
@@ -315,7 +309,7 @@ void GenericCycleInfo<ContextT>::addBlockToCycle(BlockT *Block, CycleRef C) {
unsigned Number = GraphTraits<BlockT *>::getNumber(Block);
if (Number >= BlockMap.size())
BlockMap.resize(GraphTraits<FunctionT *>::getMaxNumber(Block->getParent()),
- NoCycle);
+ CycleRef());
// Insert Block at the end of Cyc's slice and shift every later cycle's
// range right. Ranges straddling Pos belong to Cyc's ancestors and are
@@ -329,82 +323,80 @@ 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;
}
}
@@ -412,174 +404,123 @@ void GenericCycleInfoCompute<ContextT>::flatten(ArrayRef<BlockT *> Order) {
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;
+ tagLoopHeader(B0, B1);
+ } else {
+ // Climb B1's header chain outward: every enclosing header still off
+ // the DFS path heads a closed cycle that this edge re-enters, making
+ // B1 a non-header entry of it (and the cycle irreducible). Stop at the
+ // first header still on the path (genuine interior node of the loop),
+ // and attribute B0 to it.
+ for (unsigned H = B1Info.LoopHeader; H != NoBlock;
+ H = info(H).LoopHeader) {
+ if (info(H).DFSPPos > 0) {
+ tagLoopHeader(B0, H);
+ break;
+ }
+ Reentries.push_back({B1, H});
+ }
}
- LLVM_DEBUG(errs() << " already visited successor: "
- << Info.Context.print(Succ) << "\n");
- }
- if (Next) {
- open(Next);
} else {
- // Top's subtree is complete. Its end counter is the largest preorder
- // number in the subtree.
- getOrInsertDFSInfo(Top.Block).End = Counter;
- LLVM_DEBUG(errs() << "DFS block " << Info.Context.print(Top.Block)
- << " ended at " << Counter << "\n");
+ // Leave the DFS path.
+ unsigned B0 = Top.Block;
+ info(B0).DFSPPos = 0;
Stack.pop_back();
+ // And weave into the parent's chain (continue the "Tree edge" case).
+ if (!Stack.empty())
+ tagLoopHeader(Stack.back().Block, info(B0).LoopHeader);
}
}
-
- LLVM_DEBUG({
- errs() << "Preorder:\n";
- for (int I = 0, E = BlockPreorder.size(); I != E; ++I)
- errs() << " " << Info.Context.print(BlockPreorder[I]) << ": " << I
- << "\n";
- });
}
/// \brief Reset the object to its initial state.
@@ -597,7 +538,7 @@ void GenericCycleInfo<ContextT>::compute(FunctionT &F) {
GenericCycleInfoCompute<ContextT> Compute(*this);
Context = ContextT(&F);
BlockNumberEpoch = GraphTraits<FunctionT *>::getNumberEpoch(&F);
- BlockMap.assign(GraphTraits<FunctionT *>::getMaxNumber(&F), NoCycle);
+ BlockMap.assign(GraphTraits<FunctionT *>::getMaxNumber(&F), CycleRef());
LLVM_DEBUG(errs() << "Computing cycles for function: " << F.getName()
<< "\n");
diff --git a/llvm/include/llvm/ADT/GenericCycleInfo.h b/llvm/include/llvm/ADT/GenericCycleInfo.h
index 67c301dd1121c..1245ef5d49bb1 100644
--- a/llvm/include/llvm/ADT/GenericCycleInfo.h
+++ b/llvm/include/llvm/ADT/GenericCycleInfo.h
@@ -55,6 +55,7 @@ class CycleRef {
explicit CycleRef(unsigned Index) : Index(Index) {}
template <typename ContextT> friend class GenericCycleInfo;
+ template <typename ContextT> friend class GenericCycleInfoCompute;
friend struct DenseMapInfo<CycleRef>;
public:
@@ -80,17 +81,12 @@ template <typename ContextT> class GenericCycleInfo {
template <typename> friend class GenericCycleInfoCompute;
private:
- /// Sentinel for a cycle-index slot that refers to no cycle.
- static constexpr unsigned NoCycle = ~0u;
-
/// Internal, data-only storage for a cycle. Consumers name a cycle by a
/// CycleRef handle and query it through GenericCycleInfo.
class Cycle {
public:
- /// Preorder index of the parent cycle, or NoCycle for a top-level
- /// cycle. Before flatten() this holds a creation-order index into
- /// GenericCycleInfoCompute::AllCycles.
- unsigned ParentIndex = NoCycle;
+ /// The parent cycle; invalid for a top-level cycle.
+ CycleRef Parent;
/// The entry block(s) of the cycle. The header is the only entry if this
/// is a loop.
@@ -100,10 +96,6 @@ template <typename ContextT> class GenericCycleInfo {
/// half-open range [IdxBegin, IdxEnd) of BlockLayout, nested like an Euler
/// tour of the cycle tree, so containment is an interval test (see
/// contains()).
- ///
- /// During construction (before the forest is flattened), IdxEnd accumulates
- /// the number of this cycle's own blocks (those whose innermost cycle is
- /// this one).
unsigned IdxBegin = 0, IdxEnd = 0;
/// Depth of the cycle in the tree: top-level cycles are at depth 1 and each
@@ -118,7 +110,7 @@ template <typename ContextT> class GenericCycleInfo {
void appendEntry(BlockT *Block) { Entries.push_back(Block); }
/// Whether this cycle has a parent, i.e. is not top-level.
- bool hasParent() const { return ParentIndex != NoCycle; }
+ bool hasParent() const { return Parent.isValid(); }
Cycle() = default;
Cycle(const Cycle &) = delete;
@@ -131,12 +123,9 @@ template <typename ContextT> class GenericCycleInfo {
ContextT Context;
unsigned BlockNumberEpoch;
- /// Map each basic block number to the preorder index of its inner-most
- /// containing cycle, or NoCycle if none. During construction this
- /// transiently holds creation-order indices into
- /// GenericCycleInfoCompute::AllCycles, which flatten() remaps to preorder
- /// indices.
- SmallVector<unsigned> BlockMap;
+ /// Map each basic block number to its inner-most containing cycle, or an
+ /// invalid handle if none.
+ SmallVector<CycleRef> BlockMap;
/// Euler tour of the cycle forest: every cycle's blocks form a contiguous
/// slice [IdxBegin, IdxEnd) of this array, nested inside its parent's.
@@ -173,7 +162,7 @@ template <typename ContextT> class GenericCycleInfo {
GraphTraits<const FunctionT *>::getNumberEpoch(Fn) &&
"CycleInfo used with outdated block number epoch");
}
- void addToBlockMap(BlockT *Block, CycleT *Cycle);
+ void addToBlockMap(BlockT *Block, CycleRef C);
public:
/// Iteration over child cycles, yielding handles. The first child (if any)
@@ -225,17 +214,14 @@ template <typename ContextT> class GenericCycleInfo {
unsigned Number = GraphTraits<const BlockT *>::getNumber(Block);
// A block added after compute() that no cycle contains (e.g. a critical
// edge MachineSink split outside every cycle) has a number beyond BlockMap.
- if (Number >= BlockMap.size() || BlockMap[Number] == NoCycle)
+ if (Number >= BlockMap.size())
return CycleRef();
- return CycleRef(BlockMap[Number]);
+ return BlockMap[Number];
}
BlockT *getHeader(CycleRef C) const { return deref(C).Entries[0]; }
bool isReducible(CycleRef C) const { return deref(C).Entries.size() == 1; }
- CycleRef getParentCycle(CycleRef C) const {
- auto P = deref(C).ParentIndex;
- return P == NoCycle ? CycleRef() : CycleRef(P);
- }
+ CycleRef getParentCycle(CycleRef C) const { return deref(C).Parent; }
unsigned getDepth(CycleRef C) const { return deref(C).Depth; }
size_t getNumBlocks(CycleRef C) const {
const CycleT &Cyc = deref(C);
diff --git a/llvm/test/Analysis/CycleInfo/basic.ll b/llvm/test/Analysis/CycleInfo/basic.ll
index 240722b9f3788..b96f99aa05f56 100644
--- a/llvm/test/Analysis/CycleInfo/basic.ll
+++ b/llvm/test/Analysis/CycleInfo/basic.ll
@@ -204,7 +204,7 @@ exit:
define void @irreducible_into_simple_cycle(i1 %arg) {
; CHECK-LABEL: CycleInfo for function: irreducible_into_simple_cycle
-; CHECK: depth=1: entries(F C A) B D E
+; CHECK: depth=1: entries(F A C) B D E
entry:
switch i32 undef, label %A [ i32 0, label %C
i32 1, label %F ]
diff --git a/llvm/test/CodeGen/X86/cycle-info.mir b/llvm/test/CodeGen/X86/cycle-info.mir
index 22cfa96f1fac0..a4df89df323ee 100644
--- a/llvm/test/CodeGen/X86/cycle-info.mir
+++ b/llvm/test/CodeGen/X86/cycle-info.mir
@@ -225,9 +225,9 @@ body: |
---
# LEGACY-LABEL: MachineCycleInfo for function: nested_sibling_loops
# NPM-LABEL: name: nested_sibling_loops
-# CHECK: depth=1: entries(bb.1) bb.3 bb.4 bb.5 bb.2
-# CHECK: depth=2: entries(bb.4) bb.5
+# CHECK: depth=1: entries(bb.1) bb.3 bb.2 bb.4 bb.5
# CHECK: depth=2: entries(bb.2)
+# CHECK: depth=2: entries(bb.4) bb.5
name: nested_sibling_loops
alignment: 16
tracksRegLiveness: true
@@ -451,7 +451,7 @@ body: |
---
# LEGACY-LABEL: MachineCycleInfo for function: irreducible_into_simple_cycle
# NPM-LABEL: name: irreducible_into_simple_cycle
-# CHECK: depth=1: entries(bb.2 bb.7 bb.4) bb.3 bb.5 bb.6
+# CHECK: depth=1: entries(bb.2 bb.4 bb.7) bb.3 bb.5 bb.6
name: irreducible_into_simple_cycle
alignment: 16
tracksRegLiveness: true
>From 0814c071c6df5504c04536db1299069da63025b3 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sat, 18 Jul 2026 10:59:59 -0700
Subject: [PATCH 2/4] address comments
---
llvm/include/llvm/ADT/GenericCycleImpl.h | 128 +++++++++++++----------
1 file changed, 72 insertions(+), 56 deletions(-)
diff --git a/llvm/include/llvm/ADT/GenericCycleImpl.h b/llvm/include/llvm/ADT/GenericCycleImpl.h
index dc45ee19bb8a4..77b8f26c236ae 100644
--- a/llvm/include/llvm/ADT/GenericCycleImpl.h
+++ b/llvm/include/llvm/ADT/GenericCycleImpl.h
@@ -222,29 +222,42 @@ template <typename ContextT> class GenericCycleInfoCompute {
CycleInfoT &Info;
- /// Sentinel header-preorder rank meaning "no cycle".
+ // Sentinel header-preorder rank meaning "no cycle".
static constexpr unsigned NoCycle = ~0u;
- /// Sentinel block number meaning "no block".
+ // Sentinel block number meaning "no block".
static constexpr unsigned NoBlock = ~0u;
- /// Per-block state indexed by block number.
+ // Per-block state indexed by block number. All fields default to zero.
struct BlockInfo {
- /// The block this entry describes; null for an unreachable block.
+ // The block this entry describes; non-null once visited by DFS.
BlockT *Block = nullptr;
- /// Block number of the innermost loop header found so far; NoBlock if none.
- unsigned LoopHeader = NoBlock;
- /// 1-based position on the current DFS path; 0 once the block leaves it.
+ // Block number of the innermost loop header; NoBlock if none. Set to
+ // NoBlock by open() on first visit, then woven by tagLoopHeader.
+ unsigned LoopHeader = 0;
+ // 1-based position on the current DFS path; 0 if off path.
unsigned DFSPPos = 0;
- /// Header-preorder rank of the innermost cycle containing this block (the
- /// cycle it heads if IsHeader); NoCycle if none, or unreachable.
- unsigned CycleIdx = NoCycle;
+ // Header-preorder rank of the innermost cycle containing this block (the
+ // cycle it heads if IsHeader); NoCycle if none. Set by the numbering pass.
+ unsigned CycleIdx = 0;
bool IsHeader = false;
};
+
+ // Per-cycle scratch built in run() and consumed by flatten(), keyed by
+ // header-preorder rank.
+ struct CycleBuild {
+ BlockT *Header;
+ unsigned ChildHead;
+ unsigned NextSibling;
+ unsigned OwnCount;
+ };
+
SmallVector<BlockInfo, 8> BlockInfos;
- /// Reachable block numbers in DFS preorder.
+ // Reachable block numbers in DFS preorder.
SmallVector<unsigned, 8> Preorder;
- /// Records (block B, header H): an edge from outside re-enters the closed
- /// cycle headed by H at B, making B a non-header entry of it.
+ // Number of loop headers found by dfs(), i.e. the number of cycles.
+ unsigned NumHeaders = 0;
+ // Records (header H, block B): an edge from outside re-enters the closed
+ // cycle headed by H at B, making B a non-header entry of it.
SmallVector<std::pair<unsigned, unsigned>, 8> Reentries;
GenericCycleInfoCompute(const GenericCycleInfoCompute &) = delete;
@@ -256,10 +269,10 @@ template <typename ContextT> class GenericCycleInfoCompute {
BlockInfo &info(unsigned Number) { return BlockInfos[Number]; }
- /// 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
- /// DFS-path position. Building this chain on the fly is why the algorithm
- /// needs no union-find (used in the Havlak algorithm) at all.
+ // 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
+ // DFS-path position. Building this chain on the fly is why the algorithm
+ // needs no union-find (used in the Havlak algorithm) at all.
void tagLoopHeader(unsigned B, unsigned H) {
if (H == NoBlock)
return;
@@ -283,9 +296,7 @@ template <typename ContextT> class GenericCycleInfoCompute {
}
void dfs(BlockT *EntryBlock);
- void flatten(ArrayRef<BlockT *> Headers, ArrayRef<unsigned> ChildHead,
- ArrayRef<unsigned> NextSibling, ArrayRef<unsigned> OwnCount,
- unsigned TopHead);
+ void flatten(ArrayRef<CycleBuild> Build, unsigned TopHead);
public:
GenericCycleInfoCompute(CycleInfoT &Info) : Info(Info) {}
@@ -336,17 +347,12 @@ void GenericCycleInfo<ContextT>::addBlockToCycle(BlockT *Block, CycleRef C) {
/// 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.
+/// and fill BlockMap. \p Build is keyed by header-preorder rank.
template <typename ContextT>
-void GenericCycleInfoCompute<ContextT>::flatten(ArrayRef<BlockT *> Headers,
- ArrayRef<unsigned> ChildHead,
- ArrayRef<unsigned> NextSibling,
- ArrayRef<unsigned> OwnCount,
+void GenericCycleInfoCompute<ContextT>::flatten(ArrayRef<CycleBuild> Build,
unsigned TopHead) {
- unsigned N = Headers.size();
+ unsigned N = Build.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 reserves [Cursor,
@@ -366,18 +372,18 @@ void GenericCycleInfoCompute<ContextT>::flatten(ArrayRef<BlockT *> Headers,
CycleT &Flat = Info.Cycles[ID];
Flat.Parent = Parent;
Flat.Depth = Parent ? Info.deref(Parent).Depth + 1 : 1;
- Flat.appendEntry(Headers[C]);
- Cursor += OwnCount[C];
+ Flat.appendEntry(Build[C].Header);
+ Cursor += Build[C].OwnCount;
Flat.IdxBegin = Cursor;
- Stack.push_back({ID, ChildHead[C]});
+ Stack.push_back({ID, Build[C].ChildHead});
};
- for (auto TLC = TopHead; TLC != NoCycle; TLC = NextSibling[TLC]) {
+ for (auto TLC = TopHead; TLC != NoCycle; TLC = Build[TLC].NextSibling) {
enter(TLC, CycleRef());
while (!Stack.empty()) {
Frame &F = Stack.back();
if (F.Child != NoCycle) {
unsigned C = F.Child;
- F.Child = NextSibling[C];
+ F.Child = Build[C].NextSibling;
enter(C, CycleRef(F.Flat));
} else {
CycleT &Flat = Info.Cycles[F.Flat];
@@ -407,50 +413,55 @@ void GenericCycleInfoCompute<ContextT>::run(FunctionT *F) {
BlockInfos.assign(GraphTraits<FunctionT *>::getMaxNumber(F), BlockInfo{});
dfs(EntryBlock);
+ if (!NumHeaders)
+ return;
// 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;
+ // their children. dfs() counted the headers, so Build needs one allocation;
+ // the exact reserve also keeps push_back from invalidating Head.
+ SmallVector<CycleBuild, 8> Build;
+ Build.reserve(NumHeaders);
unsigned TopHead = NoCycle;
for (unsigned N : Preorder) {
BlockInfo &BI = info(N);
if (BI.IsHeader) {
- unsigned I = Headers.size();
+ unsigned I = Build.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]
+ ? Build[info(BI.LoopHeader).CycleIdx].ChildHead
: TopHead;
- NextSibling.push_back(Head);
+ Build.push_back({BI.Block, NoCycle, Head, 1}); // OwnCount 1: the header.
Head = I;
- LLVM_DEBUG(errs() << "Found cycle for header: "
+ LLVM_DEBUG(dbgs() << "Found cycle for header: "
<< Info.Context.print(BI.Block) << "\n");
} else if (BI.LoopHeader != NoBlock) {
BI.CycleIdx = info(BI.LoopHeader).CycleIdx;
- ++OwnCount[BI.CycleIdx];
+ ++Build[BI.CycleIdx].OwnCount;
+ } else {
+ BI.CycleIdx = NoCycle;
}
}
- flatten(Headers, ChildHead, NextSibling, OwnCount, TopHead);
+ flatten(Build, TopHead);
if (Reentries.empty())
return;
- // 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.
+ // Add the non-header entries recorded during the DFS. Sorting by (header,
+ // block) groups each cycle's entries together and in block preorder; a block
+ // may re-enter a cycle via several edges, so skip duplicates.
SmallVector<unsigned, 8> Rank(BlockInfos.size());
for (auto [R, N] : enumerate(Preorder))
Rank[N] = R;
- for (auto &[B, H] : Reentries)
+ for (auto &[H, B] : Reentries)
B = Rank[B];
llvm::sort(Reentries);
- Reentries.erase(llvm::unique(Reentries), Reentries.end());
- for (auto [R, H] : Reentries)
+ for (unsigned I = 0, E = Reentries.size(); I != E; ++I) {
+ if (I && Reentries[I] == Reentries[I - 1])
+ continue;
+ auto [H, R] = Reentries[I];
Info.deref(Info.BlockMap[H]).appendEntry(info(Preorder[R]).Block);
+ }
}
/// Identify (possibly irreducible) loops using a single-pass DFS algorithm of
@@ -468,14 +479,15 @@ void GenericCycleInfoCompute<ContextT>::dfs(BlockT *EntryBlock) {
};
SmallVector<Frame, 8> Stack;
unsigned Counter = 0;
- Preorder.reserve(BlockInfos.size());
+ Preorder.resize_for_overwrite(BlockInfos.size());
auto open = [&](BlockT *Block) {
unsigned N = num(Block);
BlockInfo &BI = info(N);
BI.Block = Block;
+ BI.LoopHeader = NoBlock;
BI.DFSPPos = ++Counter;
- Preorder.push_back(N);
+ Preorder[Counter - 1] = N;
auto Succs = successors(Block);
Stack.push_back({N, std::make_reverse_iterator(Succs.end()),
std::make_reverse_iterator(Succs.begin())});
@@ -494,7 +506,10 @@ void GenericCycleInfoCompute<ContextT>::dfs(BlockT *EntryBlock) {
open(B1P);
} else if (B1Info.DFSPPos > 0) {
// B1 is a loop header (including self-edge).
- B1Info.IsHeader = true;
+ if (!B1Info.IsHeader) {
+ B1Info.IsHeader = true;
+ ++NumHeaders;
+ }
tagLoopHeader(B0, B1);
} else {
// Climb B1's header chain outward: every enclosing header still off
@@ -508,7 +523,7 @@ void GenericCycleInfoCompute<ContextT>::dfs(BlockT *EntryBlock) {
tagLoopHeader(B0, H);
break;
}
- Reentries.push_back({B1, H});
+ Reentries.push_back({H, B1});
}
}
} else {
@@ -521,6 +536,7 @@ void GenericCycleInfoCompute<ContextT>::dfs(BlockT *EntryBlock) {
tagLoopHeader(Stack.back().Block, info(B0).LoopHeader);
}
}
+ Preorder.truncate(Counter);
}
/// \brief Reset the object to its initial state.
@@ -540,7 +556,7 @@ void GenericCycleInfo<ContextT>::compute(FunctionT &F) {
BlockNumberEpoch = GraphTraits<FunctionT *>::getNumberEpoch(&F);
BlockMap.assign(GraphTraits<FunctionT *>::getMaxNumber(&F), CycleRef());
- LLVM_DEBUG(errs() << "Computing cycles for function: " << F.getName()
+ LLVM_DEBUG(dbgs() << "Computing cycles for function: " << F.getName()
<< "\n");
Compute.run(&F);
}
>From 71a1a6c3c820e9cdf366793f9bd559fc43ee7c31 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sat, 18 Jul 2026 11:49:48 -0700
Subject: [PATCH 3/4] layout
---
llvm/include/llvm/ADT/GenericCycleImpl.h | 23 +++++++++++------------
1 file changed, 11 insertions(+), 12 deletions(-)
diff --git a/llvm/include/llvm/ADT/GenericCycleImpl.h b/llvm/include/llvm/ADT/GenericCycleImpl.h
index 77b8f26c236ae..af79ea4034a9b 100644
--- a/llvm/include/llvm/ADT/GenericCycleImpl.h
+++ b/llvm/include/llvm/ADT/GenericCycleImpl.h
@@ -231,11 +231,11 @@ template <typename ContextT> class GenericCycleInfoCompute {
struct BlockInfo {
// The block this entry describes; non-null once visited by DFS.
BlockT *Block = nullptr;
+ // 1-based position on the current DFS path; 0 if off path.
+ unsigned DFSPPos = 0;
// Block number of the innermost loop header; NoBlock if none. Set to
// NoBlock by open() on first visit, then woven by tagLoopHeader.
unsigned LoopHeader = 0;
- // 1-based position on the current DFS path; 0 if off path.
- unsigned DFSPPos = 0;
// Header-preorder rank of the innermost cycle containing this block (the
// cycle it heads if IsHeader); NoCycle if none. Set by the numbering pass.
unsigned CycleIdx = 0;
@@ -254,7 +254,7 @@ template <typename ContextT> class GenericCycleInfoCompute {
SmallVector<BlockInfo, 8> BlockInfos;
// Reachable block numbers in DFS preorder.
SmallVector<unsigned, 8> Preorder;
- // Number of loop headers found by dfs(), i.e. the number of cycles.
+ // Number of loop headers found by dfs().
unsigned NumHeaders = 0;
// Records (header H, block B): an edge from outside re-enters the closed
// cycle headed by H at B, making B a non-header entry of it.
@@ -419,9 +419,9 @@ void GenericCycleInfoCompute<ContextT>::run(FunctionT *F) {
// 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. dfs() counted the headers, so Build needs one allocation;
- // the exact reserve also keeps push_back from invalidating Head.
+ // their children.
SmallVector<CycleBuild, 8> Build;
+ // Exact reserve so the Head reference below survives each push_back.
Build.reserve(NumHeaders);
unsigned TopHead = NoCycle;
for (unsigned N : Preorder) {
@@ -483,11 +483,11 @@ void GenericCycleInfoCompute<ContextT>::dfs(BlockT *EntryBlock) {
auto open = [&](BlockT *Block) {
unsigned N = num(Block);
+ Preorder[Counter] = N;
BlockInfo &BI = info(N);
BI.Block = Block;
- BI.LoopHeader = NoBlock;
BI.DFSPPos = ++Counter;
- Preorder[Counter - 1] = N;
+ BI.LoopHeader = NoBlock;
auto Succs = successors(Block);
Stack.push_back({N, std::make_reverse_iterator(Succs.end()),
std::make_reverse_iterator(Succs.begin())});
@@ -512,11 +512,10 @@ void GenericCycleInfoCompute<ContextT>::dfs(BlockT *EntryBlock) {
}
tagLoopHeader(B0, B1);
} else {
- // Climb B1's header chain outward: every enclosing header still off
- // the DFS path heads a closed cycle that this edge re-enters, making
- // B1 a non-header entry of it (and the cycle irreducible). Stop at the
- // first header still on the path (genuine interior node of the loop),
- // and attribute B0 to it.
+ // Climb B1's header chain: each enclosing header still off the DFS path
+ // heads a closed cycle this edge re-enters, so B1 is a non-header entry
+ // of it (and it is irreducible). Stop at the first on-path header and
+ // attribute B0 to it.
for (unsigned H = B1Info.LoopHeader; H != NoBlock;
H = info(H).LoopHeader) {
if (info(H).DFSPPos > 0) {
>From 6ec5019008d94e5dd54f1aae0cbc597c96682e75 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sat, 18 Jul 2026 12:06:56 -0700
Subject: [PATCH 4/4] optimize tagLoopHeader
---
llvm/include/llvm/ADT/GenericCycleImpl.h | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/llvm/include/llvm/ADT/GenericCycleImpl.h b/llvm/include/llvm/ADT/GenericCycleImpl.h
index af79ea4034a9b..7609d40b0dcca 100644
--- a/llvm/include/llvm/ADT/GenericCycleImpl.h
+++ b/llvm/include/llvm/ADT/GenericCycleImpl.h
@@ -274,8 +274,7 @@ template <typename ContextT> class GenericCycleInfoCompute {
// DFS-path position. Building this chain on the fly is why the algorithm
// needs no union-find (used in the Havlak algorithm) at all.
void tagLoopHeader(unsigned B, unsigned H) {
- if (H == NoBlock)
- return;
+ assert(H != NoBlock);
// Invariant: info(B).DFSPPos >= info(H).DFSPPos.
while (B != H) {
unsigned IH = info(B).LoopHeader;
@@ -531,7 +530,7 @@ void GenericCycleInfoCompute<ContextT>::dfs(BlockT *EntryBlock) {
info(B0).DFSPPos = 0;
Stack.pop_back();
// And weave into the parent's chain (continue the "Tree edge" case).
- if (!Stack.empty())
+ if (!Stack.empty() && info(B0).LoopHeader != NoBlock)
tagLoopHeader(Stack.back().Block, info(B0).LoopHeader);
}
}
More information about the llvm-commits
mailing list