summaryrefslogtreecommitdiffstats
path: root/llvm/lib/Transforms/Coroutines
diff options
context:
space:
mode:
Diffstat (limited to 'llvm/lib/Transforms/Coroutines')
-rw-r--r--llvm/lib/Transforms/Coroutines/CoroEarly.cpp33
-rw-r--r--llvm/lib/Transforms/Coroutines/CoroSplit.cpp59
-rw-r--r--llvm/lib/Transforms/Coroutines/Coroutines.cpp18
3 files changed, 93 insertions, 17 deletions
diff --git a/llvm/lib/Transforms/Coroutines/CoroEarly.cpp b/llvm/lib/Transforms/Coroutines/CoroEarly.cpp
index 15c014faf1c..f6dfea72b4a 100644
--- a/llvm/lib/Transforms/Coroutines/CoroEarly.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroEarly.cpp
@@ -26,10 +26,11 @@ namespace {
// Created on demand if CoroEarly pass has work to do.
class Lowerer : public coro::LowererBase {
IRBuilder<> Builder;
- PointerType *AnyResumeFnPtrTy;
+ PointerType *const AnyResumeFnPtrTy;
void lowerResumeOrDestroy(CallSite CS, CoroSubFnInst::ResumeKind);
void lowerCoroPromise(CoroPromiseInst *Intrin);
+ void lowerCoroDone(IntrinsicInst *II);
public:
Lowerer(Module &M)
@@ -81,6 +82,27 @@ void Lowerer::lowerCoroPromise(CoroPromiseInst *Intrin) {
Intrin->eraseFromParent();
}
+// When a coroutine reaches final suspend point, it zeros out ResumeFnAddr in
+// the coroutine frame (it is UB to resume from a final suspend point).
+// The llvm.coro.done intrinsic is used to check whether a coroutine is
+// suspended at the final suspend point or not.
+void Lowerer::lowerCoroDone(IntrinsicInst *II) {
+ Value *Operand = II->getArgOperand(0);
+
+ // ResumeFnAddr is the first pointer sized element of the coroutine frame.
+ auto *FrameTy = Int8Ptr;
+ PointerType *FramePtrTy = FrameTy->getPointerTo();
+
+ Builder.SetInsertPoint(II);
+ auto *BCI = Builder.CreateBitCast(Operand, FramePtrTy);
+ auto *Gep = Builder.CreateConstInBoundsGEP1_32(FrameTy, BCI, 0);
+ auto *Load = Builder.CreateLoad(Gep);
+ auto *Cond = Builder.CreateICmpEQ(Load, NullPtr);
+
+ II->replaceAllUsesWith(Cond);
+ II->eraseFromParent();
+}
+
// Prior to CoroSplit, calls to coro.begin needs to be marked as NoDuplicate,
// as CoroSplit assumes there is exactly one coro.begin. After CoroSplit,
// NoDuplicate attribute will be removed from coro.begin otherwise, it will
@@ -131,6 +153,9 @@ bool Lowerer::lowerEarlyIntrinsics(Function &F) {
case Intrinsic::coro_promise:
lowerCoroPromise(cast<CoroPromiseInst>(&I));
break;
+ case Intrinsic::coro_done:
+ lowerCoroDone(cast<IntrinsicInst>(&I));
+ break;
}
Changed = true;
}
@@ -153,9 +178,9 @@ struct CoroEarly : public FunctionPass {
// This pass has work to do only if we find intrinsics we are going to lower
// in the module.
bool doInitialization(Module &M) override {
- if (coro::declaresIntrinsics(M, {"llvm.coro.begin", "llvm.coro.resume",
- "llvm.coro.destroy", "llvm.coro.suspend",
- "llvm.coro.end"}))
+ if (coro::declaresIntrinsics(M, {"llvm.coro.begin", "llvm.coro.end",
+ "llvm.coro.resume", "llvm.coro.destroy",
+ "llvm.coro.done", "llvm.coro.suspend"}))
L = llvm::make_unique<Lowerer>(M);
return false;
}
diff --git a/llvm/lib/Transforms/Coroutines/CoroSplit.cpp b/llvm/lib/Transforms/Coroutines/CoroSplit.cpp
index a03e1eb1109..6813afdcabb 100644
--- a/llvm/lib/Transforms/Coroutines/CoroSplit.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroSplit.cpp
@@ -62,8 +62,8 @@ static BasicBlock *createResumeEntryBlock(Function &F, coro::Shape &Shape) {
Builder.CreateSwitch(Index, UnreachBB, Shape.CoroSuspends.size());
Shape.ResumeSwitch = Switch;
- uint32_t SuspendIndex = 0;
- for (auto S : Shape.CoroSuspends) {
+ size_t SuspendIndex = 0;
+ for (CoroSuspendInst *S : Shape.CoroSuspends) {
ConstantInt *IndexVal = Shape.getIndex(SuspendIndex);
// Replace CoroSave with a store to Index:
@@ -71,9 +71,18 @@ static BasicBlock *createResumeEntryBlock(Function &F, coro::Shape &Shape) {
// store i32 0, i32* %index.addr1
auto *Save = S->getCoroSave();
Builder.SetInsertPoint(Save);
- auto *GepIndex = Builder.CreateConstInBoundsGEP2_32(
- FrameTy, FramePtr, 0, coro::Shape::IndexField, "index.addr");
- Builder.CreateStore(IndexVal, GepIndex);
+ if (S->isFinal()) {
+ // Final suspend point is represented by storing zero in ResumeFnAddr.
+ auto *GepIndex = Builder.CreateConstInBoundsGEP2_32(FrameTy, FramePtr, 0,
+ 0, "ResumeFn.addr");
+ auto *NullPtr = ConstantPointerNull::get(cast<PointerType>(
+ cast<PointerType>(GepIndex->getType())->getElementType()));
+ Builder.CreateStore(NullPtr, GepIndex);
+ } else {
+ auto *GepIndex = Builder.CreateConstInBoundsGEP2_32(
+ FrameTy, FramePtr, 0, coro::Shape::IndexField, "index.addr");
+ Builder.CreateStore(IndexVal, GepIndex);
+ }
Save->replaceAllUsesWith(ConstantTokenNone::get(C));
Save->eraseFromParent();
@@ -135,6 +144,37 @@ static void replaceFallthroughCoroEnd(IntrinsicInst *End,
BB->getTerminator()->eraseFromParent();
}
+// Rewrite final suspend point handling. We do not use suspend index to
+// represent the final suspend point. Instead we zero-out ResumeFnAddr in the
+// coroutine frame, since it is undefined behavior to resume a coroutine
+// suspended at the final suspend point. Thus, in the resume function, we can
+// simply remove the last case (when coro::Shape is built, the final suspend
+// point (if present) is always the last element of CoroSuspends array).
+// In the destroy function, we add a code sequence to check if ResumeFnAddress
+// is Null, and if so, jump to the appropriate label to handle cleanup from the
+// final suspend point.
+static void handleFinalSuspend(IRBuilder<> &Builder, Value *FramePtr,
+ coro::Shape &Shape, SwitchInst *Switch,
+ bool IsDestroy) {
+ assert(Shape.HasFinalSuspend);
+ auto FinalCase = --Switch->case_end();
+ BasicBlock *ResumeBB = FinalCase.getCaseSuccessor();
+ Switch->removeCase(FinalCase);
+ if (IsDestroy) {
+ BasicBlock *OldSwitchBB = Switch->getParent();
+ auto *NewSwitchBB = OldSwitchBB->splitBasicBlock(Switch, "Switch");
+ Builder.SetInsertPoint(OldSwitchBB->getTerminator());
+ auto *GepIndex = Builder.CreateConstInBoundsGEP2_32(Shape.FrameTy, FramePtr,
+ 0, 0, "ResumeFn.addr");
+ auto *Load = Builder.CreateLoad(GepIndex);
+ auto *NullPtr =
+ ConstantPointerNull::get(cast<PointerType>(Load->getType()));
+ auto *Cond = Builder.CreateICmpEQ(Load, NullPtr);
+ Builder.CreateCondBr(Cond, ResumeBB, NewSwitchBB);
+ OldSwitchBB->getTerminator()->eraseFromParent();
+ }
+}
+
// Create a resume clone by cloning the body of the original function, setting
// new entry block and replacing coro.suspend an appropriate value to force
// resume or cleanup pass for every suspend point.
@@ -205,6 +245,15 @@ static Function *createClone(Function &F, Twine Suffix, coro::Shape &Shape,
Value *OldVFrame = cast<Value>(VMap[Shape.CoroBegin]);
OldVFrame->replaceAllUsesWith(NewVFrame);
+ // Rewrite final suspend handling as it is not done via switch (allows to
+ // remove final case from the switch, since it is undefined behavior to resume
+ // the coroutine suspended at the final suspend point.
+ if (Shape.HasFinalSuspend) {
+ auto *Switch = cast<SwitchInst>(VMap[Shape.ResumeSwitch]);
+ bool IsDestroy = FnIndex != 0;
+ handleFinalSuspend(Builder, NewFramePtr, Shape, Switch, IsDestroy);
+ }
+
// Replace coro suspend with the appropriate resume index.
// Replacing coro.suspend with (0) will result in control flow proceeding to
// a resume label associated with a suspend point, replacing it with (1) will
diff --git a/llvm/lib/Transforms/Coroutines/Coroutines.cpp b/llvm/lib/Transforms/Coroutines/Coroutines.cpp
index 7eb77878d77..7e643006ffd 100644
--- a/llvm/lib/Transforms/Coroutines/Coroutines.cpp
+++ b/llvm/lib/Transforms/Coroutines/Coroutines.cpp
@@ -215,6 +215,7 @@ static CoroSaveInst *createCoroSave(CoroBeginInst *CoroBegin,
// Collect "interesting" coroutine intrinsics.
void coro::Shape::buildFrom(Function &F) {
+ size_t FinalSuspendIndex = 0;
clear(*this);
SmallVector<CoroFrameInst *, 8> CoroFrames;
for (Instruction &I : instructions(F)) {
@@ -230,16 +231,12 @@ void coro::Shape::buildFrom(Function &F) {
break;
case Intrinsic::coro_suspend:
CoroSuspends.push_back(cast<CoroSuspendInst>(II));
- // Make sure that the final suspend is the first suspend point in the
- // CoroSuspends vector.
if (CoroSuspends.back()->isFinal()) {
+ if (HasFinalSuspend)
+ report_fatal_error(
+ "Only one suspend point can be marked as final");
HasFinalSuspend = true;
- if (CoroSuspends.size() > 1) {
- if (CoroSuspends.front()->isFinal())
- report_fatal_error(
- "Only one suspend point can be marked as final");
- std::swap(CoroSuspends.front(), CoroSuspends.back());
- }
+ FinalSuspendIndex = CoroSuspends.size() - 1;
}
break;
case Intrinsic::coro_begin: {
@@ -309,4 +306,9 @@ void coro::Shape::buildFrom(Function &F) {
for (CoroSuspendInst *CS : CoroSuspends)
if (!CS->getCoroSave())
createCoroSave(CoroBegin, CS);
+
+ // Move final suspend to be the last element in the CoroSuspends vector.
+ if (HasFinalSuspend &&
+ FinalSuspendIndex != CoroSuspends.size() - 1)
+ std::swap(CoroSuspends[FinalSuspendIndex], CoroSuspends.back());
}
OpenPOWER on IntegriCloud