[C++20] [Coroutines] Implement return value optimization for get_return_object

This patch tries to implement RVO for coroutine's return object got from
get_return_object.
From [dcl.fct.def.coroutine]/p7 we could know that the return value of
get_return_object is either a reference or a prvalue. So it makes sense
to do copy elision for the return value. The return object should be
constructed directly into the storage where they would otherwise be
copied/moved to.

Test Plan: folly, check-all

Reviewed By: junparser

Differential revision: https://reviews.llvm.org/D117087
This commit is contained in:
Chuanqi Xu 2022-01-10 19:18:33 +08:00
parent 2aed07e96c
commit d30ca5e2e2
16 changed files with 81 additions and 225 deletions

View File

@ -327,7 +327,6 @@ class CoroutineBodyStmt final
Allocate, ///< Coroutine frame memory allocation.
Deallocate, ///< Coroutine frame memory deallocation.
ReturnValue, ///< Return value for thunk function: p.get_return_object().
ResultDecl, ///< Declaration holding the result of get_return_object.
ReturnStmt, ///< Return statement for the thunk function.
ReturnStmtOnAllocFailure, ///< Return statement if allocation failed.
FirstParamMove ///< First offset for move construction of parameter copies.
@ -354,7 +353,6 @@ public:
Expr *Allocate = nullptr;
Expr *Deallocate = nullptr;
Expr *ReturnValue = nullptr;
Stmt *ResultDecl = nullptr;
Stmt *ReturnStmt = nullptr;
Stmt *ReturnStmtOnAllocFailure = nullptr;
ArrayRef<Stmt *> ParamMoves;
@ -409,7 +407,11 @@ public:
Expr *getReturnValueInit() const {
return cast<Expr>(getStoredStmts()[SubStmt::ReturnValue]);
}
Stmt *getResultDecl() const { return getStoredStmts()[SubStmt::ResultDecl]; }
Expr *getReturnValue() const {
assert(getReturnStmt());
auto *RS = cast<clang::ReturnStmt>(getReturnStmt());
return RS->getRetValue();
}
Stmt *getReturnStmt() const { return getStoredStmts()[SubStmt::ReturnStmt]; }
Stmt *getReturnStmtOnAllocFailure() const {
return getStoredStmts()[SubStmt::ReturnStmtOnAllocFailure];

View File

@ -118,7 +118,6 @@ CoroutineBodyStmt::CoroutineBodyStmt(CoroutineBodyStmt::CtorArgs const &Args)
SubStmts[CoroutineBodyStmt::Allocate] = Args.Allocate;
SubStmts[CoroutineBodyStmt::Deallocate] = Args.Deallocate;
SubStmts[CoroutineBodyStmt::ReturnValue] = Args.ReturnValue;
SubStmts[CoroutineBodyStmt::ResultDecl] = Args.ResultDecl;
SubStmts[CoroutineBodyStmt::ReturnStmt] = Args.ReturnStmt;
SubStmts[CoroutineBodyStmt::ReturnStmtOnAllocFailure] =
Args.ReturnStmtOnAllocFailure;

View File

@ -465,72 +465,6 @@ struct CallCoroDelete final : public EHScopeStack::Cleanup {
};
}
namespace {
struct GetReturnObjectManager {
CodeGenFunction &CGF;
CGBuilderTy &Builder;
const CoroutineBodyStmt &S;
Address GroActiveFlag;
CodeGenFunction::AutoVarEmission GroEmission;
GetReturnObjectManager(CodeGenFunction &CGF, const CoroutineBodyStmt &S)
: CGF(CGF), Builder(CGF.Builder), S(S), GroActiveFlag(Address::invalid()),
GroEmission(CodeGenFunction::AutoVarEmission::invalid()) {}
// The gro variable has to outlive coroutine frame and coroutine promise, but,
// it can only be initialized after coroutine promise was created, thus, we
// split its emission in two parts. EmitGroAlloca emits an alloca and sets up
// cleanups. Later when coroutine promise is available we initialize the gro
// and sets the flag that the cleanup is now active.
void EmitGroAlloca() {
auto *GroDeclStmt = dyn_cast<DeclStmt>(S.getResultDecl());
if (!GroDeclStmt) {
// If get_return_object returns void, no need to do an alloca.
return;
}
auto *GroVarDecl = cast<VarDecl>(GroDeclStmt->getSingleDecl());
// Set GRO flag that it is not initialized yet
GroActiveFlag =
CGF.CreateTempAlloca(Builder.getInt1Ty(), CharUnits::One(), "gro.active");
Builder.CreateStore(Builder.getFalse(), GroActiveFlag);
GroEmission = CGF.EmitAutoVarAlloca(*GroVarDecl);
// Remember the top of EHStack before emitting the cleanup.
auto old_top = CGF.EHStack.stable_begin();
CGF.EmitAutoVarCleanups(GroEmission);
auto top = CGF.EHStack.stable_begin();
// Make the cleanup conditional on gro.active
for (auto b = CGF.EHStack.find(top), e = CGF.EHStack.find(old_top);
b != e; b++) {
if (auto *Cleanup = dyn_cast<EHCleanupScope>(&*b)) {
assert(!Cleanup->hasActiveFlag() && "cleanup already has active flag?");
Cleanup->setActiveFlag(GroActiveFlag);
Cleanup->setTestFlagInEHCleanup();
Cleanup->setTestFlagInNormalCleanup();
}
}
}
void EmitGroInit() {
if (!GroActiveFlag.isValid()) {
// No Gro variable was allocated. Simply emit the call to
// get_return_object.
CGF.EmitStmt(S.getResultDecl());
return;
}
CGF.EmitAutoVarInit(GroEmission);
Builder.CreateStore(Builder.getTrue(), GroActiveFlag);
}
};
}
static void emitBodyAndFallthrough(CodeGenFunction &CGF,
const CoroutineBodyStmt &S, Stmt *Body) {
CGF.EmitStmt(Body);
@ -597,13 +531,6 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
CGM.getIntrinsic(llvm::Intrinsic::coro_begin), {CoroId, Phi});
CurCoro.Data->CoroBegin = CoroBegin;
// We need to emit `get_­return_­object` first. According to:
// [dcl.fct.def.coroutine]p7
// The call to get_­return_­object is sequenced before the call to
// initial_­suspend and is invoked at most once.
GetReturnObjectManager GroManager(*this, S);
GroManager.EmitGroAlloca();
CurCoro.Data->CleanupJD = getJumpDestInCurrentScope(RetBB);
{
CGDebugInfo *DI = getDebugInfo();
@ -641,8 +568,23 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
// promise local variable was not emitted yet.
CoroId->setArgOperand(1, PromiseAddrVoidPtr);
// Now we have the promise, initialize the GRO
GroManager.EmitGroInit();
// ReturnValue should be valid as long as the coroutine's return type
// is not void. The assertion could help us to reduce the check later.
assert(ReturnValue.isValid() == (bool)S.getReturnStmt());
// Now we have the promise, initialize the GRO.
// We need to emit `get_return_object` first. According to:
// [dcl.fct.def.coroutine]p7
// The call to get_return_­object is sequenced before the call to
// initial_suspend and is invoked at most once.
//
// So we couldn't emit return value when we emit return statment,
// otherwise the call to get_return_object wouldn't be in front
// of initial_suspend.
if (ReturnValue.isValid()) {
EmitAnyExprToMem(S.getReturnValue(), ReturnValue,
S.getReturnValue()->getType().getQualifiers(),
/*IsInit*/ true);
}
EHStack.pushCleanup<CallCoroEnd>(EHCleanup);
@ -705,8 +647,12 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
llvm::Function *CoroEnd = CGM.getIntrinsic(llvm::Intrinsic::coro_end);
Builder.CreateCall(CoroEnd, {NullPtr, Builder.getFalse()});
if (Stmt *Ret = S.getReturnStmt())
if (Stmt *Ret = S.getReturnStmt()) {
// Since we already emitted the return value above, so we shouldn't
// emit it again here.
cast<ReturnStmt>(Ret)->setRetValue(nullptr);
EmitStmt(Ret);
}
// LLVM require the frontend to add the function attribute. See
// Coroutines.rst.

View File

@ -1577,7 +1577,6 @@ bool CoroutineStmtBuilder::makeGroDeclAndReturnStmt() {
if (Res.isInvalid())
return false;
this->ResultDecl = Res.get();
return true;
}
@ -1590,51 +1589,11 @@ bool CoroutineStmtBuilder::makeGroDeclAndReturnStmt() {
return false;
}
auto *GroDecl = VarDecl::Create(
S.Context, &FD, FD.getLocation(), FD.getLocation(),
&S.PP.getIdentifierTable().get("__coro_gro"), GroType,
S.Context.getTrivialTypeSourceInfo(GroType, Loc), SC_None);
GroDecl->setImplicit();
S.CheckVariableDeclarationType(GroDecl);
if (GroDecl->isInvalidDecl())
return false;
InitializedEntity Entity = InitializedEntity::InitializeVariable(GroDecl);
ExprResult Res =
S.PerformCopyInitialization(Entity, SourceLocation(), ReturnValue);
if (Res.isInvalid())
return false;
Res = S.ActOnFinishFullExpr(Res.get(), /*DiscardedValue*/ false);
if (Res.isInvalid())
return false;
S.AddInitializerToDecl(GroDecl, Res.get(),
/*DirectInit=*/false);
S.FinalizeDeclaration(GroDecl);
// Form a declaration statement for the return declaration, so that AST
// visitors can more easily find it.
StmtResult GroDeclStmt =
S.ActOnDeclStmt(S.ConvertDeclToDeclGroup(GroDecl), Loc, Loc);
if (GroDeclStmt.isInvalid())
return false;
this->ResultDecl = GroDeclStmt.get();
ExprResult declRef = S.BuildDeclRefExpr(GroDecl, GroType, VK_LValue, Loc);
if (declRef.isInvalid())
return false;
StmtResult ReturnStmt = S.BuildReturnStmt(Loc, declRef.get());
StmtResult ReturnStmt = S.BuildReturnStmt(Loc, ReturnValue);
if (ReturnStmt.isInvalid()) {
noteMemberDeclaredHere(S, ReturnValue, Fn);
return false;
}
if (cast<clang::ReturnStmt>(ReturnStmt.get())->getNRVOCandidate() == GroDecl)
GroDecl->setNRVOVariable(true);
this->ReturnStmt = ReturnStmt.get();
return true;

View File

@ -7903,12 +7903,6 @@ TreeTransform<Derived>::TransformCoroutineBodyStmt(CoroutineBodyStmt *S) {
return StmtError();
Builder.Deallocate = DeallocRes.get();
assert(S->getResultDecl() && "ResultDecl must already be built");
StmtResult ResultDecl = getDerived().TransformStmt(S->getResultDecl());
if (ResultDecl.isInvalid())
return StmtError();
Builder.ResultDecl = ResultDecl.get();
if (auto *ReturnStmt = S->getReturnStmt()) {
StmtResult Res = getDerived().TransformStmt(ReturnStmt);
if (Res.isInvalid())

View File

@ -226,7 +226,6 @@ struct std::experimental::coroutine_traits<int, promise_on_alloc_failure_tag> {
// CHECK-LABEL: f4(
extern "C" int f4(promise_on_alloc_failure_tag) {
// CHECK: %[[RetVal:.+]] = alloca i32
// CHECK: %[[Gro:.+]] = alloca i32
// CHECK: %[[ID:.+]] = call token @llvm.coro.id(i32 16
// CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64()
// CHECK: %[[MEM:.+]] = call noalias noundef i8* @_ZnwmRKSt9nothrow_t(i64 noundef %[[SIZE]], %"struct.std::nothrow_t"* noundef nonnull align 1 dereferenceable(1) @_ZStL7nothrow)
@ -240,13 +239,6 @@ extern "C" int f4(promise_on_alloc_failure_tag) {
// CHECK: [[OKBB]]:
// CHECK: %[[OkRet:.+]] = call noundef i32 @_ZNSt12experimental16coroutine_traitsIJi28promise_on_alloc_failure_tagEE12promise_type17get_return_objectEv(
// CHECK: store i32 %[[OkRet]], i32* %[[Gro]]
// CHECK: %[[Tmp1:.*]] = load i32, i32* %[[Gro]]
// CHECK-NEXT: store i32 %[[Tmp1]], i32* %[[RetVal]]
// CHECK-NEXT: %[[Gro_CAST:.+]] = bitcast i32* %[[Gro]] to i8*
// CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 4, i8* %[[Gro_CAST]]) #2
// CHECK-NEXT: br label %[[RetBB]]
// CHECK: [[RetBB]]:
// CHECK: %[[LoadRet:.+]] = load i32, i32* %[[RetVal]], align 4

View File

@ -224,7 +224,6 @@ struct std::coroutine_traits<int, promise_on_alloc_failure_tag> {
// CHECK-LABEL: f4(
extern "C" int f4(promise_on_alloc_failure_tag) {
// CHECK: %[[RetVal:.+]] = alloca i32
// CHECK: %[[Gro:.+]] = alloca i32
// CHECK: %[[ID:.+]] = call token @llvm.coro.id(i32 16
// CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64()
// CHECK: %[[MEM:.+]] = call noalias noundef i8* @_ZnwmRKSt9nothrow_t(i64 noundef %[[SIZE]], %"struct.std::nothrow_t"* noundef nonnull align 1 dereferenceable(1) @_ZStL7nothrow)
@ -238,13 +237,6 @@ extern "C" int f4(promise_on_alloc_failure_tag) {
// CHECK: [[OKBB]]:
// CHECK: %[[OkRet:.+]] = call noundef i32 @_ZNSt16coroutine_traitsIJi28promise_on_alloc_failure_tagEE12promise_type17get_return_objectEv(
// CHECK: store i32 %[[OkRet]], i32* %[[Gro]]
// CHECK: %[[Tmp1:.*]] = load i32, i32* %[[Gro]]
// CHECK-NEXT: store i32 %[[Tmp1]], i32* %[[RetVal]]
// CHECK-NEXT: %[[Gro_CAST:.+]] = bitcast i32* %[[Gro]] to i8*
// CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 4, i8* %[[Gro_CAST]]) #2
// CHECK-NEXT: br label %[[RetBB]]
// CHECK: [[RetBB]]:
// CHECK: %[[LoadRet:.+]] = load i32, i32* %[[RetVal]], align 4

View File

@ -48,14 +48,13 @@ void doSomething() noexcept;
// CHECK: define{{.*}} i32 @_Z1fv(
int f() {
// CHECK: %[[RetVal:.+]] = alloca i32
// CHECK: %[[GroActive:.+]] = alloca i1
// CHECK: %[[Size:.+]] = call i64 @llvm.coro.size.i64()
// CHECK: call noalias noundef nonnull i8* @_Znwm(i64 noundef %[[Size]])
// CHECK: store i1 false, i1* %[[GroActive]]
// CHECK: call void @_ZNSt12experimental16coroutine_traitsIJiEE12promise_typeC1Ev(
// CHECK: call void @_ZNSt12experimental16coroutine_traitsIJiEE12promise_type17get_return_objectEv(
// CHECK: store i1 true, i1* %[[GroActive]]
// CHECK: call void @_ZNSt12experimental16coroutine_traitsIJiEE12promise_type17get_return_objectEv(%struct.GroType* sret(%struct.GroType) align {{[0-9]+}} %[[GRO:.+]],
// CHECK: %[[Conv:.+]] = call noundef i32 @_ZN7GroTypecviEv({{.*}}[[GRO]]
// CHECK: store i32 %[[Conv]], i32* %[[RetVal]]
Cleanup cleanup;
doSomething();
@ -71,18 +70,7 @@ int f() {
// CHECK: %[[Mem:.+]] = call i8* @llvm.coro.free(
// CHECK: call void @_ZdlPv(i8* noundef %[[Mem]])
// Initialize retval from Gro and destroy Gro
// CHECK: %[[Conv:.+]] = call noundef i32 @_ZN7GroTypecviEv(
// CHECK: store i32 %[[Conv]], i32* %[[RetVal]]
// CHECK: %[[IsActive:.+]] = load i1, i1* %[[GroActive]]
// CHECK: br i1 %[[IsActive]], label %[[CleanupGro:.+]], label %[[Done:.+]]
// CHECK: [[CleanupGro]]:
// CHECK: call void @_ZN7GroTypeD1Ev(
// CHECK: br label %[[Done]]
// CHECK: [[Done]]:
// CHECK: coro.ret:
// CHECK: %[[LoadRet:.+]] = load i32, i32* %[[RetVal]]
// CHECK: ret i32 %[[LoadRet]]
}

View File

@ -46,14 +46,13 @@ void doSomething() noexcept;
// CHECK: define{{.*}} i32 @_Z1fv(
int f() {
// CHECK: %[[RetVal:.+]] = alloca i32
// CHECK: %[[GroActive:.+]] = alloca i1
// CHECK: %[[Size:.+]] = call i64 @llvm.coro.size.i64()
// CHECK: call noalias noundef nonnull i8* @_Znwm(i64 noundef %[[Size]])
// CHECK: store i1 false, i1* %[[GroActive]]
// CHECK: call void @_ZNSt16coroutine_traitsIJiEE12promise_typeC1Ev(
// CHECK: call void @_ZNSt16coroutine_traitsIJiEE12promise_type17get_return_objectEv(
// CHECK: store i1 true, i1* %[[GroActive]]
// CHECK: call void @_ZNSt16coroutine_traitsIJiEE12promise_type17get_return_objectEv(%struct.GroType* sret(%struct.GroType) align {{[0-9]+}} %[[GRO:.+]],
// CHECK: %[[Conv:.+]] = call noundef i32 @_ZN7GroTypecviEv({{.*}}[[GRO]]
// CHECK: store i32 %[[Conv]], i32* %[[RetVal]]
Cleanup cleanup;
doSomething();
@ -69,18 +68,7 @@ int f() {
// CHECK: %[[Mem:.+]] = call i8* @llvm.coro.free(
// CHECK: call void @_ZdlPv(i8* noundef %[[Mem]])
// Initialize retval from Gro and destroy Gro
// CHECK: %[[Conv:.+]] = call noundef i32 @_ZN7GroTypecviEv(
// CHECK: store i32 %[[Conv]], i32* %[[RetVal]]
// CHECK: %[[IsActive:.+]] = load i1, i1* %[[GroActive]]
// CHECK: br i1 %[[IsActive]], label %[[CleanupGro:.+]], label %[[Done:.+]]
// CHECK: [[CleanupGro]]:
// CHECK: call void @_ZN7GroTypeD1Ev(
// CHECK: br label %[[Done]]
// CHECK: [[Done]]:
// CHECK: coro.ret:
// CHECK: %[[LoadRet:.+]] = load i32, i32* %[[RetVal]]
// CHECK: ret i32 %[[LoadRet]]
}

View File

@ -32,16 +32,14 @@ struct coro {
Impl *impl;
};
// Verify that the NRVO is applied to the Gro object.
// Verify that the RVO is applied.
// CHECK-LABEL: define{{.*}} void @_Z1fi(%struct.coro* noalias sret(%struct.coro) align 8 %agg.result, i32 noundef %0)
coro f(int) {
// CHECK: %call = call noalias noundef nonnull i8* @_Znwm(
// CHECK-NEXT: br label %[[CoroInit:.*]]
// CHECK: {{.*}}[[CoroInit]]:
// CHECK: store i1 false, i1* %gro.active
// CHECK: call void @{{.*get_return_objectEv}}(%struct.coro* sret(%struct.coro) align 8 %agg.result
// CHECK-NEXT: store i1 true, i1* %gro.active
co_return;
}
@ -75,9 +73,7 @@ coro_two h(int) {
// CHECK-NEXT: br label %[[RetLabel:.*]]
// CHECK: {{.*}}[[InitOnSuccess]]:
// CHECK: store i1 false, i1* %gro.active
// CHECK: call void @{{.*get_return_objectEv}}(%struct.coro_two* sret(%struct.coro_two) align 8 %agg.result
// CHECK-NEXT: store i1 true, i1* %gro.active
// CHECK: [[RetLabel]]:
// CHECK-NEXT: ret void

View File

@ -33,17 +33,15 @@ struct coro {
Impl *impl;
};
// Verify that the NRVO is applied to the Gro object.
// Verify that the RVO is applied.
// CHECK-LABEL: define{{.*}} void @_Z1fi(%struct.coro* noalias sret(%struct.coro) align 8 %agg.result, i32 noundef %0)
coro f(int) {
// CHECK: %call = call noalias noundef nonnull i8* @_Znwm(
// CHECK-NEXT: br label %[[CoroInit:.*]]
// CHECK: {{.*}}[[CoroInit]]:
// CHECK: store i1 false, i1* %gro.active
// CHECK: call void @{{.*get_return_objectEv}}(%struct.coro* sret(%struct.coro) align 8 %agg.result
// CHECK-NEXT: store i1 true, i1* %gro.active
co_return;
co_return;
}
@ -64,24 +62,22 @@ struct coro_two {
Impl *impl;
};
// Verify that the NRVO is applied to the Gro object.
// Verify that the RVO is applied.
// CHECK-LABEL: define{{.*}} void @_Z1hi(%struct.coro_two* noalias sret(%struct.coro_two) align 8 %agg.result, i32 noundef %0)
coro_two h(int) {
coro_two h(int) {
// CHECK: %call = call noalias noundef i8* @_ZnwmRKSt9nothrow_t
// CHECK-NEXT: %[[CheckNull:.*]] = icmp ne i8* %call, null
// CHECK-NEXT: br i1 %[[CheckNull]], label %[[InitOnSuccess:.*]], label %[[InitOnFailure:.*]]
// CHECK: %call = call noalias noundef i8* @_ZnwmRKSt9nothrow_t
// CHECK-NEXT: %[[CheckNull:.*]] = icmp ne i8* %call, null
// CHECK-NEXT: br i1 %[[CheckNull]], label %[[InitOnSuccess:.*]], label %[[InitOnFailure:.*]]
// CHECK: {{.*}}[[InitOnFailure]]:
// CHECK-NEXT: call void @{{.*get_return_object_on_allocation_failureEv}}(%struct.coro_two* sret(%struct.coro_two) align 8 %agg.result
// CHECK-NEXT: br label %[[RetLabel:.*]]
// CHECK: {{.*}}[[InitOnFailure]]:
// CHECK-NEXT: call void @{{.*get_return_object_on_allocation_failureEv}}(%struct.coro_two* sret(%struct.coro_two) align 8 %agg.result
// CHECK-NEXT: br label %[[RetLabel:.*]]
// CHECK: {{.*}}[[InitOnSuccess]]:
// CHECK: store i1 false, i1* %gro.active
// CHECK: call void @{{.*get_return_objectEv}}(%struct.coro_two* sret(%struct.coro_two) align 8 %agg.result
// CHECK-NEXT: store i1 true, i1* %gro.active
// CHECK: {{.*}}[[InitOnSuccess]]:
// CHECK: call void @{{.*get_return_objectEv}}(%struct.coro_two* sret(%struct.coro_two) align 8 %agg.result
// CHECK: [[RetLabel]]:
// CHECK-NEXT: ret void
// CHECK: [[RetLabel]]:
// CHECK-NEXT: ret void
co_return;
}

View File

@ -31,19 +31,8 @@ coro_t f() {
}
// CHECK-LABEL: define dso_local void @"?f@@YA?AUcoro_t@@XZ"(
// CHECK: %gro.active = alloca i1
// CHECK: store i1 false, i1* %gro.active
// CHECK: invoke noundef %"struct.coro_t::promise_type"* @"??0promise_type@coro_t@@QEAA@XZ"(
// CHECK: invoke void @"?get_return_object@promise_type@coro_t@@QEAA?AU2@XZ"(
// CHECK: store i1 true, i1* %gro.active
// CHECK: %[[IS_ACTIVE:.+]] = load i1, i1* %gro.active
// CHECK: br i1 %[[IS_ACTIVE]], label %[[CLEANUP1:.+]], label
// CHECK: [[CLEANUP1]]:
// CHECK: %[[NRVO:.+]] = load i1, i1* %nrvo
// CHECK: br i1 %[[NRVO]], label %{{.+}}, label %[[DTOR:.+]]
// CHECK: [[DTOR]]:
// CHECK: call void @"??1coro_t@@QEAA@XZ"(
// CHECK: call void @"??1promise_type@coro_t@@QEAA@XZ"

View File

@ -27,19 +27,8 @@ coro_t f() {
}
// CHECK-LABEL: define dso_local void @"?f@@YA?AUcoro_t@@XZ"(
// CHECK: %gro.active = alloca i1
// CHECK: store i1 false, i1* %gro.active
// CHECK: invoke noundef %"struct.coro_t::promise_type"* @"??0promise_type@coro_t@@QEAA@XZ"(
// CHECK: invoke void @"?get_return_object@promise_type@coro_t@@QEAA?AU2@XZ"(
// CHECK: store i1 true, i1* %gro.active
// CHECK: %[[IS_ACTIVE:.+]] = load i1, i1* %gro.active
// CHECK: br i1 %[[IS_ACTIVE]], label %[[CLEANUP1:.+]], label
// CHECK: [[CLEANUP1]]:
// CHECK: %[[NRVO:.+]] = load i1, i1* %nrvo
// CHECK: br i1 %[[NRVO]], label %{{.+}}, label %[[DTOR:.+]]
// CHECK: [[DTOR]]:
// CHECK: call void @"??1coro_t@@QEAA@XZ"(
// CHECK: call void @"??1promise_type@coro_t@@QEAA@XZ"

View File

@ -0,0 +1,26 @@
// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++20 -fsyntax-only -verify
// expected-no-diagnostics
#include "Inputs/std-coroutine.h"
class invoker {
public:
class invoker_promise {
public:
invoker get_return_object() { return invoker{}; }
auto initial_suspend() { return std::suspend_never{}; }
auto final_suspend() noexcept { return std::suspend_never{}; }
void return_void() {}
void unhandled_exception() {}
};
using promise_type = invoker_promise;
invoker() {}
invoker(const invoker &) = delete;
invoker &operator=(const invoker &) = delete;
invoker(invoker &&) = delete;
invoker &operator=(invoker &&) = delete;
};
invoker f() {
co_return;
}

View File

@ -939,7 +939,7 @@ struct std::experimental::coroutine_traits<int, mismatch_gro_type_tag2> {
extern "C" int f(mismatch_gro_type_tag2) {
// cxx2b-error@-1 {{cannot initialize return object of type 'int' with an rvalue of type 'void *'}}
// cxx14_20-error@-2 {{cannot initialize return object of type 'int' with an lvalue of type 'void *'}}
// cxx14_20-error@-2 {{cannot initialize return object of type 'int' with an rvalue of type 'void *'}}
co_return; //expected-note {{function is a coroutine due to use of 'co_return' here}}
}

View File

@ -929,7 +929,7 @@ struct std::coroutine_traits<int, mismatch_gro_type_tag2> {
extern "C" int f(mismatch_gro_type_tag2) {
// cxx2b-error@-1 {{cannot initialize return object of type 'int' with an rvalue of type 'void *'}}
// cxx14_20-error@-2 {{cannot initialize return object of type 'int' with an lvalue of type 'void *'}}
// cxx14_20-error@-2 {{cannot initialize return object of type 'int' with an rvalue of type 'void *'}}
co_return; //expected-note {{function is a coroutine due to use of 'co_return' here}}
}