Index: clang/docs/ReleaseNotes.rst =================================================================== --- clang/docs/ReleaseNotes.rst +++ clang/docs/ReleaseNotes.rst @@ -107,6 +107,10 @@ instantiated in one module and whose definition is instantiated in another module may end up with members associated with the wrong declaration of the class, which can result in miscompiles in some cases. +- Fixed an issue that the conditional access to local variables of the awaiter + after leaking the coroutine handle in the await_suspend may be converted to + unconditional access incorrectly. + (`#56301 `_) Bug Fixes to Compiler Builtins ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Index: clang/lib/CodeGen/CGCoroutine.cpp =================================================================== --- clang/lib/CodeGen/CGCoroutine.cpp +++ clang/lib/CodeGen/CGCoroutine.cpp @@ -12,9 +12,10 @@ #include "CGCleanup.h" #include "CodeGenFunction.h" -#include "llvm/ADT/ScopeExit.h" #include "clang/AST/StmtCXX.h" #include "clang/AST/StmtVisitor.h" +#include "clang/AST/TypeVisitor.h" +#include "llvm/ADT/ScopeExit.h" using namespace clang; using namespace CodeGen; @@ -139,6 +140,173 @@ return true; } +namespace { +// We need a TypeVisitor to find the actual awaiter declaration. +// We can't use (CoroutineSuspendExpr).getCommonExpr()->getType() directly +// since its type may be AutoType, ElaboratedType, ... +class AwaiterTypeFinder : public TypeVisitor { + CXXRecordDecl *Result = nullptr; + +public: + typedef TypeVisitor Inherited; + + void Visit(const CoroutineSuspendExpr &S) { + Visit(S.getCommonExpr()->getType()); + } + + bool IsRecordEmpty() { + assert(Result && "Why can't we find the record type from the common " + "expression of a coroutine suspend expression? " + "Maybe we missed some types or the Sema get something " + "incorrect"); + + // In a release build without assertions enabled, return false directly + // to give users better user experience. It doesn't matter with the + // correctness but 1 byte memory overhead. +#ifdef NDEBUG + if (!Result) + return false; +#endif + + return Result->field_empty(); + } + + // Following off should only be called by Inherited. +public: + void Visit(QualType Type) { Visit(Type.getTypePtr()); } + + void Visit(const Type *T) { Inherited::Visit(T); } + + void VisitDeducedType(const DeducedType *T) { Visit(T->getDeducedType()); } + + void VisitTypedefType(const TypedefType *T) { + Visit(T->getDecl()->getUnderlyingType()); + } + + void VisitElaboratedType(const ElaboratedType *T) { + Visit(T->getNamedType()); + } + + void VisitReferenceType(const ReferenceType *T) { + Visit(T->getPointeeType()); + } + + void VisitTemplateSpecializationType(const TemplateSpecializationType *T) { + // In the case the type is sugared, we can only see InjectedClassNameType, + // which doesn't contain the definition information we need. + if (T->desugar().getTypePtr() != T) { + Visit(T->desugar().getTypePtr()); + return; + } + + TemplateName Name = T->getTemplateName(); + TemplateDecl *TD = Name.getAsTemplateDecl(); + + if (!TD) + return; + + if (auto *TypedD = dyn_cast(TD->getTemplatedDecl())) + Visit(TypedD->getTypeForDecl()); + } + + void VisitSubstTemplateTypeParmType(const SubstTemplateTypeParmType *T) { + Visit(T->getReplacementType()); + } + + void VisitInjectedClassNameType(const InjectedClassNameType *T) { + VisitCXXRecordDecl(T->getDecl()); + } + + void VisitCXXRecordDecl(CXXRecordDecl *Candidate) { + assert(Candidate); + +#ifdef NDEBUG + Result = Candidate; +#else + // Double check that the type we found is an awaiter class type. + // We only do this in debug mode since: + // The Sema should diagnose earlier in such cases. So this may + // be a waste of time in most cases. + // We just want to make sure our assumption is correct. + + auto HasMember = [](CXXRecordDecl *Candidate, llvm::StringRef Name, + auto HasMember) { + Candidate = Candidate->getDefinition(); + if (!Candidate) + return false; + + ASTContext &Context = Candidate->getASTContext(); + + auto IdenIter = Context.Idents.find(Name); + if (IdenIter == Context.Idents.end()) + return false; + + if (!Candidate->lookup(DeclarationName(IdenIter->second)).empty()) + return true; + + return llvm::any_of( + Candidate->bases(), [Name, &HasMember](CXXBaseSpecifier &Specifier) { + auto *RD = cast( + Specifier.getType()->getAs()->getDecl()); + return HasMember(RD, Name, HasMember); + }); + }; + + bool FoundAwaitReady = HasMember(Candidate, "await_ready", HasMember); + bool FoundAwaitSuspend = HasMember(Candidate, "await_suspend", HasMember); + bool FoundAwaitResume = HasMember(Candidate, "await_resume", HasMember); + + assert(FoundAwaitReady && FoundAwaitSuspend && FoundAwaitResume); + Result = Candidate; +#endif + } + + void VisitRecordType(const RecordType *RT) { + assert(isa(RT->getDecl())); + VisitCXXRecordDecl(cast(RT->getDecl())); + } + + void VisitType(const Type *T) {} +}; +} // namespace + +/// The middle end can't understand that the relationship between local +/// variables between local variables with the coroutine handle until CoroSplit +/// pass. However, there are a lot optimizations before CoroSplit. Luckily, it +/// is not so bothering since the C++ languages doesn't allow the programmers to +/// access the coroutine handle except in await_suspend. So it is sufficient to +/// handle await_suspend specially. Here we emit @llvm.coro.opt.blocker with the +/// address of the awaiter as the argument so that the optimizer will think the +/// awaiter is escaped at the suspend point. See +/// https://github.com/llvm/llvm-project/issues/56301 for the example and the +/// complete discussion. +static void EmitCoroOptBlockerForSuspension(CodeGenFunction &CGF, + CoroutineSuspendExpr const &S, + CGBuilderTy &Builder) { + AwaiterTypeFinder Finder; + Finder.Visit(S); + // We shouldn't generate the call since it will prevents the compiler to erase + // the empty awaiter class. + if (Finder.IsRecordEmpty()) + return; + + // TODO: It would be better to generate the call only if we observed the + // coroutine handle is leaked in the await_suspend. It is better to be + // implemented by analying the generated IR instead of the AST. + + llvm::Function *CoroOptBlocker = + CGF.CGM.getIntrinsic(llvm::Intrinsic::coro_opt_blocker); + llvm::Value *Addr = nullptr; + if (CodeGenFunction::OpaqueValueMapping::shouldBindAsLValue( + S.getOpaqueValue())) + Addr = + CGF.getOrCreateOpaqueLValueMapping(S.getOpaqueValue()).getPointer(CGF); + else + Addr = CGF.getOrCreateOpaqueRValueMapping(S.getOpaqueValue()) + .getAggregatePointer(); + Builder.CreateCall(CoroOptBlocker, Addr); +} + // Emit suspend expression which roughly looks like: // // auto && x = CommonExpr(); @@ -198,6 +366,8 @@ auto *NullPtr = llvm::ConstantPointerNull::get(CGF.CGM.Int8PtrTy); auto *SaveCall = Builder.CreateCall(CoroSave, {NullPtr}); + EmitCoroOptBlockerForSuspension(CGF, S, Builder); + CGF.CurCoro.InSuspendBlock = true; auto *SuspendRet = CGF.EmitScalarExpr(S.getSuspendExpr()); CGF.CurCoro.InSuspendBlock = false; Index: clang/test/CodeGenCoroutines/coro-awaiter-addr.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-awaiter-addr.cpp @@ -0,0 +1,209 @@ +// Tests that we can generate @llvm.coro.opt.blocker correctly and we can omit the +// call if certain conditions are met. (e.g., the awaitier is an empty class.) +// +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s \ +// RUN: -disable-llvm-passes | FileCheck %s + +#include "Inputs/coroutine.h" + +struct Task { + struct promise_type { + struct FinalAwaiter { + bool await_ready() const noexcept { return false; } + template + std::coroutine_handle<> await_suspend(std::coroutine_handle h) noexcept { + return h.promise().continuation; + } + void await_resume() noexcept {} + }; + + Task get_return_object() noexcept { + return std::coroutine_handle::from_promise(*this); + } + + std::suspend_always initial_suspend() noexcept { return {}; } + FinalAwaiter final_suspend() noexcept { return {}; } + void unhandled_exception() noexcept {} + void return_void() noexcept {} + + std::coroutine_handle<> continuation; + }; + + Task(std::coroutine_handle handle); + ~Task(); + +private: + std::coroutine_handle handle; +}; + +struct StatefulAwaiter { + int value; + bool await_ready() const noexcept { return false; } + template + void await_suspend(std::coroutine_handle h) noexcept {} + void await_resume() noexcept {} +}; + +typedef std::suspend_always NoStateAwaiter; +using AnotherStatefulAwaiter = StatefulAwaiter; + +template +struct TemplatedAwaiter { + T value; + bool await_ready() const noexcept { return false; } + template + void await_suspend(std::coroutine_handle h) noexcept {} + void await_resume() noexcept {} +}; + + +class Awaitable {}; +StatefulAwaiter operator co_await(Awaitable) { + return StatefulAwaiter{}; +} + +StatefulAwaiter GlobalAwaiter; +class Awaitable2 {}; +StatefulAwaiter& operator co_await(Awaitable2) { + return GlobalAwaiter; +} + +Task testing() { + co_await std::suspend_always{}; + co_await StatefulAwaiter{}; + co_await AnotherStatefulAwaiter{}; + + // Test lvalue case. + StatefulAwaiter awaiter; + co_await awaiter; + + co_await TemplatedAwaiter{}; + TemplatedAwaiter TemplatedAwaiterInstace; + co_await TemplatedAwaiterInstace; + + co_await Awaitable{}; + co_await Awaitable2{}; +} + +// CHECK-LABEL: @_Z7testingv + +// Check `co_await __promise__.initial_suspend();` Since it returns std::suspend_always, +// which is an empty class, we shouldn't generate optimization blocker for it. +// CHECK: call token @llvm.coro.save +// CHECK-NOT: call void @llvm.coro.opt.blocker( +// CHECK: call void @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE + +// Check the `co_await std::suspend_always{};` expression. We shouldn't emit the optimization +// blocker for it since it is an empty class. +// CHECK: call token @llvm.coro.save +// CHECK-NOT: call void @llvm.coro.opt.blocker( +// CHECK: call void @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE + +// Check `co_await StatefulAwaiter{};`. We need to emit the optimization blocker since +// the awaiter is not empty. +// CHECK: call token @llvm.coro.save +// CHECK-NEXT: call void @llvm.coro.opt.blocker( +// CHECK: call void @_ZN15StatefulAwaiter13await_suspendIN4Task12promise_typeEEEvSt16coroutine_handleIT_E + +// Check `co_await AnotherStatefulAwaiter{};` to make sure that we can handle TypedefTypes. +// CHECK: call token @llvm.coro.save +// CHECK-NEXT: call void @llvm.coro.opt.blocker( +// CHECK: call void @_ZN15StatefulAwaiter13await_suspendIN4Task12promise_typeEEEvSt16coroutine_handleIT_E + +// Check `co_await awaiter;` to make sure we can handle lvalue cases. +// CHECK: call token @llvm.coro.save +// CHECK-NEXT: call void @llvm.coro.opt.blocker( +// CHECK: call void @_ZN15StatefulAwaiter13await_suspendIN4Task12promise_typeEEEvSt16coroutine_handleIT_E + +// Check `co_await TemplatedAwaiter{};` to make sure we can handle specialized template +// type. +// CHECK: call token @llvm.coro.save +// CHECK-NEXT: call void @llvm.coro.opt.blocker( +// CHECK: call void @_ZN16TemplatedAwaiterIiE13await_suspendIN4Task12promise_typeEEEvSt16coroutine_handleIT_E + +// Check `co_await TemplatedAwaiterInstace;` to make sure we can handle the lvalue from +// specialized template type. +// CHECK: call token @llvm.coro.save +// CHECK-NEXT: call void @llvm.coro.opt.blocker( +// CHECK: call void @_ZN16TemplatedAwaiterIiE13await_suspendIN4Task12promise_typeEEEvSt16coroutine_handleIT_E + +// Check `co_await Awaitable{};` to make sure we can handle awaiter returned by +// `operator co_await`; +// CHECK: call token @llvm.coro.save +// CHECK-NEXT: call void @llvm.coro.opt.blocker( +// CHECK: call void @_ZN15StatefulAwaiter13await_suspendIN4Task12promise_typeEEEvSt16coroutine_handleIT_E + +// Check `co_await Awaitable2{};` to make sure we can handle awaiter returned by +// `operator co_await` which returns a reference; +// CHECK: call token @llvm.coro.save +// CHECK-NEXT: call void @llvm.coro.opt.blocker( +// CHECK: call void @_ZN15StatefulAwaiter13await_suspendIN4Task12promise_typeEEEvSt16coroutine_handleIT_E + +// Check `co_await __promise__.final_suspend();`. We don't emit an blocker here since it is +// empty. +// CHECK: call token @llvm.coro.save +// CHECK-NOT: call void @llvm.coro.opt.blocker( +// CHECK: call ptr @_ZN4Task12promise_type12FinalAwaiter13await_suspendIS0_EESt16coroutine_handleIvES3_IT_E + +struct AwaitTransformTask { + struct promise_type { + struct FinalAwaiter { + bool await_ready() const noexcept { return false; } + template + std::coroutine_handle<> await_suspend(std::coroutine_handle h) noexcept { + return h.promise().continuation; + } + void await_resume() noexcept {} + }; + + AwaitTransformTask get_return_object() noexcept { + return std::coroutine_handle::from_promise(*this); + } + + std::suspend_always initial_suspend() noexcept { return {}; } + FinalAwaiter final_suspend() noexcept { return {}; } + void unhandled_exception() noexcept {} + void return_void() noexcept {} + + template + auto await_transform(Awaitable &&awaitable) { + return awaitable; + } + + std::coroutine_handle<> continuation; + }; + + AwaitTransformTask(std::coroutine_handle handle); + ~AwaitTransformTask(); + +private: + std::coroutine_handle handle; +}; + +struct awaitableWithGetAwaiter { + bool await_ready() const noexcept { return false; } + template + void await_suspend(std::coroutine_handle h) noexcept {} + void await_resume() noexcept {} +}; + +AwaitTransformTask testingWithAwaitTransform() { + co_await awaitableWithGetAwaiter{}; +} + +// CHECK-LABEL: @_Z25testingWithAwaitTransformv + +// Init suspend +// CHECK: call token @llvm.coro.save +// CHECK-NOT: call void @llvm.coro.opt.blocker( +// CHECK: call void @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE + +// Check `co_await awaitableWithGetAwaiter{};`. +// CHECK: call token @llvm.coro.save +// CHECK-NOT: call void @llvm.coro.opt.blocker( +// Check call void @_ZN23awaitableWithGetAwaiter13await_suspendIN18AwaitTransformTask12promise_typeEEEvSt16coroutine_handleIT_E + +// Final suspend +// CHECK: call token @llvm.coro.save +// CHECK-NOT: call void @llvm.coro.opt.blocker( +// CHECK: call ptr @_ZN18AwaitTransformTask12promise_type12FinalAwaiter13await_suspendIS0_EESt16coroutine_handleIvES3_IT_E Index: clang/test/CodeGenCoroutines/coro-symmetric-transfer-02.cpp =================================================================== --- clang/test/CodeGenCoroutines/coro-symmetric-transfer-02.cpp +++ clang/test/CodeGenCoroutines/coro-symmetric-transfer-02.cpp @@ -89,6 +89,7 @@ // CHECK: br i1 %{{.+}}, label %[[CASE1_AWAIT_READY:.+]], label %[[CASE1_AWAIT_SUSPEND:.+]] // CHECK: [[CASE1_AWAIT_SUSPEND]]: // CHECK-NEXT: %{{.+}} = call token @llvm.coro.save(ptr null) +// CHECK-NEXT: call void @llvm.coro.opt.blocker( // CHECK-NEXT: call void @llvm.lifetime.start.p0(i64 8, ptr %[[TMP1:.+]]) // CHECK: call void @llvm.lifetime.end.p0(i64 8, ptr %[[TMP1]]) @@ -106,6 +107,7 @@ // CHECK: br i1 %{{.+}}, label %[[CASE2_AWAIT_READY:.+]], label %[[CASE2_AWAIT_SUSPEND:.+]] // CHECK: [[CASE2_AWAIT_SUSPEND]]: // CHECK-NEXT: %{{.+}} = call token @llvm.coro.save(ptr null) +// CHECK-NEXT: call void @llvm.coro.opt.blocker( // CHECK-NEXT: call void @llvm.lifetime.start.p0(i64 8, ptr %[[TMP2:.+]]) // CHECK: call void @llvm.lifetime.end.p0(i64 8, ptr %[[TMP2]]) Index: clang/test/CodeGenCoroutines/pr56301.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/pr56301.cpp @@ -0,0 +1,85 @@ +// An end-to-end test to make sure things get processed correctly. +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s -O3 | \ +// RUN: FileCheck %s + +#include "Inputs/coroutine.h" + +struct SomeAwaitable { + // Resume the supplied handle once the awaitable becomes ready, + // returning a handle that should be resumed now for the sake of symmetric transfer. + // If the awaitable is already ready, return an empty handle without doing anything. + // + // Defined in another translation unit. Note that this may contain + // code that synchronizees with another thread. + std::coroutine_handle<> Register(std::coroutine_handle<>); +}; + +// Defined in another translation unit. +void DidntSuspend(); + +struct Awaiter { + SomeAwaitable&& awaitable; + bool suspended; + + bool await_ready() { return false; } + + std::coroutine_handle<> await_suspend(const std::coroutine_handle<> h) { + // Assume we will suspend unless proven otherwise below. We must do + // this *before* calling Register, since we may be destroyed by another + // thread asynchronously as soon as we have registered. + suspended = true; + + // Attempt to hand off responsibility for resuming/destroying the coroutine. + const auto to_resume = awaitable.Register(h); + + if (!to_resume) { + // The awaitable is already ready. In this case we know that Register didn't + // hand off responsibility for the coroutine. So record the fact that we didn't + // actually suspend, and tell the compiler to resume us inline. + suspended = false; + return h; + } + + // Resume whatever Register wants us to resume. + return to_resume; + } + + void await_resume() { + // If we didn't suspend, make note of that fact. + if (!suspended) { + DidntSuspend(); + } + } +}; + +struct MyTask{ + struct promise_type { + MyTask get_return_object() { return {}; } + std::suspend_never initial_suspend() { return {}; } + std::suspend_always final_suspend() noexcept { return {}; } + void unhandled_exception(); + + Awaiter await_transform(SomeAwaitable&& awaitable) { + return Awaiter{static_cast(awaitable)}; + } + }; +}; + +MyTask FooBar() { + co_await SomeAwaitable(); +} + +// CHECK-LABEL: @_Z6FooBarv +// CHECK: %[[to_resume:.*]] = {{.*}}call ptr @_ZN13SomeAwaitable8RegisterESt16coroutine_handleIvE +// CHECK-NEXT: %[[to_bool:.*]] = icmp eq ptr %[[to_resume]], null +// CHECK-NEXT: br i1 %[[to_bool]], label %[[then:.*]], label %[[else:.*]] + +// CHECK: [[then]]: +// We only access the coroutine frame conditionally as the sources did. +// CHECK: store i8 0, +// CHECK-NEXT: br label %[[else]] + +// CHECK: [[else]]: +// No more access to the coroutine frame until suspended. +// CHECK-NOT: store +// CHECK: } Index: llvm/docs/Coroutines.rst =================================================================== --- llvm/docs/Coroutines.rst +++ llvm/docs/Coroutines.rst @@ -1591,6 +1591,40 @@ switch i8 %suspend1, label %suspend [i8 0, label %resume1 i8 1, label %cleanup] +.. _coro.opt.blocker: + +'llvm.coro.opt.blocker' Intrinsic +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + declare token @llvm.coro.opt.blocker(ptr
) + +Overview: +""""""""" + +The '``@llvm.coro.opt.blocker``' leaks the address of a certain variable to +prevent the variable get optimized out. The '``@llvm.coro.opt.blocker``' marker +will be erased at the start of CoroSplit. This is useful since the optimizer can't +know the local variables may be alias with the coroutine handle until CoroSplit pass +so that some miscompilations may happen. + +Arguments: +"""""""""" + +The arguments is the address of the certain local variable that we want to block +optimizations for. + +Semantics: +"""""""""" + +The local variable referred by '``@llvm.coro.opt.blocker``' won't get optimized out +before CoroSplit. Note that this doesn't imply such variables must live on the coroutine +frame. Also note that while every local variables may be alias with the coroutine handle, +use '``@llvm.coro.opt.blocker``' for every local variables may be an overkill since it'll +hurt the performance. The frontend can reduce the use of '``@llvm.coro.opt.blocker``' based +on the rule of the corresponding language specification. + .. _coro.suspend.async: 'llvm.coro.suspend.async' Intrinsic Index: llvm/include/llvm/IR/Intrinsics.td =================================================================== --- llvm/include/llvm/IR/Intrinsics.td +++ llvm/include/llvm/IR/Intrinsics.td @@ -1649,6 +1649,7 @@ def int_coro_align : Intrinsic<[llvm_anyint_ty], [], [IntrNoMem]>; def int_coro_save : Intrinsic<[llvm_token_ty], [llvm_ptr_ty], [IntrNoMerge]>; +def int_coro_opt_blocker : Intrinsic<[], [llvm_ptr_ty], []>; def int_coro_suspend : Intrinsic<[llvm_i8_ty], [llvm_token_ty, llvm_i1_ty], []>; def int_coro_suspend_retcon : Intrinsic<[llvm_any_ty], [llvm_vararg_ty], []>; def int_coro_prepare_retcon : Intrinsic<[llvm_ptr_ty], [llvm_ptr_ty], Index: llvm/lib/Transforms/Coroutines/CoroInstr.h =================================================================== --- llvm/lib/Transforms/Coroutines/CoroInstr.h +++ llvm/lib/Transforms/Coroutines/CoroInstr.h @@ -448,6 +448,18 @@ } }; +/// This represents the llvm.coro.opt.blocker instruction. +class LLVM_LIBRARY_VISIBILITY CoroOptBlockerInst : public IntrinsicInst { +public: + // Methods to support type inquiry through isa, cast, and dyn_cast: + static bool classof(const IntrinsicInst *I) { + return I->getIntrinsicID() == Intrinsic::coro_opt_blocker; + } + static bool classof(const Value *V) { + return isa(V) && classof(cast(V)); + } +}; + /// This represents the llvm.coro.promise instruction. class LLVM_LIBRARY_VISIBILITY CoroPromiseInst : public IntrinsicInst { enum { FrameArg, AlignArg, FromArg }; Index: llvm/lib/Transforms/Coroutines/Coroutines.cpp =================================================================== --- llvm/lib/Transforms/Coroutines/Coroutines.cpp +++ llvm/lib/Transforms/Coroutines/Coroutines.cpp @@ -176,6 +176,7 @@ clear(*this); SmallVector CoroFrames; SmallVector UnusedCoroSaves; + SmallVector CoroOptBlockers; for (Instruction &I : instructions(F)) { if (auto II = dyn_cast(&I)) { @@ -237,6 +238,9 @@ CoroBegin = CB; break; } + case Intrinsic::coro_opt_blocker: + CoroOptBlockers.push_back(cast(II)); + break; case Intrinsic::coro_end_async: case Intrinsic::coro_end: CoroEnds.push_back(cast(II)); @@ -263,6 +267,9 @@ } } + for (CoroOptBlockerInst *CAAI : CoroOptBlockers) + CAAI->eraseFromParent(); + // If for some reason, we were not able to find coro.begin, bailout. if (!CoroBegin) { // Replace coro.frame which are supposed to be lowered to the result of Index: llvm/test/Transforms/Coroutines/coro-opt-blocker.ll =================================================================== --- /dev/null +++ llvm/test/Transforms/Coroutines/coro-opt-blocker.ll @@ -0,0 +1,69 @@ +; RUN: opt < %s -O1 -S | FileCheck %s + +%"struct.std::coroutine_handle" = type { ptr } +%"struct.std::coroutine_handle.0" = type { %"struct.std::coroutine_handle" } +%"struct.lean_future::Awaiter" = type { i32, %"struct.std::coroutine_handle.0" } + +declare ptr @malloc(i64) +%empty = type { i8, i8 } +declare void @produce(ptr) +declare void @consume(ptr) +declare void @consume.i8(i8) + +; Although the use of %testval lives across suspend points, it might be optimized +; out by previous optimizations. Tests that the call to `@llvm.coro.opt.blocker` +; can block optimizations for %testval so that it won't be optimized out and can +; live on the coroutine frame. +define void @foo(ptr %to_store) presplitcoroutine { +entry: + %testval = alloca %empty + %a = alloca i8 + %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null) + %alloc = call ptr @malloc(i64 16) #3 + %vFrame = call noalias nonnull ptr @llvm.coro.begin(token %id, ptr %alloc) + + call void @produce(ptr %a) + %testval.i = getelementptr inbounds %empty, ptr %testval, i32 0, i32 0 + store i8 0, ptr %testval.i + %testval.ii = getelementptr inbounds %empty, ptr %testval, i32 0, i32 1 + store i8 1, ptr %testval.ii + + %save = call token @llvm.coro.save(ptr null) + call void @llvm.coro.opt.blocker(ptr %testval) + %suspend = call i8 @llvm.coro.suspend(token %save, i1 false) + switch i8 %suspend, label %exit [ + i8 0, label %await.ready + i8 1, label %exit + ] +await.ready: + %testval.i.l.addr = getelementptr inbounds %empty, ptr %testval, i32 0, i32 0 + %testval.i.l = load i8, ptr %testval.i.l.addr + %testval.ii.l.addr = getelementptr inbounds %empty, ptr %testval, i32 0, i32 1 + %testval.ii.l = load i8, ptr %testval.ii.l.addr + call void @consume.i8(i8 %testval.i.l) + call void @consume.i8(i8 %testval.ii.l) + call void @consume(ptr %a) + br label %exit +exit: + call i1 @llvm.coro.end(ptr null, i1 false) + ret void +} + +; Verify that the %testval lives on the frame. +; CHECK: %foo.Frame = type { ptr, ptr, %empty, i1, i8 } + +; Check that the call to @llvm.coro.opt.blocker get erased. +; CHECK-NOT: call void @llvm.coro.opt.blocker + +declare token @llvm.coro.id(i32, ptr readnone, ptr nocapture readonly, ptr) +declare i1 @llvm.coro.alloc(token) #3 +declare i64 @llvm.coro.size.i64() #5 +declare ptr @llvm.coro.begin(token, ptr writeonly) #3 +declare token @llvm.coro.save(ptr) #3 +declare ptr @llvm.coro.frame() #5 +declare i8 @llvm.coro.suspend(token, i1) #3 +declare ptr @llvm.coro.free(token, ptr nocapture readonly) #2 +declare i1 @llvm.coro.end(ptr, i1) #3 +declare void @llvm.lifetime.start.p0(i64, ptr nocapture) #4 +declare void @llvm.lifetime.end.p0(i64, ptr nocapture) #4 +declare void @llvm.coro.opt.blocker(ptr)