[clang] [clang][CodeGenCoroutine] Emit missing cleanup scope for lazy GRO conversion (PR #194281)
Weibo He via cfe-commits
cfe-commits at lists.llvm.org
Sun Apr 26 20:16:50 PDT 2026
https://github.com/NewSigma created https://github.com/llvm/llvm-project/pull/194281
In #151067, we promoted GRO ahead of `coro.end`. However, there is a regression that GRO cleanup might go into coroutine resume and destroy parts. This patch introduces a dedicated cleanup scope for GRO, ensuring that GRO cleanup does not interfere with other logic.
Close #193412
>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] [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
More information about the cfe-commits
mailing list