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

>From b42003f98bf322e82c31cc9ca229025f8c3be27a Mon Sep 17 00:00:00 2001
From: NewSigma <[email protected]>
Date: Sun, 8 Mar 2026 17:20:09 +0800
Subject: [PATCH 1/4] [CoroSplit] Keep coro.free for resume/destroy

---
 llvm/lib/Transforms/Coroutines/CoroSplit.cpp         |  8 ++++----
 .../Coroutines/coro-alloc-with-param-O0.ll           |  6 ++++--
 .../Coroutines/coro-alloc-with-param-O2.ll           |  6 ++++--
 llvm/test/Transforms/Coroutines/coro-alloca-07.ll    |  6 ++++--
 .../Coroutines/coro-await-suspend-lower-invoke.ll    |  6 ++++--
 .../Coroutines/coro-await-suspend-lower.ll           |  6 ++++--
 .../Coroutines/coro-eh-aware-edge-split-00.ll        |  2 +-
 .../Coroutines/coro-eh-aware-edge-split-01.ll        |  3 ++-
 .../Coroutines/coro-eh-aware-edge-split-02.ll        |  3 ++-
 .../Transforms/Coroutines/coro-frame-arrayalloca.ll  |  6 ++++--
 llvm/test/Transforms/Coroutines/coro-frame.ll        |  6 ++++--
 .../Coroutines/coro-only-destroy-when-complete.ll    | 12 ++++++++++--
 llvm/test/Transforms/Coroutines/coro-padding.ll      |  6 ++++--
 .../Transforms/Coroutines/coro-spill-corobegin.ll    |  6 ++++--
 .../Transforms/Coroutines/coro-spill-promise-02.ll   |  6 ++++--
 .../test/Transforms/Coroutines/coro-spill-promise.ll |  6 ++++--
 .../test/Transforms/Coroutines/coro-spill-suspend.ll |  6 ++++--
 .../test/Transforms/Coroutines/coro-split-tbaa-md.ll |  6 ++++--
 llvm/test/Transforms/Coroutines/coro-zero-alloca.ll  |  6 ++++--
 19 files changed, 75 insertions(+), 37 deletions(-)

diff --git a/llvm/lib/Transforms/Coroutines/CoroSplit.cpp 
b/llvm/lib/Transforms/Coroutines/CoroSplit.cpp
index 587f581ded8d5..295811b297984 100644
--- a/llvm/lib/Transforms/Coroutines/CoroSplit.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroSplit.cpp
@@ -1100,10 +1100,10 @@ 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)
+    coro::replaceCoroFree(cast<CoroIdInst>(VMap[Shape.CoroBegin->getId()]),
+                          /*Elide=*/FKind == coro::CloneKind::SwitchCleanup);
 }
 
 static void updateAsyncFuncPointerContextSize(coro::Shape &Shape) {
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 void @free(ptr)
 ; CHECK-NEXT:    call void @__cxa_end_catch()
 ; CHECK-NEXT:    br label %[[CLEANUP]]
 ; CHECK:       [[CLEANUP]]:
-; 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:    br label %[[COROEND]]
 ; CHECK:       [[COROEND]]:
 ; CHECK-NEXT:    ret void
@@ -179,7 +180,8 @@ declare void @free(ptr)
 ; CHECK-SAME: ptr noundef nonnull align 8 dereferenceable(24) [[HDL:%.*]]) 
personality i32 0 {
 ; CHECK-NEXT:  [[ENTRY_DESTROY:.*:]]
 ; CHECK-NEXT:    [[AWAITER_RELOAD_ADDR:%.*]] = getelementptr inbounds i8, ptr 
[[HDL]], i64 0
-; 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.ll 
b/llvm/test/Transforms/Coroutines/coro-await-suspend-lower.ll
index 05bac14f26a2d..d6c93cad08a90 100644
--- a/llvm/test/Transforms/Coroutines/coro-await-suspend-lower.ll
+++ b/llvm/test/Transforms/Coroutines/coro-await-suspend-lower.ll
@@ -133,7 +133,8 @@ declare void @free(ptr)
 ; CHECK-NEXT:    musttail call fastcc void [[TMP4]](ptr [[TMP3]])
 ; CHECK-NEXT:    ret void
 ; CHECK:       [[CLEANUP]]:
-; 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:    br label %[[COROEND]]
 ; CHECK:       [[COROEND]]:
 ; CHECK-NEXT:    ret void
@@ -145,7 +146,8 @@ declare void @free(ptr)
 ; CHECK-SAME: ptr noundef nonnull align 8 dereferenceable(24) [[HDL:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY_DESTROY:.*:]]
 ; CHECK-NEXT:    [[AWAITER_RELOAD_ADDR:%.*]] = getelementptr inbounds i8, ptr 
[[HDL]], i64 0
-; 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-eh-aware-edge-split-00.ll 
b/llvm/test/Transforms/Coroutines/coro-eh-aware-edge-split-00.ll
index ad84f7b33dc65..b03a54cd2c51d 100644
--- a/llvm/test/Transforms/Coroutines/coro-eh-aware-edge-split-00.ll
+++ b/llvm/test/Transforms/Coroutines/coro-eh-aware-edge-split-00.ll
@@ -46,7 +46,7 @@ invoke2:
 ; CHECK:   call ptr @__cxa_begin_catch(ptr %exn)
 ; CHECK:   call void @use_val(i32 %val)
 ; CHECK:   call void @__cxa_end_catch()
-; CHECK:   call void @free(ptr %hdl)
+; CHECK:   call void @free(ptr %mem)
 ; CHECK:   ret void
 
 pad.with.phi:
diff --git a/llvm/test/Transforms/Coroutines/coro-eh-aware-edge-split-01.ll 
b/llvm/test/Transforms/Coroutines/coro-eh-aware-edge-split-01.ll
index 713e36c1f8fc9..b5b01835a3e40 100644
--- a/llvm/test/Transforms/Coroutines/coro-eh-aware-edge-split-01.ll
+++ b/llvm/test/Transforms/Coroutines/coro-eh-aware-edge-split-01.ll
@@ -124,7 +124,8 @@ declare ptr @llvm.coro.free(token, ptr nocapture readonly)
 ; CHECK-LABEL: define internal fastcc void @g.destroy(
 ; CHECK-SAME: ptr noundef nonnull align 8 dereferenceable(32) [[HDL:%.*]]) 
personality i32 0 {
 ; 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-eh-aware-edge-split-02.ll 
b/llvm/test/Transforms/Coroutines/coro-eh-aware-edge-split-02.ll
index 335b9e5cd7945..428cef117292f 100644
--- a/llvm/test/Transforms/Coroutines/coro-eh-aware-edge-split-02.ll
+++ b/llvm/test/Transforms/Coroutines/coro-eh-aware-edge-split-02.ll
@@ -126,7 +126,8 @@ declare ptr @llvm.coro.free(token, ptr nocapture readonly)
 ; CHECK-LABEL: define internal fastcc void @h.destroy(
 ; CHECK-SAME: ptr noundef nonnull align 8 dereferenceable(32) [[HDL:%.*]]) 
personality i32 0 {
 ; 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-frame-arrayalloca.ll 
b/llvm/test/Transforms/Coroutines/coro-frame-arrayalloca.ll
index c397734e9fea4..2ffd43c2d2682 100644
--- a/llvm/test/Transforms/Coroutines/coro-frame-arrayalloca.ll
+++ b/llvm/test/Transforms/Coroutines/coro-frame-arrayalloca.ll
@@ -81,14 +81,16 @@ declare void @free(ptr)
 ; CHECK-NEXT:    call void @consume.double.ptr(ptr [[PREFIX_RELOAD_ADDR]])
 ; CHECK-NEXT:    call void @consume.i32.ptr(ptr [[DATA_RELOAD_ADDR]])
 ; CHECK-NEXT:    call void @consume.double.ptr(ptr [[SUFFIX_RELOAD_ADDR]])
-; 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(56) [[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-frame.ll 
b/llvm/test/Transforms/Coroutines/coro-frame.ll
index 1beccdf5de7b0..59bf963d068e8 100644
--- a/llvm/test/Transforms/Coroutines/coro-frame.ll
+++ b/llvm/test/Transforms/Coroutines/coro-frame.ll
@@ -81,14 +81,16 @@ declare void @free(ptr)
 ; CHECK-NEXT:    [[THIS1_RELOAD:%.*]] = load i64, ptr [[THIS1_RELOAD_ADDR]], 
align 4
 ; CHECK-NEXT:    [[TMP0:%.*]] = call double @print(double [[R_RELOAD]])
 ; CHECK-NEXT:    call void @print2(i64 [[THIS1_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(40) [[HDL:%.*]]) 
personality i32 0 {
 ; 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-only-destroy-when-complete.ll 
b/llvm/test/Transforms/Coroutines/coro-only-destroy-when-complete.ll
index e40ac4e0ec162..292603767494d 100644
--- a/llvm/test/Transforms/Coroutines/coro-only-destroy-when-complete.ll
+++ b/llvm/test/Transforms/Coroutines/coro-only-destroy-when-complete.ll
@@ -129,8 +129,16 @@ attributes #12 = { noduplicate }
 
 ; CHECK: define{{.*}}@foo.destroy(
 ; CHECK-NEXT: entry.destroy:
-; CHECK-NEXT: call void @_ZdlPv
-; CHECK-NEXT: ret void
+; CHECK-NEXT:   call ptr @llvm.coro.free(
+; CHECK-NEXT:   %.not = icmp eq ptr %1, null
+; CHECK-NEXT:   br i1 %.not, label %CoroEnd, label %coro.free 
+
+; CHECK: coro.free:
+; CHECK-NEXT:   call void @_ZdlPv
+; CHECK-NEXT:   br label %CoroEnd 
+
+; CHECK: CoroEnd:
+; CHECK-NEXT:   ret void
 
 ; CHECK: define{{.*}}@foo.cleanup(
 ; CHECK-NEXT: entry.cleanup:
diff --git a/llvm/test/Transforms/Coroutines/coro-padding.ll 
b/llvm/test/Transforms/Coroutines/coro-padding.ll
index c8749bc43db8e..1b8855b06e0b0 100644
--- a/llvm/test/Transforms/Coroutines/coro-padding.ll
+++ b/llvm/test/Transforms/Coroutines/coro-padding.ll
@@ -66,14 +66,16 @@ declare void @free(ptr)
 ; CHECK-NEXT:  [[ENTRY_RESUME:.*:]]
 ; CHECK-NEXT:    [[DATA_RELOAD_ADDR:%.*]] = getelementptr inbounds i8, ptr 
[[HDL]], i64 32
 ; CHECK-NEXT:    call void @consume(ptr [[DATA_RELOAD_ADDR]])
-; 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 32 dereferenceable(64) [[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-spill-corobegin.ll 
b/llvm/test/Transforms/Coroutines/coro-spill-corobegin.ll
index 7a62885ec37ea..45fc76c08f99d 100644
--- a/llvm/test/Transforms/Coroutines/coro-spill-corobegin.ll
+++ b/llvm/test/Transforms/Coroutines/coro-spill-corobegin.ll
@@ -77,14 +77,16 @@ declare void @free(ptr)
 ; CHECK-NEXT:    [[GVAR_ADDR:%.*]] = getelementptr inbounds [[G_FRAME:%.*]], 
ptr [[INNERHDL_RELOAD]], i32 0, i32 4
 ; CHECK-NEXT:    [[GVAR:%.*]] = load i32, ptr [[GVAR_ADDR]], align 4
 ; CHECK-NEXT:    call void @print.i32(i32 [[GVAR]])
-; 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(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-spill-promise-02.ll 
b/llvm/test/Transforms/Coroutines/coro-spill-promise-02.ll
index 1211873500430..408a7b1d7a0f3 100644
--- a/llvm/test/Transforms/Coroutines/coro-spill-promise-02.ll
+++ b/llvm/test/Transforms/Coroutines/coro-spill-promise-02.ll
@@ -78,14 +78,16 @@ declare void @free(ptr)
 ; CHECK-NEXT:    [[__PROMISE_RELOAD_ADDR:%.*]] = getelementptr inbounds i8, 
ptr [[HDL]], i64 64
 ; CHECK-NEXT:    call void @consume(ptr [[DATA_RELOAD_ADDR]])
 ; CHECK-NEXT:    call void @consume2(ptr [[__PROMISE_RELOAD_ADDR]])
-; 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 64 dereferenceable(128) [[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-spill-promise.ll 
b/llvm/test/Transforms/Coroutines/coro-spill-promise.ll
index 530bfa3ec1d48..675b5fb637c1a 100644
--- a/llvm/test/Transforms/Coroutines/coro-spill-promise.ll
+++ b/llvm/test/Transforms/Coroutines/coro-spill-promise.ll
@@ -72,14 +72,16 @@ declare void @free(ptr)
 ; CHECK-NEXT:    [[__PROMISE_RELOAD_ADDR:%.*]] = getelementptr inbounds i8, 
ptr [[HDL]], i64 64
 ; CHECK-NEXT:    call void @consume(ptr [[DATA_RELOAD_ADDR]])
 ; CHECK-NEXT:    call void @consume2(ptr [[__PROMISE_RELOAD_ADDR]])
-; 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 64 dereferenceable(128) [[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-spill-suspend.ll 
b/llvm/test/Transforms/Coroutines/coro-spill-suspend.ll
index 35fd078643c89..33404ad587687 100644
--- a/llvm/test/Transforms/Coroutines/coro-spill-suspend.ll
+++ b/llvm/test/Transforms/Coroutines/coro-spill-suspend.ll
@@ -81,7 +81,8 @@ declare void @free(ptr)
 ; CHECK-NEXT:    [[SP1_RELOAD_ADDR:%.*]] = getelementptr inbounds i8, ptr 
[[HDL]], i64 17
 ; CHECK-NEXT:    [[SP1_RELOAD:%.*]] = load i8, ptr [[SP1_RELOAD_ADDR]], align 1
 ; CHECK-NEXT:    call void @print(i8 [[SP1_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:    br label %[[COROEND]]
 ; CHECK:       [[COROEND]]:
 ; CHECK-NEXT:    ret void
@@ -106,7 +107,8 @@ declare void @free(ptr)
 ; CHECK-NEXT:    [[SP1_RELOAD_ADDR:%.*]] = getelementptr inbounds i8, ptr 
[[HDL]], i64 17
 ; CHECK-NEXT:    [[SP1_RELOAD:%.*]] = load i8, ptr [[SP1_RELOAD_ADDR]], align 1
 ; CHECK-NEXT:    call void @print(i8 [[SP1_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:       [[UNREACHABLE]]:
 ; CHECK-NEXT:    unreachable
diff --git a/llvm/test/Transforms/Coroutines/coro-split-tbaa-md.ll 
b/llvm/test/Transforms/Coroutines/coro-split-tbaa-md.ll
index ec4c4c42769b0..3c1ba8a434665 100644
--- a/llvm/test/Transforms/Coroutines/coro-split-tbaa-md.ll
+++ b/llvm/test/Transforms/Coroutines/coro-split-tbaa-md.ll
@@ -87,14 +87,16 @@ declare void @free(ptr) willreturn allockind("free") 
"alloc-family"="malloc"
 ; CHECK-NEXT:    [[X_RELOAD_ADDR:%.*]] = getelementptr inbounds i8, ptr 
[[HDL]], i64 16
 ; CHECK-NEXT:    [[X_RELOAD:%.*]] = load i32, ptr [[X_RELOAD_ADDR]], align 4, 
!tbaa [[F_FRAME_SLOT_TBAA4:![0-9]+]]
 ; CHECK-NEXT:    call void @print(i32 [[X_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(24) [[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-zero-alloca.ll 
b/llvm/test/Transforms/Coroutines/coro-zero-alloca.ll
index 5a0de393007c0..63e784ab440d3 100644
--- a/llvm/test/Transforms/Coroutines/coro-zero-alloca.ll
+++ b/llvm/test/Transforms/Coroutines/coro-zero-alloca.ll
@@ -78,14 +78,16 @@ cleanup:                                          ; preds = 
%wakeup, %entry
 ; CHECK-NEXT:    call void @usePointer(ptr [[CORO_STATE]])
 ; CHECK-NEXT:    call void @usePointer(ptr [[A4_RELOAD_ADDR]])
 ; CHECK-NEXT:    call void @usePointer2(ptr [[CORO_STATE]])
-; CHECK-NEXT:    call void @free(ptr [[CORO_STATE]])
+; CHECK-NEXT:    [[CORO_MEMFREE:%.*]] = call ptr @llvm.coro.free(token poison, 
ptr [[CORO_STATE]])
+; CHECK-NEXT:    call void @free(ptr [[CORO_MEMFREE]])
 ; CHECK-NEXT:    ret void
 ;
 ;
 ; CHECK-LABEL: define internal fastcc void @foo.destroy(
 ; CHECK-SAME: ptr noundef nonnull align 8 dereferenceable(24) 
[[CORO_STATE:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY_DESTROY:.*:]]
-; CHECK-NEXT:    call void @free(ptr [[CORO_STATE]])
+; CHECK-NEXT:    [[CORO_MEMFREE:%.*]] = call ptr @llvm.coro.free(token poison, 
ptr [[CORO_STATE]])
+; CHECK-NEXT:    call void @free(ptr [[CORO_MEMFREE]])
 ; CHECK-NEXT:    ret void
 ;
 ;

>From acda43bb5ff9c40d78dda484beb8b7a66dcc8540 Mon Sep 17 00:00:00 2001
From: NewSigma <[email protected]>
Date: Sun, 8 Mar 2026 17:22:40 +0800
Subject: [PATCH 2/4] [Coroutines][NFC] Elide coro.free based on frame instead
 of coro.id

---
 llvm/lib/Transforms/Coroutines/CoroElide.cpp  |  2 +-
 llvm/lib/Transforms/Coroutines/CoroInternal.h |  2 +-
 llvm/lib/Transforms/Coroutines/CoroSplit.cpp  | 15 ++++++---------
 llvm/lib/Transforms/Coroutines/Coroutines.cpp | 12 ++++--------
 4 files changed, 12 insertions(+), 19 deletions(-)

diff --git a/llvm/lib/Transforms/Coroutines/CoroElide.cpp 
b/llvm/lib/Transforms/Coroutines/CoroElide.cpp
index 1c8d4a8592d60..3e260d48a5d88 100644
--- a/llvm/lib/Transforms/Coroutines/CoroElide.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroElide.cpp
@@ -228,6 +228,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();
   }
@@ -410,7 +411,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 295811b297984..d43519ab0d3e9 100644
--- a/llvm/lib/Transforms/Coroutines/CoroSplit.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroSplit.cpp
@@ -1102,8 +1102,7 @@ void coro::SwitchCloner::create() {
 
   // Replacing coro.free with 'null' in cleanup to suppress deallocation code.
   if (FKind == coro::CloneKind::SwitchCleanup)
-    coro::replaceCoroFree(cast<CoroIdInst>(VMap[Shape.CoroBegin->getId()]),
-                          /*Elide=*/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();

>From 06c51a689aac6cb1e706c30f2c6b3812fdf2069e Mon Sep 17 00:00:00 2001
From: NewSigma <[email protected]>
Date: Mon, 9 Mar 2026 20:42:08 +0800
Subject: [PATCH 3/4] [CoroElide][IR] Add llvm.coro.dead

---
 llvm/docs/Coroutines.rst                      | 44 ++++++++++++++
 llvm/include/llvm/IR/Intrinsics.td            |  1 +
 .../lib/Transforms/Coroutines/CoroCleanup.cpp | 14 +++--
 llvm/lib/Transforms/Coroutines/CoroElide.cpp  | 59 ++++++++++---------
 4 files changed, 84 insertions(+), 34 deletions(-)

diff --git a/llvm/docs/Coroutines.rst b/llvm/docs/Coroutines.rst
index 0e6b49c84acee..e643497d180ef 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,48 @@ 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)
+mark the end of lifetime of the coroutine frame.
+
+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 3e260d48a5d88..f2e44e59706da 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 &CoroDeads = 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);
+          CoroDeads.push_back(II); // coro.destroy implies coro.dead
+          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)
+        CoroDeads.push_back(II);
+    }
   }
 }
 
@@ -240,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());
@@ -250,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/destroy as visited one, so that we
+  // skip the path pass through it.
   for (auto *DA : It->second)
     Visited.insert(DA->getParent());
 
@@ -327,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
-  // referencing the SSA value of that coro.begin along each
+  // Check that for every coro.begin there is at least one coro.dead/destroy
+  // 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/destroy would have been referencing a
   // memory location storing that value and not the virtual register.
 
   SmallPtrSet<BasicBlock *, 8> Terminators;
@@ -347,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/destroy 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/destroy, 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());
       });
     };
@@ -371,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/destroy. This is a slower analysis.
     //
     // canCoroBeginEscape is relatively slow, so we avoid to run it as much as
     // possible.
@@ -401,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));
 

>From 6073c0ba4be9195819831c31926e5d849f04eb1b Mon Sep 17 00:00:00 2001
From: NewSigma <[email protected]>
Date: Sun, 8 Mar 2026 17:26:37 +0800
Subject: [PATCH 4/4] [clang][CodeGen] Emit coro.dead for coroutines

---
 clang/lib/CodeGen/CGCoroutine.cpp           |  3 +++
 clang/test/CodeGenCoroutines/coro-elide.cpp | 27 ++++++++++++++++++++-
 2 files changed, 29 insertions(+), 1 deletion(-)

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

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to