[clang] [clang][CodeGenCoroutine] Emit missing cleanup scope for lazy GRO conversion (PR #194281)
Weibo He via cfe-commits
cfe-commits at lists.llvm.org
Mon Apr 27 02:37:26 PDT 2026
https://github.com/NewSigma updated https://github.com/llvm/llvm-project/pull/194281
>From 9784315537fc3431ff9cf77b860cef2e8d31a901 Mon Sep 17 00:00:00 2001
From: NewSigma <NewSigma at 163.com>
Date: Thu, 23 Apr 2026 21:06:43 +0800
Subject: [PATCH 1/2] [clang][CodeGenCoroutine] Emit missing cleanup scope for
lazy GRO conversion
---
clang/lib/CodeGen/CGCoroutine.cpp | 19 +++++---
.../coro-await-resume-eh.cpp | 3 +-
.../test/CodeGenCoroutines/coro-dest-slot.cpp | 8 +---
clang/test/CodeGenCoroutines/coro-gro.cpp | 13 +++---
clang/test/CodeGenCoroutines/coro-gro4.cpp | 46 +++++++++++++++++++
.../coro-suspend-cleanups.cpp | 2 +-
6 files changed, 69 insertions(+), 22 deletions(-)
create mode 100644 clang/test/CodeGenCoroutines/coro-gro4.cpp
diff --git a/clang/lib/CodeGen/CGCoroutine.cpp b/clang/lib/CodeGen/CGCoroutine.cpp
index 9d9d2450c3d68..14d297a646d45 100644
--- a/clang/lib/CodeGen/CGCoroutine.cpp
+++ b/clang/lib/CodeGen/CGCoroutine.cpp
@@ -713,14 +713,14 @@ struct GetReturnObjectManager {
Builder.CreateStore(Builder.getFalse(), GroActiveFlag);
}
- void EmitGroAlloca() {
+ std::unique_ptr<CodeGenFunction::RunCleanupsScope> EmitGroAlloca() {
if (DirectEmit)
- return;
+ return nullptr;
auto *GroDeclStmt = dyn_cast_or_null<DeclStmt>(S.getResultDecl());
if (!GroDeclStmt) {
// If get_return_object returns void, no need to do an alloca.
- return;
+ return nullptr;
}
auto *GroVarDecl = cast<VarDecl>(GroDeclStmt->getSingleDecl());
@@ -736,6 +736,7 @@ struct GetReturnObjectManager {
llvm::MDNode::get(CGF.CGM.getLLVMContext(), {}));
}
+ auto GroScope = std::make_unique<CodeGenFunction::RunCleanupsScope>(CGF);
// Remember the top of EHStack before emitting the cleanup.
auto old_top = CGF.EHStack.stable_begin();
CGF.EmitAutoVarCleanups(GroEmission);
@@ -751,6 +752,7 @@ struct GetReturnObjectManager {
Cleanup->setTestFlagInNormalCleanup();
}
}
+ return GroScope;
}
void EmitGroInit() {
@@ -878,6 +880,7 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
auto *AllocBB = createBasicBlock("coro.alloc");
auto *InitBB = createBasicBlock("coro.init");
auto *FinalBB = createBasicBlock("coro.final");
+ auto *CleanupBB = createBasicBlock("coro.cleanup");
auto *RetBB = createBasicBlock("coro.ret");
auto *CoroId = Builder.CreateCall(
@@ -930,8 +933,6 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
auto *CoroBegin = Builder.CreateCall(
CGM.getIntrinsic(llvm::Intrinsic::coro_begin), {CoroId, Phi});
CurCoro.Data->CoroBegin = CoroBegin;
-
- CurCoro.Data->CleanupJD = getJumpDestInCurrentScope(RetBB);
{
CGDebugInfo *DI = getDebugInfo();
ParamReferenceReplacerRAII ParamReplacer(LocalDeclMap);
@@ -982,11 +983,12 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
CoroId->setArgOperand(1, PromiseAddr.emitRawPointer(*this));
// Now we have the promise, initialize the GRO
- GroManager.EmitGroAlloca();
+ auto GroScope = GroManager.EmitGroAlloca();
GroManager.EmitGroInit();
EHStack.pushCleanup<CallCoroEnd>(EHCleanup);
+ CurCoro.Data->CleanupJD = getJumpDestInCurrentScope(CleanupBB);
CurCoro.Data->CurrentAwaitKind = AwaitKind::Init;
CurCoro.Data->ExceptionHandler = S.getExceptionHandler();
EmitStmt(S.getInitSuspendStmt());
@@ -1041,8 +1043,11 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
// We need conversion if get_return_object's type doesn't matches the
// coroutine return type.
- if (!GroManager.DirectEmit)
+ if (!GroManager.DirectEmit) {
GroManager.EmitGroConv(RetBB);
+ GroScope->ForceCleanup();
+ }
+ EmitBlock(CleanupBB);
}
EmitBlock(RetBB);
diff --git a/clang/test/CodeGenCoroutines/coro-await-resume-eh.cpp b/clang/test/CodeGenCoroutines/coro-await-resume-eh.cpp
index 8253337ba7390..da1ce8a5f174b 100644
--- a/clang/test/CodeGenCoroutines/coro-await-resume-eh.cpp
+++ b/clang/test/CodeGenCoroutines/coro-await-resume-eh.cpp
@@ -58,8 +58,9 @@ throwing_task f() {
// CHECK: [[RESUMETRYCONT]]:
// CHECK-NEXT: br label %[[CLEANUP:.+]]
// CHECK: [[CLEANUP]]:
- // CHECK: switch i32 %{{.+}}, label %{{.+}} [
+ // CHECK: switch i32 %{{.+}}, label %unreachable [
// CHECK-NEXT: i32 0, label %[[CLEANUPCONT:.+]]
+ // CHECK-NEXT: i32 2, label %{{.+}}
// CHECK-NEXT: ]
// The variable RESUMETHREW is loaded and if true, then 'await_resume'
diff --git a/clang/test/CodeGenCoroutines/coro-dest-slot.cpp b/clang/test/CodeGenCoroutines/coro-dest-slot.cpp
index d794c74cd52d6..2b78d4e71a97d 100644
--- a/clang/test/CodeGenCoroutines/coro-dest-slot.cpp
+++ b/clang/test/CodeGenCoroutines/coro-dest-slot.cpp
@@ -32,9 +32,5 @@ extern "C" coro f(int) { co_return; }
// CHECK: call void @_ZNSt13suspend_never12await_resumeEv(
// CHECK: %[[CLEANUP_DEST1:.+]] = phi i32 [ 0, %[[FINAL_READY]] ], [ 2, %[[FINAL_CLEANUP]] ]
-// CHECK: %[[CLEANUP_DEST2:.+]] = phi i32 [ %[[CLEANUP_DEST0]], %{{.+}} ], [ %[[CLEANUP_DEST1]], %{{.+}} ], [ 0, %{{.+}} ]
-// CHECK: call ptr @llvm.coro.free(
-// CHECK: switch i32 %[[CLEANUP_DEST2]], label %{{.+}} [
-// CHECK-NEXT: i32 0
-// CHECK-NEXT: i32 2
-// CHECK-NEXT: ]
+
+// CHECK-NOT: phi i32
diff --git a/clang/test/CodeGenCoroutines/coro-gro.cpp b/clang/test/CodeGenCoroutines/coro-gro.cpp
index 3ba6f349fdde1..02f20c1a64fda 100644
--- a/clang/test/CodeGenCoroutines/coro-gro.cpp
+++ b/clang/test/CodeGenCoroutines/coro-gro.cpp
@@ -66,23 +66,22 @@ int f() {
// CHECK-NEXT: br label %[[AfterGroConv]]
// CHECK: [[AfterGroConv]]:
- // CHECK-NEXT: br i1 %[[IsFinalExit]], label %cleanup.cont10, label %[[CoroRet:.+]]
-
- // CHECK: cleanup.cont10:
- // CHECK-NEXT: br label %[[Cleanup:.+]]
+ // CHECK-NEXT: br i1 %[[IsFinalExit]], label %[[Cleanup:.+]], label %[[CoroRet:.+]]
// CHECK: [[Cleanup]]:
- // CHECK-NEXT: %{{.*}} = phi i32
// CHECK-NEXT: %[[IsActive:.+]] = load i1, ptr %[[GroActive]]
// CHECK-NEXT: br i1 %[[IsActive]], label %[[CleanupGro:.+]], label %[[Done:.+]]
// CHECK: [[CleanupGro]]:
// CHECK-NEXT: call void @_ZN7GroTypeD1Ev(
- // CHECK-NEXT: br label %[[Done]]
+ // CHECK-NEXT: br label %cleanup.done
+
+ // CHECK: cleanup.done:
+ // CHECK-NEXT: br label %coro.cleanup
// Destroy promise and free the memory.
- // CHECK: [[Done]]:
+ // CHECK: coro.cleanup:
// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %[[CoroGro]])
// CHECK-NEXT: call void @_ZNSt16coroutine_traitsIiJEE12promise_typeD1Ev(
// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %[[Promise]])
diff --git a/clang/test/CodeGenCoroutines/coro-gro4.cpp b/clang/test/CodeGenCoroutines/coro-gro4.cpp
new file mode 100644
index 0000000000000..e4c0b980e019d
--- /dev/null
+++ b/clang/test/CodeGenCoroutines/coro-gro4.cpp
@@ -0,0 +1,46 @@
+// Test that the GRO destructor does not enter the resume or destroy parts
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -O2 -disable-llvm-passes -emit-llvm %s -o - | opt -passes='default<O0>,default<O2>' -S | FileCheck %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -O2 -emit-llvm %s -o - | FileCheck %s
+
+#include "Inputs/coroutine.h"
+
+extern "C" void gro_destroy() noexcept;
+
+struct task {
+ struct promise_type {
+ task get_return_object() noexcept { return task{std::coroutine_handle<promise_type>::from_promise(*this)}; }
+ std::suspend_always initial_suspend() noexcept { return {}; }
+ std::suspend_always final_suspend() noexcept { return {}; }
+ void return_void() noexcept {}
+ void unhandled_exception() {}
+ };
+
+ task(std::coroutine_handle<promise_type> handle) : m_coro(handle) {}
+ task(task &&o) noexcept : m_coro(o.m_coro) { o.m_coro = nullptr; }
+ task(const task&) = delete;
+ task& operator=(const task&) = delete;
+ task& operator=(task&&) = delete;
+
+ ~task() {
+ gro_destroy();
+ if (m_coro)
+ m_coro.destroy();
+ }
+
+ std::coroutine_handle<promise_type> m_coro;
+};
+
+struct wrapper {
+ using promise_type = task::promise_type;
+
+ wrapper(task &&t) noexcept : m_task(static_cast<task&&>(t)) {}
+
+ task m_task;
+};
+
+wrapper fn() { co_return; }
+
+// CHECK: define internal fastcc void @_Z2fnv.resume
+// CHECK-NOT: call void @gro_destroy()
+// CHECK: define internal fastcc void @_Z2fnv.destroy
+// CHECK-NOT: call void @gro_destroy()
diff --git a/clang/test/CodeGenCoroutines/coro-suspend-cleanups.cpp b/clang/test/CodeGenCoroutines/coro-suspend-cleanups.cpp
index 5872a75b26de5..6c05d02d0642f 100644
--- a/clang/test/CodeGenCoroutines/coro-suspend-cleanups.cpp
+++ b/clang/test/CodeGenCoroutines/coro-suspend-cleanups.cpp
@@ -50,7 +50,7 @@ coroutine ArrayInitCoro() {
// CHECK-NEXT: store ptr %arrayinit.element, ptr %arrayinit.endOfInit.reload.addr, align 8
co_await Awaiter{}
// CHECK-NEXT: @_ZNSt14suspend_always11await_readyEv
- // CHECK-NEXT: br i1 %{{.+}}, label %await.ready, label %CoroSave30
+ // CHECK-NEXT: br i1 %{{.+}}, label %await.ready, label %{{CoroSave[0-9]+}}
};
// CHECK: await.cleanup: ; preds = %AfterCoroSuspend{{.*}}
// CHECK-NEXT: br label %cleanup{{.*}}.from.await.cleanup
>From f9d68460a6949f228255c8a8adac870aff624905 Mon Sep 17 00:00:00 2001
From: NewSigma <NewSigma at 163.com>
Date: Mon, 27 Apr 2026 17:29:25 +0800
Subject: [PATCH 2/2] Add positive test
---
clang/lib/CodeGen/CGCoroutine.cpp | 17 ++++++++---------
clang/test/CodeGenCoroutines/coro-gro.cpp | 13 +++++--------
clang/test/CodeGenCoroutines/coro-gro4.cpp | 5 +++++
3 files changed, 18 insertions(+), 17 deletions(-)
diff --git a/clang/lib/CodeGen/CGCoroutine.cpp b/clang/lib/CodeGen/CGCoroutine.cpp
index 14d297a646d45..2972f015aa893 100644
--- a/clang/lib/CodeGen/CGCoroutine.cpp
+++ b/clang/lib/CodeGen/CGCoroutine.cpp
@@ -661,6 +661,7 @@ struct GetReturnObjectManager {
Address GroActiveFlag;
CodeGenFunction::AutoVarEmission GroEmission;
+ std::unique_ptr<CodeGenFunction::RunCleanupsScope> GroScope;
GetReturnObjectManager(CodeGenFunction &CGF, const CoroutineBodyStmt &S)
: CGF(CGF), Builder(CGF.Builder), S(S), GroActiveFlag(Address::invalid()),
@@ -713,14 +714,14 @@ struct GetReturnObjectManager {
Builder.CreateStore(Builder.getFalse(), GroActiveFlag);
}
- std::unique_ptr<CodeGenFunction::RunCleanupsScope> EmitGroAlloca() {
+ void EmitGroAlloca() {
if (DirectEmit)
- return nullptr;
+ return;
auto *GroDeclStmt = dyn_cast_or_null<DeclStmt>(S.getResultDecl());
if (!GroDeclStmt) {
// If get_return_object returns void, no need to do an alloca.
- return nullptr;
+ return;
}
auto *GroVarDecl = cast<VarDecl>(GroDeclStmt->getSingleDecl());
@@ -736,7 +737,7 @@ struct GetReturnObjectManager {
llvm::MDNode::get(CGF.CGM.getLLVMContext(), {}));
}
- auto GroScope = std::make_unique<CodeGenFunction::RunCleanupsScope>(CGF);
+ GroScope = std::make_unique<CodeGenFunction::RunCleanupsScope>(CGF);
// Remember the top of EHStack before emitting the cleanup.
auto old_top = CGF.EHStack.stable_begin();
CGF.EmitAutoVarCleanups(GroEmission);
@@ -752,7 +753,6 @@ struct GetReturnObjectManager {
Cleanup->setTestFlagInNormalCleanup();
}
}
- return GroScope;
}
void EmitGroInit() {
@@ -850,6 +850,7 @@ struct GetReturnObjectManager {
CGF.EmitAnyExprToMem(S.getReturnValue(), CGF.ReturnValue,
S.getReturnValue()->getType().getQualifiers(),
/*IsInit*/ true);
+ GroScope->ForceCleanup();
Builder.CreateBr(AfterConvBB);
CGF.EmitBlock(AfterConvBB);
@@ -983,7 +984,7 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
CoroId->setArgOperand(1, PromiseAddr.emitRawPointer(*this));
// Now we have the promise, initialize the GRO
- auto GroScope = GroManager.EmitGroAlloca();
+ GroManager.EmitGroAlloca();
GroManager.EmitGroInit();
EHStack.pushCleanup<CallCoroEnd>(EHCleanup);
@@ -1043,10 +1044,8 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
// We need conversion if get_return_object's type doesn't matches the
// coroutine return type.
- if (!GroManager.DirectEmit) {
+ if (!GroManager.DirectEmit)
GroManager.EmitGroConv(RetBB);
- GroScope->ForceCleanup();
- }
EmitBlock(CleanupBB);
}
diff --git a/clang/test/CodeGenCoroutines/coro-gro.cpp b/clang/test/CodeGenCoroutines/coro-gro.cpp
index 02f20c1a64fda..99b0afcedccf6 100644
--- a/clang/test/CodeGenCoroutines/coro-gro.cpp
+++ b/clang/test/CodeGenCoroutines/coro-gro.cpp
@@ -63,20 +63,17 @@ int f() {
// CHECK: [[GroConv]]:
// CHECK-NEXT: %[[Conv:.+]] = call noundef i32 @_ZN7GroTypecviEv(
// CHECK-NEXT: store i32 %[[Conv]], ptr %[[RetVal]]
- // CHECK-NEXT: br label %[[AfterGroConv]]
-
- // CHECK: [[AfterGroConv]]:
- // CHECK-NEXT: br i1 %[[IsFinalExit]], label %[[Cleanup:.+]], label %[[CoroRet:.+]]
-
- // CHECK: [[Cleanup]]:
// CHECK-NEXT: %[[IsActive:.+]] = load i1, ptr %[[GroActive]]
- // CHECK-NEXT: br i1 %[[IsActive]], label %[[CleanupGro:.+]], label %[[Done:.+]]
+ // CHECK-NEXT: br i1 %[[IsActive]], label %[[CleanupGro:.+]], label %{{.*}}
// CHECK: [[CleanupGro]]:
// CHECK-NEXT: call void @_ZN7GroTypeD1Ev(
// CHECK-NEXT: br label %cleanup.done
- // CHECK: cleanup.done:
+ // CHECK: after.gro.conv:
+ // CHECK-NEXT: br i1 %[[IsFinalExit]], label %cleanup.cont10, label %[[CoroRet:.+]]
+
+ // CHECK: cleanup.cont10:
// CHECK-NEXT: br label %coro.cleanup
// Destroy promise and free the memory.
diff --git a/clang/test/CodeGenCoroutines/coro-gro4.cpp b/clang/test/CodeGenCoroutines/coro-gro4.cpp
index e4c0b980e019d..4ee15bb688981 100644
--- a/clang/test/CodeGenCoroutines/coro-gro4.cpp
+++ b/clang/test/CodeGenCoroutines/coro-gro4.cpp
@@ -40,7 +40,12 @@ struct wrapper {
wrapper fn() { co_return; }
+// CHECK: define dso_local void @_Z2fnv
+// CHECK: call void @gro_destroy()
+// CHECK: ret void
+
// CHECK: define internal fastcc void @_Z2fnv.resume
// CHECK-NOT: call void @gro_destroy()
+
// CHECK: define internal fastcc void @_Z2fnv.destroy
// CHECK-NOT: call void @gro_destroy()
More information about the cfe-commits
mailing list