Skip to content

Commit 5f5ebec

Browse files
Refactor how we generate RValue vs LValue coawait expressions
1 parent 4a294b5 commit 5f5ebec

File tree

1 file changed

+185
-147
lines changed

1 file changed

+185
-147
lines changed

clang/lib/CodeGen/CGCoroutine.cpp

Lines changed: 185 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -129,49 +129,7 @@ static SmallString<32> buildSuspendPrefixStr(CGCoroData &Coro, AwaitKind Kind) {
129129
return Prefix;
130130
}
131131

132-
// Check if function can throw based on prototype noexcept, also works for
133-
// destructors which are implicitly noexcept but can be marked noexcept(false).
134-
static bool FunctionCanThrow(const FunctionDecl *D) {
135-
const auto *Proto = D->getType()->getAs<FunctionProtoType>();
136-
if (!Proto) {
137-
// Function proto is not found, we conservatively assume throwing.
138-
return true;
139-
}
140-
return !isNoexceptExceptionSpec(Proto->getExceptionSpecType()) ||
141-
Proto->canThrow() != CT_Cannot;
142-
}
143-
144-
static bool ResumeStmtCanThrow(const Stmt *S) {
145-
if (const auto *CE = dyn_cast<CallExpr>(S)) {
146-
const auto *Callee = CE->getDirectCallee();
147-
if (!Callee)
148-
// We don't have direct callee. Conservatively assume throwing.
149-
return true;
150-
151-
if (FunctionCanThrow(Callee))
152-
return true;
153-
154-
// Fall through to visit the children.
155-
}
156-
157-
if (const auto *TE = dyn_cast<CXXBindTemporaryExpr>(S)) {
158-
// Special handling of CXXBindTemporaryExpr here as calling of Dtor of the
159-
// temporary is not part of `children()` as covered in the fall through.
160-
// We need to mark entire statement as throwing if the destructor of the
161-
// temporary throws.
162-
const auto *Dtor = TE->getTemporary()->getDestructor();
163-
if (FunctionCanThrow(Dtor))
164-
return true;
165-
166-
// Fall through to visit the children.
167-
}
168-
169-
for (const auto *child : S->children())
170-
if (ResumeStmtCanThrow(child))
171-
return true;
172-
173-
return false;
174-
}
132+
namespace {
175133

176134
// Emit suspend expression which roughly looks like:
177135
//
@@ -200,117 +158,198 @@ static bool ResumeStmtCanThrow(const Stmt *S) {
200158
//
201159
// See llvm's docs/Coroutines.rst for more details.
202160
//
203-
namespace {
204-
struct LValueOrRValue {
205-
LValue LV;
206-
RValue RV;
207-
};
208-
}
209-
static LValueOrRValue emitSuspendExpression(CodeGenFunction &CGF, CGCoroData &Coro,
210-
CoroutineSuspendExpr const &S,
211-
AwaitKind Kind, AggValueSlot aggSlot,
212-
bool ignoreResult, bool forLValue) {
213-
auto *E = S.getCommonExpr();
214-
215-
auto Binder =
216-
CodeGenFunction::OpaqueValueMappingData::bind(CGF, S.getOpaqueValue(), E);
217-
auto UnbindOnExit = llvm::make_scope_exit([&] { Binder.unbind(CGF); });
218-
219-
auto Prefix = buildSuspendPrefixStr(Coro, Kind);
220-
BasicBlock *ReadyBlock = CGF.createBasicBlock(Prefix + Twine(".ready"));
221-
BasicBlock *SuspendBlock = CGF.createBasicBlock(Prefix + Twine(".suspend"));
222-
BasicBlock *CleanupBlock = CGF.createBasicBlock(Prefix + Twine(".cleanup"));
223-
224-
// If expression is ready, no need to suspend.
225-
CGF.EmitBranchOnBoolExpr(S.getReadyExpr(), ReadyBlock, SuspendBlock, 0);
226-
227-
// Otherwise, emit suspend logic.
228-
CGF.EmitBlock(SuspendBlock);
229-
230-
auto &Builder = CGF.Builder;
231-
llvm::Function *CoroSave = CGF.CGM.getIntrinsic(llvm::Intrinsic::coro_save);
232-
auto *NullPtr = llvm::ConstantPointerNull::get(CGF.CGM.Int8PtrTy);
233-
auto *SaveCall = Builder.CreateCall(CoroSave, {NullPtr});
234-
235-
CGF.CurCoro.InSuspendBlock = true;
236-
auto *SuspendRet = CGF.EmitScalarExpr(S.getSuspendExpr());
237-
CGF.CurCoro.InSuspendBlock = false;
238-
239-
if (SuspendRet != nullptr && SuspendRet->getType()->isIntegerTy(1)) {
240-
// Veto suspension if requested by bool returning await_suspend.
241-
BasicBlock *RealSuspendBlock =
242-
CGF.createBasicBlock(Prefix + Twine(".suspend.bool"));
243-
CGF.Builder.CreateCondBr(SuspendRet, RealSuspendBlock, ReadyBlock);
244-
CGF.EmitBlock(RealSuspendBlock);
161+
class SuspendExpressionEmitter final {
162+
public:
163+
SuspendExpressionEmitter(CodeGenFunction &CGF, CGCoroData &Coro,
164+
CoroutineSuspendExpr const &S, AwaitKind Kind)
165+
: CGF(CGF), Coro(Coro), SuspendExpr(S), Kind(Kind),
166+
SuspendPrefix(buildSuspendPrefixStr(Coro, Kind)) {
167+
CommonExpr = SuspendExpr.getCommonExpr();
168+
Binder = CodeGenFunction::OpaqueValueMappingData::bind(
169+
CGF, SuspendExpr.getOpaqueValue(), CommonExpr);
245170
}
246171

247-
// Emit the suspend point.
248-
const bool IsFinalSuspend = (Kind == AwaitKind::Final);
249-
llvm::Function *CoroSuspend =
250-
CGF.CGM.getIntrinsic(llvm::Intrinsic::coro_suspend);
251-
auto *SuspendResult = Builder.CreateCall(
252-
CoroSuspend, {SaveCall, Builder.getInt1(IsFinalSuspend)});
253-
254-
// Create a switch capturing three possible continuations.
255-
auto *Switch = Builder.CreateSwitch(SuspendResult, Coro.SuspendBB, 2);
256-
Switch->addCase(Builder.getInt8(0), ReadyBlock);
257-
Switch->addCase(Builder.getInt8(1), CleanupBlock);
258-
259-
// Emit cleanup for this suspend point.
260-
CGF.EmitBlock(CleanupBlock);
261-
CGF.EmitBranchThroughCleanup(Coro.CleanupJD);
262-
263-
// Emit await_resume expression.
264-
CGF.EmitBlock(ReadyBlock);
265-
266-
// Exception handling requires additional IR. If the 'await_resume' function
267-
// is marked as 'noexcept', we avoid generating this additional IR.
268-
CXXTryStmt *TryStmt = nullptr;
269-
if (Coro.ExceptionHandler && Kind == AwaitKind::Init &&
270-
ResumeStmtCanThrow(S.getResumeExpr())) {
271-
Coro.ResumeEHVar =
272-
CGF.CreateTempAlloca(Builder.getInt1Ty(), Prefix + Twine("resume.eh"));
273-
Builder.CreateFlagStore(true, Coro.ResumeEHVar);
274-
275-
auto Loc = S.getResumeExpr()->getExprLoc();
276-
auto *Catch = new (CGF.getContext())
277-
CXXCatchStmt(Loc, /*exDecl=*/nullptr, Coro.ExceptionHandler);
278-
auto *TryBody = CompoundStmt::Create(CGF.getContext(), S.getResumeExpr(),
279-
FPOptionsOverride(), Loc, Loc);
280-
TryStmt = CXXTryStmt::Create(CGF.getContext(), Loc, TryBody, Catch);
281-
CGF.EnterCXXTryStmt(*TryStmt);
282-
CGF.EmitStmt(TryBody);
283-
// We don't use EmitCXXTryStmt here. We need to store to ResumeEHVar that
284-
// doesn't exist in the body.
285-
Builder.CreateFlagStore(false, Coro.ResumeEHVar);
286-
CGF.ExitCXXTryStmt(*TryStmt);
287-
LValueOrRValue Res;
288-
// We are not supposed to obtain the value from init suspend await_resume().
289-
Res.RV = RValue::getIgnored();
290-
return Res;
172+
SuspendExpressionEmitter(const SuspendExpressionEmitter &) = delete;
173+
SuspendExpressionEmitter(SuspendExpressionEmitter &&) = delete;
174+
175+
~SuspendExpressionEmitter() { Binder.unbind(CGF); }
176+
177+
LValue EmitAsLValue() {
178+
assert(Kind != AwaitKind::Init);
179+
emitCommonBlocks();
180+
return CGF.EmitLValue(SuspendExpr.getResumeExpr());
291181
}
292182

293-
LValueOrRValue Res;
294-
if (forLValue)
295-
Res.LV = CGF.EmitLValue(S.getResumeExpr());
296-
else
297-
Res.RV = CGF.EmitAnyExpr(S.getResumeExpr(), aggSlot, ignoreResult);
183+
RValue EmitAsRValue(AggValueSlot AggSlot, bool IgnoreResult) {
184+
emitCommonBlocks();
185+
auto &Builder = CGF.Builder;
186+
auto *ResumeExpr = SuspendExpr.getResumeExpr();
187+
188+
// Exception handling requires additional IR. If the 'await_resume' function
189+
// is marked as 'noexcept', we avoid generating this additional IR.
190+
CXXTryStmt *TryStmt = nullptr;
191+
if (Coro.ExceptionHandler && Kind == AwaitKind::Init &&
192+
resumeExprCanThrow()) {
193+
Coro.ResumeEHVar = CGF.CreateTempAlloca(
194+
Builder.getInt1Ty(), SuspendPrefix + Twine("resume.eh"));
195+
Builder.CreateFlagStore(true, Coro.ResumeEHVar);
196+
197+
auto Loc = ResumeExpr->getExprLoc();
198+
auto *Catch = new (CGF.getContext())
199+
CXXCatchStmt(Loc, /*exDecl=*/nullptr, Coro.ExceptionHandler);
200+
201+
auto *TryBody = CompoundStmt::Create(CGF.getContext(), ResumeExpr,
202+
FPOptionsOverride(), Loc, Loc);
203+
TryStmt = CXXTryStmt::Create(CGF.getContext(), Loc, TryBody, Catch);
204+
CGF.EnterCXXTryStmt(*TryStmt);
205+
CGF.EmitStmt(TryBody);
206+
// We don't use EmitCXXTryStmt here. We need to store to ResumeEHVar that
207+
// doesn't exist in the body.
208+
Builder.CreateFlagStore(false, Coro.ResumeEHVar);
209+
CGF.ExitCXXTryStmt(*TryStmt);
210+
// We are not supposed to obtain the value from init suspend
211+
// await_resume().
212+
return RValue::getIgnored();
213+
}
298214

299-
return Res;
300-
}
215+
auto Ret = CGF.EmitAnyExpr(ResumeExpr, AggSlot, IgnoreResult);
216+
return Ret;
217+
}
218+
219+
private:
220+
CodeGenFunction &CGF;
221+
CGCoroData &Coro;
222+
CoroutineSuspendExpr const &SuspendExpr;
223+
AwaitKind Kind;
224+
SmallString<32> SuspendPrefix;
225+
Expr *CommonExpr;
226+
CodeGenFunction::OpaqueValueMappingData Binder;
227+
228+
// Emit all the common blocks for this suspend expression until the ready
229+
// block, from which point there are three possible outcomes:
230+
// 1) Emit as LValue;
231+
// 2) Emit as RValue;
232+
// 3) This suspend is the initial suspend of the coroutine, run
233+
// `try { promise.await_resume() } catch { ... }` and store a flag if it
234+
// didn't throw. In such case we continue to the coroutine function body,
235+
// otherwise, continue to the catch logic in the coroutine's exception
236+
// handler.
237+
void emitCommonBlocks() {
238+
BasicBlock *ReadyBlock =
239+
CGF.createBasicBlock(SuspendPrefix + Twine(".ready"));
240+
BasicBlock *SuspendBlock =
241+
CGF.createBasicBlock(SuspendPrefix + Twine(".suspend"));
242+
BasicBlock *CleanupBlock =
243+
CGF.createBasicBlock(SuspendPrefix + Twine(".cleanup"));
244+
245+
// If expression is ready, no need to suspend.
246+
CGF.EmitBranchOnBoolExpr(SuspendExpr.getReadyExpr(), ReadyBlock,
247+
SuspendBlock, 0);
248+
249+
// Otherwise, emit suspend logic.
250+
CGF.EmitBlock(SuspendBlock);
251+
252+
auto &Builder = CGF.Builder;
253+
llvm::Function *CoroSave = CGF.CGM.getIntrinsic(llvm::Intrinsic::coro_save);
254+
auto *NullPtr = llvm::ConstantPointerNull::get(CGF.CGM.Int8PtrTy);
255+
auto *SaveCall = Builder.CreateCall(CoroSave, {NullPtr});
256+
257+
CGF.CurCoro.InSuspendBlock = true;
258+
auto *SuspendRet = CGF.EmitScalarExpr(SuspendExpr.getSuspendExpr());
259+
CGF.CurCoro.InSuspendBlock = false;
260+
261+
if (SuspendRet != nullptr && SuspendRet->getType()->isIntegerTy(1)) {
262+
// Veto suspension if requested by bool returning await_suspend.
263+
BasicBlock *RealSuspendBlock =
264+
CGF.createBasicBlock(SuspendPrefix + Twine(".suspend.bool"));
265+
CGF.Builder.CreateCondBr(SuspendRet, RealSuspendBlock, ReadyBlock);
266+
CGF.EmitBlock(RealSuspendBlock);
267+
}
268+
269+
// Emit the suspend point.
270+
const bool IsFinalSuspend = (Kind == AwaitKind::Final);
271+
llvm::Function *CoroSuspend =
272+
CGF.CGM.getIntrinsic(llvm::Intrinsic::coro_suspend);
273+
auto *SuspendResult = Builder.CreateCall(
274+
CoroSuspend, {SaveCall, Builder.getInt1(IsFinalSuspend)});
275+
276+
// Create a switch capturing three possible continuations.
277+
auto *Switch = Builder.CreateSwitch(SuspendResult, Coro.SuspendBB, 2);
278+
Switch->addCase(Builder.getInt8(0), ReadyBlock);
279+
Switch->addCase(Builder.getInt8(1), CleanupBlock);
280+
281+
// Emit cleanup for this suspend point.
282+
CGF.EmitBlock(CleanupBlock);
283+
CGF.EmitBranchThroughCleanup(Coro.CleanupJD);
284+
285+
// Emit await_resume expression.
286+
CGF.EmitBlock(ReadyBlock);
287+
}
288+
289+
// Check if function can throw based on prototype noexcept, also works for
290+
// destructors which are implicitly noexcept but can be marked
291+
// noexcept(false).
292+
static bool functionCanThrow(const FunctionDecl *D) {
293+
const auto *Proto = D->getType()->getAs<FunctionProtoType>();
294+
if (!Proto) {
295+
// Function proto is not found, we conservatively assume throwing.
296+
return true;
297+
}
298+
return !isNoexceptExceptionSpec(Proto->getExceptionSpecType()) ||
299+
Proto->canThrow() != CT_Cannot;
300+
}
301+
302+
static bool resumeStmtCanThrow(const Stmt *S) {
303+
if (const auto *CE = dyn_cast<CallExpr>(S)) {
304+
const auto *Callee = CE->getDirectCallee();
305+
if (!Callee)
306+
// We don't have direct callee. Conservatively assume throwing.
307+
return true;
308+
309+
if (functionCanThrow(Callee))
310+
return true;
311+
312+
// Fall through to visit the children.
313+
}
314+
315+
if (const auto *TE = dyn_cast<CXXBindTemporaryExpr>(S)) {
316+
// Special handling of CXXBindTemporaryExpr here as calling of Dtor of the
317+
// temporary is not part of `children()` as covered in the fall through.
318+
// We need to mark entire statement as throwing if the destructor of the
319+
// temporary throws.
320+
const auto *Dtor = TE->getTemporary()->getDestructor();
321+
if (functionCanThrow(Dtor))
322+
return true;
323+
324+
// Fall through to visit the children.
325+
}
326+
327+
for (const auto *child : S->children())
328+
if (resumeStmtCanThrow(child))
329+
return true;
330+
331+
return false;
332+
}
333+
334+
bool resumeExprCanThrow() {
335+
const Expr *E = SuspendExpr.getResumeExpr();
336+
return resumeStmtCanThrow(E);
337+
}
338+
};
339+
} // namespace
301340

302341
RValue CodeGenFunction::EmitCoawaitExpr(const CoawaitExpr &E,
303342
AggValueSlot aggSlot,
304343
bool ignoreResult) {
305-
return emitSuspendExpression(*this, *CurCoro.Data, E,
306-
CurCoro.Data->CurrentAwaitKind, aggSlot,
307-
ignoreResult, /*forLValue*/false).RV;
344+
return SuspendExpressionEmitter(*this, *CurCoro.Data, E,
345+
CurCoro.Data->CurrentAwaitKind)
346+
.EmitAsRValue(aggSlot, ignoreResult);
308347
}
309348
RValue CodeGenFunction::EmitCoyieldExpr(const CoyieldExpr &E,
310349
AggValueSlot aggSlot,
311350
bool ignoreResult) {
312-
return emitSuspendExpression(*this, *CurCoro.Data, E, AwaitKind::Yield,
313-
aggSlot, ignoreResult, /*forLValue*/false).RV;
351+
return SuspendExpressionEmitter(*this, *CurCoro.Data, E, AwaitKind::Yield)
352+
.EmitAsRValue(aggSlot, ignoreResult);
314353
}
315354

316355
void CodeGenFunction::EmitCoreturnStmt(CoreturnStmt const &S) {
@@ -343,19 +382,18 @@ CodeGenFunction::EmitCoawaitLValue(const CoawaitExpr *E) {
343382
assert(getCoroutineSuspendExprReturnType(getContext(), E)->isReferenceType() &&
344383
"Can't have a scalar return unless the return type is a "
345384
"reference type!");
346-
return emitSuspendExpression(*this, *CurCoro.Data, *E,
347-
CurCoro.Data->CurrentAwaitKind, AggValueSlot::ignored(),
348-
/*ignoreResult*/false, /*forLValue*/true).LV;
385+
return SuspendExpressionEmitter(*this, *CurCoro.Data, *E,
386+
CurCoro.Data->CurrentAwaitKind)
387+
.EmitAsLValue();
349388
}
350389

351390
LValue
352391
CodeGenFunction::EmitCoyieldLValue(const CoyieldExpr *E) {
353392
assert(getCoroutineSuspendExprReturnType(getContext(), E)->isReferenceType() &&
354393
"Can't have a scalar return unless the return type is a "
355394
"reference type!");
356-
return emitSuspendExpression(*this, *CurCoro.Data, *E,
357-
AwaitKind::Yield, AggValueSlot::ignored(),
358-
/*ignoreResult*/false, /*forLValue*/true).LV;
395+
return SuspendExpressionEmitter(*this, *CurCoro.Data, *E, AwaitKind::Yield)
396+
.EmitAsLValue();
359397
}
360398

361399
// Hunts for the parameter reference in the parameter copy/move declaration.

0 commit comments

Comments
 (0)