[llvm] [MemorySSA] Make getPreviousDef iterative to avoid stack overflow (PR #205159)
Antonio Frighetto via llvm-commits
llvm-commits at lists.llvm.org
Wed Jun 24 07:05:03 PDT 2026
================
@@ -33,101 +33,232 @@ using namespace llvm;
// that there are two or more definitions needing to be merged.
// This still will leave non-minimal form in the case of irreducible control
// flow, where phi nodes may be in cycles with themselves, but unnecessary.
-MemoryAccess *MemorySSAUpdater::getPreviousDefRecursive(
+//
+// The predecessor walk is driven by an explicit worklist rather than native
+// recursion: it used to be mutually recursive with getPreviousDefFromEnd, and
+// the recursion depth scaled with the length of the walk, so deep CFGs (e.g.
+// long block chains in large generated kernels/shaders) could overflow the
+// native stack. Each StackFrame mirrors one activation of that walk; the
+// ResumePoint records where to resume after a spawned child block has produced
+// its result (returned via Returned). All observable behaviour is preserved:
+// cache lookups/inserts, VisitedBlocks cycle detection (including the
+// single-predecessor insert-without-erase asymmetry), predecessor operand
+// order, and phi simplification/creation.
+MemoryAccess *MemorySSAUpdater::getPreviousDefIterative(
BasicBlock *BB,
DenseMap<BasicBlock *, TrackingVH<MemoryAccess>> &CachedPreviousDef) {
- // First, do a cache lookup. Without this cache, certain CFG structures
- // (like a series of if statements) take exponential time to visit.
- auto Cached = CachedPreviousDef.find(BB);
- if (Cached != CachedPreviousDef.end())
- return Cached->second;
-
- // If this method is called from an unreachable block, return LoE.
- if (!MSSA->DT->isReachableFromEntry(BB))
- return MSSA->getLiveOnEntryDef();
+ // One frame of the explicit worklist, standing in for a single activation of
+ // the old recursive walk. A frame advances through a small state machine
+ // driven by its ResumePoint, suspending whenever it needs the result of a
+ // child block and resuming once that result is available in Returned:
+ //
+ // EnterBlock First visit to the frame's block: do the cache /
+ // unreachable / unique-predecessor / cycle checks. May
+ // finish the frame outright, or push a child frame and
+ // move to ResumeSinglePred or RunPredLoop.
+ // ResumeSinglePred Resume after the unique predecessor has been resolved:
+ // cache its result for this block and finish the frame.
+ // RunPredLoop Walk the predecessors, gathering phi operands and
+ // suspending into a child frame for each predecessor that
+ // has no local definition; once all are gathered, place or
+ // simplify the phi and finish the frame.
+ struct StackFrame {
+ enum class ResumePoint { EnterBlock, ResumeSinglePred, RunPredLoop };
+
+ BasicBlock *BB;
+ ResumePoint Resume = ResumePoint::EnterBlock;
+ // Multi-predecessor loop state.
+ SmallVector<TrackingVH<MemoryAccess>, 8> PhiOps;
+ SmallVector<BasicBlock *, 8> Preds;
+ unsigned PredIdx = 0;
+ // When set, `Returned` holds the result for Preds[PredIdx].
+ bool PendingIncoming = false;
+ bool UniqueIncomingAccess = true;
+ MemoryAccess *SingleAccess = nullptr;
- if (BasicBlock *Pred = BB->getUniquePredecessor()) {
- VisitedBlocks.insert(BB);
- // Single predecessor case, just recurse, we can only have one definition.
- MemoryAccess *Result = getPreviousDefFromEnd(Pred, CachedPreviousDef);
- CachedPreviousDef.insert({BB, Result});
- return Result;
- }
+ // Fold an incoming predecessor access into this frame's phi operands,
+ // tracking whether all incoming accesses are identical (so the phi may be
+ // elided). Mirrors the per-predecessor body of the walk.
+ void incorporate(MemoryAccess *Incoming) {
+ if (!SingleAccess)
+ SingleAccess = Incoming;
+ else if (Incoming != SingleAccess)
+ UniqueIncomingAccess = false;
+ PhiOps.push_back(Incoming);
+ }
+ };
- if (VisitedBlocks.count(BB)) {
- // We hit our node again, meaning we had a cycle, we must insert a phi
- // node to break it so we have an operand. The only case this will
- // insert useless phis is if we have irreducible control flow.
- MemoryAccess *Result = MSSA->createMemoryPhi(BB);
- CachedPreviousDef.insert({BB, Result});
+ // Non-recursive part of getPreviousDefFromEnd(Pred): if Pred has a local
+ // definition, return its last def and cache it under Pred (exactly as that
+ // helper does). Returns nullptr when Pred has no local def, signalling that
+ // it must instead be visited via its own worklist frame (the part of
+ // getPreviousDefFromEnd that used to recurse).
+ auto GetLocalDefFromEnd = [&](BasicBlock *Pred) -> MemoryAccess * {
+ auto *Defs = MSSA->getBlockDefs(Pred);
+ if (!Defs)
+ return nullptr;
+ MemoryAccess *Result = &*Defs->rbegin();
+ CachedPreviousDef.insert({Pred, Result});
return Result;
- }
+ };
- if (VisitedBlocks.insert(BB).second) {
- // Mark us visited so we can detect a cycle
- SmallVector<TrackingVH<MemoryAccess>, 8> PhiOps;
+ SmallVector<StackFrame, 8> WorkStack;
+ WorkStack.push_back({BB});
+ // Carries a completed child frame's result back to its parent, the way a
+ // return value would propagate up a call stack.
+ MemoryAccess *Returned = nullptr;
+
+ while (!WorkStack.empty()) {
+ // NOTE: WorkStack.push_back below may reallocate and invalidate this
+ // reference, so every code path that pushes a new frame sets the
+ // ResumePoint on the current frame *before* pushing and then continues the
+ // loop without touching the reference again.
+ StackFrame &F = WorkStack.back();
+ BasicBlock *CurBB = F.BB;
+
+ switch (F.Resume) {
+ case StackFrame::ResumePoint::EnterBlock: {
----------------
antoniofrighetto wrote:
If we added an alias `using ResumePoint = StackFrame::ResumePoint;` on top, then the case would read `case ResumePoint::EnterBlock`, same below for the assignment (`F.Resume = ResumePoint::RunPredLoop;`).
https://github.com/llvm/llvm-project/pull/205159
More information about the llvm-commits
mailing list