Skip to content

Commit 1999c90

Browse files
committed
[clang][CodeGen] Extends lifetime of coroutine promise(CWG2563)
1 parent 2b3b718 commit 1999c90

File tree

6 files changed

+154
-25
lines changed

6 files changed

+154
-25
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,9 @@ C++20 Feature Support
169169
As a result, Clang no longer incorrectly diagnoses substitution failures in template arguments only
170170
used in concept-ids, and produces better diagnostics for satisfaction failure. (#GH61811) (#GH135190)
171171

172+
- Fixed premature coroutine promise destruction when the coroutine completes
173+
without suspending, as part of CWG2563. (#GH120200)
174+
172175
C++17 Feature Support
173176
^^^^^^^^^^^^^^^^^^^^^
174177

clang/lib/CodeGen/CGCoroutine.cpp

Lines changed: 86 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "clang/AST/StmtCXX.h"
1717
#include "clang/AST/StmtVisitor.h"
1818
#include "llvm/ADT/ScopeExit.h"
19+
#include "llvm/Transforms/Utils/Cloning.h"
1920

2021
using namespace clang;
2122
using namespace CodeGen;
@@ -73,6 +74,13 @@ struct clang::CodeGen::CGCoroData {
7374
// the address of the coroutine frame of the current coroutine.
7475
llvm::CallInst *CoroBegin = nullptr;
7576

77+
// Stores the cloned cleanup block.
78+
BasicBlock *RampCleanupBB = nullptr;
79+
80+
// Stores the llvm.coro.end that identifies if the coroutine exit without
81+
// suspend.
82+
llvm::CallInst *InRamp = nullptr;
83+
7684
// Stores the last emitted coro.free for the deallocate expressions, we use it
7785
// to wrap dealloc code with if(auto mem = coro.free) dealloc(mem).
7886
llvm::CallInst *LastCoroFree = nullptr;
@@ -597,7 +605,7 @@ struct CallCoroEnd final : public EHScopeStack::Cleanup {
597605
namespace {
598606
// Make sure to call coro.delete on scope exit.
599607
struct CallCoroDelete final : public EHScopeStack::Cleanup {
600-
Stmt *Deallocate;
608+
const CoroutineBodyStmt *S;
601609

602610
// Emit "if (coro.free(CoroId, CoroBegin)) Deallocate;"
603611

@@ -607,16 +615,54 @@ struct CallCoroDelete final : public EHScopeStack::Cleanup {
607615
// builds a single call to a deallocation function which is safe to emit
608616
// multiple times.
609617
void Emit(CodeGenFunction &CGF, Flags) override {
618+
bool Splited = CGF.CurCoro.Data->InRamp != nullptr;
619+
if (!Splited)
620+
splitCleanupBB(CGF);
621+
EmitCoroFree(CGF);
622+
}
623+
624+
// Clone the cleanup block for ramp function if the coroutine completes
625+
// without suspending
626+
void splitCleanupBB(CodeGenFunction &CGF) {
627+
auto &Builder = CGF.Builder;
628+
auto &CoroData = *CGF.CurCoro.Data;
629+
auto *SaveInsertPt = Builder.GetInsertBlock();
630+
auto *PreCleanupBB = SaveInsertPt->getSinglePredecessor();
631+
632+
auto *CleanupBB =
633+
PreCleanupBB->splitBasicBlock(PreCleanupBB->begin(), "coro.cleanup");
634+
635+
PreCleanupBB->getTerminator()->eraseFromParent();
636+
Builder.SetInsertPoint(PreCleanupBB);
637+
638+
llvm::Function *CoroInRamp =
639+
CGF.CGM.getIntrinsic(llvm::Intrinsic::coro_is_in_ramp);
640+
CoroData.InRamp = Builder.CreateCall(CoroInRamp, {}, "InRamp");
641+
642+
BasicBlock *EndBB = CoroData.CleanupJD.getBlock();
643+
Builder.CreateCondBr(CoroData.InRamp, EndBB, CleanupBB);
644+
Builder.SetInsertPoint(SaveInsertPt);
645+
646+
if (S->getReturnStmt()) {
647+
// Clone cleanup block before EmitCoroFree()
648+
llvm::ValueToValueMapTy VMap{};
649+
CoroData.RampCleanupBB =
650+
llvm::CloneBasicBlock(CleanupBB, VMap, ".ramp");
651+
}
652+
}
653+
654+
void EmitCoroFree(CodeGenFunction &CGF, const Twine &NameSuffix = "") {
610655
// Remember the current point, as we are going to emit deallocation code
611656
// first to get to coro.free instruction that is an argument to a delete
612657
// call.
613658
BasicBlock *SaveInsertBlock = CGF.Builder.GetInsertBlock();
614659

615-
auto *FreeBB = CGF.createBasicBlock("coro.free");
660+
Stmt *Deallocate = S->getDeallocate();
661+
auto *FreeBB = CGF.createBasicBlock("coro.free" + NameSuffix);
616662
CGF.EmitBlock(FreeBB);
617663
CGF.EmitStmt(Deallocate);
618664

619-
auto *AfterFreeBB = CGF.createBasicBlock("after.coro.free");
665+
auto *AfterFreeBB = CGF.createBasicBlock("after.coro.free" + NameSuffix);
620666
CGF.EmitBlock(AfterFreeBB);
621667

622668
// We should have captured coro.free from the emission of deallocate.
@@ -641,7 +687,7 @@ struct CallCoroDelete final : public EHScopeStack::Cleanup {
641687
InsertPt->eraseFromParent();
642688
CGF.Builder.SetInsertPoint(AfterFreeBB);
643689
}
644-
explicit CallCoroDelete(Stmt *DeallocStmt) : Deallocate(DeallocStmt) {}
690+
explicit CallCoroDelete(const CoroutineBodyStmt *S) : S(S) {}
645691
};
646692
}
647693

@@ -789,13 +835,13 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
789835
auto *AllocBB = createBasicBlock("coro.alloc");
790836
auto *InitBB = createBasicBlock("coro.init");
791837
auto *FinalBB = createBasicBlock("coro.final");
792-
auto *RetBB = createBasicBlock("coro.ret");
838+
auto *EndBB = createBasicBlock("coro.end");
793839

794840
auto *CoroId = Builder.CreateCall(
795841
CGM.getIntrinsic(llvm::Intrinsic::coro_id),
796842
{Builder.getInt32(NewAlign), NullPtr, NullPtr, NullPtr});
797843
createCoroData(*this, CurCoro, CoroId);
798-
CurCoro.Data->SuspendBB = RetBB;
844+
CurCoro.Data->SuspendBB = EndBB;
799845
assert(ShouldEmitLifetimeMarkers &&
800846
"Must emit lifetime intrinsics for coroutines");
801847

@@ -831,23 +877,25 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
831877

832878
EmitBlock(InitBB);
833879

834-
// Pass the result of the allocation to coro.begin.
835-
auto *Phi = Builder.CreatePHI(VoidPtrTy, 2);
836-
Phi->addIncoming(NullPtr, EntryBB);
837-
Phi->addIncoming(AllocateCall, AllocOrInvokeContBB);
838-
auto *CoroBegin = Builder.CreateCall(
839-
CGM.getIntrinsic(llvm::Intrinsic::coro_begin), {CoroId, Phi});
840-
CurCoro.Data->CoroBegin = CoroBegin;
880+
{
881+
// Pass the result of the allocation to coro.begin.
882+
auto *Phi = Builder.CreatePHI(VoidPtrTy, 2);
883+
Phi->addIncoming(NullPtr, EntryBB);
884+
Phi->addIncoming(AllocateCall, AllocOrInvokeContBB);
885+
auto *CoroBegin = Builder.CreateCall(
886+
CGM.getIntrinsic(llvm::Intrinsic::coro_begin), {CoroId, Phi});
887+
CurCoro.Data->CoroBegin = CoroBegin;
888+
}
841889

842890
GetReturnObjectManager GroManager(*this, S);
843891
GroManager.EmitGroAlloca();
844892

845-
CurCoro.Data->CleanupJD = getJumpDestInCurrentScope(RetBB);
893+
CurCoro.Data->CleanupJD = getJumpDestInCurrentScope(EndBB);
846894
{
847895
CGDebugInfo *DI = getDebugInfo();
848896
ParamReferenceReplacerRAII ParamReplacer(LocalDeclMap);
849897
CodeGenFunction::RunCleanupsScope ResumeScope(*this);
850-
EHStack.pushCleanup<CallCoroDelete>(NormalAndEHCleanup, S.getDeallocate());
898+
EHStack.pushCleanup<CallCoroDelete>(NormalAndEHCleanup, &S);
851899

852900
// Create mapping between parameters and copy-params for coroutine function.
853901
llvm::ArrayRef<const Stmt *> ParamMoves = S.getParamMoves();
@@ -952,7 +1000,14 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
9521000
}
9531001
}
9541002

955-
EmitBlock(RetBB);
1003+
EmitBlock(EndBB);
1004+
auto *Phi = Builder.CreatePHI(Builder.getInt1Ty(), llvm::pred_size(EndBB),
1005+
"never.suspend");
1006+
BasicBlock *CleanupBB = CurCoro.Data->InRamp->getParent();
1007+
for (auto *Pred : llvm::predecessors(EndBB)) {
1008+
auto *V = (Pred == CleanupBB) ? Builder.getTrue() : Builder.getFalse();
1009+
Phi->addIncoming(V, Pred);
1010+
}
9561011
// Emit coro.end before getReturnStmt (and parameter destructors), since
9571012
// resume and destroy parts of the coroutine should not include them.
9581013
llvm::Function *CoroEnd = CGM.getIntrinsic(llvm::Intrinsic::coro_end);
@@ -973,7 +1028,21 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
9731028
// shouldn't change the AST.
9741029
if (PreviousRetValue)
9751030
cast<ReturnStmt>(Ret)->setRetValue(PreviousRetValue);
976-
}
1031+
1032+
// Emit cleanup for ramp function
1033+
auto *RampCleanupBB = CurCoro.Data->RampCleanupBB;
1034+
auto *RetBB = EndBB->splitBasicBlock(EndBB->getTerminator(), "coro.ret");
1035+
EndBB->getTerminator()->eraseFromParent();
1036+
1037+
Builder.SetInsertPoint(EndBB);
1038+
Builder.CreateCondBr(Phi, RampCleanupBB, RetBB);
1039+
1040+
EmitBlock(RampCleanupBB);
1041+
CallCoroDelete(&S).EmitCoroFree(*this, ".ramp");
1042+
Builder.CreateBr(RetBB);
1043+
Builder.ClearInsertionPoint();
1044+
} else
1045+
EndBB->setName("coro.ret");
9771046

9781047
// LLVM require the frontend to mark the coroutine.
9791048
CurFn->setPresplitCoroutine();

clang/test/CodeGenCXX/type-aware-coroutines.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ extern "C" resumable f1(int) {
5555
co_return;
5656
// CHECK: coro.alloc:
5757
// CHECK: _ZN9resumable12promise_typenwEmi
58-
// CHECK: coro.free:
58+
// CHECK: coro.free.ramp:
5959
// CHECK: _ZN9resumable12promise_typedlEPv
6060
}
6161

@@ -64,7 +64,7 @@ extern "C" resumable f2(float) {
6464
co_return;
6565
// CHECK: coro.alloc:
6666
// CHECK: _ZN9resumable12promise_typenwEmf
67-
// CHECK: coro.free:
67+
// CHECK: coro.free.ramp:
6868
// CHECK: _ZN9resumable12promise_typedlEPv
6969
}
7070

@@ -74,7 +74,7 @@ extern "C" resumable2 f3(int, float, const char*, Allocator) {
7474
co_return;
7575
// CHECK: coro.alloc:
7676
// CHECK: _ZN10resumable212promise_typenwIJifPKc9AllocatorEEEPvmDpT_
77-
// CHECK: coro.free:
77+
// CHECK: coro.free.ramp:
7878
// CHECK: _ZN10resumable212promise_typedlEPv
7979
// CHECK: _ZN10resumable212promise_typedlEPv
8080
}
@@ -84,7 +84,7 @@ extern "C" resumable f4(int n = 10) {
8484
for (int i = 0; i < n; i++) co_yield i;
8585
// CHECK: coro.alloc:
8686
// CHECK: call {{.*}}@_ZN9resumable12promise_typenwEmi(
87-
// CHECK: coro.free:
87+
// CHECK: coro.free.ramp:
8888
// CHECK: call void @_ZN9resumable12promise_typedlEPv(
8989
// CHECK: call void @_ZN9resumable12promise_typedlEPv(
9090
}
@@ -110,7 +110,7 @@ extern "C" resumable3 f5(float) {
110110
co_return;
111111
// CHECK: coro.alloc:
112112
// CHECK: call {{.*}}@_Znwm(
113-
// CHECK: coro.free:
113+
// CHECK: coro.free.ramp:
114114
// CHECK: call void @_ZdlPvm(
115115
// CHECK: call void @_ZdlPvm(
116116
}

clang/test/CodeGenCoroutines/coro-dest-slot.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ extern "C" coro f(int) { co_return; }
2525
// CHECK: %[[CLEANUP_DEST0:.+]] = phi i32 [ 0, %[[INIT_READY]] ], [ 2, %[[INIT_CLEANUP]] ]
2626

2727
// CHECK: %[[FINAL_SUSPEND:.+]] = call i8 @llvm.coro.suspend(
28-
// CHECK-NEXT: switch i8 %{{.*}}, label %coro.ret [
28+
// CHECK-NEXT: switch i8 %{{.*}}, label %coro.end [
2929
// CHECK-NEXT: i8 0, label %[[FINAL_READY:.+]]
3030
// CHECK-NEXT: i8 1, label %[[FINAL_CLEANUP:.+]]
3131
// CHECK-NEXT: ]
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Test that we do not destroy the coroutine promise before coroutine completion, even if the coroutine never suspends.
2+
// RUN: %clang_cc1 -std=c++20 -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s \
3+
// RUN: -fexceptions -fcxx-exceptions -disable-llvm-passes | FileCheck %s --check-prefix=CHECK-O0
4+
// RUN: %clang_cc1 -std=c++20 -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s \
5+
// RUN: -fexceptions -fcxx-exceptions -O2 | FileCheck %s --check-prefix=CHECK-O2
6+
7+
#include "Inputs/coroutine.h"
8+
9+
template<class T>
10+
struct coro {
11+
struct RValueWrapper {
12+
T* p;
13+
14+
operator T&&() const noexcept { return static_cast<T&&>(*p); }
15+
};
16+
17+
using promise_type = T;
18+
19+
T& getDerived() noexcept { return *static_cast<T*>(this); }
20+
21+
auto get_return_object() noexcept { return RValueWrapper(&getDerived()); }
22+
std::suspend_never initial_suspend() noexcept { return {}; }
23+
std::suspend_never final_suspend() noexcept { return {}; }
24+
void return_value(T&& x) noexcept { getDerived() = static_cast<T&&>(x); }
25+
void unhandled_exception() {}
26+
};
27+
28+
struct A : public coro<A> {
29+
int a;
30+
31+
~A() {}
32+
};
33+
34+
A func() {
35+
A aa{};
36+
aa.a = 5;
37+
co_return static_cast<A&&>(aa);
38+
}
39+
40+
// CHECK-O0-LABEL: define {{.*}} void @_Z4funcv(
41+
// CHECK-O0: coro.end:
42+
// CHECK-O0-NEXT: %never.suspend = phi i1 [ false, %cleanup.cont16 ], [ true, %cleanup12 ], [ false, %after.coro.free ], [ false, %final.suspend ], [ false, %init.suspend ]
43+
// CHECK-O0-NEXT: call void @llvm.coro.end
44+
// CHECK-O0-NEXT: ptr @_ZNK4coroI1AE13RValueWrappercvOS0_Ev
45+
// CHECK-O0-NEXT: call void @llvm.memcpy.p0.p0.i64
46+
// CHECK-O0-NEXT: br i1 %never.suspend, label %coro.cleanup.ramp, label %coro.ret
47+
48+
// CHECK-O0: coro.cleanup.ramp:
49+
// CHECK-O0-NEXT: call void @_ZN1AD1Ev
50+
// CHECK-O0-NEXT: call void @llvm.lifetime.end.p0
51+
// CHECK-O0-NEXT: call ptr @llvm.coro.free
52+
53+
// CHECK-O0: coro.ret:
54+
// CHECK-O0-NEXT: call void @llvm.lifetime.end.p0
55+
// CHECK-O0-NEXT: ret void
56+
57+
// CHECK-O2: store i32 5

clang/test/CodeGenCoroutines/coro-symmetric-transfer-02.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ Task bar() {
9191
// CHECK-NEXT: %{{.+}} = call token @llvm.coro.save(ptr null)
9292
// CHECK-NEXT: call void @llvm.coro.await.suspend.handle
9393
// CHECK-NEXT: %{{.+}} = call i8 @llvm.coro.suspend
94-
// CHECK-NEXT: switch i8 %{{.+}}, label %coro.ret [
94+
// CHECK-NEXT: switch i8 %{{.+}}, label %coro.end [
9595
// CHECK-NEXT: i8 0, label %[[CASE1_AWAIT_READY]]
9696
// CHECK-NEXT: i8 1, label %[[CASE1_AWAIT_CLEANUP:.+]]
9797
// CHECK-NEXT: ]
@@ -105,7 +105,7 @@ Task bar() {
105105
// CHECK-NEXT: %{{.+}} = call token @llvm.coro.save(ptr null)
106106
// CHECK-NEXT: call void @llvm.coro.await.suspend.handle
107107
// CHECK-NEXT: %{{.+}} = call i8 @llvm.coro.suspend
108-
// CHECK-NEXT: switch i8 %{{.+}}, label %coro.ret [
108+
// CHECK-NEXT: switch i8 %{{.+}}, label %coro.end [
109109
// CHECK-NEXT: i8 0, label %[[CASE2_AWAIT_READY]]
110110
// CHECK-NEXT: i8 1, label %[[CASE2_AWAIT_CLEANUP:.+]]
111111
// CHECK-NEXT: ]

0 commit comments

Comments
 (0)