[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