Skip to content

Commit d30ca5e

Browse files
committed
[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
1 parent 2aed07e commit d30ca5e

16 files changed

+81
-225
lines changed

clang/include/clang/AST/StmtCXX.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,6 @@ class CoroutineBodyStmt final
327327
Allocate, ///< Coroutine frame memory allocation.
328328
Deallocate, ///< Coroutine frame memory deallocation.
329329
ReturnValue, ///< Return value for thunk function: p.get_return_object().
330-
ResultDecl, ///< Declaration holding the result of get_return_object.
331330
ReturnStmt, ///< Return statement for the thunk function.
332331
ReturnStmtOnAllocFailure, ///< Return statement if allocation failed.
333332
FirstParamMove ///< First offset for move construction of parameter copies.
@@ -354,7 +353,6 @@ class CoroutineBodyStmt final
354353
Expr *Allocate = nullptr;
355354
Expr *Deallocate = nullptr;
356355
Expr *ReturnValue = nullptr;
357-
Stmt *ResultDecl = nullptr;
358356
Stmt *ReturnStmt = nullptr;
359357
Stmt *ReturnStmtOnAllocFailure = nullptr;
360358
ArrayRef<Stmt *> ParamMoves;
@@ -409,7 +407,11 @@ class CoroutineBodyStmt final
409407
Expr *getReturnValueInit() const {
410408
return cast<Expr>(getStoredStmts()[SubStmt::ReturnValue]);
411409
}
412-
Stmt *getResultDecl() const { return getStoredStmts()[SubStmt::ResultDecl]; }
410+
Expr *getReturnValue() const {
411+
assert(getReturnStmt());
412+
auto *RS = cast<clang::ReturnStmt>(getReturnStmt());
413+
return RS->getRetValue();
414+
}
413415
Stmt *getReturnStmt() const { return getStoredStmts()[SubStmt::ReturnStmt]; }
414416
Stmt *getReturnStmtOnAllocFailure() const {
415417
return getStoredStmts()[SubStmt::ReturnStmtOnAllocFailure];

clang/lib/AST/StmtCXX.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,6 @@ CoroutineBodyStmt::CoroutineBodyStmt(CoroutineBodyStmt::CtorArgs const &Args)
118118
SubStmts[CoroutineBodyStmt::Allocate] = Args.Allocate;
119119
SubStmts[CoroutineBodyStmt::Deallocate] = Args.Deallocate;
120120
SubStmts[CoroutineBodyStmt::ReturnValue] = Args.ReturnValue;
121-
SubStmts[CoroutineBodyStmt::ResultDecl] = Args.ResultDecl;
122121
SubStmts[CoroutineBodyStmt::ReturnStmt] = Args.ReturnStmt;
123122
SubStmts[CoroutineBodyStmt::ReturnStmtOnAllocFailure] =
124123
Args.ReturnStmtOnAllocFailure;

clang/lib/CodeGen/CGCoroutine.cpp

Lines changed: 22 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -465,72 +465,6 @@ struct CallCoroDelete final : public EHScopeStack::Cleanup {
465465
};
466466
}
467467

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

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

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

647589
EHStack.pushCleanup<CallCoroEnd>(EHCleanup);
648590

@@ -705,8 +647,12 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
705647
llvm::Function *CoroEnd = CGM.getIntrinsic(llvm::Intrinsic::coro_end);
706648
Builder.CreateCall(CoroEnd, {NullPtr, Builder.getFalse()});
707649

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

711657
// LLVM require the frontend to add the function attribute. See
712658
// Coroutines.rst.

clang/lib/Sema/SemaCoroutine.cpp

Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1577,7 +1577,6 @@ bool CoroutineStmtBuilder::makeGroDeclAndReturnStmt() {
15771577
if (Res.isInvalid())
15781578
return false;
15791579

1580-
this->ResultDecl = Res.get();
15811580
return true;
15821581
}
15831582

@@ -1590,51 +1589,11 @@ bool CoroutineStmtBuilder::makeGroDeclAndReturnStmt() {
15901589
return false;
15911590
}
15921591

1593-
auto *GroDecl = VarDecl::Create(
1594-
S.Context, &FD, FD.getLocation(), FD.getLocation(),
1595-
&S.PP.getIdentifierTable().get("__coro_gro"), GroType,
1596-
S.Context.getTrivialTypeSourceInfo(GroType, Loc), SC_None);
1597-
GroDecl->setImplicit();
1598-
1599-
S.CheckVariableDeclarationType(GroDecl);
1600-
if (GroDecl->isInvalidDecl())
1601-
return false;
1602-
1603-
InitializedEntity Entity = InitializedEntity::InitializeVariable(GroDecl);
1604-
ExprResult Res =
1605-
S.PerformCopyInitialization(Entity, SourceLocation(), ReturnValue);
1606-
if (Res.isInvalid())
1607-
return false;
1608-
1609-
Res = S.ActOnFinishFullExpr(Res.get(), /*DiscardedValue*/ false);
1610-
if (Res.isInvalid())
1611-
return false;
1612-
1613-
S.AddInitializerToDecl(GroDecl, Res.get(),
1614-
/*DirectInit=*/false);
1615-
1616-
S.FinalizeDeclaration(GroDecl);
1617-
1618-
// Form a declaration statement for the return declaration, so that AST
1619-
// visitors can more easily find it.
1620-
StmtResult GroDeclStmt =
1621-
S.ActOnDeclStmt(S.ConvertDeclToDeclGroup(GroDecl), Loc, Loc);
1622-
if (GroDeclStmt.isInvalid())
1623-
return false;
1624-
1625-
this->ResultDecl = GroDeclStmt.get();
1626-
1627-
ExprResult declRef = S.BuildDeclRefExpr(GroDecl, GroType, VK_LValue, Loc);
1628-
if (declRef.isInvalid())
1629-
return false;
1630-
1631-
StmtResult ReturnStmt = S.BuildReturnStmt(Loc, declRef.get());
1592+
StmtResult ReturnStmt = S.BuildReturnStmt(Loc, ReturnValue);
16321593
if (ReturnStmt.isInvalid()) {
16331594
noteMemberDeclaredHere(S, ReturnValue, Fn);
16341595
return false;
16351596
}
1636-
if (cast<clang::ReturnStmt>(ReturnStmt.get())->getNRVOCandidate() == GroDecl)
1637-
GroDecl->setNRVOVariable(true);
16381597

16391598
this->ReturnStmt = ReturnStmt.get();
16401599
return true;

clang/lib/Sema/TreeTransform.h

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

7906-
assert(S->getResultDecl() && "ResultDecl must already be built");
7907-
StmtResult ResultDecl = getDerived().TransformStmt(S->getResultDecl());
7908-
if (ResultDecl.isInvalid())
7909-
return StmtError();
7910-
Builder.ResultDecl = ResultDecl.get();
7911-
79127906
if (auto *ReturnStmt = S->getReturnStmt()) {
79137907
StmtResult Res = getDerived().TransformStmt(ReturnStmt);
79147908
if (Res.isInvalid())

clang/test/CodeGenCoroutines/coro-alloc-exp-namespace.cpp

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,6 @@ struct std::experimental::coroutine_traits<int, promise_on_alloc_failure_tag> {
226226
// CHECK-LABEL: f4(
227227
extern "C" int f4(promise_on_alloc_failure_tag) {
228228
// CHECK: %[[RetVal:.+]] = alloca i32
229-
// CHECK: %[[Gro:.+]] = alloca i32
230229
// CHECK: %[[ID:.+]] = call token @llvm.coro.id(i32 16
231230
// CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64()
232231
// 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) {
240239

241240
// CHECK: [[OKBB]]:
242241
// CHECK: %[[OkRet:.+]] = call noundef i32 @_ZNSt12experimental16coroutine_traitsIJi28promise_on_alloc_failure_tagEE12promise_type17get_return_objectEv(
243-
// CHECK: store i32 %[[OkRet]], i32* %[[Gro]]
244-
245-
// CHECK: %[[Tmp1:.*]] = load i32, i32* %[[Gro]]
246-
// CHECK-NEXT: store i32 %[[Tmp1]], i32* %[[RetVal]]
247-
// CHECK-NEXT: %[[Gro_CAST:.+]] = bitcast i32* %[[Gro]] to i8*
248-
// CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 4, i8* %[[Gro_CAST]]) #2
249-
// CHECK-NEXT: br label %[[RetBB]]
250242

251243
// CHECK: [[RetBB]]:
252244
// CHECK: %[[LoadRet:.+]] = load i32, i32* %[[RetVal]], align 4

clang/test/CodeGenCoroutines/coro-alloc.cpp

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,6 @@ struct std::coroutine_traits<int, promise_on_alloc_failure_tag> {
224224
// CHECK-LABEL: f4(
225225
extern "C" int f4(promise_on_alloc_failure_tag) {
226226
// CHECK: %[[RetVal:.+]] = alloca i32
227-
// CHECK: %[[Gro:.+]] = alloca i32
228227
// CHECK: %[[ID:.+]] = call token @llvm.coro.id(i32 16
229228
// CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64()
230229
// 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) {
238237

239238
// CHECK: [[OKBB]]:
240239
// CHECK: %[[OkRet:.+]] = call noundef i32 @_ZNSt16coroutine_traitsIJi28promise_on_alloc_failure_tagEE12promise_type17get_return_objectEv(
241-
// CHECK: store i32 %[[OkRet]], i32* %[[Gro]]
242-
243-
// CHECK: %[[Tmp1:.*]] = load i32, i32* %[[Gro]]
244-
// CHECK-NEXT: store i32 %[[Tmp1]], i32* %[[RetVal]]
245-
// CHECK-NEXT: %[[Gro_CAST:.+]] = bitcast i32* %[[Gro]] to i8*
246-
// CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 4, i8* %[[Gro_CAST]]) #2
247-
// CHECK-NEXT: br label %[[RetBB]]
248240

249241
// CHECK: [[RetBB]]:
250242
// CHECK: %[[LoadRet:.+]] = load i32, i32* %[[RetVal]], align 4

clang/test/CodeGenCoroutines/coro-gro-exp-namespace.cpp

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

5352
// CHECK: %[[Size:.+]] = call i64 @llvm.coro.size.i64()
5453
// CHECK: call noalias noundef nonnull i8* @_Znwm(i64 noundef %[[Size]])
55-
// CHECK: store i1 false, i1* %[[GroActive]]
5654
// CHECK: call void @_ZNSt12experimental16coroutine_traitsIJiEE12promise_typeC1Ev(
57-
// CHECK: call void @_ZNSt12experimental16coroutine_traitsIJiEE12promise_type17get_return_objectEv(
58-
// CHECK: store i1 true, i1* %[[GroActive]]
55+
// CHECK: call void @_ZNSt12experimental16coroutine_traitsIJiEE12promise_type17get_return_objectEv(%struct.GroType* sret(%struct.GroType) align {{[0-9]+}} %[[GRO:.+]],
56+
// CHECK: %[[Conv:.+]] = call noundef i32 @_ZN7GroTypecviEv({{.*}}[[GRO]]
57+
// CHECK: store i32 %[[Conv]], i32* %[[RetVal]]
5958

6059
Cleanup cleanup;
6160
doSomething();
@@ -71,18 +70,7 @@ int f() {
7170
// CHECK: %[[Mem:.+]] = call i8* @llvm.coro.free(
7271
// CHECK: call void @_ZdlPv(i8* noundef %[[Mem]])
7372

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

clang/test/CodeGenCoroutines/coro-gro.cpp

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

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

5857
Cleanup cleanup;
5958
doSomething();
@@ -69,18 +68,7 @@ int f() {
6968
// CHECK: %[[Mem:.+]] = call i8* @llvm.coro.free(
7069
// CHECK: call void @_ZdlPv(i8* noundef %[[Mem]])
7170

72-
// Initialize retval from Gro and destroy Gro
73-
74-
// CHECK: %[[Conv:.+]] = call noundef i32 @_ZN7GroTypecviEv(
75-
// CHECK: store i32 %[[Conv]], i32* %[[RetVal]]
76-
// CHECK: %[[IsActive:.+]] = load i1, i1* %[[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]]:
71+
// CHECK: coro.ret:
8472
// CHECK: %[[LoadRet:.+]] = load i32, i32* %[[RetVal]]
8573
// CHECK: ret i32 %[[LoadRet]]
8674
}

clang/test/CodeGenCoroutines/coro-gro-nrvo-exp-namespace.cpp renamed to clang/test/CodeGenCoroutines/coro-gro2-exp-namespace.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,14 @@ struct coro {
3232
Impl *impl;
3333
};
3434

35-
// Verify that the NRVO is applied to the Gro object.
35+
// Verify that the RVO is applied.
3636
// CHECK-LABEL: define{{.*}} void @_Z1fi(%struct.coro* noalias sret(%struct.coro) align 8 %agg.result, i32 noundef %0)
3737
coro f(int) {
3838
// CHECK: %call = call noalias noundef nonnull i8* @_Znwm(
3939
// CHECK-NEXT: br label %[[CoroInit:.*]]
4040

4141
// CHECK: {{.*}}[[CoroInit]]:
42-
// CHECK: store i1 false, i1* %gro.active
4342
// CHECK: call void @{{.*get_return_objectEv}}(%struct.coro* sret(%struct.coro) align 8 %agg.result
44-
// CHECK-NEXT: store i1 true, i1* %gro.active
4543
co_return;
4644
}
4745

@@ -75,9 +73,7 @@ coro_two h(int) {
7573
// CHECK-NEXT: br label %[[RetLabel:.*]]
7674

7775
// CHECK: {{.*}}[[InitOnSuccess]]:
78-
// CHECK: store i1 false, i1* %gro.active
7976
// CHECK: call void @{{.*get_return_objectEv}}(%struct.coro_two* sret(%struct.coro_two) align 8 %agg.result
80-
// CHECK-NEXT: store i1 true, i1* %gro.active
8177

8278
// CHECK: [[RetLabel]]:
8379
// CHECK-NEXT: ret void

0 commit comments

Comments
 (0)