diff options
author | Gor Nishanov <GorNishanov@gmail.com> | 2016-08-31 00:35:41 +0000 |
---|---|---|
committer | Gor Nishanov <GorNishanov@gmail.com> | 2016-08-31 00:35:41 +0000 |
commit | b6a139826bc247ccea315b796bb50116af7ad636 (patch) | |
tree | da60e8cba95f557ba09a100bc217b2c832bd2fe8 /lib/Transforms/Coroutines | |
parent | 1a31aefb30c134dbccffa4d9fb4a3e9258522203 (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.cpp | 42 | ||||
-rw-r--r-- | lib/Transforms/Coroutines/CoroFrame.cpp | 29 | ||||
-rw-r--r-- | lib/Transforms/Coroutines/CoroInstr.h | 54 | ||||
-rw-r--r-- | lib/Transforms/Coroutines/CoroInternal.h | 10 | ||||
-rw-r--r-- | lib/Transforms/Coroutines/Coroutines.cpp | 1 |
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; } |