1
0
mirror of https://github.com/RPCS3/llvm-mirror.git synced 2024-11-26 12:43:36 +01:00

[Coroutines] Part 9: Add cleanup subfunction.

Summary:
[Coroutines] Part 9: Add cleanup subfunction.

This patch completes coroutine heap allocation elision. Now, the heap elision example from docs\Coroutines.rst compiles and produces expected result (see test/Transform/Coroutines/ex3.ll)

Intrinsic Changes:
* coro.free gets a token parameter tying it to coro.id to allow reliably discovering all coro.frees associated with a particular coroutine.
* coro.id gets an extra parameter that points back to a coroutine function. This allows to check whether a coro.id describes the enclosing function or it belongs to a different function that was later inlined.

CoroSplit now creates three subfunctions:
# f$resume - resume logic
# f$destroy - cleanup logic, followed by a deallocation code
# f$cleanup - just the cleanup code

CoroElide pass during devirtualization replaces coro.destroy with either f$destroy or f$cleanup depending whether heap elision is performed or not.

Other fixes, improvements:
* Fixed buglet in Shape::buildFrame that was not creating coro.save properly if coroutine has more than one suspend point.

* Switched to using variable width suspend index field (no longer limited to 32 bit index field can be as little as i1 or as large as i<whatever-size_t-is>)

Reviewers: majnemer

Subscribers: llvm-commits, mehdi_amini

Differential Revision: https://reviews.llvm.org/D23844

llvm-svn: 279971
This commit is contained in:
Gor Nishanov 2016-08-29 14:34:12 +00:00
parent e3955cf340
commit cc3ff9c81d
19 changed files with 328 additions and 137 deletions

View File

@ -93,7 +93,7 @@ The LLVM IR for this coroutine looks like this:
define i8* @f(i32 %n) { define i8* @f(i32 %n) {
entry: entry:
%id = call token @llvm.coro.id(i32 0, i8* null, i8* null) %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
%size = call i32 @llvm.coro.size.i32() %size = call i32 @llvm.coro.size.i32()
%alloc = call i8* @malloc(i32 %size) %alloc = call i8* @malloc(i32 %size)
%hdl = call noalias i8* @llvm.coro.begin(token %id, i8* %alloc) %hdl = call noalias i8* @llvm.coro.begin(token %id, i8* %alloc)
@ -106,7 +106,7 @@ The LLVM IR for this coroutine looks like this:
switch i8 %0, label %suspend [i8 0, label %loop switch i8 %0, label %suspend [i8 0, label %loop
i8 1, label %cleanup] i8 1, label %cleanup]
cleanup: cleanup:
%mem = call i8* @llvm.coro.free(i8* %hdl) %mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
call void @free(i8* %mem) call void @free(i8* %mem)
br label %suspend br label %suspend
suspend: suspend:
@ -168,7 +168,7 @@ execution of the coroutine until a suspend point is reached:
define i8* @f(i32 %n) { define i8* @f(i32 %n) {
entry: entry:
%id = call token @llvm.coro.id(i32 0, i8* null, i8* null) %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
%alloc = call noalias i8* @malloc(i32 24) %alloc = call noalias i8* @malloc(i32 24)
%0 = call noalias i8* @llvm.coro.begin(token %id, i8* %alloc) %0 = call noalias i8* @llvm.coro.begin(token %id, i8* %alloc)
%frame = bitcast i8* %0 to %f.frame* %frame = bitcast i8* %0 to %f.frame*
@ -227,7 +227,7 @@ elided.
.. code-block:: none .. code-block:: none
entry: entry:
%id = call token @llvm.coro.id(i32 0, i8* null, i8* null) %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
%need.dyn.alloc = call i1 @llvm.coro.alloc(token %id) %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id)
br i1 %need.dyn.alloc, label %dyn.alloc, label %coro.begin br i1 %need.dyn.alloc, label %dyn.alloc, label %coro.begin
dyn.alloc: dyn.alloc:
@ -245,7 +245,7 @@ thus skipping the deallocation code:
.. code-block:: llvm .. code-block:: llvm
cleanup: cleanup:
%mem = call i8* @llvm.coro.free(i8* %hdl) %mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
%need.dyn.free = icmp ne i8* %mem, null %need.dyn.free = icmp ne i8* %mem, null
br i1 %need.dyn.free, label %dyn.free, label %if.end br i1 %need.dyn.free, label %dyn.free, label %if.end
dyn.free: dyn.free:
@ -417,7 +417,7 @@ store the current value produced by a coroutine.
entry: entry:
%promise = alloca i32 %promise = alloca i32
%pv = bitcast i32* %promise to i8* %pv = bitcast i32* %promise to i8*
%id = call token @llvm.coro.id(i32 0, i8* %pv, i8* null) %id = call token @llvm.coro.id(i32 0, i8* %pv, i8* null, i8* null)
%need.dyn.alloc = call i1 @llvm.coro.alloc(token %id) %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id)
br i1 %need.dyn.alloc, label %dyn.alloc, label %coro.begin br i1 %need.dyn.alloc, label %dyn.alloc, label %coro.begin
dyn.alloc: dyn.alloc:
@ -436,7 +436,7 @@ store the current value produced by a coroutine.
switch i8 %0, label %suspend [i8 0, label %loop switch i8 %0, label %suspend [i8 0, label %loop
i8 1, label %cleanup] i8 1, label %cleanup]
cleanup: cleanup:
%mem = call i8* @llvm.coro.free(i8* %hdl) %mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
call void @free(i8* %mem) call void @free(i8* %mem)
br label %suspend br label %suspend
suspend: suspend:
@ -699,7 +699,7 @@ Example:
%promise = alloca i32 %promise = alloca i32
%pv = bitcast i32* %promise to i8* %pv = bitcast i32* %promise to i8*
; the second argument to coro.id points to the coroutine promise. ; the second argument to coro.id points to the coroutine promise.
%id = call token @llvm.coro.id(i32 0, i8* %pv, i8* null) %id = call token @llvm.coro.id(i32 0, i8* %pv, i8* null, i8* null)
... ...
%hdl = call noalias i8* @llvm.coro.begin(token %id, i8* %alloc) %hdl = call noalias i8* @llvm.coro.begin(token %id, i8* %alloc)
... ...
@ -791,7 +791,7 @@ A frontend should emit exactly one `coro.begin` intrinsic per coroutine.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:: ::
declare i8* @llvm.coro.free(i8* <frame>) declare i8* @llvm.coro.free(token %id, i8* <frame>)
Overview: Overview:
""""""""" """""""""
@ -803,8 +803,11 @@ dynamically allocated memory for its coroutine frame.
Arguments: Arguments:
"""""""""" """"""""""
A pointer to the coroutine frame. This should be the same pointer that was The first argument is a token returned by a call to '``llvm.coro.id``'
returned by prior `coro.begin` call. identifying the coroutine.
The second argument is a pointer to the coroutine frame. This should be the same
pointer that was returned by prior `coro.begin` call.
Example (custom deallocation function): Example (custom deallocation function):
""""""""""""""""""""""""""""""""""""""" """""""""""""""""""""""""""""""""""""""
@ -812,7 +815,7 @@ Example (custom deallocation function):
.. code-block:: llvm .. code-block:: llvm
cleanup: cleanup:
%mem = call i8* @llvm.coro.free(i8* %frame) %mem = call i8* @llvm.coro.free(token %id, i8* %frame)
%mem_not_null = icmp ne i8* %mem, null %mem_not_null = icmp ne i8* %mem, null
br i1 %mem_not_null, label %if.then, label %if.end br i1 %mem_not_null, label %if.then, label %if.end
if.then: if.then:
@ -827,7 +830,7 @@ Example (standard deallocation functions):
.. code-block:: llvm .. code-block:: llvm
cleanup: cleanup:
%mem = call i8* @llvm.coro.free(i8* %frame) %mem = call i8* @llvm.coro.free(token %id, i8* %frame)
call void @free(i8* %mem) call void @free(i8* %mem)
ret void ret void
@ -864,7 +867,7 @@ Example:
.. code-block:: text .. code-block:: text
entry: entry:
%id = call token @llvm.coro.id(i32 0, i8* null, i8* null) %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
%dyn.alloc.required = call i1 @llvm.coro.alloc(token %id) %dyn.alloc.required = call i1 @llvm.coro.alloc(token %id)
br i1 %dyn.alloc.required, label %coro.alloc, label %coro.begin br i1 %dyn.alloc.required, label %coro.alloc, label %coro.begin
@ -909,7 +912,8 @@ coroutine frame.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:: ::
declare token @llvm.coro.id(i32 <align>, i8* <promise>, i8* <fnaddr>) declare token @llvm.coro.id(i32 <align>, i8* <promise>, i8* <coroaddr>,
i8* <fnaddrs>)
Overview: Overview:
""""""""" """""""""
@ -927,7 +931,10 @@ This argument only accepts constants.
The second argument, if not `null`, designates a particular alloca instruction The second argument, if not `null`, designates a particular alloca instruction
to be a `coroutine promise`_. to be a `coroutine promise`_.
The third argument is `null` before coroutine is split, and later is replaced The third argument is `null` coming out of the frontend. The CoroEarly pass sets
this argument to point to the function this coro.id belongs to.
The fourth argument is `null` before coroutine is split, and later is replaced
to point to a private global constant array containing function pointers to to point to a private global constant array containing function pointers to
outlined resume and destroy parts of the coroutine. outlined resume and destroy parts of the coroutine.
@ -1210,19 +1217,6 @@ CoroCleanup
This pass runs late to lower all coroutine related intrinsics not replaced by This pass runs late to lower all coroutine related intrinsics not replaced by
earlier passes. earlier passes.
Upstreaming sequence (rough plan)
=================================
#. Add documentation.
#. Add coroutine intrinsics.
#. Add empty coroutine passes.
#. Add coroutine devirtualization + tests.
#. Add CGSCC restart trigger + tests.
#. Add coroutine heap elision + tests.
#. Add custom allocation heap elision + tests. <== we are here
#. Add coroutine splitting logic + tests.
#. Add simple coroutine frame builder + tests.
#. Add the rest of the logic + tests. (Maybe split further as needed).
Areas Requiring Attention Areas Requiring Attention
========================= =========================
#. A coroutine frame is bigger than it could be. Adding stack packing and stack #. A coroutine frame is bigger than it could be. Adding stack packing and stack

View File

@ -603,15 +603,16 @@ def int_experimental_gc_relocate : Intrinsic<[llvm_any_ty],
// Coroutine Structure Intrinsics. // Coroutine Structure Intrinsics.
def int_coro_id : Intrinsic<[llvm_token_ty], [llvm_i32_ty, llvm_ptr_ty, def int_coro_id : Intrinsic<[llvm_token_ty], [llvm_i32_ty, llvm_ptr_ty,
llvm_ptr_ty], llvm_ptr_ty, llvm_ptr_ty],
[IntrArgMemOnly, IntrReadMem, [IntrArgMemOnly, IntrReadMem,
ReadNone<1>, ReadOnly<2>, NoCapture<2>]>; ReadNone<1>, ReadOnly<2>, NoCapture<2>]>;
def int_coro_alloc : Intrinsic<[llvm_i1_ty], [llvm_token_ty], []>; def int_coro_alloc : Intrinsic<[llvm_i1_ty], [llvm_token_ty], []>;
def int_coro_begin : Intrinsic<[llvm_ptr_ty], [llvm_token_ty, llvm_ptr_ty], def int_coro_begin : Intrinsic<[llvm_ptr_ty], [llvm_token_ty, llvm_ptr_ty],
[WriteOnly<1>]>; [WriteOnly<1>]>;
def int_coro_free : Intrinsic<[llvm_ptr_ty], [llvm_ptr_ty], def int_coro_free : Intrinsic<[llvm_ptr_ty], [llvm_token_ty, llvm_ptr_ty],
[IntrArgMemOnly, ReadOnly<0>, NoCapture<0>]>; [IntrReadMem, IntrArgMemOnly, ReadOnly<1>,
NoCapture<1>]>;
def int_coro_end : Intrinsic<[], [llvm_ptr_ty, llvm_i1_ty], []>; def int_coro_end : Intrinsic<[], [llvm_ptr_ty, llvm_i1_ty], []>;
def int_coro_frame : Intrinsic<[llvm_ptr_ty], [], [IntrNoMem]>; def int_coro_frame : Intrinsic<[llvm_ptr_ty], [], [IntrNoMem]>;

View File

@ -3873,7 +3873,7 @@ void Verifier::visitIntrinsicCallSite(Intrinsic::ID ID, CallSite CS) {
default: default:
break; break;
case Intrinsic::coro_id: { case Intrinsic::coro_id: {
auto *InfoArg = CS.getArgOperand(2)->stripPointerCasts(); auto *InfoArg = CS.getArgOperand(3)->stripPointerCasts();
if (isa<ConstantPointerNull>(InfoArg)) if (isa<ConstantPointerNull>(InfoArg))
break; break;
auto *GV = dyn_cast<GlobalVariable>(InfoArg); auto *GV = dyn_cast<GlobalVariable>(InfoArg);

View File

@ -81,6 +81,7 @@ bool Lowerer::lowerEarlyIntrinsics(Function &F) {
if (CII->getInfo().isPreSplit()) { if (CII->getInfo().isPreSplit()) {
F.addFnAttr(CORO_PRESPLIT_ATTR, UNPREPARED_FOR_SPLIT); F.addFnAttr(CORO_PRESPLIT_ATTR, UNPREPARED_FOR_SPLIT);
setCannotDuplicate(CII); setCannotDuplicate(CII);
CII->setCoroutineSelf();
} }
} }
break; break;

View File

@ -35,6 +35,7 @@ struct Lowerer : coro::LowererBase {
Lowerer(Module &M) : LowererBase(M) {} Lowerer(Module &M) : LowererBase(M) {}
void elideHeapAllocations(Function *F, Type *FrameTy, AAResults &AA); void elideHeapAllocations(Function *F, Type *FrameTy, AAResults &AA);
bool shouldElide() const;
bool processCoroId(CoroIdInst *, AAResults &AA); bool processCoroId(CoroIdInst *, AAResults &AA);
}; };
} // end anonymous namespace } // end anonymous namespace
@ -122,14 +123,6 @@ void Lowerer::elideHeapAllocations(Function *F, Type *FrameTy, AAResults &AA) {
CA->eraseFromParent(); CA->eraseFromParent();
} }
// To suppress deallocation code, we replace all llvm.coro.free intrinsics
// associated with this coro.begin with null constant.
auto *NullPtr = ConstantPointerNull::get(Type::getInt8PtrTy(C));
for (auto *CF : CoroFrees) {
CF->replaceAllUsesWith(NullPtr);
CF->eraseFromParent();
}
// FIXME: Design how to transmit alignment information for every alloca that // FIXME: Design how to transmit alignment information for every alloca that
// is spilled into the coroutine frame and recreate the alignment information // is spilled into the coroutine frame and recreate the alignment information
// here. Possibly we will need to do a mini SROA here and break the coroutine // here. Possibly we will need to do a mini SROA here and break the coroutine
@ -148,9 +141,36 @@ void Lowerer::elideHeapAllocations(Function *F, Type *FrameTy, AAResults &AA) {
removeTailCallAttribute(Frame, AA); removeTailCallAttribute(Frame, AA);
} }
bool Lowerer::shouldElide() const {
// If no CoroAllocs, we cannot suppress allocation, so elision is not
// possible.
if (CoroAllocs.empty())
return false;
// Check that for every coro.begin there is a coro.destroy directly
// referencing the SSA value of that coro.begin. If the value escaped, then
// coro.destroy would have been referencing a memory location storing that
// value and not the virtual register.
SmallPtrSet<CoroBeginInst *, 8> ReferencedCoroBegins;
for (CoroSubFnInst *DA : DestroyAddr) {
if (auto *CB = dyn_cast<CoroBeginInst>(DA->getFrame()))
ReferencedCoroBegins.insert(CB);
else
return false;
}
// If size of the set is the same as total number of CoroBegins, means we
// found a coro.free or coro.destroy mentioning a coro.begin and we can
// perform heap elision.
return ReferencedCoroBegins.size() == CoroBegins.size();
}
bool Lowerer::processCoroId(CoroIdInst *CoroId, AAResults &AA) { bool Lowerer::processCoroId(CoroIdInst *CoroId, AAResults &AA) {
CoroBegins.clear(); CoroBegins.clear();
CoroAllocs.clear(); CoroAllocs.clear();
CoroFrees.clear();
ResumeAddr.clear(); ResumeAddr.clear();
DestroyAddr.clear(); DestroyAddr.clear();
@ -160,6 +180,8 @@ bool Lowerer::processCoroId(CoroIdInst *CoroId, AAResults &AA) {
CoroBegins.push_back(CB); CoroBegins.push_back(CB);
else if (auto *CA = dyn_cast<CoroAllocInst>(U)) else if (auto *CA = dyn_cast<CoroAllocInst>(U))
CoroAllocs.push_back(CA); CoroAllocs.push_back(CA);
else if (auto *CF = dyn_cast<CoroFreeInst>(U))
CoroFrees.push_back(CF);
} }
// Collect all coro.subfn.addrs associated with coro.begin. // Collect all coro.subfn.addrs associated with coro.begin.
@ -191,27 +213,20 @@ bool Lowerer::processCoroId(CoroIdInst *CoroId, AAResults &AA) {
replaceWithConstant(ResumeAddrConstant, ResumeAddr); replaceWithConstant(ResumeAddrConstant, ResumeAddr);
if (DestroyAddr.empty()) bool ShouldElide = shouldElide();
return true;
auto *DestroyAddrConstant = auto *DestroyAddrConstant = ConstantExpr::getExtractValue(
ConstantExpr::getExtractValue(Resumers, CoroSubFnInst::DestroyIndex); Resumers,
ShouldElide ? CoroSubFnInst::CleanupIndex : CoroSubFnInst::DestroyIndex);
replaceWithConstant(DestroyAddrConstant, DestroyAddr); replaceWithConstant(DestroyAddrConstant, DestroyAddr);
// If there is a coro.alloc that llvm.coro.id refers to, we have the ability if (ShouldElide) {
// to suppress dynamic allocation.
if (!CoroAllocs.empty()) {
// FIXME: The check above is overly lax. It only checks for whether we have
// an ability to elide heap allocations, not whether it is safe to do so.
// We need to do something like:
// If for every exit from the function where coro.begin is
// live, there is a coro.free or coro.destroy dominating that exit block,
// then it is safe to elide heap allocation, since the lifetime of coroutine
// is fully enclosed in its caller.
auto *FrameTy = getFrameType(cast<Function>(ResumeAddrConstant)); auto *FrameTy = getFrameType(cast<Function>(ResumeAddrConstant));
elideHeapAllocations(CoroId->getFunction(), FrameTy, AA); elideHeapAllocations(CoroId->getFunction(), FrameTy, AA);
coro::replaceCoroFree(CoroId, /*Elide=*/true);
} }
return true; return true;
} }
@ -262,21 +277,21 @@ struct CoroElide : FunctionPass {
Changed = replaceDevirtTrigger(F); Changed = replaceDevirtTrigger(F);
L->CoroIds.clear(); L->CoroIds.clear();
L->CoroFrees.clear();
// Collect all PostSplit coro.ids and all coro.free. // Collect all PostSplit coro.ids.
for (auto &I : instructions(F)) for (auto &I : instructions(F))
if (auto *CF = dyn_cast<CoroFreeInst>(&I)) if (auto *CII = dyn_cast<CoroIdInst>(&I))
L->CoroFrees.push_back(CF);
else if (auto *CII = dyn_cast<CoroIdInst>(&I))
if (CII->getInfo().isPostSplit()) if (CII->getInfo().isPostSplit())
L->CoroIds.push_back(CII); // If it is the coroutine itself, don't touch it.
if (CII->getCoroutine() != CII->getFunction())
L->CoroIds.push_back(CII);
// If we did not find any coro.id, there is nothing to do. // If we did not find any coro.id, there is nothing to do.
if (L->CoroIds.empty()) if (L->CoroIds.empty())
return Changed; return Changed;
AAResults &AA = getAnalysis<AAResultsWrapperPass>().getAAResults(); AAResults &AA = getAnalysis<AAResultsWrapperPass>().getAAResults();
for (auto *CII : L->CoroIds) for (auto *CII : L->CoroIds)
Changed |= L->processCoroId(CII, AA); Changed |= L->processCoroId(CII, AA);
@ -284,7 +299,6 @@ struct CoroElide : FunctionPass {
} }
void getAnalysisUsage(AnalysisUsage &AU) const override { void getAnalysisUsage(AnalysisUsage &AU) const override {
AU.addRequired<AAResultsWrapperPass>(); AU.addRequired<AAResultsWrapperPass>();
AU.setPreservesCFG();
} }
}; };
} }

View File

@ -24,6 +24,7 @@
#include "llvm/IR/IRBuilder.h" #include "llvm/IR/IRBuilder.h"
#include "llvm/IR/InstIterator.h" #include "llvm/IR/InstIterator.h"
#include "llvm/Support/Debug.h" #include "llvm/Support/Debug.h"
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/circular_raw_ostream.h" #include "llvm/Support/circular_raw_ostream.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h" #include "llvm/Transforms/Utils/BasicBlockUtils.h"
@ -182,8 +183,9 @@ SuspendCrossingInfo::SuspendCrossingInfo(Function &F, coro::Shape &Shape)
} }
// Iterate propagating consumes and kills until they stop changing // Iterate propagating consumes and kills until they stop changing
int Iteration = 0; (void)Iteration; int Iteration = 0;
(void)Iteration;
bool Changed; bool Changed;
do { do {
DEBUG(dbgs() << "iteration " << ++Iteration); DEBUG(dbgs() << "iteration " << ++Iteration);
@ -307,10 +309,10 @@ static StructType *buildFrameType(Function &F, coro::Shape &Shape,
/*IsVarArgs=*/false); /*IsVarArgs=*/false);
auto *FnPtrTy = FnTy->getPointerTo(); auto *FnPtrTy = FnTy->getPointerTo();
if (Shape.CoroSuspends.size() > UINT32_MAX) // Figure out how wide should be an integer type storing the suspend index.
report_fatal_error("Cannot handle coroutine with this many suspend points"); unsigned IndexBits = std::max(1U, Log2_64_Ceil(Shape.CoroSuspends.size()));
SmallVector<Type *, 8> Types{FnPtrTy, FnPtrTy, Type::getInt32Ty(C)}; SmallVector<Type *, 8> Types{FnPtrTy, FnPtrTy, Type::getIntNTy(C, IndexBits)};
Value *CurrentDef = nullptr; Value *CurrentDef = nullptr;
// Create an entry for every spilled value. // Create an entry for every spilled value.
@ -333,13 +335,6 @@ static StructType *buildFrameType(Function &F, coro::Shape &Shape,
return FrameTy; return FrameTy;
} }
// Returns the index of the last non-spill field in the coroutine frame.
// 2 - if there is no coroutine promise specified or 3, if there is.
static unsigned getLastNonSpillIndex(coro::Shape &Shape) {
// TODO: Add support for coroutine promise.
return 2;
}
// Replace all alloca and SSA values that are accessed across suspend points // Replace all alloca and SSA values that are accessed across suspend points
// with GetElementPointer from coroutine frame + loads and stores. Create an // with GetElementPointer from coroutine frame + loads and stores. Create an
// AllocaSpillBB that will become the new entry block for the resume parts of // AllocaSpillBB that will become the new entry block for the resume parts of
@ -373,7 +368,7 @@ static Instruction *insertSpills(SpillInfo &Spills, coro::Shape &Shape) {
Value *CurrentValue = nullptr; Value *CurrentValue = nullptr;
BasicBlock *CurrentBlock = nullptr; BasicBlock *CurrentBlock = nullptr;
Value *CurrentReload = nullptr; Value *CurrentReload = nullptr;
unsigned Index = getLastNonSpillIndex(Shape); unsigned Index = coro::Shape::LastKnownField;
// We need to keep track of any allocas that need "spilling" // We need to keep track of any allocas that need "spilling"
// since they will live in the coroutine frame now, all access to them // since they will live in the coroutine frame now, all access to them
@ -622,6 +617,10 @@ void coro::buildCoroutineFrame(Function &F, Shape &Shape) {
// frame. // frame.
if (isa<CoroBeginInst>(&I)) if (isa<CoroBeginInst>(&I))
continue; continue;
// A token returned CoroIdInst is used to tie together structural intrinsics
// in a coroutine. It should not be saved to the coroutine frame.
if (isa<CoroIdInst>(&I))
continue;
for (User *U : I.users()) for (User *U : I.users())
if (Checker.isDefinitionAcrossSuspend(I, U)) { if (Checker.isDefinitionAcrossSuspend(I, U)) {

View File

@ -11,7 +11,7 @@
// allows you to do things like: // allows you to do things like:
// //
// if (auto *SF = dyn_cast<CoroSubFnInst>(Inst)) // if (auto *SF = dyn_cast<CoroSubFnInst>(Inst))
// ... SF->getFrame() ... // ... SF->getFrame() ...
// //
// All intrinsic function calls are instances of the call instruction, so these // All intrinsic function calls are instances of the call instruction, so these
// are all subclasses of the CallInst class. Note that none of these classes // are all subclasses of the CallInst class. Note that none of these classes
@ -37,6 +37,7 @@ public:
RestartTrigger = -1, RestartTrigger = -1,
ResumeIndex, ResumeIndex,
DestroyIndex, DestroyIndex,
CleanupIndex,
IndexLast, IndexLast,
IndexFirst = RestartTrigger IndexFirst = RestartTrigger
}; };
@ -76,7 +77,8 @@ public:
/// This represents the llvm.coro.alloc instruction. /// This represents the llvm.coro.alloc instruction.
class LLVM_LIBRARY_VISIBILITY CoroIdInst : public IntrinsicInst { class LLVM_LIBRARY_VISIBILITY CoroIdInst : public IntrinsicInst {
enum { AlignArg, PromiseArg, InfoArg }; enum { AlignArg, PromiseArg, CoroutineArg, InfoArg };
public: public:
// Info argument of coro.id is // Info argument of coro.id is
// fresh out of the frontend: null ; // fresh out of the frontend: null ;
@ -118,6 +120,16 @@ public:
void setInfo(Constant *C) { setArgOperand(InfoArg, C); } void setInfo(Constant *C) { setArgOperand(InfoArg, C); }
Function *getCoroutine() const {
return cast<Function>(getArgOperand(CoroutineArg)->stripPointerCasts());
}
void setCoroutineSelf() {
assert(isa<ConstantPointerNull>(getArgOperand(CoroutineArg)) &&
"Coroutine argument is already assigned");
auto *const Int8PtrTy = Type::getInt8PtrTy(getContext());
setArgOperand(CoroutineArg,
ConstantExpr::getBitCast(getFunction(), Int8PtrTy));
}
// Methods to support type inquiry through isa, cast, and dyn_cast: // Methods to support type inquiry through isa, cast, and dyn_cast:
static inline bool classof(const IntrinsicInst *I) { static inline bool classof(const IntrinsicInst *I) {
@ -142,7 +154,11 @@ public:
/// This represents the llvm.coro.free instruction. /// This represents the llvm.coro.free instruction.
class LLVM_LIBRARY_VISIBILITY CoroFreeInst : public IntrinsicInst { class LLVM_LIBRARY_VISIBILITY CoroFreeInst : public IntrinsicInst {
enum { IdArg, FrameArg };
public: public:
Value *getFrame() const { return getArgOperand(FrameArg); }
// Methods to support type inquiry through isa, cast, and dyn_cast: // Methods to support type inquiry through isa, cast, and dyn_cast:
static inline bool classof(const IntrinsicInst *I) { static inline bool classof(const IntrinsicInst *I) {
return I->getIntrinsicID() == Intrinsic::coro_free; return I->getIntrinsicID() == Intrinsic::coro_free;
@ -157,9 +173,7 @@ class LLVM_LIBRARY_VISIBILITY CoroBeginInst : public IntrinsicInst {
enum { IdArg, MemArg }; enum { IdArg, MemArg };
public: public:
CoroIdInst *getId() const { CoroIdInst *getId() const { return cast<CoroIdInst>(getArgOperand(IdArg)); }
return cast<CoroIdInst>(getArgOperand(IdArg));
}
Value *getMem() const { return getArgOperand(MemArg); } Value *getMem() const { return getArgOperand(MemArg); }

View File

@ -46,6 +46,7 @@ namespace coro {
bool declaresIntrinsics(Module &M, std::initializer_list<StringRef>); bool declaresIntrinsics(Module &M, std::initializer_list<StringRef>);
void replaceAllCoroAllocs(CoroBeginInst *CB, bool Replacement); void replaceAllCoroAllocs(CoroBeginInst *CB, bool Replacement);
void replaceAllCoroFrees(CoroBeginInst *CB, Value *Replacement); void replaceAllCoroFrees(CoroBeginInst *CB, Value *Replacement);
void replaceCoroFree(CoroIdInst *CoroId, bool Elide);
void updateCallGraph(Function &Caller, ArrayRef<Function *> Funcs, void updateCallGraph(Function &Caller, ArrayRef<Function *> Funcs,
CallGraph &CG, CallGraphSCC &SCC); CallGraph &CG, CallGraphSCC &SCC);
@ -71,9 +72,10 @@ struct LLVM_LIBRARY_VISIBILITY Shape {
// Field Indexes for known coroutine frame fields. // Field Indexes for known coroutine frame fields.
enum { enum {
ResumeField = 0, ResumeField,
DestroyField = 1, DestroyField,
IndexField = 2, IndexField,
LastKnownField = IndexField
}; };
StructType *FrameTy; StructType *FrameTy;
@ -82,6 +84,14 @@ struct LLVM_LIBRARY_VISIBILITY Shape {
SwitchInst* ResumeSwitch; SwitchInst* ResumeSwitch;
bool HasFinalSuspend; bool HasFinalSuspend;
IntegerType* getIndexType() const {
assert(FrameTy && "frame type not assigned");
return cast<IntegerType>(FrameTy->getElementType(IndexField));
}
ConstantInt *getIndex(uint64_t Value) const {
return ConstantInt::get(getIndexType(), Value);
}
Shape() = default; Shape() = default;
explicit Shape(Function &F) { buildFrom(F); } explicit Shape(Function &F) { buildFrom(F); }
void buildFrom(Function &F); void buildFrom(Function &F);

View File

@ -64,7 +64,7 @@ static BasicBlock *createResumeEntryBlock(Function &F, coro::Shape &Shape) {
uint32_t SuspendIndex = 0; uint32_t SuspendIndex = 0;
for (auto S : Shape.CoroSuspends) { for (auto S : Shape.CoroSuspends) {
ConstantInt *IndexVal = Builder.getInt32(SuspendIndex); ConstantInt *IndexVal = Shape.getIndex(SuspendIndex);
// Replace CoroSave with a store to Index: // Replace CoroSave with a store to Index:
// %index.addr = getelementptr %f.frame... (index field number) // %index.addr = getelementptr %f.frame... (index field number)
@ -140,7 +140,6 @@ static void replaceFallthroughCoroEnd(IntrinsicInst *End,
// resume or cleanup pass for every suspend point. // resume or cleanup pass for every suspend point.
static Function *createClone(Function &F, Twine Suffix, coro::Shape &Shape, static Function *createClone(Function &F, Twine Suffix, coro::Shape &Shape,
BasicBlock *ResumeEntry, int8_t FnIndex) { BasicBlock *ResumeEntry, int8_t FnIndex) {
Module *M = F.getParent(); Module *M = F.getParent();
auto *FrameTy = Shape.FrameTy; auto *FrameTy = Shape.FrameTy;
auto *FnPtrTy = cast<PointerType>(FrameTy->getElementType(0)); auto *FnPtrTy = cast<PointerType>(FrameTy->getElementType(0));
@ -207,7 +206,11 @@ static Function *createClone(Function &F, Twine Suffix, coro::Shape &Shape,
OldVFrame->replaceAllUsesWith(NewVFrame); OldVFrame->replaceAllUsesWith(NewVFrame);
// Replace coro suspend with the appropriate resume index. // Replace coro suspend with the appropriate resume index.
auto *NewValue = Builder.getInt8(FnIndex); // 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
// result in control flow proceeding to a cleanup label associated with this
// suspend point.
auto *NewValue = Builder.getInt8(FnIndex ? 1 : 0);
for (CoroSuspendInst *CS : Shape.CoroSuspends) { for (CoroSuspendInst *CS : Shape.CoroSuspends) {
auto *MappedCS = cast<CoroSuspendInst>(VMap[CS]); auto *MappedCS = cast<CoroSuspendInst>(VMap[CS]);
MappedCS->replaceAllUsesWith(NewValue); MappedCS->replaceAllUsesWith(NewValue);
@ -219,11 +222,23 @@ static Function *createClone(Function &F, Twine Suffix, coro::Shape &Shape,
// FIXME: coming in upcoming patches: // FIXME: coming in upcoming patches:
// replaceUnwindCoroEnds(Shape.CoroEnds, VMap); // replaceUnwindCoroEnds(Shape.CoroEnds, VMap);
// Store the address of this clone in the coroutine frame. // We only store resume(0) and destroy(1) addresses in the coroutine frame.
Builder.SetInsertPoint(Shape.FramePtr->getNextNode()); // The cleanup(2) clone is only used during devirtualization when coroutine is
auto *G = Builder.CreateConstInBoundsGEP2_32(Shape.FrameTy, Shape.FramePtr, 0, // eligible for heap elision and thus does not participate in indirect calls
FnIndex, "fn.addr"); // and does not need its address to be stored in the coroutine frame.
Builder.CreateStore(NewF, G); if (FnIndex < 2) {
// Store the address of this clone in the coroutine frame.
Builder.SetInsertPoint(Shape.FramePtr->getNextNode());
auto *G = Builder.CreateConstInBoundsGEP2_32(Shape.FrameTy, Shape.FramePtr,
0, FnIndex, "fn.addr");
Builder.CreateStore(NewF, G);
}
// Eliminate coro.free from the clones, replacing it with 'null' in cleanup,
// to suppress deallocation code.
coro::replaceCoroFree(cast<CoroIdInst>(VMap[Shape.CoroBegin->getId()]),
/*Elide=*/FnIndex == 2);
NewF->setCallingConv(CallingConv::Fast); NewF->setCallingConv(CallingConv::Fast);
return NewF; return NewF;
@ -303,10 +318,12 @@ static void splitCoroutine(Function &F, CallGraph &CG, CallGraphSCC &SCC) {
return; return;
buildCoroutineFrame(F, Shape); buildCoroutineFrame(F, Shape);
replaceFrameSize(Shape);
auto *ResumeEntry = createResumeEntryBlock(F, Shape); auto *ResumeEntry = createResumeEntryBlock(F, Shape);
auto *ResumeClone = createClone(F, ".resume", Shape, ResumeEntry, 0); auto ResumeClone = createClone(F, ".resume", Shape, ResumeEntry, 0);
auto *DestroyClone = createClone(F, ".destroy", Shape, ResumeEntry, 1); auto DestroyClone = createClone(F, ".destroy", Shape, ResumeEntry, 1);
auto CleanupClone = createClone(F, ".cleanup", Shape, ResumeEntry, 2);
// We no longer need coro.end in F. // We no longer need coro.end in F.
removeCoroEnds(Shape); removeCoroEnds(Shape);
@ -314,11 +331,10 @@ static void splitCoroutine(Function &F, CallGraph &CG, CallGraphSCC &SCC) {
postSplitCleanup(F); postSplitCleanup(F);
postSplitCleanup(*ResumeClone); postSplitCleanup(*ResumeClone);
postSplitCleanup(*DestroyClone); postSplitCleanup(*DestroyClone);
postSplitCleanup(*CleanupClone);
replaceFrameSize(Shape); setCoroInfo(F, Shape.CoroBegin, {ResumeClone, DestroyClone, CleanupClone});
coro::updateCallGraph(F, {ResumeClone, DestroyClone, CleanupClone}, CG, SCC);
setCoroInfo(F, Shape.CoroBegin, {ResumeClone, DestroyClone});
coro::updateCallGraph(F, {ResumeClone, DestroyClone}, CG, SCC);
} }
// When we see the coroutine the first time, we insert an indirect call to a // When we see the coroutine the first time, we insert an indirect call to a

View File

@ -127,6 +127,27 @@ bool coro::declaresIntrinsics(Module &M,
return false; return false;
} }
// Replace all coro.frees associated with the provided CoroId either with 'null'
// if Elide is true and with its frame parameter otherwise.
void coro::replaceCoroFree(CoroIdInst *CoroId, bool Elide) {
SmallVector<CoroFreeInst *, 4> CoroFrees;
for (User *U : CoroId->users())
if (auto CF = dyn_cast<CoroFreeInst>(U))
CoroFrees.push_back(CF);
if (CoroFrees.empty())
return;
Value *Replacement =
Elide ? ConstantPointerNull::get(Type::getInt8PtrTy(CoroId->getContext()))
: CoroFrees.front()->getFrame();
for (CoroFreeInst *CF : CoroFrees) {
CF->replaceAllUsesWith(Replacement);
CF->eraseFromParent();
}
}
// FIXME: This code is stolen from CallGraph::addToCallGraph(Function *F), which // FIXME: This code is stolen from CallGraph::addToCallGraph(Function *F), which
// happens to be private. It is better for this functionality exposed by the // happens to be private. It is better for this functionality exposed by the
// CallGraph. // CallGraph.
@ -286,5 +307,5 @@ void coro::Shape::buildFrom(Function &F) {
// Canonicalize coro.suspend by inserting a coro.save if needed. // Canonicalize coro.suspend by inserting a coro.save if needed.
for (CoroSuspendInst *CS : CoroSuspends) for (CoroSuspendInst *CS : CoroSuspends)
if (!CS->getCoroSave()) if (!CS->getCoroSave())
createCoroSave(CoroBegin, CoroSuspends.back()); createCoroSave(CoroBegin, CS);
} }

View File

@ -23,6 +23,7 @@ define fastcc void @f.destroy(i8*) {
define i8* @f() { define i8* @f() {
entry: entry:
%id = call token @llvm.coro.id(i32 0, i8* null, %id = call token @llvm.coro.id(i32 0, i8* null,
i8* bitcast (i8*()* @f to i8*),
i8* bitcast ([2 x void (i8*)*]* @f.resumers to i8*)) i8* bitcast ([2 x void (i8*)*]* @f.resumers to i8*))
%hdl = call i8* @llvm.coro.begin(token %id, i8* null) %hdl = call i8* @llvm.coro.begin(token %id, i8* null)
ret i8* %hdl ret i8* %hdl
@ -31,10 +32,9 @@ entry:
; CHECK-LABEL: @callResume( ; CHECK-LABEL: @callResume(
define void @callResume() { define void @callResume() {
entry: entry:
; CHECK: call i8* @llvm.coro.begin
%hdl = call i8* @f() %hdl = call i8* @f()
; CHECK-NEXT: call void @print(i32 0) ; CHECK: call void @print(i32 0)
%0 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 0) %0 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 0)
%1 = bitcast i8* %0 to void (i8*)* %1 = bitcast i8* %0 to void (i8*)*
call fastcc void %1(i8* %hdl) call fastcc void %1(i8* %hdl)
@ -51,10 +51,9 @@ entry:
; CHECK-LABEL: @eh( ; CHECK-LABEL: @eh(
define void @eh() personality i8* null { define void @eh() personality i8* null {
entry: entry:
; CHECK: call i8* @llvm.coro.begin
%hdl = call i8* @f() %hdl = call i8* @f()
; CHECK-NEXT: call void @print(i32 0) ; CHECK: call void @print(i32 0)
%0 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 0) %0 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 0)
%1 = bitcast i8* %0 to void (i8*)* %1 = bitcast i8* %0 to void (i8*)*
invoke void %1(i8* %hdl) invoke void %1(i8* %hdl)
@ -71,7 +70,7 @@ ehcleanup:
; no devirtualization here, since coro.begin info parameter is null ; no devirtualization here, since coro.begin info parameter is null
define void @no_devirt_info_null() { define void @no_devirt_info_null() {
entry: entry:
%id = call token @llvm.coro.id(i32 0, i8* null, i8* null) %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
%hdl = call i8* @llvm.coro.begin(token %id, i8* null) %hdl = call i8* @llvm.coro.begin(token %id, i8* null)
; CHECK: call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 0) ; CHECK: call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 0)
@ -107,7 +106,7 @@ entry:
ret void ret void
} }
declare token @llvm.coro.id(i32, i8*, i8*) declare token @llvm.coro.id(i32, i8*, i8*, i8*)
declare i8* @llvm.coro.begin(token, i8*) declare i8* @llvm.coro.begin(token, i8*)
declare i8* @llvm.coro.frame() declare i8* @llvm.coro.frame()
declare i8* @llvm.coro.subfn.addr(i8*, i8) declare i8* @llvm.coro.subfn.addr(i8*, i8)

View File

@ -11,19 +11,21 @@ declare void @bar(i8*)
declare fastcc void @f.resume(%f.frame*) declare fastcc void @f.resume(%f.frame*)
declare fastcc void @f.destroy(%f.frame*) declare fastcc void @f.destroy(%f.frame*)
declare fastcc void @f.cleanup(%f.frame*)
declare void @may_throw() declare void @may_throw()
declare i8* @CustomAlloc(i32) declare i8* @CustomAlloc(i32)
declare void @CustomFree(i8*) declare void @CustomFree(i8*)
@f.resumers = internal constant @f.resumers = internal constant [3 x void (%f.frame*)*]
[2 x void (%f.frame*)*] [void (%f.frame*)* @f.resume, void (%f.frame*)* @f.destroy] [void (%f.frame*)* @f.resume, void (%f.frame*)* @f.destroy, void (%f.frame*)* @f.cleanup]
; a coroutine start function ; a coroutine start function
define i8* @f() personality i8* null { define i8* @f() personality i8* null {
entry: entry:
%id = call token @llvm.coro.id(i32 0, i8* null, %id = call token @llvm.coro.id(i32 0, i8* null,
i8* bitcast ([2 x void (%f.frame*)*]* @f.resumers to i8*)) i8* bitcast (i8*()* @f to i8*),
i8* bitcast ([3 x void (%f.frame*)*]* @f.resumers to i8*))
%need.dyn.alloc = call i1 @llvm.coro.alloc(token %id) %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id)
br i1 %need.dyn.alloc, label %dyn.alloc, label %coro.begin br i1 %need.dyn.alloc, label %dyn.alloc, label %coro.begin
dyn.alloc: dyn.alloc:
@ -39,7 +41,7 @@ ret:
ehcleanup: ehcleanup:
%tok = cleanuppad within none [] %tok = cleanuppad within none []
%mem = call i8* @llvm.coro.free(i8* %hdl) %mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
%need.dyn.free = icmp ne i8* %mem, null %need.dyn.free = icmp ne i8* %mem, null
br i1 %need.dyn.free, label %dyn.free, label %if.end br i1 %need.dyn.free, label %dyn.free, label %if.end
dyn.free: dyn.free:
@ -62,7 +64,7 @@ entry:
; CHECK-NOT: tail call void @bar( ; CHECK-NOT: tail call void @bar(
; CHECK: call void @bar( ; CHECK: call void @bar(
tail call void @bar(i8* %hdl) tail call void @bar(i8* %hdl)
; CHECK: tail call void @bar( ; CHECK: tail call void @bar(
tail call void @bar(i8* null) tail call void @bar(i8* null)
; CHECK-NEXT: call fastcc void bitcast (void (%f.frame*)* @f.resume to void (i8*)*)(i8* %vFrame) ; CHECK-NEXT: call fastcc void bitcast (void (%f.frame*)* @f.resume to void (i8*)*)(i8* %vFrame)
@ -70,7 +72,7 @@ entry:
%1 = bitcast i8* %0 to void (i8*)* %1 = bitcast i8* %0 to void (i8*)*
call fastcc void %1(i8* %hdl) call fastcc void %1(i8* %hdl)
; CHECK-NEXT: call fastcc void bitcast (void (%f.frame*)* @f.destroy to void (i8*)*)(i8* %vFrame) ; CHECK-NEXT: call fastcc void bitcast (void (%f.frame*)* @f.cleanup to void (i8*)*)(i8* %vFrame)
%2 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 1) %2 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 1)
%3 = bitcast i8* %2 to void (i8*)* %3 = bitcast i8* %2 to void (i8*)*
call fastcc void %3(i8* %hdl) call fastcc void %3(i8* %hdl)
@ -84,7 +86,8 @@ entry:
define i8* @f_no_elision() personality i8* null { define i8* @f_no_elision() personality i8* null {
entry: entry:
%id = call token @llvm.coro.id(i32 0, i8* null, %id = call token @llvm.coro.id(i32 0, i8* null,
i8* bitcast ([2 x void (%f.frame*)*]* @f.resumers to i8*)) i8* bitcast (i8*()* @f_no_elision to i8*),
i8* bitcast ([3 x void (%f.frame*)*]* @f.resumers to i8*))
%alloc = call i8* @CustomAlloc(i32 4) %alloc = call i8* @CustomAlloc(i32 4)
%hdl = call i8* @llvm.coro.begin(token %id, i8* %alloc) %hdl = call i8* @llvm.coro.begin(token %id, i8* %alloc)
ret i8* %hdl ret i8* %hdl
@ -116,9 +119,9 @@ entry:
ret void ret void
} }
declare token @llvm.coro.id(i32, i8*, i8*) declare token @llvm.coro.id(i32, i8*, i8*, i8*)
declare i1 @llvm.coro.alloc(token) declare i1 @llvm.coro.alloc(token)
declare i8* @llvm.coro.free(i8*) declare i8* @llvm.coro.free(token, i8*)
declare i8* @llvm.coro.begin(token, i8*) declare i8* @llvm.coro.begin(token, i8*)
declare i8* @llvm.coro.frame(token) declare i8* @llvm.coro.frame(token)
declare i8* @llvm.coro.subfn.addr(i8*, i8) declare i8* @llvm.coro.subfn.addr(i8*, i8)

View File

@ -3,7 +3,7 @@
define i8* @f() "coroutine.presplit"="1" { define i8* @f() "coroutine.presplit"="1" {
entry: entry:
%id = call token @llvm.coro.id(i32 0, i8* null, i8* null) %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
%size = call i32 @llvm.coro.size.i32() %size = call i32 @llvm.coro.size.i32()
%alloc = call i8* @malloc(i32 %size) %alloc = call i8* @malloc(i32 %size)
%hdl = call i8* @llvm.coro.begin(token %id, i8* %alloc) %hdl = call i8* @llvm.coro.begin(token %id, i8* %alloc)
@ -16,7 +16,7 @@ resume:
br label %cleanup br label %cleanup
cleanup: cleanup:
%mem = call i8* @llvm.coro.free(i8* %hdl) %mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
call void @free(i8* %mem) call void @free(i8* %mem)
br label %suspend br label %suspend
suspend: suspend:
@ -45,13 +45,13 @@ suspend:
; CHECK: call void @free( ; CHECK: call void @free(
; CHECK: ret void ; CHECK: ret void
declare i8* @llvm.coro.free(i8*) declare i8* @llvm.coro.free(token, i8*)
declare i32 @llvm.coro.size.i32() declare i32 @llvm.coro.size.i32()
declare i8 @llvm.coro.suspend(token, i1) declare i8 @llvm.coro.suspend(token, i1)
declare void @llvm.coro.resume(i8*) declare void @llvm.coro.resume(i8*)
declare void @llvm.coro.destroy(i8*) declare void @llvm.coro.destroy(i8*)
declare token @llvm.coro.id(i32, i8*, i8*) declare token @llvm.coro.id(i32, i8*, i8*, i8*)
declare i8* @llvm.coro.alloc(token) declare i8* @llvm.coro.alloc(token)
declare i8* @llvm.coro.begin(token, i8*) declare i8* @llvm.coro.begin(token, i8*)
declare void @llvm.coro.end(i8*, i1) declare void @llvm.coro.end(i8*, i1)

View File

@ -3,7 +3,7 @@
define i8* @f() { define i8* @f() {
entry: entry:
%id = call token @llvm.coro.id(i32 0, i8* null, i8* null) %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
%need.dyn.alloc = call i1 @llvm.coro.alloc(token %id) %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id)
br i1 %need.dyn.alloc, label %dyn.alloc, label %coro.begin br i1 %need.dyn.alloc, label %dyn.alloc, label %coro.begin
dyn.alloc: dyn.alloc:
@ -22,7 +22,7 @@ resume:
br label %cleanup br label %cleanup
cleanup: cleanup:
%mem = call i8* @llvm.coro.free(i8* %hdl) %mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
call void @free(i8* %mem) call void @free(i8* %mem)
br label %suspend br label %suspend
suspend: suspend:
@ -35,22 +35,18 @@ entry:
call void @llvm.coro.resume(i8* %hdl) call void @llvm.coro.resume(i8* %hdl)
ret i32 0 ret i32 0
; CHECK-LABEL: @main( ; CHECK-LABEL: @main(
; CHECK: call i8* @malloc
; CHECK-NOT: call void @free
; CHECK: call void @print(i32 0) ; CHECK: call void @print(i32 0)
; CHECK-NOT: call void @free
; CHECK: call void @print(i32 1) ; CHECK: call void @print(i32 1)
; CHECK: call void @free
; CHECK: ret i32 0 ; CHECK: ret i32 0
} }
declare i8* @llvm.coro.free(i8*) declare i8* @llvm.coro.free(token, i8*)
declare i32 @llvm.coro.size.i32() declare i32 @llvm.coro.size.i32()
declare i8 @llvm.coro.suspend(token, i1) declare i8 @llvm.coro.suspend(token, i1)
declare void @llvm.coro.resume(i8*) declare void @llvm.coro.resume(i8*)
declare void @llvm.coro.destroy(i8*) declare void @llvm.coro.destroy(i8*)
declare token @llvm.coro.id(i32, i8*, i8*) declare token @llvm.coro.id(i32, i8*, i8*, i8*)
declare i1 @llvm.coro.alloc(token) declare i1 @llvm.coro.alloc(token)
declare i8* @llvm.coro.begin(token, i8*) declare i8* @llvm.coro.begin(token, i8*)
declare void @llvm.coro.end(i8*, i1) declare void @llvm.coro.end(i8*, i1)

View File

@ -3,7 +3,7 @@
define i8* @f(i32 %n) { define i8* @f(i32 %n) {
entry: entry:
%id = call token @llvm.coro.id(i32 0, i8* null, i8* null) %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
%size = call i32 @llvm.coro.size.i32() %size = call i32 @llvm.coro.size.i32()
%alloc = call i8* @malloc(i32 %size) %alloc = call i8* @malloc(i32 %size)
%hdl = call i8* @llvm.coro.begin(token %id, i8* %alloc) %hdl = call i8* @llvm.coro.begin(token %id, i8* %alloc)
@ -20,7 +20,7 @@ resume:
br label %loop br label %loop
cleanup: cleanup:
%mem = call i8* @llvm.coro.free(i8* %hdl) %mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
call void @free(i8* %mem) call void @free(i8* %mem)
br label %suspend br label %suspend
suspend: suspend:
@ -43,9 +43,9 @@ entry:
; CHECK: ret i32 0 ; CHECK: ret i32 0
} }
declare token @llvm.coro.id(i32, i8*, i8*) declare token @llvm.coro.id(i32, i8*, i8*, i8*)
declare i8* @llvm.coro.alloc(token) declare i8* @llvm.coro.alloc(token)
declare i8* @llvm.coro.free(i8*) declare i8* @llvm.coro.free(token, i8*)
declare i32 @llvm.coro.size.i32() declare i32 @llvm.coro.size.i32()
declare i8 @llvm.coro.suspend(token, i1) declare i8 @llvm.coro.suspend(token, i1)
declare void @llvm.coro.resume(i8*) declare void @llvm.coro.resume(i8*)

View File

@ -3,7 +3,7 @@
define i8* @f(i32 %n) { define i8* @f(i32 %n) {
entry: entry:
%id = call token @llvm.coro.id(i32 0, i8* null, i8* null) %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
%size = call i32 @llvm.coro.size.i32() %size = call i32 @llvm.coro.size.i32()
%alloc = call i8* @malloc(i32 %size) %alloc = call i8* @malloc(i32 %size)
%hdl = call noalias i8* @llvm.coro.begin(token %id, i8* %alloc) %hdl = call noalias i8* @llvm.coro.begin(token %id, i8* %alloc)
@ -16,7 +16,7 @@ loop:
switch i8 %0, label %suspend [i8 0, label %loop switch i8 %0, label %suspend [i8 0, label %loop
i8 1, label %cleanup] i8 1, label %cleanup]
cleanup: cleanup:
%mem = call i8* @llvm.coro.free(i8* %hdl) %mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
call void @free(i8* %mem) call void @free(i8* %mem)
br label %suspend br label %suspend
suspend: suspend:
@ -43,11 +43,11 @@ declare i8* @malloc(i32)
declare void @free(i8*) declare void @free(i8*)
declare void @print(i32) declare void @print(i32)
declare token @llvm.coro.id(i32, i8*, i8*) declare token @llvm.coro.id(i32, i8*, i8*, i8*)
declare i32 @llvm.coro.size.i32() declare i32 @llvm.coro.size.i32()
declare i8* @llvm.coro.begin(token, i8*) declare i8* @llvm.coro.begin(token, i8*)
declare i8 @llvm.coro.suspend(token, i1) declare i8 @llvm.coro.suspend(token, i1)
declare i8* @llvm.coro.free(i8*) declare i8* @llvm.coro.free(token, i8*)
declare void @llvm.coro.end(i8*, i1) declare void @llvm.coro.end(i8*, i1)
declare void @llvm.coro.resume(i8*) declare void @llvm.coro.resume(i8*)

View File

@ -0,0 +1,63 @@
; Second example from Doc/Coroutines.rst (custom alloc and free functions)
; RUN: opt < %s -O2 -enable-coroutines -S | FileCheck %s
define i8* @f(i32 %n) {
entry:
%id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
%need.dyn.alloc = call i1 @llvm.coro.alloc(token %id)
br i1 %need.dyn.alloc, label %dyn.alloc, label %coro.begin
dyn.alloc:
%size = call i32 @llvm.coro.size.i32()
%alloc = call i8* @CustomAlloc(i32 %size)
br label %coro.begin
coro.begin:
%phi = phi i8* [ null, %entry ], [ %alloc, %dyn.alloc ]
%hdl = call noalias i8* @llvm.coro.begin(token %id, i8* %phi)
br label %loop
loop:
%n.val = phi i32 [ %n, %coro.begin ], [ %inc, %loop ]
%inc = add nsw i32 %n.val, 1
call void @print(i32 %n.val)
%0 = call i8 @llvm.coro.suspend(token none, i1 false)
switch i8 %0, label %suspend [i8 0, label %loop
i8 1, label %cleanup]
cleanup:
%mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
%need.dyn.free = icmp ne i8* %mem, null
br i1 %need.dyn.free, label %dyn.free, label %suspend
dyn.free:
call void @CustomFree(i8* %mem)
br label %suspend
suspend:
call void @llvm.coro.end(i8* %hdl, i1 false)
ret i8* %hdl
}
; CHECK-LABEL: @main
define i32 @main() {
entry:
%hdl = call i8* @f(i32 4)
call void @llvm.coro.resume(i8* %hdl)
call void @llvm.coro.resume(i8* %hdl)
call void @llvm.coro.destroy(i8* %hdl)
ret i32 0
; CHECK: call void @print(i32 4)
; CHECK-NEXT: call void @print(i32 5)
; CHECK-NEXT: call void @print(i32 6)
; CHECK-NEXT: ret i32 0
}
declare i8* @CustomAlloc(i32)
declare void @CustomFree(i8*)
declare void @print(i32)
declare token @llvm.coro.id(i32, i8*, i8*, i8*)
declare i1 @llvm.coro.alloc(token)
declare i32 @llvm.coro.size.i32()
declare i8* @llvm.coro.begin(token, i8*)
declare i8 @llvm.coro.suspend(token, i1)
declare i8* @llvm.coro.free(token, i8*)
declare void @llvm.coro.end(i8*, i1)
declare void @llvm.coro.resume(i8*)
declare void @llvm.coro.destroy(i8*)

View File

@ -0,0 +1,60 @@
; Third example from Doc/Coroutines.rst (two suspend points)
; RUN: opt < %s -O2 -enable-coroutines -S | FileCheck %s
define i8* @f(i32 %n) {
entry:
%id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
%size = call i32 @llvm.coro.size.i32()
%alloc = call i8* @malloc(i32 %size)
%hdl = call noalias i8* @llvm.coro.begin(token %id, i8* %alloc)
br label %loop
loop:
%n.val = phi i32 [ %n, %entry ], [ %inc, %loop.resume ]
call void @print(i32 %n.val) #4
%0 = call i8 @llvm.coro.suspend(token none, i1 false)
switch i8 %0, label %suspend [i8 0, label %loop.resume
i8 1, label %cleanup]
loop.resume:
%inc = add nsw i32 %n.val, 1
%sub = xor i32 %n.val, -1
call void @print(i32 %sub)
%1 = call i8 @llvm.coro.suspend(token none, i1 false)
switch i8 %1, label %suspend [i8 0, label %loop
i8 1, label %cleanup]
cleanup:
%mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
call void @free(i8* %mem)
br label %suspend
suspend:
call void @llvm.coro.end(i8* %hdl, i1 false)
ret i8* %hdl
}
; CHECK-LABEL: @main
define i32 @main() {
entry:
%hdl = call i8* @f(i32 4)
call void @llvm.coro.resume(i8* %hdl)
call void @llvm.coro.resume(i8* %hdl)
call void @llvm.coro.destroy(i8* %hdl)
ret i32 0
; CHECK: call void @print(i32 4)
; CHECK-NEXT: call void @print(i32 -5)
; CHECK-NEXT: call void @print(i32 5)
; CHECK: ret i32 0
}
declare i8* @malloc(i32)
declare void @free(i8*)
declare void @print(i32)
declare token @llvm.coro.id(i32, i8*, i8*, i8*)
declare i1 @llvm.coro.alloc(token)
declare i32 @llvm.coro.size.i32()
declare i8* @llvm.coro.begin(token, i8*)
declare i8 @llvm.coro.suspend(token, i1)
declare i8* @llvm.coro.free(token, i8*)
declare void @llvm.coro.end(i8*, i1)
declare void @llvm.coro.resume(i8*)
declare void @llvm.coro.destroy(i8*)

View File

@ -8,7 +8,7 @@
; CHECK-NEXT: CoroSplit: Processing coroutine 'f' state: 1 ; CHECK-NEXT: CoroSplit: Processing coroutine 'f' state: 1
define void @f() { define void @f() {
%id = call token @llvm.coro.id(i32 0, i8* null, i8* null) %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
%size = call i32 @llvm.coro.size.i32() %size = call i32 @llvm.coro.size.i32()
%alloc = call i8* @malloc(i32 %size) %alloc = call i8* @malloc(i32 %size)
%hdl = call i8* @llvm.coro.begin(token %id, i8* %alloc) %hdl = call i8* @llvm.coro.begin(token %id, i8* %alloc)
@ -21,7 +21,7 @@ resume:
br label %cleanup br label %cleanup
cleanup: cleanup:
%mem = call i8* @llvm.coro.free(i8* %hdl) %mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
call void @free(i8* %mem) call void @free(i8* %mem)
br label %suspend br label %suspend
suspend: suspend:
@ -29,9 +29,9 @@ suspend:
ret void ret void
} }
declare token @llvm.coro.id(i32, i8*, i8*) declare token @llvm.coro.id(i32, i8*, i8*, i8*)
declare i8* @llvm.coro.begin(token, i8*) declare i8* @llvm.coro.begin(token, i8*)
declare i8* @llvm.coro.free(i8*) declare i8* @llvm.coro.free(token, i8*)
declare i32 @llvm.coro.size.i32() declare i32 @llvm.coro.size.i32()
declare i8 @llvm.coro.suspend(token, i1) declare i8 @llvm.coro.suspend(token, i1)
declare void @llvm.coro.resume(i8*) declare void @llvm.coro.resume(i8*)