Skip to content

Commit 54225c4

Browse files
committed
[Coroutines] Fix premature conversion of return object
Fix #56532 Effectively, this reverts behavior introduced in https://reviews.llvm.org/D117087, which did two things: 1. Change delayed to early conversion of return object. 2. Introduced RVO possibilities because of early conversion. This patches fixes (1) and removes (2). I already worked on a follow up for (2) in a separated patch. I believe it's important to split these two because if the RVO causes any problems we can explore reverting (2) while maintaining (1). Notes on some testcase changes: - `pr59221.cpp` changed to `-O1` so we can check that the front-end honors the value checked for. Sounds like `-O3` without RVO is more likely to work with LLVM optimizations... - Comment out delete members `coroutine-no-move-ctor.cpp` since behavior now requires copies again. Differential Revision: https://reviews.llvm.org/D145639
1 parent 32be340 commit 54225c4

File tree

10 files changed

+155
-35
lines changed

10 files changed

+155
-35
lines changed

clang/include/clang/AST/StmtCXX.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ class CoroutineBodyStmt final
326326
OnFallthrough, ///< Handler for control flow falling off the body.
327327
Allocate, ///< Coroutine frame memory allocation.
328328
Deallocate, ///< Coroutine frame memory deallocation.
329+
ResultDecl, ///< Declaration holding the result of get_return_object.
329330
ReturnValue, ///< Return value for thunk function: p.get_return_object().
330331
ReturnStmt, ///< Return statement for the thunk function.
331332
ReturnStmtOnAllocFailure, ///< Return statement if allocation failed.
@@ -352,6 +353,7 @@ class CoroutineBodyStmt final
352353
Stmt *OnFallthrough = nullptr;
353354
Expr *Allocate = nullptr;
354355
Expr *Deallocate = nullptr;
356+
Stmt *ResultDecl = nullptr;
355357
Expr *ReturnValue = nullptr;
356358
Stmt *ReturnStmt = nullptr;
357359
Stmt *ReturnStmtOnAllocFailure = nullptr;
@@ -404,6 +406,7 @@ class CoroutineBodyStmt final
404406
Expr *getDeallocate() const {
405407
return cast_or_null<Expr>(getStoredStmts()[SubStmt::Deallocate]);
406408
}
409+
Stmt *getResultDecl() const { return getStoredStmts()[SubStmt::ResultDecl]; }
407410
Expr *getReturnValueInit() const {
408411
return cast<Expr>(getStoredStmts()[SubStmt::ReturnValue]);
409412
}

clang/lib/AST/StmtCXX.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ CoroutineBodyStmt::CoroutineBodyStmt(CoroutineBodyStmt::CtorArgs const &Args)
117117
SubStmts[CoroutineBodyStmt::OnFallthrough] = Args.OnFallthrough;
118118
SubStmts[CoroutineBodyStmt::Allocate] = Args.Allocate;
119119
SubStmts[CoroutineBodyStmt::Deallocate] = Args.Deallocate;
120+
SubStmts[CoroutineBodyStmt::ResultDecl] = Args.ResultDecl;
120121
SubStmts[CoroutineBodyStmt::ReturnValue] = Args.ReturnValue;
121122
SubStmts[CoroutineBodyStmt::ReturnStmt] = Args.ReturnStmt;
122123
SubStmts[CoroutineBodyStmt::ReturnStmtOnAllocFailure] =

clang/lib/CodeGen/CGCoroutine.cpp

Lines changed: 75 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,71 @@ struct CallCoroDelete final : public EHScopeStack::Cleanup {
467467
};
468468
}
469469

470+
namespace {
471+
struct GetReturnObjectManager {
472+
CodeGenFunction &CGF;
473+
CGBuilderTy &Builder;
474+
const CoroutineBodyStmt &S;
475+
476+
Address GroActiveFlag;
477+
CodeGenFunction::AutoVarEmission GroEmission;
478+
479+
GetReturnObjectManager(CodeGenFunction &CGF, const CoroutineBodyStmt &S)
480+
: CGF(CGF), Builder(CGF.Builder), S(S), GroActiveFlag(Address::invalid()),
481+
GroEmission(CodeGenFunction::AutoVarEmission::invalid()) {}
482+
483+
// The gro variable has to outlive coroutine frame and coroutine promise, but,
484+
// it can only be initialized after coroutine promise was created, thus, we
485+
// split its emission in two parts. EmitGroAlloca emits an alloca and sets up
486+
// cleanups. Later when coroutine promise is available we initialize the gro
487+
// and sets the flag that the cleanup is now active.
488+
void EmitGroAlloca() {
489+
auto *GroDeclStmt = dyn_cast<DeclStmt>(S.getResultDecl());
490+
if (!GroDeclStmt) {
491+
// If get_return_object returns void, no need to do an alloca.
492+
return;
493+
}
494+
495+
auto *GroVarDecl = cast<VarDecl>(GroDeclStmt->getSingleDecl());
496+
497+
// Set GRO flag that it is not initialized yet
498+
GroActiveFlag = CGF.CreateTempAlloca(Builder.getInt1Ty(), CharUnits::One(),
499+
"gro.active");
500+
Builder.CreateStore(Builder.getFalse(), GroActiveFlag);
501+
502+
GroEmission = CGF.EmitAutoVarAlloca(*GroVarDecl);
503+
504+
// Remember the top of EHStack before emitting the cleanup.
505+
auto old_top = CGF.EHStack.stable_begin();
506+
CGF.EmitAutoVarCleanups(GroEmission);
507+
auto top = CGF.EHStack.stable_begin();
508+
509+
// Make the cleanup conditional on gro.active
510+
for (auto b = CGF.EHStack.find(top), e = CGF.EHStack.find(old_top); b != e;
511+
b++) {
512+
if (auto *Cleanup = dyn_cast<EHCleanupScope>(&*b)) {
513+
assert(!Cleanup->hasActiveFlag() && "cleanup already has active flag?");
514+
Cleanup->setActiveFlag(GroActiveFlag);
515+
Cleanup->setTestFlagInEHCleanup();
516+
Cleanup->setTestFlagInNormalCleanup();
517+
}
518+
}
519+
}
520+
521+
void EmitGroInit() {
522+
if (!GroActiveFlag.isValid()) {
523+
// No Gro variable was allocated. Simply emit the call to
524+
// get_return_object.
525+
CGF.EmitStmt(S.getResultDecl());
526+
return;
527+
}
528+
529+
CGF.EmitAutoVarInit(GroEmission);
530+
Builder.CreateStore(Builder.getTrue(), GroActiveFlag);
531+
}
532+
};
533+
} // namespace
534+
470535
static void emitBodyAndFallthrough(CodeGenFunction &CGF,
471536
const CoroutineBodyStmt &S, Stmt *Body) {
472537
CGF.EmitStmt(Body);
@@ -533,6 +598,13 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
533598
CGM.getIntrinsic(llvm::Intrinsic::coro_begin), {CoroId, Phi});
534599
CurCoro.Data->CoroBegin = CoroBegin;
535600

601+
// We need to emit `get_­return_­object` first. According to:
602+
// [dcl.fct.def.coroutine]p7
603+
// The call to get_­return_­object is sequenced before the call to
604+
// initial_­suspend and is invoked at most once.
605+
GetReturnObjectManager GroManager(*this, S);
606+
GroManager.EmitGroAlloca();
607+
536608
CurCoro.Data->CleanupJD = getJumpDestInCurrentScope(RetBB);
537609
{
538610
CGDebugInfo *DI = getDebugInfo();
@@ -570,23 +642,8 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
570642
// promise local variable was not emitted yet.
571643
CoroId->setArgOperand(1, PromiseAddrVoidPtr);
572644

573-
// ReturnValue should be valid as long as the coroutine's return type
574-
// is not void. The assertion could help us to reduce the check later.
575-
assert(ReturnValue.isValid() == (bool)S.getReturnStmt());
576-
// Now we have the promise, initialize the GRO.
577-
// We need to emit `get_return_object` first. According to:
578-
// [dcl.fct.def.coroutine]p7
579-
// The call to get_return_­object is sequenced before the call to
580-
// initial_suspend and is invoked at most once.
581-
//
582-
// So we couldn't emit return value when we emit return statment,
583-
// otherwise the call to get_return_object wouldn't be in front
584-
// of initial_suspend.
585-
if (ReturnValue.isValid()) {
586-
EmitAnyExprToMem(S.getReturnValue(), ReturnValue,
587-
S.getReturnValue()->getType().getQualifiers(),
588-
/*IsInit*/ true);
589-
}
645+
// Now we have the promise, initialize the GRO
646+
GroManager.EmitGroInit();
590647

591648
EHStack.pushCleanup<CallCoroEnd>(EHCleanup);
592649

@@ -649,12 +706,8 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
649706
llvm::Function *CoroEnd = CGM.getIntrinsic(llvm::Intrinsic::coro_end);
650707
Builder.CreateCall(CoroEnd, {NullPtr, Builder.getFalse()});
651708

652-
if (Stmt *Ret = S.getReturnStmt()) {
653-
// Since we already emitted the return value above, so we shouldn't
654-
// emit it again here.
655-
cast<ReturnStmt>(Ret)->setRetValue(nullptr);
709+
if (Stmt *Ret = S.getReturnStmt())
656710
EmitStmt(Ret);
657-
}
658711

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

clang/lib/Sema/SemaCoroutine.cpp

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1736,6 +1736,7 @@ bool CoroutineStmtBuilder::makeGroDeclAndReturnStmt() {
17361736
if (Res.isInvalid())
17371737
return false;
17381738

1739+
this->ResultDecl = Res.get();
17391740
return true;
17401741
}
17411742

@@ -1748,12 +1749,55 @@ bool CoroutineStmtBuilder::makeGroDeclAndReturnStmt() {
17481749
return false;
17491750
}
17501751

1751-
StmtResult ReturnStmt = S.BuildReturnStmt(Loc, ReturnValue);
1752+
// StmtResult ReturnStmt = S.BuildReturnStmt(Loc, ReturnValue);
1753+
auto *GroDecl = VarDecl::Create(
1754+
S.Context, &FD, FD.getLocation(), FD.getLocation(),
1755+
&S.PP.getIdentifierTable().get("__coro_gro"), GroType,
1756+
S.Context.getTrivialTypeSourceInfo(GroType, Loc), SC_None);
1757+
GroDecl->setImplicit();
1758+
1759+
S.CheckVariableDeclarationType(GroDecl);
1760+
if (GroDecl->isInvalidDecl())
1761+
return false;
1762+
1763+
InitializedEntity Entity = InitializedEntity::InitializeVariable(GroDecl);
1764+
ExprResult Res =
1765+
S.PerformCopyInitialization(Entity, SourceLocation(), ReturnValue);
1766+
if (Res.isInvalid())
1767+
return false;
1768+
1769+
Res = S.ActOnFinishFullExpr(Res.get(), /*DiscardedValue*/ false);
1770+
if (Res.isInvalid())
1771+
return false;
1772+
1773+
S.AddInitializerToDecl(GroDecl, Res.get(),
1774+
/*DirectInit=*/false);
1775+
1776+
S.FinalizeDeclaration(GroDecl);
1777+
1778+
// Form a declaration statement for the return declaration, so that AST
1779+
// visitors can more easily find it.
1780+
StmtResult GroDeclStmt =
1781+
S.ActOnDeclStmt(S.ConvertDeclToDeclGroup(GroDecl), Loc, Loc);
1782+
if (GroDeclStmt.isInvalid())
1783+
return false;
1784+
1785+
this->ResultDecl = GroDeclStmt.get();
1786+
1787+
ExprResult declRef = S.BuildDeclRefExpr(GroDecl, GroType, VK_LValue, Loc);
1788+
if (declRef.isInvalid())
1789+
return false;
1790+
1791+
StmtResult ReturnStmt = S.BuildReturnStmt(Loc, declRef.get());
1792+
17521793
if (ReturnStmt.isInvalid()) {
17531794
noteMemberDeclaredHere(S, ReturnValue, Fn);
17541795
return false;
17551796
}
17561797

1798+
if (cast<clang::ReturnStmt>(ReturnStmt.get())->getNRVOCandidate() == GroDecl)
1799+
GroDecl->setNRVOVariable(true);
1800+
17571801
this->ReturnStmt = ReturnStmt.get();
17581802
return true;
17591803
}

clang/lib/Sema/TreeTransform.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8051,6 +8051,12 @@ TreeTransform<Derived>::TransformCoroutineBodyStmt(CoroutineBodyStmt *S) {
80518051
return StmtError();
80528052
Builder.Deallocate = DeallocRes.get();
80538053

8054+
assert(S->getResultDecl() && "ResultDecl must already be built");
8055+
StmtResult ResultDecl = getDerived().TransformStmt(S->getResultDecl());
8056+
if (ResultDecl.isInvalid())
8057+
return StmtError();
8058+
Builder.ResultDecl = ResultDecl.get();
8059+
80548060
if (auto *ReturnStmt = S->getReturnStmt()) {
80558061
StmtResult Res = getDerived().TransformStmt(ReturnStmt);
80568062
if (Res.isInvalid())

clang/test/CodeGenCoroutines/coro-gro.cpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,14 @@ void doSomething() noexcept;
4646
// CHECK: define{{.*}} i32 @_Z1fv(
4747
int f() {
4848
// CHECK: %[[RetVal:.+]] = alloca i32
49+
// CHECK: %[[GroActive:.+]] = alloca i1
4950

5051
// CHECK: %[[Size:.+]] = call i64 @llvm.coro.size.i64()
5152
// CHECK: call noalias noundef nonnull ptr @_Znwm(i64 noundef %[[Size]])
53+
// CHECK: store i1 false, ptr %[[GroActive]]
5254
// CHECK: call void @_ZNSt16coroutine_traitsIJiEE12promise_typeC1Ev(
53-
// CHECK: call void @_ZNSt16coroutine_traitsIJiEE12promise_type17get_return_objectEv(ptr sret(%struct.GroType) align {{[0-9]+}} %[[GRO:.+]],
54-
// CHECK: %[[Conv:.+]] = call noundef i32 @_ZN7GroTypecviEv({{.*}}[[GRO]]
55-
// CHECK: store i32 %[[Conv]], ptr %[[RetVal]]
55+
// CHECK: call void @_ZNSt16coroutine_traitsIJiEE12promise_type17get_return_objectEv(
56+
// CHECK: store i1 true, ptr %[[GroActive]]
5657

5758
Cleanup cleanup;
5859
doSomething();
@@ -68,7 +69,18 @@ int f() {
6869
// CHECK: %[[Mem:.+]] = call ptr @llvm.coro.free(
6970
// CHECK: call void @_ZdlPv(ptr noundef %[[Mem]])
7071

71-
// CHECK: coro.ret:
72+
// Initialize retval from Gro and destroy Gro
73+
74+
// CHECK: %[[Conv:.+]] = call noundef i32 @_ZN7GroTypecviEv(
75+
// CHECK: store i32 %[[Conv]], ptr %[[RetVal]]
76+
// CHECK: %[[IsActive:.+]] = load i1, ptr %[[GroActive]]
77+
// CHECK: br i1 %[[IsActive]], label %[[CleanupGro:.+]], label %[[Done:.+]]
78+
79+
// CHECK: [[CleanupGro]]:
80+
// CHECK: call void @_ZN7GroTypeD1Ev(
81+
// CHECK: br label %[[Done]]
82+
83+
// CHECK: [[Done]]:
7284
// CHECK: %[[LoadRet:.+]] = load i32, ptr %[[RetVal]]
7385
// CHECK: ret i32 %[[LoadRet]]
7486
}

clang/test/CodeGenCoroutines/pr59221.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// REQUIRES: x86-registered-target
44
//
5-
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 %s -O3 -S -emit-llvm -o - | FileCheck %s
5+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 %s -O1 -S -emit-llvm -o - | FileCheck %s
66

77
#include "Inputs/coroutine.h"
88

clang/test/SemaCXX/coroutine-no-move-ctor.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@ class invoker {
1515
};
1616
using promise_type = invoker_promise;
1717
invoker() {}
18-
invoker(const invoker &) = delete;
19-
invoker &operator=(const invoker &) = delete;
20-
invoker(invoker &&) = delete;
21-
invoker &operator=(invoker &&) = delete;
18+
// TODO: implement RVO for get_return_object type matching
19+
// function return type.
20+
//
21+
// invoker(const invoker &) = delete;
22+
// invoker &operator=(const invoker &) = delete;
23+
// invoker(invoker &&) = delete;
24+
// invoker &operator=(invoker &&) = delete;
2225
};
2326

2427
invoker f() {

clang/test/SemaCXX/coroutines.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -934,7 +934,7 @@ struct std::coroutine_traits<int, mismatch_gro_type_tag2> {
934934

935935
extern "C" int f(mismatch_gro_type_tag2) {
936936
// cxx2b-error@-1 {{cannot initialize return object of type 'int' with an rvalue of type 'void *'}}
937-
// cxx14_20-error@-2 {{cannot initialize return object of type 'int' with an rvalue of type 'void *'}}
937+
// cxx14_20-error@-2 {{cannot initialize return object of type 'int' with an lvalue of type 'void *'}}
938938
co_return; //expected-note {{function is a coroutine due to use of 'co_return' here}}
939939
}
940940

clang/test/SemaCXX/warn-throw-out-noexcept-coro.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ struct task {
1313

1414
explicit task(promise_type& p) { throw 1; p.return_val = this; }
1515

16-
task(const task&) = delete;
17-
1816
T value;
1917
};
2018

0 commit comments

Comments
 (0)