[clang] [llvm] [RFC][Coroutines] Implement HALO for coroutines that flow off final suspend (PR #185336)

via cfe-commits cfe-commits at lists.llvm.org
Sun Mar 8 19:19:09 PDT 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-ir

Author: Weibo He (NewSigma)

<details>
<summary>Changes</summary>

`CoroElide` requires that we see the `llvm.coro.destroy` intrinsic. However, we never emit the intrinsic if a coroutine flows off the final suspending point, and HALO does not work, even for trivial cases. See https://godbolt.org/z/3KqfzMY7e

``` C++
#include <coroutine>

struct coro {
    using promise_type = coro;

    auto get_return_object() noexcept { return coro{std::coroutine_handle<coro>::from_promise(*this)}; }
    std::suspend_never initial_suspend() noexcept { return {}; }
    std::suspend_never final_suspend() noexcept { return {}; }
    void return_void() noexcept {}
    void unhandled_exception() {}

    std::coroutine_handle<> handle;
};

coro fn() {
  co_await std::suspend_always{};
}

int main() {
  fn().handle.resume();
}
```

This patch proposes a optimization hint `llvm.coro.dead` for cases where `llvm.coro.destroy` is not available so that HALO works. It contains 4 commits:
- [CoroSplit] Keep coro.free for resume/destroy
Keep these coro.free so that we can elide them
- [Coroutines][NFC] Elide coro.free based on frame instead of coro.id
coro.id is unavailable in resumers
- [CoroElide][IR] Add llvm.coro.dead
ME part
- [clang][CodeGen] Emit coro.dead for coroutines
Final FE part: see if we break anything

---

Patch is 35.50 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/185336.diff


27 Files Affected:

- (modified) clang/lib/CodeGen/CGCoroutine.cpp (+3) 
- (modified) clang/test/CodeGenCoroutines/coro-elide.cpp (+26-1) 
- (modified) llvm/docs/Coroutines.rst (+43) 
- (modified) llvm/include/llvm/IR/Intrinsics.td (+1) 
- (modified) llvm/lib/Transforms/Coroutines/CoroCleanup.cpp (+8-6) 
- (modified) llvm/lib/Transforms/Coroutines/CoroElide.cpp (+31-28) 
- (modified) llvm/lib/Transforms/Coroutines/CoroInternal.h (+1-1) 
- (modified) llvm/lib/Transforms/Coroutines/CoroSplit.cpp (+8-11) 
- (modified) llvm/lib/Transforms/Coroutines/Coroutines.cpp (+4-8) 
- (modified) llvm/test/Transforms/Coroutines/coro-alloc-with-param-O0.ll (+4-2) 
- (modified) llvm/test/Transforms/Coroutines/coro-alloc-with-param-O2.ll (+4-2) 
- (modified) llvm/test/Transforms/Coroutines/coro-alloca-07.ll (+4-2) 
- (modified) llvm/test/Transforms/Coroutines/coro-await-suspend-lower-invoke.ll (+4-2) 
- (modified) llvm/test/Transforms/Coroutines/coro-await-suspend-lower.ll (+4-2) 
- (modified) llvm/test/Transforms/Coroutines/coro-eh-aware-edge-split-00.ll (+1-1) 
- (modified) llvm/test/Transforms/Coroutines/coro-eh-aware-edge-split-01.ll (+2-1) 
- (modified) llvm/test/Transforms/Coroutines/coro-eh-aware-edge-split-02.ll (+2-1) 
- (modified) llvm/test/Transforms/Coroutines/coro-frame-arrayalloca.ll (+4-2) 
- (modified) llvm/test/Transforms/Coroutines/coro-frame.ll (+4-2) 
- (modified) llvm/test/Transforms/Coroutines/coro-only-destroy-when-complete.ll (+10-2) 
- (modified) llvm/test/Transforms/Coroutines/coro-padding.ll (+4-2) 
- (modified) llvm/test/Transforms/Coroutines/coro-spill-corobegin.ll (+4-2) 
- (modified) llvm/test/Transforms/Coroutines/coro-spill-promise-02.ll (+4-2) 
- (modified) llvm/test/Transforms/Coroutines/coro-spill-promise.ll (+4-2) 
- (modified) llvm/test/Transforms/Coroutines/coro-spill-suspend.ll (+4-2) 
- (modified) llvm/test/Transforms/Coroutines/coro-split-tbaa-md.ll (+4-2) 
- (modified) llvm/test/Transforms/Coroutines/coro-zero-alloca.ll (+4-2) 


``````````diff
diff --git a/clang/lib/CodeGen/CGCoroutine.cpp b/clang/lib/CodeGen/CGCoroutine.cpp
index 7282c42420657..9d9d2450c3d68 100644
--- a/clang/lib/CodeGen/CGCoroutine.cpp
+++ b/clang/lib/CodeGen/CGCoroutine.cpp
@@ -643,6 +643,9 @@ struct CallCoroDelete final : public EHScopeStack::Cleanup {
     // No longer need old terminator.
     InsertPt->eraseFromParent();
     CGF.Builder.SetInsertPoint(AfterFreeBB);
+
+    auto *CoroDeadFn = CGF.CGM.getIntrinsic(llvm::Intrinsic::coro_dead);
+    CGF.Builder.CreateCall(CoroDeadFn, {CGF.CurCoro.Data->CoroBegin});
   }
   explicit CallCoroDelete(Stmt *DeallocStmt) : Deallocate(DeallocStmt) {}
 };
diff --git a/clang/test/CodeGenCoroutines/coro-elide.cpp b/clang/test/CodeGenCoroutines/coro-elide.cpp
index d7569c3b4d087..1902dd64b5e5a 100644
--- a/clang/test/CodeGenCoroutines/coro-elide.cpp
+++ b/clang/test/CodeGenCoroutines/coro-elide.cpp
@@ -1,8 +1,32 @@
-// This tests that the coroutine elide optimization could happen succesfully.
 // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -O2 -emit-llvm %s -o - | FileCheck %s
 
 #include "Inputs/coroutine.h"
 
+namespace {
+struct coro {
+    using promise_type = coro;
+
+    auto get_return_object() noexcept { return coro{std::coroutine_handle<coro>::from_promise(*this)}; }
+    std::suspend_never initial_suspend() noexcept { return {}; }
+    std::suspend_never final_suspend() noexcept { return {}; }
+    void return_void() noexcept {}
+    void unhandled_exception() {}
+
+    std::coroutine_handle<> handle;
+};
+}
+
+void flowoff() {
+  []() -> coro {
+    co_await std::suspend_always{};
+  }().handle.resume();
+}
+
+// Tests that the coroutine elide optimization could happen if control flows off the end of the coroutine
+// CHECK-LABEL: define{{.*}} void @_Z7flowoffv
+// CHECK-NEXT: entry:
+// CHECK-NEXT: ret void
+
 struct Task {
   struct promise_type {
     struct FinalAwaiter {
@@ -58,5 +82,6 @@ Task task1() {
   co_return co_await task0();
 }
 
+// Tests that the coroutine elide optimization could happen if handle.destroy() is invoked
 // CHECK-LABEL: define{{.*}} void @_Z5task1v.resume
 // CHECK-NOT: call{{.*}}_Znwm
diff --git a/llvm/docs/Coroutines.rst b/llvm/docs/Coroutines.rst
index 0e6b49c84acee..34de250ce5e4f 100644
--- a/llvm/docs/Coroutines.rst
+++ b/llvm/docs/Coroutines.rst
@@ -873,6 +873,8 @@ the coroutine destroy function. Otherwise it is replaced with an indirect call
 based on the function pointer for the destroy function stored in the coroutine
 frame. Destroying a coroutine that is not suspended leads to undefined behavior.
 
+This intrinsic implies `coro.dead`.
+
 .. _coro.resume:
 
 'llvm.coro.resume' Intrinsic
@@ -1169,6 +1171,47 @@ Example (standard deallocation functions):
     call void @free(ptr %mem)
     ret void
 
+.. _coro.dead:
+
+'llvm.coro.dead' Intrinsic
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+::
+
+  declare void @llvm.coro.dead(ptr <frame>)
+
+Overview:
+"""""""""
+
+The '``llvm.coro.dead``' intrinsic is an optimization hint to help Heap Allocation eLision Optimization(HALO).
+
+Arguments:
+""""""""""
+
+The argument is a pointer to the coroutine frame. This should be the same
+pointer that was returned by prior `coro.begin` call.
+
+Semantics:
+""""""""""
+
+A frontend can delegate this intrinsic to indicate that the coroutine frame is dead, allowing
+coroutines that are not explicitly destroyed via `coro.destroy` to be elided.
+
+Example:
+"""""""""""""""""""""""""""""""""""""""
+
+.. code-block:: llvm
+
+  cleanup:
+    %mem = call ptr @llvm.coro.free(token %id, ptr %frame)
+    %mem_not_null = icmp ne ptr %mem, null
+    br i1 %mem_not_null, label %if.then, label %if.end
+  if.then:
+    call void @CustomFree(ptr %mem)
+    br label %if.end
+  if.end:
+    call void @llvm.coro.dead(ptr %frame)
+    ret void
+
 .. _coro.alloc:
 
 'llvm.coro.alloc' Intrinsic
diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td
index 5b5fffaa48951..d3ca2529dc1bd 100644
--- a/llvm/include/llvm/IR/Intrinsics.td
+++ b/llvm/include/llvm/IR/Intrinsics.td
@@ -1849,6 +1849,7 @@ def int_coro_free : Intrinsic<[llvm_ptr_ty], [llvm_token_ty, llvm_ptr_ty],
                               [IntrReadMem, IntrArgMemOnly,
                                ReadOnly<ArgIndex<1>>,
                                NoCapture<ArgIndex<1>>]>;
+def int_coro_dead : Intrinsic<[], [llvm_ptr_ty], [IntrNoMem]>;
 def int_coro_end : Intrinsic<[], [llvm_ptr_ty, llvm_i1_ty, llvm_token_ty], []>;
 def int_coro_end_results : Intrinsic<[llvm_token_ty], [llvm_vararg_ty]>;
 def int_coro_end_async
diff --git a/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp b/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp
index c44eaddd7ee55..2ba4c4d953f7e 100644
--- a/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp
@@ -108,6 +108,8 @@ bool Lowerer::lower(Function &F) {
       case Intrinsic::coro_free:
         II->replaceAllUsesWith(II->getArgOperand(1));
         break;
+      case Intrinsic::coro_dead:
+        break;
       case Intrinsic::coro_alloc:
         II->replaceAllUsesWith(ConstantInt::getTrue(Context));
         break;
@@ -256,12 +258,12 @@ void NoopCoroElider::eraseFromWorklist(Instruction *I) {
 
 static bool declaresCoroCleanupIntrinsics(const Module &M) {
   return coro::declaresIntrinsics(
-      M,
-      {Intrinsic::coro_alloc, Intrinsic::coro_begin, Intrinsic::coro_subfn_addr,
-       Intrinsic::coro_free, Intrinsic::coro_id, Intrinsic::coro_id_retcon,
-       Intrinsic::coro_id_async, Intrinsic::coro_id_retcon_once,
-       Intrinsic::coro_noop, Intrinsic::coro_async_size_replace,
-       Intrinsic::coro_async_resume, Intrinsic::coro_begin_custom_abi});
+      M, {Intrinsic::coro_alloc, Intrinsic::coro_begin,
+          Intrinsic::coro_subfn_addr, Intrinsic::coro_free,
+          Intrinsic::coro_dead, Intrinsic::coro_id, Intrinsic::coro_id_retcon,
+          Intrinsic::coro_id_async, Intrinsic::coro_id_retcon_once,
+          Intrinsic::coro_noop, Intrinsic::coro_async_size_replace,
+          Intrinsic::coro_async_resume, Intrinsic::coro_begin_custom_abi});
 }
 
 PreservedAnalyses CoroCleanupPass::run(Module &M,
diff --git a/llvm/lib/Transforms/Coroutines/CoroElide.cpp b/llvm/lib/Transforms/Coroutines/CoroElide.cpp
index 1c8d4a8592d60..adff67a472fb4 100644
--- a/llvm/lib/Transforms/Coroutines/CoroElide.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroElide.cpp
@@ -73,7 +73,8 @@ class CoroIdElider {
   SmallVector<CoroBeginInst *, 1> CoroBegins;
   SmallVector<CoroAllocInst *, 1> CoroAllocs;
   SmallVector<CoroSubFnInst *, 4> ResumeAddr;
-  DenseMap<CoroBeginInst *, SmallVector<CoroSubFnInst *, 4>> DestroyAddr;
+  SmallVector<CoroSubFnInst *, 4> DestroyAddr;
+  DenseMap<CoroBeginInst *, SmallVector<IntrinsicInst *, 4>> BeginDeadMap;
 };
 } // end anonymous namespace
 
@@ -177,23 +178,31 @@ CoroIdElider::CoroIdElider(CoroIdInst *CoroId, FunctionElideInfo &FEI,
       CoroAllocs.push_back(CA);
   }
 
-  // Collect all coro.subfn.addrs associated with coro.begin.
-  // Note, we only devirtualize the calls if their coro.subfn.addr refers to
-  // coro.begin directly. If we run into cases where this check is too
-  // conservative, we can consider relaxing the check.
   for (CoroBeginInst *CB : CoroBegins) {
-    for (User *U : CB->users())
-      if (auto *II = dyn_cast<CoroSubFnInst>(U))
+    for (User *U : CB->users()) {
+      auto &LifetimeHints = BeginDeadMap[CB];
+      // Collect all coro.subfn.addrs associated with coro.begin.
+      // Note, we only devirtualize the calls if their coro.subfn.addr refers to
+      // coro.begin directly. If we run into cases where this check is too
+      // conservative, we can consider relaxing the check.
+      if (auto *II = dyn_cast<CoroSubFnInst>(U)) {
         switch (II->getIndex()) {
         case CoroSubFnInst::ResumeIndex:
           ResumeAddr.push_back(II);
           break;
         case CoroSubFnInst::DestroyIndex:
-          DestroyAddr[CB].push_back(II);
+          LifetimeHints.push_back(II);
+          DestroyAddr.push_back(II);
           break;
         default:
           llvm_unreachable("unexpected coro.subfn.addr constant");
         }
+      }
+
+      auto *II = dyn_cast<IntrinsicInst>(U);
+      if (II && II->getIntrinsicID() == Intrinsic::coro_dead)
+        LifetimeHints.push_back(II);
+    }
   }
 }
 
@@ -228,6 +237,7 @@ void CoroIdElider::elideHeapAllocations(uint64_t FrameSize, Align FrameAlign) {
       new BitCastInst(Frame, PointerType::getUnqual(C), "vFrame", InsertPt);
 
   for (auto *CB : CoroBegins) {
+    coro::elideCoroFree(CB);
     CB->replaceAllUsesWith(FrameVoidPtr);
     CB->eraseFromParent();
   }
@@ -239,8 +249,8 @@ void CoroIdElider::elideHeapAllocations(uint64_t FrameSize, Align FrameAlign) {
 
 bool CoroIdElider::canCoroBeginEscape(
     const CoroBeginInst *CB, const SmallPtrSetImpl<BasicBlock *> &TIs) const {
-  const auto &It = DestroyAddr.find(CB);
-  assert(It != DestroyAddr.end());
+  const auto &It = BeginDeadMap.find(CB);
+  assert(It != BeginDeadMap.end());
 
   // Limit the number of blocks we visit.
   unsigned Limit = 32 * (1 + It->second.size());
@@ -249,8 +259,8 @@ bool CoroIdElider::canCoroBeginEscape(
   Worklist.push_back(CB->getParent());
 
   SmallPtrSet<const BasicBlock *, 32> Visited;
-  // Consider basicblock of coro.destroy as visited one, so that we
-  // skip the path pass through coro.destroy.
+  // Consider basicblock of coro.dead as visited one, so that we
+  // skip the path pass through coro.dead.
   for (auto *DA : It->second)
     Visited.insert(DA->getParent());
 
@@ -326,11 +336,11 @@ bool CoroIdElider::lifetimeEligibleForElide() const {
   if (CoroAllocs.empty())
     return false;
 
-  // Check that for every coro.begin there is at least one coro.destroy directly
+  // Check that for every coro.begin there is at least one coro.dead directly
   // referencing the SSA value of that coro.begin along each
   // non-exceptional path.
   //
-  // If the value escaped, then coro.destroy would have been referencing a
+  // If the value escaped, then coro.dead would have been referencing a
   // memory location storing that value and not the virtual register.
 
   SmallPtrSet<BasicBlock *, 8> Terminators;
@@ -346,21 +356,16 @@ bool CoroIdElider::lifetimeEligibleForElide() const {
     Terminators.insert(&B);
   }
 
-  // Filter out the coro.destroy that lie along exceptional paths.
+  // Filter out the coro.dead that lie along exceptional paths.
   for (const auto *CB : CoroBegins) {
-    auto It = DestroyAddr.find(CB);
-
-    // FIXME: If we have not found any destroys for this coro.begin, we
-    // disqualify this elide.
-    if (It == DestroyAddr.end())
+    auto It = BeginDeadMap.find(CB);
+    if (It == BeginDeadMap.end())
       return false;
 
-    const auto &CorrespondingDestroyAddrs = It->second;
-
-    // If every terminators is dominated by coro.destroy, we could know the
+    // If every terminators is dominated by coro.dead, we could know the
     // corresponding coro.begin wouldn't escape.
     auto DominatesTerminator = [&](auto *TI) {
-      return llvm::any_of(CorrespondingDestroyAddrs, [&](auto *Destroy) {
+      return llvm::any_of(It->second, [&](auto *Destroy) {
         return DT.dominates(Destroy, TI->getTerminator());
       });
     };
@@ -370,7 +375,7 @@ bool CoroIdElider::lifetimeEligibleForElide() const {
 
     // Otherwise canCoroBeginEscape would decide whether there is any paths from
     // coro.begin to Terminators which not pass through any of the
-    // coro.destroys. This is a slower analysis.
+    // coro.dead. This is a slower analysis.
     //
     // canCoroBeginEscape is relatively slow, so we avoid to run it as much as
     // possible.
@@ -400,8 +405,7 @@ bool CoroIdElider::attemptElide() {
       EligibleForElide ? CoroSubFnInst::CleanupIndex
                        : CoroSubFnInst::DestroyIndex);
 
-  for (auto &It : DestroyAddr)
-    replaceWithConstant(DestroyAddrConstant, It.second);
+  replaceWithConstant(DestroyAddrConstant, DestroyAddr);
 
   auto FrameSizeAndAlign = getFrameLayout(cast<Function>(ResumeAddrConstant));
 
@@ -410,7 +414,6 @@ bool CoroIdElider::attemptElide() {
 
   if (EligibleForElide && FrameSizeAndAlign) {
     elideHeapAllocations(FrameSizeAndAlign->first, FrameSizeAndAlign->second);
-    coro::replaceCoroFree(CoroId, /*Elide=*/true);
     NumOfCoroElided++;
 
 #ifndef NDEBUG
diff --git a/llvm/lib/Transforms/Coroutines/CoroInternal.h b/llvm/lib/Transforms/Coroutines/CoroInternal.h
index cc47a557ee5c0..319e600870091 100644
--- a/llvm/lib/Transforms/Coroutines/CoroInternal.h
+++ b/llvm/lib/Transforms/Coroutines/CoroInternal.h
@@ -21,7 +21,7 @@ namespace llvm::coro {
 bool isSuspendBlock(BasicBlock *BB);
 bool declaresAnyIntrinsic(const Module &M);
 bool declaresIntrinsics(const Module &M, ArrayRef<Intrinsic::ID> List);
-void replaceCoroFree(CoroIdInst *CoroId, bool Elide);
+void elideCoroFree(Value *FramePtr);
 
 /// Replaces all @llvm.coro.alloc intrinsics calls associated with a given
 /// call @llvm.coro.id instruction with boolean value false.
diff --git a/llvm/lib/Transforms/Coroutines/CoroSplit.cpp b/llvm/lib/Transforms/Coroutines/CoroSplit.cpp
index 587f581ded8d5..d43519ab0d3e9 100644
--- a/llvm/lib/Transforms/Coroutines/CoroSplit.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroSplit.cpp
@@ -1100,10 +1100,9 @@ void coro::SwitchCloner::create() {
   // Clone the function
   coro::BaseCloner::create();
 
-  // 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=*/FKind == coro::CloneKind::SwitchCleanup);
+  // Replacing coro.free with 'null' in cleanup to suppress deallocation code.
+  if (FKind == coro::CloneKind::SwitchCleanup)
+    elideCoroFree(NewFramePtr);
 }
 
 static void updateAsyncFuncPointerContextSize(coro::Shape &Shape) {
@@ -1163,10 +1162,9 @@ static void handleNoSuspendCoroutine(coro::Shape &Shape) {
   auto *CoroBegin = Shape.CoroBegin;
   switch (Shape.ABI) {
   case coro::ABI::Switch: {
-    auto SwitchId = Shape.getSwitchCoroId();
-    auto *AllocInst = SwitchId->getCoroAlloc();
-    coro::replaceCoroFree(SwitchId, /*Elide=*/AllocInst != nullptr);
-    if (AllocInst) {
+    if (auto *AllocInst = Shape.getSwitchCoroId()->getCoroAlloc()) {
+      coro::elideCoroFree(CoroBegin);
+
       IRBuilder<> Builder(AllocInst);
       // Create an alloca for a byte array of the frame size
       auto *FrameTy = ArrayType::get(Type::getInt8Ty(Builder.getContext()),
@@ -1441,9 +1439,8 @@ struct SwitchCoroutineSplitter {
     if (Shape.CoroBegin) {
       auto *NewCoroBegin =
           cast_if_present<CoroBeginInst>(VMap[Shape.CoroBegin]);
-      auto *NewCoroId = cast<CoroIdInst>(NewCoroBegin->getId());
-      coro::replaceCoroFree(NewCoroId, /*Elide=*/true);
-      coro::suppressCoroAllocs(NewCoroId);
+      coro::elideCoroFree(NewCoroBegin);
+      coro::suppressCoroAllocs(cast<CoroIdInst>(NewCoroBegin->getId()));
       NewCoroBegin->replaceAllUsesWith(NoAllocF->getArg(FrameIdx));
       NewCoroBegin->eraseFromParent();
     }
diff --git a/llvm/lib/Transforms/Coroutines/Coroutines.cpp b/llvm/lib/Transforms/Coroutines/Coroutines.cpp
index 2922e39a85e81..a68a5bf1623b5 100644
--- a/llvm/lib/Transforms/Coroutines/Coroutines.cpp
+++ b/llvm/lib/Transforms/Coroutines/Coroutines.cpp
@@ -118,11 +118,10 @@ bool coro::declaresIntrinsics(const Module &M, ArrayRef<Intrinsic::ID> List) {
   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) {
+// Replace all coro.frees associated with the provided frame with 'null'
+void coro::elideCoroFree(Value *FramePtr) {
   SmallVector<CoroFreeInst *, 4> CoroFrees;
-  for (User *U : CoroId->users())
+  for (User *U : FramePtr->users())
     if (auto CF = dyn_cast<CoroFreeInst>(U))
       CoroFrees.push_back(CF);
 
@@ -130,10 +129,7 @@ void coro::replaceCoroFree(CoroIdInst *CoroId, bool Elide) {
     return;
 
   Value *Replacement =
-      Elide
-          ? ConstantPointerNull::get(PointerType::get(CoroId->getContext(), 0))
-          : CoroFrees.front()->getFrame();
-
+      ConstantPointerNull::get(PointerType::get(FramePtr->getContext(), 0));
   for (CoroFreeInst *CF : CoroFrees) {
     CF->replaceAllUsesWith(Replacement);
     CF->eraseFromParent();
diff --git a/llvm/test/Transforms/Coroutines/coro-alloc-with-param-O0.ll b/llvm/test/Transforms/Coroutines/coro-alloc-with-param-O0.ll
index 2db096f13136d..77f383a4ff387 100644
--- a/llvm/test/Transforms/Coroutines/coro-alloc-with-param-O0.ll
+++ b/llvm/test/Transforms/Coroutines/coro-alloc-with-param-O0.ll
@@ -69,14 +69,16 @@ declare void @free(ptr)
 ; CHECK-NEXT:    [[THIS_RELOAD_ADDR:%.*]] = getelementptr inbounds i8, ptr [[HDL]], i64 16
 ; CHECK-NEXT:    [[THIS_RELOAD:%.*]] = load i64, ptr [[THIS_RELOAD_ADDR]], align 4
 ; CHECK-NEXT:    call void @print2(i64 [[THIS_RELOAD]])
-; CHECK-NEXT:    call void @free(ptr [[HDL]])
+; CHECK-NEXT:    [[MEM:%.*]] = call ptr @llvm.coro.free(token poison, ptr [[HDL]])
+; CHECK-NEXT:    call void @free(ptr [[MEM]])
 ; CHECK-NEXT:    ret void
 ;
 ;
 ; CHECK-LABEL: define internal fastcc void @f_copy.destroy(
 ; CHECK-SAME: ptr noundef nonnull align 8 dereferenceable(32) [[HDL:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY_DESTROY:.*:]]
-; CHECK-NEXT:    call void @free(ptr [[HDL]])
+; CHECK-NEXT:    [[MEM:%.*]] = call ptr @llvm.coro.free(token poison, ptr [[HDL]])
+; CHECK-NEXT:    call void @free(ptr [[MEM]])
 ; CHECK-NEXT:    ret void
 ;
 ;
diff --git a/llvm/test/Transforms/Coroutines/coro-alloc-with-param-O2.ll b/llvm/test/Transforms/Coroutines/coro-alloc-with-param-O2.ll
index e079d8114cc23..953f937b62e17 100644
--- a/llvm/test/Transforms/Coroutines/coro-alloc-with-param-O2.ll
+++ b/llvm/test/Transforms/Coroutines/coro-alloc-with-param-O2.ll
@@ -64,14 +64,16 @@ declare void @free(ptr)
 ; CHECK-NEXT:    [[THIS_RELOAD_ADDR:%.*]] = getelementptr inbounds i8, ptr [[HDL]], i64 16
 ; CHECK-NEXT:    [[THIS_RELOAD:%.*]] = load i64, ptr [[THIS_RELOAD_ADDR]], align 4
 ; CHECK-NEXT:    call void @print2(i64 [[THIS_RELOAD]])
-; CHECK-NEXT:    call void @free(ptr [[HDL]])
+; CHECK-NEXT:    [[MEM:%.*]] = call ptr @llvm.coro.free(token poison, ptr [[HDL]])
+; CHECK-NEXT:    call void @free(ptr [[MEM]])
 ; CHECK-NEXT:    ret void
 ;
 ;
 ; CHECK-LABEL: define internal fastcc void @f_direct.destroy(
 ; CHECK-SAME: ptr noundef nonnull align 8 dereferenceable(32) [[HDL:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY_DESTROY:.*:]]
-; CHECK-NEXT:    call void @free(ptr [[HDL]])
+; CHECK-NEXT:    [[MEM:%.*]] = call ptr @llvm.coro.free(token poison, ptr [[HDL]])
+; CHECK-NEXT:    call void @free(ptr [[MEM]])
 ; CHECK-NEXT:    ret void
 ;
 ;
diff --git a/llvm/test/Transforms/Coroutines/coro-alloca-07.ll b/llvm/test/Transforms/Coroutines/coro-alloca-07.ll
index 76aca567f785b..4c86abb1e8dc2 100644
--- a/llvm/test/Transforms/Coroutines/coro-alloca-07.ll
+++ b/llvm/test/Transforms/Coroutines/coro-alloca-07.ll
@@ -90,14 +90,16 @@ declare void @free(ptr)
 ; CHECK-NEXT:    [[ALIAS_PHI_RELOAD_ADDR:%.*]] = getelementptr inbounds i8, ptr [[HDL]], i64 32
 ; CHECK-NEXT:    [[ALIAS_PHI_RELOAD:%.*]] = load ptr, ptr [[ALIAS_PHI_RELOAD_ADDR]], align 8
 ; CHECK-NEXT:    call void @print(ptr [[ALIAS_PHI_RELOAD]])
-; CHECK-NEXT:    call void @free(ptr [[HDL]])
+; CHECK-NEXT:    [[MEM:%.*]] = call ptr @llvm.coro.free(token poison, ptr [[HDL]])
+; CHECK-NEXT:    call void @free(ptr [[MEM]])
 ; CHECK-NEXT:    ret void
 ;
 ;
 ; CHECK-LABEL: define internal fastcc void @f.destroy(
 ; CHECK-SAME: ptr noundef nonnull align 8 dereferenceable(48) [[HDL:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY_DESTROY:.*:]]
-; CHECK-NEXT:    call void @free(ptr [[HDL]])
+; CHECK-NEXT:    [[MEM:%.*]] = call ptr @llvm.coro.free(token poison, ptr [[HDL]])
+; CHECK-NEXT:    call void @free(ptr [[MEM]])
 ; CHECK-NEXT:    ret void
 ;
 ;
diff --git a/llvm/test/Transforms/Coroutines/coro-await-suspend-lower-invoke.ll b/llvm/test/Transforms/Coroutines/coro-await-suspend-lower-invoke.ll
index 72bb8fcf5b610..78540d9363cc6 100644
--- a/llvm/test/Transforms/Coroutines/coro-await-suspend-lower-invoke.ll
+++ b/llvm/test/Transforms/Coroutines/coro-await-suspend-lower-invoke.ll
@@ -167,7 +167,8 @@ declare...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/185336


More information about the cfe-commits mailing list