[llvm] [CycleInfo] Identify cycles with a single-pass DFS algorithm (PR #210491)
Alexis Engelke via llvm-commits
llvm-commits at lists.llvm.org
Sat Jul 18 01:22:27 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);
----------------
aengelke wrote:
Sort by (H, B) instead, then all entries of a cycle will be grouped together. (Also will help with header storage change.) Also unsure about the erase part, I'd just write it explicitly to avoid double iteration. (Ok, irreducible control flow is uncommon, but still.)
https://github.com/llvm/llvm-project/pull/210491
More information about the llvm-commits
mailing list