diff --git a/docs/Coroutines.rst b/docs/Coroutines.rst index 1bea04ebdd2..f3667585c6c 100644 --- a/docs/Coroutines.rst +++ b/docs/Coroutines.rst @@ -880,6 +880,32 @@ Example: %phi = phi i8* [ null, %entry ], [ %alloc, %coro.alloc ] %frame = call i8* @llvm.coro.begin(token %id, i8* %phi) +.. _coro.noop: + +'llvm.coro.noop' Intrinsic +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +:: + + declare i8* @llvm.coro.noop() + +Overview: +""""""""" + +The '``llvm.coro.noop``' intrinsic returns an address of the coroutine frame of +a coroutine that does nothing when resumed or destroyed. + +Arguments: +"""""""""" + +None + +Semantics: +"""""""""" + +This intrinsic is lowered to refer to a private constant coroutine frame. The +resume and destroy handlers for this frame are empty functions that do nothing. +Note that in different translation units llvm.coro.noop may return different pointers. + .. _coro.frame: 'llvm.coro.frame' Intrinsic diff --git a/include/llvm/IR/Intrinsics.td b/include/llvm/IR/Intrinsics.td index fbb1c5c3fe6..4361c257d90 100644 --- a/include/llvm/IR/Intrinsics.td +++ b/include/llvm/IR/Intrinsics.td @@ -768,6 +768,7 @@ def int_coro_free : Intrinsic<[llvm_ptr_ty], [llvm_token_ty, llvm_ptr_ty], def int_coro_end : Intrinsic<[llvm_i1_ty], [llvm_ptr_ty, llvm_i1_ty], []>; def int_coro_frame : Intrinsic<[llvm_ptr_ty], [], [IntrNoMem]>; +def int_coro_noop : Intrinsic<[llvm_ptr_ty], [], [IntrNoMem]>; def int_coro_size : Intrinsic<[llvm_anyint_ty], [], [IntrNoMem]>; def int_coro_save : Intrinsic<[llvm_token_ty], [llvm_ptr_ty], []>; diff --git a/lib/Transforms/Coroutines/CoroEarly.cpp b/lib/Transforms/Coroutines/CoroEarly.cpp index ba05896af15..ac47a06281a 100644 --- a/lib/Transforms/Coroutines/CoroEarly.cpp +++ b/lib/Transforms/Coroutines/CoroEarly.cpp @@ -27,10 +27,12 @@ namespace { class Lowerer : public coro::LowererBase { IRBuilder<> Builder; PointerType *const AnyResumeFnPtrTy; + Constant *NoopCoro = nullptr; void lowerResumeOrDestroy(CallSite CS, CoroSubFnInst::ResumeKind); void lowerCoroPromise(CoroPromiseInst *Intrin); void lowerCoroDone(IntrinsicInst *II); + void lowerCoroNoop(IntrinsicInst *II); public: Lowerer(Module &M) @@ -103,6 +105,41 @@ void Lowerer::lowerCoroDone(IntrinsicInst *II) { II->eraseFromParent(); } +void Lowerer::lowerCoroNoop(IntrinsicInst *II) { + if (!NoopCoro) { + LLVMContext &C = Builder.getContext(); + Module &M = *II->getModule(); + + // Create a noop.frame struct type. + StructType *FrameTy = StructType::create(C, "NoopCoro.Frame"); + auto *FramePtrTy = FrameTy->getPointerTo(); + auto *FnTy = FunctionType::get(Type::getVoidTy(C), FramePtrTy, + /*IsVarArgs=*/false); + auto *FnPtrTy = FnTy->getPointerTo(); + FrameTy->setBody({FnPtrTy, FnPtrTy}); + + // Create a Noop function that does nothing. + Function *NoopFn = + Function::Create(FnTy, GlobalValue::LinkageTypes::PrivateLinkage, + "NoopCoro.ResumeDestroy", &M); + NoopFn->setCallingConv(CallingConv::Fast); + auto *Entry = BasicBlock::Create(C, "entry", NoopFn); + ReturnInst::Create(C, Entry); + + // Create a constant struct for the frame. + Constant* Values[] = {NoopFn, NoopFn}; + Constant* NoopCoroConst = ConstantStruct::get(FrameTy, Values); + NoopCoro = new GlobalVariable(M, NoopCoroConst->getType(), /*isConstant=*/true, + GlobalVariable::PrivateLinkage, NoopCoroConst, + "NoopCoro.Frame.Const"); + } + + Builder.SetInsertPoint(II); + auto *NoopCoroVoidPtr = Builder.CreateBitCast(NoopCoro, Int8Ptr); + II->replaceAllUsesWith(NoopCoroVoidPtr); + 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 @@ -138,6 +175,9 @@ bool Lowerer::lowerEarlyIntrinsics(Function &F) { if (cast(&I)->isFallthrough()) CS.setCannotDuplicate(); break; + case Intrinsic::coro_noop: + lowerCoroNoop(cast(&I)); + break; case Intrinsic::coro_id: // Mark a function that comes out of the frontend that has a coro.id // with a coroutine attribute. @@ -192,10 +232,10 @@ 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.id", "llvm.coro.destroy", - "llvm.coro.done", "llvm.coro.end", - "llvm.coro.free", "llvm.coro.promise", - "llvm.coro.resume", "llvm.coro.suspend"})) + if (coro::declaresIntrinsics( + M, {"llvm.coro.id", "llvm.coro.destroy", "llvm.coro.done", + "llvm.coro.end", "llvm.coro.noop", "llvm.coro.free", + "llvm.coro.promise", "llvm.coro.resume", "llvm.coro.suspend"})) L = llvm::make_unique(M); return false; } diff --git a/lib/Transforms/Coroutines/Coroutines.cpp b/lib/Transforms/Coroutines/Coroutines.cpp index fb09bd4ba44..7723cdfab54 100644 --- a/lib/Transforms/Coroutines/Coroutines.cpp +++ b/lib/Transforms/Coroutines/Coroutines.cpp @@ -125,9 +125,10 @@ static bool isCoroutineIntrinsicName(StringRef Name) { static const char *const CoroIntrinsics[] = { "llvm.coro.alloc", "llvm.coro.begin", "llvm.coro.destroy", "llvm.coro.done", "llvm.coro.end", "llvm.coro.frame", - "llvm.coro.free", "llvm.coro.id", "llvm.coro.param", - "llvm.coro.promise", "llvm.coro.resume", "llvm.coro.save", - "llvm.coro.size", "llvm.coro.subfn.addr", "llvm.coro.suspend", + "llvm.coro.free", "llvm.coro.id", "llvm.coro.noop", + "llvm.coro.param", "llvm.coro.promise", "llvm.coro.resume", + "llvm.coro.save", "llvm.coro.size", "llvm.coro.subfn.addr", + "llvm.coro.suspend", }; return Intrinsic::lookupLLVMIntrinsicByName(CoroIntrinsics, Name) != -1; } diff --git a/test/Transforms/Coroutines/coro-early.ll b/test/Transforms/Coroutines/coro-early.ll index 7b8b74e2e22..44e7169254e 100644 --- a/test/Transforms/Coroutines/coro-early.ll +++ b/test/Transforms/Coroutines/coro-early.ll @@ -1,7 +1,10 @@ -; Tests that CoroEarly pass correctly lowers coro.resume and coro.destroy -; intrinsics. +; Tests that CoroEarly pass correctly lowers coro.resume, coro.destroy +; and other intrinsics managed by this pass. ; RUN: opt < %s -S -coro-early | FileCheck %s +; CHECK: %NoopCoro.Frame = type { void (%NoopCoro.Frame*)*, void (%NoopCoro.Frame*)* } +; CHECK: @NoopCoro.Frame.Const = private constant %NoopCoro.Frame { void (%NoopCoro.Frame*)* @NoopCoro.ResumeDestroy, void (%NoopCoro.Frame*)* @NoopCoro.ResumeDestroy } + ; CHECK-LABEL: @callResume( define void @callResume(i8* %hdl) { ; CHECK-NEXT: entry @@ -37,5 +40,21 @@ ehcleanup: cleanupret from %0 unwind to caller } + +; CHECK-LABEL: @noop( +define i8* @noop() { +; CHECK-NEXT: entry +entry: +; CHECK-NEXT: ret i8* bitcast (%NoopCoro.Frame* @NoopCoro.Frame.Const to i8*) + %n = call i8* @llvm.coro.noop() + ret i8* %n +} + +; CHECK-LABEL: define private fastcc void @NoopCoro.ResumeDestroy(%NoopCoro.Frame*) { +; CHECK-NEXT: entry +; CHECK-NEXT: ret void + + declare void @llvm.coro.resume(i8*) declare void @llvm.coro.destroy(i8*) +declare i8* @llvm.coro.noop()