Skip to content

Commit aa85989

Browse files
authored
[clang][CodeGen] Promote point of GRO(CWG2563) (#151067)
This patch implement piece of the proposed solution to [CWG2563](https://cplusplus.github.io/CWG/issues/2563.html): > [9.6.4 dcl.fct.def.coroutine.p8] This return exits the scope of gro. It exits the scope of promise only if the coroutine completed without suspending. If a coroutine completes without suspending, it does not exit the scope of the promise until GRO conversion is done, because GRO conversion is considered part of the coroutine execution. The current behavior performs conversion after coroutine state cleanup, which does not conform to the standard: ``` LLVM before.cleanup: ; ... br label %coro.cleanup coro.cleanup: ; cleanup logics br %coro.end coro.end: call void @llvm.coro.end(ptr null, i1 false, token none) ; GRO conversion ; ret GRO or void ``` This patch proposes the following codegen: ``` LLVM any.suspend: %suspend = call i8 @llvm.coro.suspend(token %8, i1 true) switch i8 %suspend, label %pre.gro.conv [ i8 0, label %ready i8 1, label %coro.cleanup ] ready: ; ... pre.gro.conv: %body.done = phi i1 [ false, %any.suspend ], [ true, %any.ready ] %InRamp = call i1 @llvm.coro.is_in_ramp() br i1 %InRamp, label %gro.conv, label %after.gro.conv gro.conv: ; GRO conversion br label %after.gro.conv after.gro.conv: br i1 %body.done, label %coro.cleanup, label %coro.ret coro.cleanup: ; cleanup logics br %coro.ret coro.ret: call void @llvm.coro.end(ptr null, i1 false, token none) ; ret GRO or void ``` Close #120200
1 parent 5e215e2 commit aa85989

File tree

2 files changed

+159
-45
lines changed

2 files changed

+159
-45
lines changed

clang/lib/CodeGen/CGCoroutine.cpp

Lines changed: 111 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ struct clang::CodeGen::CGCoroData {
4343

4444
// A branch to this block is emitted when coroutine needs to suspend.
4545
llvm::BasicBlock *SuspendBB = nullptr;
46+
// A branch to this block after final.cleanup or final.ready
47+
llvm::BasicBlock *FinalExit = nullptr;
4648

4749
// The promise type's 'unhandled_exception' handler, if it defines one.
4850
Stmt *ExceptionHandler = nullptr;
@@ -332,6 +334,8 @@ static LValueOrRValue emitSuspendExpression(CodeGenFunction &CGF, CGCoroData &Co
332334
// Emit cleanup for this suspend point.
333335
CGF.EmitBlock(CleanupBlock);
334336
CGF.EmitBranchThroughCleanup(Coro.CleanupJD);
337+
if (IsFinalSuspend)
338+
Coro.FinalExit = CleanupBlock->getSingleSuccessor();
335339

336340
// Emit await_resume expression.
337341
CGF.EmitBlock(ReadyBlock);
@@ -687,11 +691,11 @@ struct GetReturnObjectManager {
687691
}
688692

689693
// The gro variable has to outlive coroutine frame and coroutine promise, but,
690-
// it can only be initialized after coroutine promise was created, thus, we
691-
// split its emission in two parts. EmitGroAlloca emits an alloca and sets up
692-
// cleanups. Later when coroutine promise is available we initialize the gro
693-
// and sets the flag that the cleanup is now active.
694-
void EmitGroAlloca() {
694+
// it can only be initialized after coroutine promise was created. Thus,
695+
// EmitGroActive emits a flag and sets it to false. Later when coroutine
696+
// promise is available we initialize the gro and set the flag indicating that
697+
// the cleanup is now active.
698+
void EmitGroActive() {
695699
if (DirectEmit)
696700
return;
697701

@@ -701,12 +705,23 @@ struct GetReturnObjectManager {
701705
return;
702706
}
703707

704-
auto *GroVarDecl = cast<VarDecl>(GroDeclStmt->getSingleDecl());
705-
706708
// Set GRO flag that it is not initialized yet
707709
GroActiveFlag = CGF.CreateTempAlloca(Builder.getInt1Ty(), CharUnits::One(),
708710
"gro.active");
709711
Builder.CreateStore(Builder.getFalse(), GroActiveFlag);
712+
}
713+
714+
void EmitGroAlloca() {
715+
if (DirectEmit)
716+
return;
717+
718+
auto *GroDeclStmt = dyn_cast_or_null<DeclStmt>(S.getResultDecl());
719+
if (!GroDeclStmt) {
720+
// If get_return_object returns void, no need to do an alloca.
721+
return;
722+
}
723+
724+
auto *GroVarDecl = cast<VarDecl>(GroDeclStmt->getSingleDecl());
710725

711726
GroEmission = CGF.EmitAutoVarAlloca(*GroVarDecl);
712727

@@ -768,6 +783,78 @@ struct GetReturnObjectManager {
768783
CGF.EmitAutoVarInit(GroEmission);
769784
Builder.CreateStore(Builder.getTrue(), GroActiveFlag);
770785
}
786+
// The GRO returns either when it is first suspended or when it completes
787+
// without ever being suspended. The EmitGroConv function evaluates these
788+
// conditions and perform the conversion if needed.
789+
//
790+
// Before EmitGroConv():
791+
// final.exit:
792+
// switch i32 %cleanup.dest, label %destroy [
793+
// i32 0, label %after.ready
794+
// ]
795+
//
796+
// after.ready:
797+
// ; (empty)
798+
//
799+
// After EmitGroConv():
800+
// final.exit:
801+
// switch i32 %cleanup.dest, label %destroy [
802+
// i32 0, label %pre.gro.conv
803+
// ]
804+
//
805+
// pre.gro.conv:
806+
// %IsFinalExit = phi i1 [ false, %any.suspend ], [ true, %final.exit ]
807+
// %InRamp = call i1 @llvm.coro.is_in_ramp()
808+
// br i1 %InRamp, label %gro.conv, label %after.gro.conv
809+
//
810+
// gro.conv:
811+
// ; GRO conversion
812+
// br label %after.gro.conv
813+
//
814+
// after.gro.conv:
815+
// br i1 %IsFinalExit, label %after.ready, label %coro.ret
816+
void EmitGroConv(BasicBlock *RetBB) {
817+
auto *AfterReadyBB = Builder.GetInsertBlock();
818+
Builder.ClearInsertionPoint();
819+
820+
auto *PreConvBB = CGF.CurCoro.Data->SuspendBB;
821+
CGF.EmitBlock(PreConvBB);
822+
// If final.exit exists, redirect it to PreConvBB
823+
llvm::PHINode *IsFinalExit = nullptr;
824+
if (BasicBlock *FinalExit = CGF.CurCoro.Data->FinalExit) {
825+
assert(AfterReadyBB &&
826+
AfterReadyBB->getSinglePredecessor() == FinalExit &&
827+
"Expect fallthrough from final.exit block");
828+
AfterReadyBB->replaceAllUsesWith(PreConvBB);
829+
PreConvBB->moveBefore(AfterReadyBB);
830+
831+
// If true, coroutine completes and should be destroyed after conversion
832+
IsFinalExit =
833+
Builder.CreatePHI(Builder.getInt1Ty(), llvm::pred_size(PreConvBB));
834+
for (auto *Pred : llvm::predecessors(PreConvBB)) {
835+
auto *V = (Pred == FinalExit) ? Builder.getTrue() : Builder.getFalse();
836+
IsFinalExit->addIncoming(V, Pred);
837+
}
838+
}
839+
auto *InRampFn = CGF.CGM.getIntrinsic(llvm::Intrinsic::coro_is_in_ramp);
840+
auto *InRamp = Builder.CreateCall(InRampFn, {}, "InRamp");
841+
auto *ConvBB = CGF.createBasicBlock("gro.conv");
842+
auto *AfterConvBB = CGF.createBasicBlock("after.gro.conv");
843+
Builder.CreateCondBr(InRamp, ConvBB, AfterConvBB);
844+
845+
CGF.EmitBlock(ConvBB);
846+
CGF.EmitAnyExprToMem(S.getReturnValue(), CGF.ReturnValue,
847+
S.getReturnValue()->getType().getQualifiers(),
848+
/*IsInit*/ true);
849+
Builder.CreateBr(AfterConvBB);
850+
851+
CGF.EmitBlock(AfterConvBB);
852+
if (IsFinalExit)
853+
Builder.CreateCondBr(IsFinalExit, AfterReadyBB, RetBB);
854+
else
855+
Builder.CreateBr(RetBB);
856+
Builder.SetInsertPoint(AfterReadyBB);
857+
}
771858
};
772859
} // namespace
773860

@@ -795,7 +882,10 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
795882
CGM.getIntrinsic(llvm::Intrinsic::coro_id),
796883
{Builder.getInt32(NewAlign), NullPtr, NullPtr, NullPtr});
797884
createCoroData(*this, CurCoro, CoroId);
798-
CurCoro.Data->SuspendBB = RetBB;
885+
886+
GetReturnObjectManager GroManager(*this, S);
887+
CurCoro.Data->SuspendBB =
888+
GroManager.DirectEmit ? RetBB : createBasicBlock("pre.gvo.conv");
799889
assert(ShouldEmitLifetimeMarkers &&
800890
"Must emit lifetime intrinsics for coroutines");
801891

@@ -839,9 +929,6 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
839929
CGM.getIntrinsic(llvm::Intrinsic::coro_begin), {CoroId, Phi});
840930
CurCoro.Data->CoroBegin = CoroBegin;
841931

842-
GetReturnObjectManager GroManager(*this, S);
843-
GroManager.EmitGroAlloca();
844-
845932
CurCoro.Data->CleanupJD = getJumpDestInCurrentScope(RetBB);
846933
{
847934
CGDebugInfo *DI = getDebugInfo();
@@ -884,6 +971,7 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
884971
// not needed.
885972
}
886973

974+
GroManager.EmitGroActive();
887975
EmitStmt(S.getPromiseDeclStmt());
888976

889977
Address PromiseAddr = GetAddrOfLocalVar(S.getPromiseDecl());
@@ -895,6 +983,7 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
895983
CoroId->setArgOperand(1, PromiseAddrVoidPtr);
896984

897985
// Now we have the promise, initialize the GRO
986+
GroManager.EmitGroAlloca();
898987
GroManager.EmitGroInit();
899988

900989
EHStack.pushCleanup<CallCoroEnd>(EHCleanup);
@@ -950,31 +1039,31 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
9501039
// We don't need FinalBB. Emit it to make sure the block is deleted.
9511040
EmitBlock(FinalBB, /*IsFinished=*/true);
9521041
}
1042+
1043+
// We need conversion if get_return_object's type doesn't matches the
1044+
// coroutine return type.
1045+
if (!GroManager.DirectEmit)
1046+
GroManager.EmitGroConv(RetBB);
9531047
}
9541048

9551049
EmitBlock(RetBB);
956-
// Emit coro.end before getReturnStmt (and parameter destructors), since
957-
// resume and destroy parts of the coroutine should not include them.
1050+
// Emit coro.end before ret instruction, since resume and destroy parts of the
1051+
// coroutine should return void.
9581052
llvm::Function *CoroEnd = CGM.getIntrinsic(llvm::Intrinsic::coro_end);
9591053
Builder.CreateCall(CoroEnd,
9601054
{NullPtr, Builder.getFalse(),
9611055
llvm::ConstantTokenNone::get(CoroEnd->getContext())});
9621056

963-
if (Stmt *Ret = S.getReturnStmt()) {
1057+
if (auto *Ret = cast_or_null<ReturnStmt>(S.getReturnStmt())) {
9641058
// Since we already emitted the return value above, so we shouldn't
9651059
// emit it again here.
966-
Expr *PreviousRetValue = nullptr;
967-
if (GroManager.DirectEmit) {
968-
PreviousRetValue = cast<ReturnStmt>(Ret)->getRetValue();
969-
cast<ReturnStmt>(Ret)->setRetValue(nullptr);
970-
}
1060+
Expr *PreviousRetValue = Ret->getRetValue();
1061+
Ret->setRetValue(nullptr);
9711062
EmitStmt(Ret);
9721063
// Set the return value back. The code generator, as the AST **Consumer**,
9731064
// shouldn't change the AST.
974-
if (PreviousRetValue)
975-
cast<ReturnStmt>(Ret)->setRetValue(PreviousRetValue);
1065+
Ret->setRetValue(PreviousRetValue);
9761066
}
977-
9781067
// LLVM require the frontend to mark the coroutine.
9791068
CurFn->setPresplitCoroutine();
9801069

clang/test/CodeGenCoroutines/coro-gro.cpp

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,47 +29,72 @@ void doSomething() noexcept;
2929
// CHECK: define{{.*}} i32 @_Z1fv(
3030
int f() {
3131
// CHECK: %[[RetVal:.+]] = alloca i32
32-
// CHECK: %[[GroActive:.+]] = alloca i1
33-
// CHECK: %[[CoroGro:.+]] = alloca %struct.GroType, {{.*}} !coro.outside.frame ![[OutFrameMetadata:.+]]
32+
// CHECK-NEXT: %[[GroActive:.+]] = alloca i1
33+
// CHECK-NEXT: %[[Promise:.+]] = alloca %"struct.std::coroutine_traits<int>::promise_type", align 1
34+
// CHECK-NEXT: %[[CoroGro:.+]] = alloca %struct.GroType, {{.*}} !coro.outside.frame ![[OutFrameMetadata:.+]]
3435

3536
// CHECK: %[[Size:.+]] = call i64 @llvm.coro.size.i64()
36-
// CHECK: call noalias noundef nonnull ptr @_Znwm(i64 noundef %[[Size]])
37+
// CHECK-NEXT: call noalias noundef nonnull ptr @_Znwm(i64 noundef %[[Size]])
38+
3739
// CHECK: store i1 false, ptr %[[GroActive]]
38-
// CHECK: call void @_ZNSt16coroutine_traitsIiJEE12promise_typeC1Ev(
39-
// CHECK: call void @_ZNSt16coroutine_traitsIiJEE12promise_type17get_return_objectEv({{.*}} %[[CoroGro]]
40-
// CHECK: store i1 true, ptr %[[GroActive]]
40+
// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[Promise]])
41+
// CHECK-NEXT: call void @_ZNSt16coroutine_traitsIiJEE12promise_typeC1Ev(
42+
// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[CoroGro]])
43+
// CHECK-NEXT: call void @_ZNSt16coroutine_traitsIiJEE12promise_type17get_return_objectEv({{.*}} %[[CoroGro]]
44+
// CHECK-NEXT: store i1 true, ptr %[[GroActive]]
4145

4246
Cleanup cleanup;
4347
doSomething();
4448
co_return;
4549

4650
// CHECK: call void @_Z11doSomethingv(
47-
// CHECK: call void @_ZNSt16coroutine_traitsIiJEE12promise_type11return_voidEv(
48-
// CHECK: call void @_ZN7CleanupD1Ev(
49-
50-
// Destroy promise and free the memory.
51-
52-
// CHECK: call void @_ZNSt16coroutine_traitsIiJEE12promise_typeD1Ev(
53-
// CHECK: %[[Mem:.+]] = call ptr @llvm.coro.free(
54-
// CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64()
55-
// CHECK: call void @_ZdlPvm(ptr noundef %[[Mem]], i64 noundef %[[SIZE]])
51+
// CHECK-NEXT: call void @_ZNSt16coroutine_traitsIiJEE12promise_type11return_voidEv(
52+
// CHECK-NEXT: call void @_ZN7CleanupD1Ev(
5653

5754
// Initialize retval from Gro and destroy Gro
5855
// Note this also tests delaying initialization when Gro and function return
5956
// types mismatch (see cwg2563).
6057

61-
// CHECK: %[[Conv:.+]] = call noundef i32 @_ZN7GroTypecviEv(
62-
// CHECK: store i32 %[[Conv]], ptr %[[RetVal]]
63-
// CHECK: %[[IsActive:.+]] = load i1, ptr %[[GroActive]]
64-
// CHECK: br i1 %[[IsActive]], label %[[CleanupGro:.+]], label %[[Done:.+]]
58+
// CHECK: pre.gvo.conv:
59+
// CHECK-NEXT: %10 = phi i1 [ true, %cleanup8 ], [ false, %final.suspend ], [ false, %init.suspend ]
60+
// CHECK-NEXT: %InRamp = call i1 @llvm.coro.is_in_ramp()
61+
// CHECK-NEXT: br i1 %InRamp, label %[[GroConv:.+]], label %[[AfterGroConv:.+]]
62+
63+
// CHECK: [[GroConv]]:
64+
// CHECK-NEXT: %[[Conv:.+]] = call noundef i32 @_ZN7GroTypecviEv(
65+
// CHECK-NEXT: store i32 %[[Conv]], ptr %[[RetVal]]
66+
// CHECK-NEXT: br label %[[AfterGroConv]]
67+
68+
// CHECK: [[AfterGroConv]]:
69+
// CHECK-NEXT: br i1 %10, label %cleanup.cont10, label %[[CoroRet:.+]]
70+
71+
// CHECK: cleanup.cont10:
72+
// CHECK-NEXT: br label %[[Cleanup:.+]]
73+
74+
// CHECK: [[Cleanup]]:
75+
// CHECK-NEXT: %{{.*}} = phi i32
76+
// CHECK-NEXT: %[[IsActive:.+]] = load i1, ptr %[[GroActive]]
77+
// CHECK-NEXT: br i1 %[[IsActive]], label %[[CleanupGro:.+]], label %[[Done:.+]]
6578

6679
// CHECK: [[CleanupGro]]:
67-
// CHECK: call void @_ZN7GroTypeD1Ev(
68-
// CHECK: br label %[[Done]]
80+
// CHECK-NEXT: call void @_ZN7GroTypeD1Ev(
81+
// CHECK-NEXT: br label %[[Done]]
82+
83+
// Destroy promise and free the memory.
6984

7085
// CHECK: [[Done]]:
71-
// CHECK: %[[LoadRet:.+]] = load i32, ptr %[[RetVal]]
72-
// CHECK: ret i32 %[[LoadRet]]
86+
// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %[[CoroGro]])
87+
// CHECK-NEXT: call void @_ZNSt16coroutine_traitsIiJEE12promise_typeD1Ev(
88+
// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %[[Promise]])
89+
// CHECK-NEXT: %[[Mem:.+]] = call ptr @llvm.coro.free(
90+
91+
// CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64()
92+
// CHECK-NEXT: call void @_ZdlPvm(ptr noundef %[[Mem]], i64 noundef %[[SIZE]])
93+
94+
// CHECK: [[CoroRet]]:
95+
// CHECK-NEXT: call void @llvm.coro.end(
96+
// CHECK-NEXT: %[[LoadRet:.+]] = load i32, ptr %[[RetVal]]
97+
// CHECK-NEXT: ret i32 %[[LoadRet]]
7398
}
7499

75100
class invoker {

0 commit comments

Comments
 (0)