Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
6ccbdcd
[Coroutines] added 5 tests that fail on the validations errors in #14…
tzuralon Jul 19, 2025
1d303ca
[Coroutines] fixed a bug when coro.end had an associated bundle, and …
tzuralon Jul 20, 2025
887bcb2
[Coroutines] fixed a bug when insertSpills performed spilling of Coro…
tzuralon Jul 20, 2025
7f00eeb
[Coroutines] added more 3 tests that are still failing due to complex…
tzuralon Jul 27, 2025
5899a93
[Coroutines] fixed the bugs introduced in the last 3 tests, improved …
tzuralon Jul 27, 2025
0b8e37a
Merge branch 'main' into users/tzuralon/coro-async-exceptions-fixes
tzuralon Jul 28, 2025
428d6b4
[Coroutines] removed 3 non-reduced tests that are redundant due to th…
tzuralon Jul 31, 2025
ffe4587
[Coroutines] reduced a test IR that introduces an unhandled edge-case…
tzuralon Jul 31, 2025
c8b944c
[Coroutines] reduced the IR tests a bit more by reducing unneccesary …
tzuralon Aug 3, 2025
8fa7b44
[Coroutines] applied formatter comments
tzuralon Aug 10, 2025
9ec807f
Merge branch 'main' into users/tzuralon/coro-async-exceptions-fixes
tzuralon Aug 18, 2025
3c5929a
[Coroutines] Reduce storage size for SmallSet, enhance naming
tzuralon Aug 22, 2025
3551cc8
[Coroutines] make comments more coherent
tzuralon Aug 22, 2025
c4795ae
[Coroutines] some formatting enhancements, improved performance by no…
tzuralon Aug 22, 2025
dbafc22
[Coroutines] - Remove redundant function finalizeBasicBlockCloneAndTr…
tzuralon Aug 22, 2025
0e1afdf
[Coroutines] - Make variable names more meaningful
tzuralon Sep 2, 2025
380a643
[Coroutines] - aligned the IR tests to have basic verification
tzuralon Sep 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions llvm/lib/Transforms/Coroutines/CoroFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "llvm/Transforms/Coroutines/SpillUtils.h"
#include "llvm/Transforms/Coroutines/SuspendCrossingInfo.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#include "llvm/Transforms/Utils/Cloning.h"
#include "llvm/Transforms/Utils/Local.h"
#include "llvm/Transforms/Utils/PromoteMemToReg.h"
#include <algorithm>
Expand Down Expand Up @@ -974,6 +975,159 @@ static StructType *buildFrameType(Function &F, coro::Shape &Shape,
return FrameTy;
}

// Fixer for the "Instruction does not dominate all uses!" bug
// The fix consists of mapping problematic paths (where CoroBegin does not
// dominate cleanup BBs) and clones them to 2 flows - the one that insertSpills
// intended to create (using the spill) and another one, preserving the logics
// of pre-splitting, which would be triggered if unwinding happened before
// CoroBegin
static void
splitBasicBlocksNotDominatedByCoroBegin(const FrameDataInfo &FrameData,
coro::Shape &Shape, Function *F,
DominatorTree &DT) {
ValueToValueMapTy VMap;
DenseMap<BasicBlock *, BasicBlock *>
ProcessedSpillBlockToAlternativeUnspilledBlockMap;
bool FunctionHasSomeBlockNotDominatedByCoroBegin;
SmallVector<BasicBlock *> SpillUserBlocks;

for (const auto &E : FrameData.Spills) {
for (auto *U : E.second) {
if (std::find(SpillUserBlocks.begin(), SpillUserBlocks.end(),
U->getParent()) == SpillUserBlocks.end()) {
SpillUserBlocks.push_back(U->getParent());
}
}
}
SpillUserBlocks.push_back(Shape.AllocaSpillBlock);

do {
FunctionHasSomeBlockNotDominatedByCoroBegin = false;
// We want to traverse the function post-order (predecessors first),
// and check dominance starting CoroBegin
bool HaveTraversedCoroBegin = false;
for (BasicBlock *CurrentBlock : ReversePostOrderTraversal<Function *>(F)) {
if (!HaveTraversedCoroBegin &&
CurrentBlock != Shape.CoroBegin->getParent()) {
continue;
}
HaveTraversedCoroBegin = true;

// Focus on 2 types of users that produce errors - those in
// FrameData.Spills, and decendants of Shape.AllocaSpillBlocks
if (!DT.dominates(Shape.CoroBegin, CurrentBlock) &&
std::find(SpillUserBlocks.begin(), SpillUserBlocks.end(),
CurrentBlock) != SpillUserBlocks.end()) {
// Mark another iteration of the loop is needed, to verify that no more
// dominance issues after current run
FunctionHasSomeBlockNotDominatedByCoroBegin = true;

// Clone (preserve) the current basic block, before it will be modified
// by insertSpills
auto UnspilledAlternativeBlock =
CloneBasicBlock(CurrentBlock, VMap, ".unspilled_alternative", F);

// Remap the instructions, VMap here aggregates instructions across
// multiple BasicBlocks, and we assume that traversal is post-order,
// therefore successor blocks (for example instructions having funclet
// tags) will be mapped correctly to the new cloned cleanuppad
for (Instruction &I : *UnspilledAlternativeBlock) {
RemapInstruction(&I, VMap,
RF_NoModuleLevelChanges | RF_IgnoreMissingLocals);
}

// Keep track between the processed spill basic block and the cloned
// alternative unspilled basic block Will help us fix instructions that
// their context is complex (for example cleanuppad of funclet is
// defined in another BB)
ProcessedSpillBlockToAlternativeUnspilledBlockMap[CurrentBlock] =
UnspilledAlternativeBlock;

SmallVector<Instruction *> FixUpPredTerminators;

// Find the specific predecessors that does not dominated by
// Shape.CoroBegin We don't fix them here but later because it's not
// safe while using predecessors as iterator function
for (BasicBlock *Pred : predecessors(CurrentBlock)) {
if (!DT.dominates(Shape.CoroBegin, Pred)) {
FixUpPredTerminators.push_back(Pred->getTerminator());
}
}

// Fixups for current block terminator
const auto &CurrentBlockTerminator = CurrentBlock->getTerminator();

// If it's cleanupret, find the correspondant cleanuppad (use the map to
// find it)
if (auto CurrentBlockCleanupReturnTerminator =
dyn_cast<CleanupReturnInst>(CurrentBlockTerminator)) {
BasicBlock *CBCPBB =
CurrentBlockCleanupReturnTerminator->getCleanupPad()->getParent();
CleanupReturnInst *DBT = dyn_cast<CleanupReturnInst>(
UnspilledAlternativeBlock->getTerminator());

// Again assuming post-order traversal - if we mapped the predecessing
// cleanuppad block before, we should find it here If not, do nothing
if (ProcessedSpillBlockToAlternativeUnspilledBlockMap.contains(
CBCPBB)) {
Instruction *DCPr =
&ProcessedSpillBlockToAlternativeUnspilledBlockMap[CBCPBB]
->front();
CleanupPadInst *DCP = cast<CleanupPadInst>(DCPr);
DBT->setCleanupPad(DCP);
}

// If it's a branch/invoke, keep track of its successors, we want to
// calculate dominance between CoroBegin and them also They might need
// clone as well
} else if (auto CurrentBlockBranchTerminator =
dyn_cast<BranchInst>(CurrentBlockTerminator)) {
for (unsigned int successorIdx = 0;
successorIdx < CurrentBlockBranchTerminator->getNumSuccessors();
++successorIdx) {
SpillUserBlocks.push_back(
CurrentBlockBranchTerminator->getSuccessor(successorIdx));
}
} else if (auto CurrentBlockInvokeTerminator =
dyn_cast<InvokeInst>(CurrentBlockTerminator)) {
SpillUserBlocks.push_back(
CurrentBlockInvokeTerminator->getUnwindDest());
} else {
report_fatal_error("Not implemented terminator for this specific "
"instruction fixup in current block fixups");
}

// Fixups on the predecessors terminator - direct them to out untouched
// alternative block to break dominance error.
for (auto FixUpPredTerminator : FixUpPredTerminators) {
if (auto FixUpPredTerminatorInvoke =
dyn_cast<InvokeInst>(FixUpPredTerminator)) {
FixUpPredTerminatorInvoke->setUnwindDest(UnspilledAlternativeBlock);
} else if (auto FixUpPredTerminatorBranch =
dyn_cast<BranchInst>(FixUpPredTerminator)) {
for (unsigned int successorIdx = 0;
successorIdx < FixUpPredTerminatorBranch->getNumSuccessors();
++successorIdx) {
if (CurrentBlock ==
FixUpPredTerminatorBranch->getSuccessor(successorIdx)) {
FixUpPredTerminatorBranch->setSuccessor(
successorIdx, UnspilledAlternativeBlock);
}
}
} else {
report_fatal_error("Not implemented terminator for this specific "
"instruction in pred fixups");
}
}

// We changed dominance tree, so recalculate.
DT.recalculate(*F);
continue;
}
}
} while (FunctionHasSomeBlockNotDominatedByCoroBegin);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks expensive. Is it possible to avoid this do-while?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, this while was redundant and I removed it.

}

// Replace all alloca and SSA values that are accessed across suspend points
// with GetElementPointer from coroutine frame + loads and stores. Create an
// AllocaSpillBB that will become the new entry block for the resume parts of
Expand Down Expand Up @@ -1053,6 +1207,8 @@ static void insertSpills(const FrameDataInfo &FrameData, coro::Shape &Shape) {
return GEP;
};

splitBasicBlocksNotDominatedByCoroBegin(FrameData, Shape, F, DT);

for (auto const &E : FrameData.Spills) {
Value *Def = E.first;
auto SpillAlignment = Align(FrameData.getAlign(Def));
Expand Down
11 changes: 10 additions & 1 deletion llvm/lib/Transforms/Coroutines/CoroSplit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,16 @@ static void replaceUnwindCoroEnd(AnyCoroEndInst *End, const coro::Shape &Shape,
// If coro.end has an associated bundle, add cleanupret instruction.
if (auto Bundle = End->getOperandBundle(LLVMContext::OB_funclet)) {
auto *FromPad = cast<CleanupPadInst>(Bundle->Inputs[0]);
auto *CleanupRet = Builder.CreateCleanupRet(FromPad, nullptr);

// If the terminator is an invoke,
// set the cleanupret unwind destination the same as the other edges, to
// avoid validation errors
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "validation errors" is confusing to maintainers that not familiar to this bug. Maybe we could drop it.

BTW, there are some similar comments that are context-dependent. It would be great if you could reword them.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rewritten comments

BasicBlock *UBB = nullptr;
if (auto II = dyn_cast<InvokeInst>(FromPad->getParent()->getTerminator())) {
UBB = II->getUnwindDest();
}

auto *CleanupRet = Builder.CreateCleanupRet(FromPad, UBB);
End->getParent()->splitBasicBlock(End);
CleanupRet->getParent()->getTerminator()->eraseFromParent();
}
Expand Down
Loading