Index: clang/lib/CodeGen/CGCoroutine.cpp =================================================================== --- clang/lib/CodeGen/CGCoroutine.cpp +++ clang/lib/CodeGen/CGCoroutine.cpp @@ -547,6 +547,7 @@ auto *EntryBB = Builder.GetInsertBlock(); auto *AllocBB = createBasicBlock("coro.alloc"); + auto *AlignBB = createBasicBlock("coro.align"); auto *InitBB = createBasicBlock("coro.init"); auto *FinalBB = createBasicBlock("coro.final"); auto *RetBB = createBasicBlock("coro.ret"); @@ -568,7 +569,6 @@ EmitBlock(AllocBB); auto *AllocateCall = EmitScalarExpr(S.getAllocate()); - auto *AllocOrInvokeContBB = Builder.GetInsertBlock(); // Handle allocation failure if 'ReturnStmtOnAllocFailure' was provided. if (auto *RetOnAllocFailure = S.getReturnStmtOnAllocFailure()) { @@ -577,22 +577,33 @@ // See if allocation was successful. auto *NullPtr = llvm::ConstantPointerNull::get(Int8PtrTy); auto *Cond = Builder.CreateICmpNE(AllocateCall, NullPtr); - Builder.CreateCondBr(Cond, InitBB, RetOnFailureBB); + Builder.CreateCondBr(Cond, AlignBB, RetOnFailureBB); // If not, return OnAllocFailure object. EmitBlock(RetOnFailureBB); EmitStmt(RetOnAllocFailure); + } else { + Builder.CreateBr(AlignBB); } - else { - Builder.CreateBr(InitBB); - } + + EmitBlock(AlignBB); + // Since the standard doesn't support the aligned allocator, + // `operator new(size_t, align_t)`, if there is variable in frame + // whose alignment requirement exceeds `std::max_align_t` may + // not get aligned correctly. + // Pass `std::max_align_t` to the middle end to make the compiler + // to overalign more space to make the frame align correctly. + auto *AlignedAllocateCall = Builder.CreateCall( + CGM.getIntrinsic(llvm::Intrinsic::coro_overalign), + {AllocateCall, llvm::ConstantInt::get( + llvm::Type::getInt64Ty(getLLVMContext()), NewAlign)}); EmitBlock(InitBB); // Pass the result of the allocation to coro.begin. auto *Phi = Builder.CreatePHI(VoidPtrTy, 2); Phi->addIncoming(NullPtr, EntryBB); - Phi->addIncoming(AllocateCall, AllocOrInvokeContBB); + Phi->addIncoming(AlignedAllocateCall, AlignBB); auto *CoroBegin = Builder.CreateCall( CGM.getIntrinsic(llvm::Intrinsic::coro_begin), {CoroId, Phi}); CurCoro.Data->CoroBegin = CoroBegin; Index: clang/test/CodeGenCoroutines/coro-alloc.cpp =================================================================== --- clang/test/CodeGenCoroutines/coro-alloc.cpp +++ clang/test/CodeGenCoroutines/coro-alloc.cpp @@ -62,10 +62,13 @@ // CHECK: [[AllocBB]]: // CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64() // CHECK: %[[MEM:.+]] = call noalias nonnull i8* @_Znwm(i64 %[[SIZE]]) - // CHECK: br label %[[InitBB]] + // CHECK: br label %[[AlignedBB:.+]] + + // CHECK: [[AlignedBB]]: + // CHECK: %[[ALIGNED_MEM:.+]] = call i8* @llvm.coro.overalign(i8* %[[MEM]] // CHECK: [[InitBB]]: - // CHECK: %[[PHI:.+]] = phi i8* [ null, %{{.+}} ], [ %call, %[[AllocBB]] ] + // CHECK: %[[PHI:.+]] = phi i8* [ null, %{{.+}} ], [ %[[ALIGNED_MEM]], %[[AlignedBB]] ] // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.begin(token %[[ID]], i8* %[[PHI]]) // CHECK: %[[MEM:.+]] = call i8* @llvm.coro.free(token %[[ID]], i8* %[[FRAME]]) Index: clang/test/CodeGenCoroutines/coro-gro-nrvo.cpp =================================================================== --- clang/test/CodeGenCoroutines/coro-gro-nrvo.cpp +++ clang/test/CodeGenCoroutines/coro-gro-nrvo.cpp @@ -36,13 +36,16 @@ // Verify that the NRVO is applied to the Gro object. // CHECK-LABEL: define{{.*}} void @_Z1fi(%struct.coro* noalias sret(%struct.coro) align 8 %agg.result, i32 %0) coro f(int) { -// CHECK: %call = call noalias nonnull i8* @_Znwm( -// CHECK-NEXT: br label %[[CoroInit:.*]] - -// CHECK: {{.*}}[[CoroInit]]: -// CHECK: store i1 false, i1* %gro.active -// CHECK: call void @{{.*get_return_objectEv}}(%struct.coro* sret(%struct.coro) align 8 %agg.result -// CHECK-NEXT: store i1 true, i1* %gro.active + // CHECK: %call = call noalias nonnull i8* @_Znwm( + // CHECK-NEXT: br label %[[CoroAlign:.*]] + // CHECK: [[CoroAlign:.*]]: + // CHECK-NEXT: call i8* @llvm.coro.overalign(i8* %call, + // CHECK-NEXT: br label %[[CoroInit:.+]] + + // CHECK: {{.*}}[[CoroInit]]: + // CHECK: store i1 false, i1* %gro.active + // CHECK: call void @{{.*get_return_objectEv}}(%struct.coro* sret(%struct.coro) align 8 %agg.result + // CHECK-NEXT: store i1 true, i1* %gro.active co_return; } @@ -68,20 +71,24 @@ // CHECK-LABEL: define{{.*}} void @_Z1hi(%struct.coro_two* noalias sret(%struct.coro_two) align 8 %agg.result, i32 %0) coro_two h(int) { -// CHECK: %call = call noalias i8* @_ZnwmRKSt9nothrow_t -// CHECK-NEXT: %[[CheckNull:.*]] = icmp ne i8* %call, null -// CHECK-NEXT: br i1 %[[CheckNull]], label %[[InitOnSuccess:.*]], label %[[InitOnFailure:.*]] + // CHECK: %call = call noalias i8* @_ZnwmRKSt9nothrow_t + // CHECK-NEXT: %[[CheckNull:.*]] = icmp ne i8* %call, null + // CHECK-NEXT: br i1 %[[CheckNull]], label %[[AlignBB:.*]], label %[[InitOnFailure:.*]] -// CHECK: {{.*}}[[InitOnFailure]]: -// CHECK-NEXT: call void @{{.*get_return_object_on_allocation_failureEv}}(%struct.coro_two* sret(%struct.coro_two) align 8 %agg.result -// CHECK-NEXT: br label %[[RetLabel:.*]] + // CHECK: {{.*}}[[InitOnFailure]]: + // CHECK-NEXT: call void @{{.*get_return_object_on_allocation_failureEv}}(%struct.coro_two* sret(%struct.coro_two) align 8 %agg.result + // CHECK-NEXT: br label %[[RetLabel:.*]] -// CHECK: {{.*}}[[InitOnSuccess]]: -// CHECK: store i1 false, i1* %gro.active -// CHECK: call void @{{.*get_return_objectEv}}(%struct.coro_two* sret(%struct.coro_two) align 8 %agg.result -// CHECK-NEXT: store i1 true, i1* %gro.active + // CHECK: [[AlignBB]]: + // CHECK-NEXT: call i8* @llvm.coro.overalign + // CHECK-NEXT: br label %[[InitOnSuccess:.+]] -// CHECK: [[RetLabel]]: -// CHECK-NEXT: ret void - co_return; + // CHECK: {{.*}}[[InitOnSuccess]]: + // CHECK: store i1 false, i1* %gro.active + // CHECK: call void @{{.*get_return_objectEv}}(%struct.coro_two* sret(%struct.coro_two) align 8 %agg.result + // CHECK-NEXT: store i1 true, i1* %gro.active + + // CHECK: [[RetLabel]]: + // CHECK-NEXT: ret void + co_return; } Index: llvm/docs/Coroutines.rst =================================================================== --- llvm/docs/Coroutines.rst +++ llvm/docs/Coroutines.rst @@ -934,7 +934,7 @@ """"""""" The '``llvm.coro.size``' intrinsic returns the number of bytes -required to store a `coroutine frame`_. This is only supported for +required to store a `coroutine state`. This is only supported for switched-resume coroutines. Arguments: @@ -946,7 +946,8 @@ """""""""" The `coro.size` intrinsic is lowered to a constant representing the size of -the coroutine frame. +the `coroutine state`. The `coroutine state` may contain coroutine frame and +padding for alignment if needned. .. _coro.begin: @@ -1005,7 +1006,9 @@ 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. +pointer that was returned by prior `coro.begin` call even when overalign happens. +The compiler would handle it to make sure this intrinsic returns a pointer to the +coroutine state. Example (custom deallocation function): """"""""""""""""""""""""""""""""""""""" @@ -1079,6 +1082,57 @@ %phi = phi i8* [ null, %entry ], [ %alloc, %coro.alloc ] %frame = call i8* @llvm.coro.begin(token %id, i8* %phi) +.. _coro.overalign + +'llvm.coro.overalign' Intrinsic +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +:: + + declare i8* @llvm.coro.overalign(i8* allocated, i64 max_align) + +Overview: +""""""""" + +The '``llvm.coro.overalign``' intrinsic returns the address of the coroutine frame +which would satisfied the alignment required by `max_align`. This intrinsics is used +when the alignment requirement of coroutine frame may exceed the max available align +of corresponding allocator. In this case, compiler would allocate more space to +satisfy the alignment requirement of coroutine frame. This is only supported for +switched-resume coroutines. + +Arguments: +"""""""""" + +The first argument is a pointer to the allocated space. The second argument is the +max available alignment for the allocator. The second argument must be an constant. + +Semantics: +"""""""""" + +A frontend should emit '``llvm.coro.overalign``' when the max available alignment of +allocator is limited. Then the frontend should use the return value of this intrinsic +as the address of coroutine frame. + +Example: +"""""""" + +.. code-block:: llvm + + entry: + %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null) + %dyn.alloc.required = call i1 @llvm.coro.alloc(token %id) + br i1 %dyn.alloc.required, label %coro.alloc, label %coro.begin + + coro.alloc: + %frame.size = call i32 @llvm.coro.size() + %alloc = call i8* @MyAlloc(i32 %frame.size) + %aligned = call i8* @llvm.coro.overalign(%alloc, 16) + br label %coro.begin + + coro.begin: + %phi = phi i8* [ null, %entry ], [ %aligned, %coro.alloc ] + %frame = call i8* @llvm.coro.begin(token %id, i8* %phi) + .. _coro.noop: 'llvm.coro.noop' Intrinsic Index: llvm/include/llvm/IR/Intrinsics.td =================================================================== --- llvm/include/llvm/IR/Intrinsics.td +++ llvm/include/llvm/IR/Intrinsics.td @@ -1233,6 +1233,7 @@ llvm_ptr_ty, llvm_ptr_ty, llvm_ptr_ty], []>; def int_coro_alloc : Intrinsic<[llvm_i1_ty], [llvm_token_ty], []>; +def int_coro_overalign: Intrinsic<[llvm_ptr_ty], [llvm_ptr_ty, llvm_i64_ty], []>; def int_coro_id_async : Intrinsic<[llvm_token_ty], [llvm_i32_ty, llvm_i32_ty, llvm_i32_ty, llvm_ptr_ty], []>; Index: llvm/lib/Transforms/Coroutines/CoroFrame.cpp =================================================================== --- llvm/lib/Transforms/Coroutines/CoroFrame.cpp +++ llvm/lib/Transforms/Coroutines/CoroFrame.cpp @@ -314,6 +314,8 @@ bool MayWriteBeforeCoroBegin) : Alloca(Alloca), Aliases(std::move(Aliases)), MayWriteBeforeCoroBegin(MayWriteBeforeCoroBegin) {} + + Align getAlign() const { return Alloca->getAlign(); } }; struct FrameDataInfo { // All the values (that are not allocas) that needs to be spilled to the @@ -1083,6 +1085,85 @@ Shape.FramePtr->getNextNode()); } +/// Handle the case that the alignment requirement of the coroutine frame +/// exceeds the max available alignment of the allocator, which specified +/// in `@llvm.coro.overalign`. +static AllocaInst *tryOveralignFrame(coro::Shape &Shape, Align FrameAlign) { + assert(Shape.ABI == coro::ABI::Switch); + auto *AlignedAlloc = Shape.SwitchLowering.AlignedAlloc; + // A frontend is allowed not to emit `@llvm.coro.overalign` if it feels + // unneeded. + if (!AlignedAlloc) + return nullptr; + + ConstantInt *MaxAlignment = + dyn_cast(AlignedAlloc->getOperand(1)); + if (!MaxAlignment) + report_fatal_error( + "The align value passed to @llvm.coro.overalign is not constant.\n"); + if (!isPowerOf2_64(MaxAlignment->getZExtValue())) + report_fatal_error( + "The align value Passed to @llvm.coro.overalign is not power of 2.\n"); + + if (FrameAlign.value() <= MaxAlignment->getZExtValue()) + return nullptr; + + Shape.SwitchLowering.OveralignedSpace = + FrameAlign.value() - MaxAlignment->getZExtValue(); + + auto *CB = Shape.CoroBegin; + LLVMContext &C = CB->getContext(); + IRBuilder<> Builder(C); + Function &F = *CB->getParent()->getParent(); + + Builder.SetInsertPoint(F.getEntryBlock().getFirstNonPHIOrDbgOrLifetime()); + // Alloca to store the allocated address to make sure we could delete it + // correctly. + auto *RawFrameAddrAlloca = Builder.CreateAlloca(llvm::Type::getInt8PtrTy(C), + nullptr, "RawFrameAddr"); + // To calculate the address for the frame to make it align correctly. + // ``` + // (AllocatedAddr + Align - 1) & (~(Align - 1)) + // ``` + // given Align equals to 2^m, `& (~(Align - 1))` would make the lowest m bit + // for (AllocatedAddr + Align - 1) to 0. And (AllocatedAddr + Align - 1) must + // exceed the address that just fit. + Builder.SetInsertPoint(AlignedAlloc->getNextNode()); + Value *Allocated = AlignedAlloc->getOperand(0); + auto *AllocatedAddr = + Builder.CreatePtrToInt(Allocated, llvm::Type::getInt64Ty(C)); + auto *AlignConstant = + ConstantInt::get(llvm::Type::getInt64Ty(C), FrameAlign.value()); + auto *Mask = Builder.CreateSub( + AlignConstant, ConstantInt::get(llvm::Type::getInt64Ty(C), 1), "mask"); + auto *Boundary = Builder.CreateAdd(AllocatedAddr, Mask); + auto *InvertedMask = Builder.CreateNot(Mask); + auto *FrameAddr = Builder.CreateAnd(Boundary, InvertedMask); + /// NOTE: Would the int2ptr cast breaks the noalias attribute for the frame? + auto *Frame = Builder.CreateIntToPtr(FrameAddr, llvm::Type::getInt8PtrTy(C), + "CoroFrame"); + Builder.CreateStore(Allocated, RawFrameAddrAlloca); + + AlignedAlloc->replaceAllUsesWith(Frame); + AlignedAlloc->eraseFromParent(); + Shape.SwitchLowering.AlignedAlloc = nullptr; + + SmallVector CoroFrees; + for (User *U : Shape.CoroBegin->getId()->users()) + if (auto CF = dyn_cast(U)) + CoroFrees.push_back(CF); + + /// Get the allocated address to make sure memory leak. + for (CoroFreeInst *Free : CoroFrees) { + Builder.SetInsertPoint(Free); + auto *RawFrameAddr = Builder.CreateLoad(Frame->getType(), + RawFrameAddrAlloca, "RawFrameAddr"); + Free->setOperand(1, RawFrameAddr); + } + + return cast(RawFrameAddrAlloca); +} + // Build a struct that will keep state for an active coroutine. // struct f.frame { // ResumeFnTy ResumeFnAddr; @@ -1163,6 +1244,22 @@ FrameData.setFieldIndex(S.first, Id); } + if (Shape.ABI == coro::ABI::Switch && !FrameData.Allocas.empty()) { + Align FrameAlign = + std::max_element( + FrameData.Allocas.begin(), FrameData.Allocas.end(), + [](auto &A1, auto &A2) { return A1.getAlign() < A2.getAlign(); }) + ->getAlign(); + AllocaInst *RawFrameAlloca = tryOveralignFrame(Shape, FrameAlign); + if (RawFrameAlloca) { + FrameData.Allocas.emplace_back( + RawFrameAlloca, DenseMap>{}, + /*MayWriteBeforeCoroBegin=*/true); + FrameData.setFieldIndex(RawFrameAlloca, + B.addFieldForAlloca(RawFrameAlloca)); + } + } + B.finish(FrameTy); FrameData.updateLayoutIndex(B); Shape.FrameAlign = B.getStructAlign(); @@ -1179,6 +1276,13 @@ // Also round the frame size up to a multiple of its alignment, as is // generally expected in C/C++. Shape.FrameSize = alignTo(Shape.FrameSize, Shape.FrameAlign); + + if (Shape.SwitchLowering.AlignedAlloc) { + Shape.SwitchLowering.AlignedAlloc->replaceAllUsesWith( + Shape.SwitchLowering.AlignedAlloc->getOperand(0)); + Shape.SwitchLowering.AlignedAlloc->eraseFromParent(); + Shape.SwitchLowering.AlignedAlloc = nullptr; + } break; } Index: llvm/lib/Transforms/Coroutines/CoroInstr.h =================================================================== --- llvm/lib/Transforms/Coroutines/CoroInstr.h +++ llvm/lib/Transforms/Coroutines/CoroInstr.h @@ -78,6 +78,18 @@ } }; +/// This represents the llvm.coro.overalign instruction. +class LLVM_LIBRARY_VISIBILITY CoroOveralignInst : 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_overalign; + } + static bool classof(const Value *V) { + return isa(V) && classof(cast(V)); + } +}; + /// This represents a common base class for llvm.coro.id instructions. class LLVM_LIBRARY_VISIBILITY AnyCoroIdInst : public IntrinsicInst { public: Index: llvm/lib/Transforms/Coroutines/CoroInternal.h =================================================================== --- llvm/lib/Transforms/Coroutines/CoroInternal.h +++ llvm/lib/Transforms/Coroutines/CoroInternal.h @@ -132,9 +132,11 @@ SwitchInst *ResumeSwitch; AllocaInst *PromiseAlloca; BasicBlock *ResumeEntryBlock; + CoroOveralignInst *AlignedAlloc; unsigned IndexField; unsigned IndexAlign; unsigned IndexOffset; + unsigned OveralignedSpace; bool HasFinalSuspend; }; Index: llvm/lib/Transforms/Coroutines/CoroSplit.cpp =================================================================== --- llvm/lib/Transforms/Coroutines/CoroSplit.cpp +++ llvm/lib/Transforms/Coroutines/CoroSplit.cpp @@ -1073,7 +1073,12 @@ Module *M = SizeIntrin->getModule(); const DataLayout &DL = M->getDataLayout(); auto Size = DL.getTypeAllocSize(Shape.FrameTy); - auto *SizeConstant = ConstantInt::get(SizeIntrin->getType(), Size); + // To make sure the allocated space is enough for coroutine frame to satisfy + // the align requirement. See tryOveralignFrame in CoroFrame.cpp for details. + auto *SizeConstant = ConstantInt::get( + SizeIntrin->getType(), Size + (Shape.ABI == coro::ABI::Switch + ? Shape.SwitchLowering.OveralignedSpace + : 0)); for (CoroSizeInst *CS : Shape.CoroSizes) { CS->replaceAllUsesWith(SizeConstant); Index: llvm/lib/Transforms/Coroutines/Coroutines.cpp =================================================================== --- llvm/lib/Transforms/Coroutines/Coroutines.cpp +++ llvm/lib/Transforms/Coroutines/Coroutines.cpp @@ -180,12 +180,10 @@ if (CoroFrees.empty()) return; - Value *Replacement = - Elide ? ConstantPointerNull::get(Type::getInt8PtrTy(CoroId->getContext())) - : CoroFrees.front()->getFrame(); - for (CoroFreeInst *CF : CoroFrees) { - CF->replaceAllUsesWith(Replacement); + CF->replaceAllUsesWith(Elide ? ConstantPointerNull::get( + Type::getInt8PtrTy(CoroId->getContext())) + : CF->getFrame()); CF->eraseFromParent(); } } @@ -259,6 +257,7 @@ clear(*this); SmallVector CoroFrames; SmallVector UnusedCoroSaves; + CoroOveralignInst *AlignedAlloc = nullptr; for (Instruction &I : instructions(F)) { if (auto II = dyn_cast(&I)) { @@ -271,6 +270,9 @@ case Intrinsic::coro_frame: CoroFrames.push_back(cast(II)); break; + case Intrinsic::coro_overalign: + AlignedAlloc = cast(II); + break; case Intrinsic::coro_save: // After optimizations, coro_suspends using this coro_save might have // been removed, remember orphaned coro_saves to remove them later. @@ -375,6 +377,8 @@ this->SwitchLowering.ResumeSwitch = nullptr; this->SwitchLowering.PromiseAlloca = SwitchId->getPromise(); this->SwitchLowering.ResumeEntryBlock = nullptr; + this->SwitchLowering.AlignedAlloc = AlignedAlloc; + this->SwitchLowering.OveralignedSpace = 0; for (auto AnySuspend : CoroSuspends) { auto Suspend = dyn_cast(AnySuspend); Index: llvm/test/Transforms/Coroutines/coro-alloca-overalign.ll =================================================================== --- /dev/null +++ llvm/test/Transforms/Coroutines/coro-alloca-overalign.ll @@ -0,0 +1,70 @@ +; Tests that the frame could overalign correctly if the input contains coro.overalign +; RUN: opt < %s -passes='cgscc(coro-split)' -S | FileCheck %s + +define i8* @f(i1 %n) "coroutine.presplit"="1" { +entry: + %x = alloca i64, align 64 + %y = alloca i64, align 64 + %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) + %aligned_alloc = call i8* @llvm.coro.overalign(i8* %alloc, i64 16) + %hdl = call i8* @llvm.coro.begin(token %id, i8* %aligned_alloc) + br i1 %n, label %flag_true, label %flag_false + +flag_true: + %x.alias = bitcast i64* %x to i32* + br label %merge + +flag_false: + %y.alias = bitcast i64* %y to i32* + br label %merge + +merge: + %alias_phi = phi i32* [ %x.alias, %flag_true ], [ %y.alias, %flag_false ] + %sp1 = call i8 @llvm.coro.suspend(token none, i1 false) + switch i8 %sp1, label %suspend [i8 0, label %resume + i8 1, label %cleanup] +resume: + call void @print(i32* %alias_phi) + br label %cleanup + +cleanup: + %mem = call i8* @llvm.coro.free(token %id, i8* %hdl) + call void @free(i8* %mem) + br label %suspend + +suspend: + call i1 @llvm.coro.end(i8* %hdl, i1 0) + ret i8* %hdl +} + +; CHECK: define i8* @f(i1 %n) +; CHECK: entry: +; CHECK: %[[MEM:.+]] = call i8* @malloc(i32 184) +; CHECK-NEXT: %[[ADDR:.+]] = ptrtoint i8* %[[MEM]] to i64 +; CHECK-NEXT: %[[MASK:.+]] = add i64 %[[ADDR]], 63 +; CHECK-NEXT: %[[FRAME_ADDR:.+]] = and i64 %[[MASK]], -64 +; CHECK-NEXT: %[[FRAME:.+]] = inttoptr i64 %[[FRAME_ADDR]] to i8* +; CHECK-NEXT: store i8* %[[MEM]], i8** %RawFrameAddr, align 8 +; CHECK-NEXT: %hdl = call noalias nonnull i8* @llvm.coro.begin(token %id, i8* %[[FRAME]]) +; +; CHECK: cleanup: +; CHECK: %[[RawFrameAddr:.+]] = load i8*, i8** %RawFrameAddr.reload.addr, align 8 +; CHECK: %mem = call i8* @llvm.coro.free(token %id, i8* %[[RawFrameAddr]]) + +declare i8* @llvm.coro.free(token, i8*) +declare i32 @llvm.coro.size.i32() +declare i8 @llvm.coro.suspend(token, i1) +declare void @llvm.coro.resume(i8*) +declare void @llvm.coro.destroy(i8*) + +declare token @llvm.coro.id(i32, i8*, i8*, i8*) +declare i1 @llvm.coro.alloc(token) +declare i8* @llvm.coro.overalign(i8*, i64) +declare i8* @llvm.coro.begin(token, i8*) +declare i1 @llvm.coro.end(i8*, i1) + +declare void @print(i32*) +declare noalias i8* @malloc(i32) +declare void @free(i8*)