summaryrefslogtreecommitdiff
path: root/lib/Transforms/Coroutines
diff options
context:
space:
mode:
authorGor Nishanov <GorNishanov@gmail.com>2016-08-31 00:35:41 +0000
committerGor Nishanov <GorNishanov@gmail.com>2016-08-31 00:35:41 +0000
commitb6a139826bc247ccea315b796bb50116af7ad636 (patch)
treeda60e8cba95f557ba09a100bc217b2c832bd2fe8 /lib/Transforms/Coroutines
parent1a31aefb30c134dbccffa4d9fb4a3e9258522203 (diff)
[Coroutines] Part 10: Add coroutine promise support.
Summary: 1) CoroEarly now lowers llvm.coro.promise intrinsic that allows to obtain a coroutine promise pointer from a coroutine frame and vice versa. 2) CoroFrame now interprets Promise argument of llvm.coro.begin to place CoroutinPromise alloca at a deterministic offset from the coroutine frame. Now, the coroutine promise example from docs\Coroutines.rst compiles and produces expected result (see test/Transform/Coroutines/ex4.ll). Reviewers: majnemer Subscribers: llvm-commits, mehdi_amini Differential Revision: https://reviews.llvm.org/D23993 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@280184 91177308-0d34-0410-b5e6-96231b3b80d8
Diffstat (limited to 'lib/Transforms/Coroutines')
-rw-r--r--lib/Transforms/Coroutines/CoroEarly.cpp42
-rw-r--r--lib/Transforms/Coroutines/CoroFrame.cpp29
-rw-r--r--lib/Transforms/Coroutines/CoroInstr.h54
-rw-r--r--lib/Transforms/Coroutines/CoroInternal.h10
-rw-r--r--lib/Transforms/Coroutines/Coroutines.cpp1
5 files changed, 127 insertions, 9 deletions
diff --git a/lib/Transforms/Coroutines/CoroEarly.cpp b/lib/Transforms/Coroutines/CoroEarly.cpp
index eed4a98fc91..15c014faf1c 100644
--- a/lib/Transforms/Coroutines/CoroEarly.cpp
+++ b/lib/Transforms/Coroutines/CoroEarly.cpp
@@ -13,6 +13,7 @@
#include "CoroInternal.h"
#include "llvm/IR/CallSite.h"
+#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/InstIterator.h"
#include "llvm/IR/Module.h"
#include "llvm/Pass.h"
@@ -24,10 +25,18 @@ using namespace llvm;
namespace {
// Created on demand if CoroEarly pass has work to do.
class Lowerer : public coro::LowererBase {
+ IRBuilder<> Builder;
+ PointerType *AnyResumeFnPtrTy;
+
void lowerResumeOrDestroy(CallSite CS, CoroSubFnInst::ResumeKind);
+ void lowerCoroPromise(CoroPromiseInst *Intrin);
public:
- Lowerer(Module &M) : LowererBase(M) {}
+ Lowerer(Module &M)
+ : LowererBase(M), Builder(Context),
+ AnyResumeFnPtrTy(FunctionType::get(Type::getVoidTy(Context), Int8Ptr,
+ /*isVarArg=*/false)
+ ->getPointerTo()) {}
bool lowerEarlyIntrinsics(Function &F);
};
}
@@ -44,6 +53,34 @@ void Lowerer::lowerResumeOrDestroy(CallSite CS,
CS.setCallingConv(CallingConv::Fast);
}
+// Coroutine promise field is always at the fixed offset from the beginning of
+// the coroutine frame. i8* coro.promise(i8*, i1 from) intrinsic adds an offset
+// to a passed pointer to move from coroutine frame to coroutine promise and
+// vice versa. Since we don't know exactly which coroutine frame it is, we build
+// a coroutine frame mock up starting with two function pointers, followed by a
+// properly aligned coroutine promise field.
+// TODO: Handle the case when coroutine promise alloca has align override.
+void Lowerer::lowerCoroPromise(CoroPromiseInst *Intrin) {
+ Value *Operand = Intrin->getArgOperand(0);
+ unsigned Alignement = Intrin->getAlignment();
+ Type *Int8Ty = Builder.getInt8Ty();
+
+ auto *SampleStruct =
+ StructType::get(Context, {AnyResumeFnPtrTy, AnyResumeFnPtrTy, Int8Ty});
+ const DataLayout &DL = TheModule.getDataLayout();
+ int64_t Offset = alignTo(
+ DL.getStructLayout(SampleStruct)->getElementOffset(2), Alignement);
+ if (Intrin->isFromPromise())
+ Offset = -Offset;
+
+ Builder.SetInsertPoint(Intrin);
+ Value *Replacement =
+ Builder.CreateConstInBoundsGEP1_32(Int8Ty, Operand, Offset);
+
+ Intrin->replaceAllUsesWith(Replacement);
+ Intrin->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
@@ -91,6 +128,9 @@ bool Lowerer::lowerEarlyIntrinsics(Function &F) {
case Intrinsic::coro_destroy:
lowerResumeOrDestroy(CS, CoroSubFnInst::DestroyIndex);
break;
+ case Intrinsic::coro_promise:
+ lowerCoroPromise(cast<CoroPromiseInst>(&I));
+ break;
}
Changed = true;
}
diff --git a/lib/Transforms/Coroutines/CoroFrame.cpp b/lib/Transforms/Coroutines/CoroFrame.cpp
index bf1d296f03d..accffb12ad9 100644
--- a/lib/Transforms/Coroutines/CoroFrame.cpp
+++ b/lib/Transforms/Coroutines/CoroFrame.cpp
@@ -311,8 +311,11 @@ static StructType *buildFrameType(Function &F, coro::Shape &Shape,
// Figure out how wide should be an integer type storing the suspend index.
unsigned IndexBits = std::max(1U, Log2_64_Ceil(Shape.CoroSuspends.size()));
-
- SmallVector<Type *, 8> Types{FnPtrTy, FnPtrTy, Type::getIntNTy(C, IndexBits)};
+ Type *PromiseType = Shape.PromiseAlloca
+ ? Shape.PromiseAlloca->getType()->getElementType()
+ : Type::getInt1Ty(C);
+ SmallVector<Type *, 8> Types{FnPtrTy, FnPtrTy, PromiseType,
+ Type::getIntNTy(C, IndexBits)};
Value *CurrentDef = nullptr;
// Create an entry for every spilled value.
@@ -321,6 +324,9 @@ static StructType *buildFrameType(Function &F, coro::Shape &Shape,
continue;
CurrentDef = S.def();
+ // PromiseAlloca was already added to Types array earlier.
+ if (CurrentDef == Shape.PromiseAlloca)
+ continue;
Type *Ty = nullptr;
if (auto *AI = dyn_cast<AllocaInst>(CurrentDef))
@@ -376,6 +382,9 @@ static Instruction *insertSpills(SpillInfo &Spills, coro::Shape &Shape) {
// we remember allocas and their indices to be handled once we processed
// all the spills.
SmallVector<std::pair<AllocaInst *, unsigned>, 4> Allocas;
+ // Promise alloca (if present) has a fixed field number (Shape::PromiseField)
+ if (Shape.PromiseAlloca)
+ Allocas.emplace_back(Shape.PromiseAlloca, coro::Shape::PromiseField);
// Create a load instruction to reload the spilled value from the coroutine
// frame.
@@ -400,7 +409,7 @@ static Instruction *insertSpills(SpillInfo &Spills, coro::Shape &Shape) {
++Index;
if (auto *AI = dyn_cast<AllocaInst>(CurrentValue)) {
- // Spiled AllocaInst will be replaced with GEP from the coroutine frame
+ // Spilled AllocaInst will be replaced with GEP from the coroutine frame
// there is no spill required.
Allocas.emplace_back(AI, Index);
if (!AI->isStaticAlloca())
@@ -444,7 +453,11 @@ static Instruction *insertSpills(SpillInfo &Spills, coro::Shape &Shape) {
for (auto &P : Allocas) {
auto *G =
Builder.CreateConstInBoundsGEP2_32(FrameTy, FramePtr, 0, P.second);
- ReplaceInstWithInst(P.first, cast<Instruction>(G));
+ // We are not using ReplaceInstWithInst(P.first, cast<Instruction>(G)) here,
+ // as we are changing location of the instruction.
+ G->takeName(P.first);
+ P.first->replaceAllUsesWith(G);
+ P.first->eraseFromParent();
}
return FramePtr;
}
@@ -568,6 +581,10 @@ static void splitAround(Instruction *I, const Twine &Name) {
}
void coro::buildCoroutineFrame(Function &F, Shape &Shape) {
+ Shape.PromiseAlloca = Shape.CoroBegin->getId()->getPromise();
+ if (Shape.PromiseAlloca) {
+ Shape.CoroBegin->getId()->clearPromise();
+ }
// Make sure that all coro.saves and the fallthrough coro.end are in their
// own block to simplify the logic of building up SuspendCrossing data.
@@ -621,6 +638,10 @@ void coro::buildCoroutineFrame(Function &F, Shape &Shape) {
// in a coroutine. It should not be saved to the coroutine frame.
if (isa<CoroIdInst>(&I))
continue;
+ // The Coroutine Promise always included into coroutine frame, no need to
+ // check for suspend crossing.
+ if (Shape.PromiseAlloca == &I)
+ continue;
for (User *U : I.users())
if (Checker.isDefinitionAcrossSuspend(I, U)) {
diff --git a/lib/Transforms/Coroutines/CoroInstr.h b/lib/Transforms/Coroutines/CoroInstr.h
index ec60bfb7bea..06571dd1e5c 100644
--- a/lib/Transforms/Coroutines/CoroInstr.h
+++ b/lib/Transforms/Coroutines/CoroInstr.h
@@ -80,6 +80,39 @@ class LLVM_LIBRARY_VISIBILITY CoroIdInst : public IntrinsicInst {
enum { AlignArg, PromiseArg, CoroutineArg, InfoArg };
public:
+ IntrinsicInst *getCoroBegin() {
+ for (User *U : users())
+ if (auto *II = dyn_cast<IntrinsicInst>(U))
+ if (II->getIntrinsicID() == Intrinsic::coro_begin)
+ return II;
+ llvm_unreachable("no coro.begin associated with coro.id");
+ }
+
+ AllocaInst *getPromise() const {
+ Value *Arg = getArgOperand(PromiseArg);
+ return isa<ConstantPointerNull>(Arg)
+ ? nullptr
+ : cast<AllocaInst>(Arg->stripPointerCasts());
+ }
+
+ void clearPromise() {
+ Value *Arg = getArgOperand(PromiseArg);
+ setArgOperand(PromiseArg,
+ ConstantPointerNull::get(Type::getInt8PtrTy(getContext())));
+ if (isa<AllocaInst>(Arg))
+ return;
+ assert((isa<BitCastInst>(Arg) || isa<GetElementPtrInst>(Arg)) &&
+ "unexpected instruction designating the promise");
+ // TODO: Add a check that any remaining users of Inst are after coro.begin
+ // or add code to move the users after coro.begin.
+ auto *Inst = cast<Instruction>(Arg);
+ if (Inst->use_empty()) {
+ Inst->eraseFromParent();
+ return;
+ }
+ Inst->moveBefore(getCoroBegin()->getNextNode());
+ }
+
// Info argument of coro.id is
// fresh out of the frontend: null ;
// outlined : {Init, Return, Susp1, Susp2, ...} ;
@@ -198,6 +231,27 @@ public:
}
};
+/// This represents the llvm.coro.promise instruction.
+class LLVM_LIBRARY_VISIBILITY CoroPromiseInst : public IntrinsicInst {
+ enum { FrameArg, AlignArg, FromArg };
+
+public:
+ bool isFromPromise() const {
+ return cast<Constant>(getArgOperand(FromArg))->isOneValue();
+ }
+ unsigned getAlignment() const {
+ return cast<ConstantInt>(getArgOperand(AlignArg))->getZExtValue();
+ }
+
+ // Methods to support type inquiry through isa, cast, and dyn_cast:
+ static inline bool classof(const IntrinsicInst *I) {
+ return I->getIntrinsicID() == Intrinsic::coro_promise;
+ }
+ static inline bool classof(const Value *V) {
+ return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V));
+ }
+};
+
/// This represents the llvm.coro.suspend instruction.
class LLVM_LIBRARY_VISIBILITY CoroSuspendInst : public IntrinsicInst {
enum { SaveArg, FinalArg };
diff --git a/lib/Transforms/Coroutines/CoroInternal.h b/lib/Transforms/Coroutines/CoroInternal.h
index 8e6cc0c9560..1eac88dbac3 100644
--- a/lib/Transforms/Coroutines/CoroInternal.h
+++ b/lib/Transforms/Coroutines/CoroInternal.h
@@ -74,17 +74,19 @@ struct LLVM_LIBRARY_VISIBILITY Shape {
enum {
ResumeField,
DestroyField,
+ PromiseField,
IndexField,
LastKnownField = IndexField
};
StructType *FrameTy;
Instruction *FramePtr;
- BasicBlock* AllocaSpillBlock;
- SwitchInst* ResumeSwitch;
+ BasicBlock *AllocaSpillBlock;
+ SwitchInst *ResumeSwitch;
+ AllocaInst *PromiseAlloca;
bool HasFinalSuspend;
- IntegerType* getIndexType() const {
+ IntegerType *getIndexType() const {
assert(FrameTy && "frame type not assigned");
return cast<IntegerType>(FrameTy->getElementType(IndexField));
}
@@ -97,7 +99,7 @@ struct LLVM_LIBRARY_VISIBILITY Shape {
void buildFrom(Function &F);
};
-void buildCoroutineFrame(Function& F, Shape& Shape);
+void buildCoroutineFrame(Function &F, Shape &Shape);
} // End namespace coro.
} // End namespace llvm
diff --git a/lib/Transforms/Coroutines/Coroutines.cpp b/lib/Transforms/Coroutines/Coroutines.cpp
index daded73d8fb..7eb77878d77 100644
--- a/lib/Transforms/Coroutines/Coroutines.cpp
+++ b/lib/Transforms/Coroutines/Coroutines.cpp
@@ -198,6 +198,7 @@ static void clear(coro::Shape &Shape) {
Shape.FramePtr = nullptr;
Shape.AllocaSpillBlock = nullptr;
Shape.ResumeSwitch = nullptr;
+ Shape.PromiseAlloca = nullptr;
Shape.HasFinalSuspend = false;
}