[Coroutines] Less IR for noexcept await_resume

Summary:
In his review of https://reviews.llvm.org/D45860, @GorNishanov suggested
avoiding generating additional exception-handling IR in the case that
the resume function was marked as 'noexcept', and exceptions could not
occur. This implements that suggestion.

Test Plan: `check-clang`

Reviewers: GorNishanov, EricWF

Reviewed By: GorNishanov

Subscribers: cfe-commits, GorNishanov

Differential Revision: https://reviews.llvm.org/D47673

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@335422 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Brian Gesiak 2018-06-23 18:57:26 +00:00
parent 86ed6a4248
commit d80dd82cc4
2 changed files with 64 additions and 14 deletions

View File

@ -130,6 +130,16 @@ static SmallString<32> buildSuspendPrefixStr(CGCoroData &Coro, AwaitKind Kind) {
return Prefix; return Prefix;
} }
static bool memberCallExpressionCanThrow(const Expr *E) {
if (const auto *CE = dyn_cast<CXXMemberCallExpr>(E))
if (const auto *Proto =
CE->getMethodDecl()->getType()->getAs<FunctionProtoType>())
if (isNoexceptExceptionSpec(Proto->getExceptionSpecType()) &&
Proto->canThrow() == CT_Cannot)
return false;
return true;
}
// Emit suspend expression which roughly looks like: // Emit suspend expression which roughly looks like:
// //
// auto && x = CommonExpr(); // auto && x = CommonExpr();
@ -217,8 +227,12 @@ static LValueOrRValue emitSuspendExpression(CodeGenFunction &CGF, CGCoroData &Co
// Emit await_resume expression. // Emit await_resume expression.
CGF.EmitBlock(ReadyBlock); CGF.EmitBlock(ReadyBlock);
// Exception handling requires additional IR. If the 'await_resume' function
// is marked as 'noexcept', we avoid generating this additional IR.
CXXTryStmt *TryStmt = nullptr; CXXTryStmt *TryStmt = nullptr;
if (Coro.ExceptionHandler && Kind == AwaitKind::Init) { if (Coro.ExceptionHandler && Kind == AwaitKind::Init &&
memberCallExpressionCanThrow(S.getResumeExpr())) {
Coro.ResumeEHVar = Coro.ResumeEHVar =
CGF.CreateTempAlloca(Builder.getInt1Ty(), Prefix + Twine("resume.eh")); CGF.CreateTempAlloca(Builder.getInt1Ty(), Prefix + Twine("resume.eh"));
Builder.CreateFlagStore(true, Coro.ResumeEHVar); Builder.CreateFlagStore(true, Coro.ResumeEHVar);
@ -625,12 +639,20 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
CurCoro.Data->CurrentAwaitKind = AwaitKind::Normal; CurCoro.Data->CurrentAwaitKind = AwaitKind::Normal;
if (CurCoro.Data->ExceptionHandler) { if (CurCoro.Data->ExceptionHandler) {
BasicBlock *BodyBB = createBasicBlock("coro.resumed.body"); // If we generated IR to record whether an exception was thrown from
BasicBlock *ContBB = createBasicBlock("coro.resumed.cont"); // 'await_resume', then use that IR to determine whether the coroutine
Value *SkipBody = // body should be skipped.
Builder.CreateFlagLoad(CurCoro.Data->ResumeEHVar, "coro.resumed.eh"); // If we didn't generate the IR (perhaps because 'await_resume' was marked
Builder.CreateCondBr(SkipBody, ContBB, BodyBB); // as 'noexcept'), then we skip this check.
EmitBlock(BodyBB); BasicBlock *ContBB = nullptr;
if (CurCoro.Data->ResumeEHVar) {
BasicBlock *BodyBB = createBasicBlock("coro.resumed.body");
ContBB = createBasicBlock("coro.resumed.cont");
Value *SkipBody = Builder.CreateFlagLoad(CurCoro.Data->ResumeEHVar,
"coro.resumed.eh");
Builder.CreateCondBr(SkipBody, ContBB, BodyBB);
EmitBlock(BodyBB);
}
auto Loc = S.getLocStart(); auto Loc = S.getLocStart();
CXXCatchStmt Catch(Loc, /*exDecl=*/nullptr, CXXCatchStmt Catch(Loc, /*exDecl=*/nullptr,
@ -642,7 +664,8 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
emitBodyAndFallthrough(*this, S, TryStmt->getTryBlock()); emitBodyAndFallthrough(*this, S, TryStmt->getTryBlock());
ExitCXXTryStmt(*TryStmt); ExitCXXTryStmt(*TryStmt);
EmitBlock(ContBB); if (ContBB)
EmitBlock(ContBB);
} }
else { else {
emitBodyAndFallthrough(*this, S, S.getBody()); emitBodyAndFallthrough(*this, S, S.getBody());

View File

@ -18,9 +18,9 @@ struct throwing_awaitable {
void await_resume() { throw 42; } void await_resume() { throw 42; }
}; };
struct task { struct throwing_task {
struct promise_type { struct promise_type {
task get_return_object() { return task{}; } auto get_return_object() { return throwing_task{}; }
auto initial_suspend() { return throwing_awaitable{}; } auto initial_suspend() { return throwing_awaitable{}; }
auto final_suspend() { return coro::suspend_never{}; } auto final_suspend() { return coro::suspend_never{}; }
void return_void() {} void return_void() {}
@ -29,7 +29,7 @@ struct task {
}; };
// CHECK-LABEL: define void @_Z1fv() // CHECK-LABEL: define void @_Z1fv()
task f() { throwing_task f() {
// A variable RESUMETHREW is used to keep track of whether the body // A variable RESUMETHREW is used to keep track of whether the body
// of 'await_resume' threw an exception. Exceptions thrown in // of 'await_resume' threw an exception. Exceptions thrown in
// 'await_resume' are unwound to RESUMELPAD. // 'await_resume' are unwound to RESUMELPAD.
@ -50,7 +50,7 @@ task f() {
// CHECK: [[RESUMELPAD]]: // CHECK: [[RESUMELPAD]]:
// CHECK: br label %[[RESUMECATCH:.+]] // CHECK: br label %[[RESUMECATCH:.+]]
// CHECK: [[RESUMECATCH]]: // CHECK: [[RESUMECATCH]]:
// CHECK: invoke void @_ZN4task12promise_type19unhandled_exceptionEv // CHECK: invoke void @_ZN13throwing_task12promise_type19unhandled_exceptionEv
// CHECK-NEXT: to label %[[RESUMEENDCATCH:.+]] unwind label // CHECK-NEXT: to label %[[RESUMEENDCATCH:.+]] unwind label
// CHECK: [[RESUMEENDCATCH]]: // CHECK: [[RESUMEENDCATCH]]:
// CHECK-NEXT: invoke void @__cxa_end_catch() // CHECK-NEXT: invoke void @__cxa_end_catch()
@ -67,7 +67,7 @@ task f() {
// CHECK-NEXT: br i1 %[[RESUMETHREWLOAD]], label %[[RESUMEDCONT:.+]], label %[[RESUMEDBODY:.+]] // CHECK-NEXT: br i1 %[[RESUMETHREWLOAD]], label %[[RESUMEDCONT:.+]], label %[[RESUMEDBODY:.+]]
// CHECK: [[RESUMEDBODY]]: // CHECK: [[RESUMEDBODY]]:
// CHECK: invoke void @_ZN4task12promise_type11return_voidEv // CHECK: invoke void @_ZN13throwing_task12promise_type11return_voidEv
// CHECK-NEXT: to label %[[REDUMEDBODYCONT:.+]] unwind label // CHECK-NEXT: to label %[[REDUMEDBODYCONT:.+]] unwind label
// CHECK: [[REDUMEDBODYCONT]]: // CHECK: [[REDUMEDBODYCONT]]:
// CHECK-NEXT: br label %[[COROFINAL:.+]] // CHECK-NEXT: br label %[[COROFINAL:.+]]
@ -76,6 +76,33 @@ task f() {
// CHECK-NEXT: br label %[[COROFINAL]] // CHECK-NEXT: br label %[[COROFINAL]]
// CHECK: [[COROFINAL]]: // CHECK: [[COROFINAL]]:
// CHECK-NEXT: invoke void @_ZN4task12promise_type13final_suspendEv // CHECK-NEXT: invoke void @_ZN13throwing_task12promise_type13final_suspendEv
co_return;
}
struct noexcept_awaitable {
bool await_ready() { return true; }
void await_suspend(coro::coroutine_handle<>) {}
void await_resume() noexcept {}
};
struct noexcept_task {
struct promise_type {
auto get_return_object() { return noexcept_task{}; }
auto initial_suspend() { return noexcept_awaitable{}; }
auto final_suspend() { return coro::suspend_never{}; }
void return_void() {}
void unhandled_exception() {}
};
};
// CHECK-LABEL: define void @_Z1gv()
noexcept_task g() {
// If the await_resume function is marked as noexcept, none of the additional
// conditions that are present in f() above are added to the IR.
// This means that no i1 are stored before or after calling await_resume:
// CHECK: init.ready:
// CHECK-NEXT: call void @_ZN18noexcept_awaitable12await_resumeEv
// CHECK-NOT: store i1 false, i1*
co_return; co_return;
} }