[clang] [llvm] [RFC][Coroutines] Implement HALO for coroutines that flow off final suspend (PR #185336)
Weibo He via cfe-commits
cfe-commits at lists.llvm.org
Mon Mar 9 19:17:03 PDT 2026
https://github.com/NewSigma updated https://github.com/llvm/llvm-project/pull/185336
>From b42003f98bf322e82c31cc9ca229025f8c3be27a Mon Sep 17 00:00:00 2001
From: NewSigma <NewSigma at 163.com>
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 <NewSigma at 163.com>
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 <NewSigma at 163.com>
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 <NewSigma at 163.com>
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
More information about the cfe-commits
mailing list