[Coroutines] Catch exceptions in await_resume

Summary:
http://wg21.link/P0664r2 section "Evolution/Core Issues 24" describes a
proposed change to Coroutines TS that would have any exceptions thrown
after the initial suspend point of a coroutine be caught by the handler
specified by the promise type's 'unhandled_exception' member function.
This commit provides a sample implementation of the specified behavior.

Test Plan: `check-clang`

Reviewers: GorNishanov, EricWF

Reviewed By: GorNishanov

Subscribers: cfe-commits, lewissbaker, eric_niebler

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

llvm-svn: 331519
This commit is contained in:
Brian Gesiak 2018-05-04 14:02:37 +00:00
parent af73d2bdd9
commit ea9144e818
3 changed files with 130 additions and 3 deletions

View File

@ -44,6 +44,15 @@ struct clang::CodeGen::CGCoroData {
// A branch to this block is emitted when coroutine needs to suspend.
llvm::BasicBlock *SuspendBB = nullptr;
// The promise type's 'unhandled_exception' handler, if it defines one.
Stmt *ExceptionHandler = nullptr;
// A temporary i1 alloca that stores whether 'await_resume' threw an
// exception. If it did, 'true' is stored in this variable, and the coroutine
// body must be skipped. If the promise type does not define an exception
// handler, this is null.
llvm::Value *ResumeEHVar = nullptr;
// Stores the jump destination just before the coroutine memory is freed.
// This is the destination that every suspend point jumps to for the cleanup
// branch.
@ -208,11 +217,32 @@ static LValueOrRValue emitSuspendExpression(CodeGenFunction &CGF, CGCoroData &Co
// Emit await_resume expression.
CGF.EmitBlock(ReadyBlock);
CXXTryStmt *TryStmt = nullptr;
if (Coro.ExceptionHandler && Kind == AwaitKind::Init) {
Coro.ResumeEHVar =
CGF.CreateTempAlloca(Builder.getInt1Ty(), Prefix + Twine("resume.eh"));
Builder.CreateFlagStore(true, Coro.ResumeEHVar);
auto Loc = S.getResumeExpr()->getExprLoc();
auto *Catch = new (CGF.getContext())
CXXCatchStmt(Loc, /*exDecl=*/nullptr, Coro.ExceptionHandler);
auto *TryBody =
CompoundStmt::Create(CGF.getContext(), S.getResumeExpr(), Loc, Loc);
TryStmt = CXXTryStmt::Create(CGF.getContext(), Loc, TryBody, Catch);
CGF.EnterCXXTryStmt(*TryStmt);
}
LValueOrRValue Res;
if (forLValue)
Res.LV = CGF.EmitLValue(S.getResumeExpr());
else
Res.RV = CGF.EmitAnyExpr(S.getResumeExpr(), aggSlot, ignoreResult);
if (TryStmt) {
Builder.CreateFlagStore(false, Coro.ResumeEHVar);
CGF.ExitCXXTryStmt(*TryStmt);
}
return Res;
}
@ -588,19 +618,31 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
EHStack.pushCleanup<CallCoroEnd>(EHCleanup);
CurCoro.Data->CurrentAwaitKind = AwaitKind::Init;
CurCoro.Data->ExceptionHandler = S.getExceptionHandler();
EmitStmt(S.getInitSuspendStmt());
CurCoro.Data->FinalJD = getJumpDestInCurrentScope(FinalBB);
CurCoro.Data->CurrentAwaitKind = AwaitKind::Normal;
if (auto *OnException = S.getExceptionHandler()) {
if (CurCoro.Data->ExceptionHandler) {
BasicBlock *BodyBB = createBasicBlock("coro.resumed.body");
BasicBlock *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();
CXXCatchStmt Catch(Loc, /*exDecl=*/nullptr, OnException);
auto *TryStmt = CXXTryStmt::Create(getContext(), Loc, S.getBody(), &Catch);
CXXCatchStmt Catch(Loc, /*exDecl=*/nullptr,
CurCoro.Data->ExceptionHandler);
auto *TryStmt =
CXXTryStmt::Create(getContext(), Loc, S.getBody(), &Catch);
EnterCXXTryStmt(*TryStmt);
emitBodyAndFallthrough(*this, S, TryStmt->getTryBlock());
ExitCXXTryStmt(*TryStmt);
EmitBlock(ContBB);
}
else {
emitBodyAndFallthrough(*this, S, S.getBody());

View File

@ -0,0 +1,81 @@
// Test the behavior of http://wg21.link/P0664, a proposal to catch any
// exceptions thrown after the initial suspend point of a coroutine by
// executing the handler specified by the promise type's 'unhandled_exception'
// member function.
//
// RUN: %clang_cc1 -std=c++14 -fcoroutines-ts \
// RUN: -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s \
// RUN: -fexceptions -fcxx-exceptions -disable-llvm-passes \
// RUN: | FileCheck %s
#include "Inputs/coroutine.h"
namespace coro = std::experimental::coroutines_v1;
struct throwing_awaitable {
bool await_ready() { return true; }
void await_suspend(coro::coroutine_handle<>) {}
void await_resume() { throw 42; }
};
struct task {
struct promise_type {
task get_return_object() { return task{}; }
auto initial_suspend() { return throwing_awaitable{}; }
auto final_suspend() { return coro::suspend_never{}; }
void return_void() {}
void unhandled_exception() {}
};
};
// CHECK-LABEL: define void @_Z1fv()
task f() {
// A variable RESUMETHREW is used to keep track of whether the body
// of 'await_resume' threw an exception. Exceptions thrown in
// 'await_resume' are unwound to RESUMELPAD.
// CHECK: init.ready:
// CHECK-NEXT: store i1 true, i1* %[[RESUMETHREW:.+]], align 1
// CHECK-NEXT: invoke void @_ZN18throwing_awaitable12await_resumeEv
// CHECK-NEXT: to label %[[RESUMECONT:.+]] unwind label %[[RESUMELPAD:.+]]
// If 'await_resume' does not throw an exception, 'false' is stored in
// variable RESUMETHREW.
// CHECK: [[RESUMECONT]]:
// CHECK-NEXT: store i1 false, i1* %[[RESUMETHREW]]
// CHECK-NEXT: br label %[[RESUMETRYCONT:.+]]
// 'unhandled_exception' is called for the exception thrown in
// 'await_resume'. The variable RESUMETHREW is never set to false,
// and a jump is made to RESUMETRYCONT.
// CHECK: [[RESUMELPAD]]:
// CHECK: br label %[[RESUMECATCH:.+]]
// CHECK: [[RESUMECATCH]]:
// CHECK: invoke void @_ZN4task12promise_type19unhandled_exceptionEv
// CHECK-NEXT: to label %[[RESUMEENDCATCH:.+]] unwind label
// CHECK: [[RESUMEENDCATCH]]:
// CHECK-NEXT: invoke void @__cxa_end_catch()
// CHECK-NEXT: to label %[[RESUMEENDCATCHCONT:.+]] unwind label
// CHECK: [[RESUMEENDCATCHCONT]]:
// CHECK-NEXT: br label %[[RESUMETRYCONT]]
// The variable RESUMETHREW is loaded and if true, then 'await_resume'
// threw an exception and the coroutine body is skipped, and the final
// suspend is executed immediately. Otherwise, the coroutine body is
// executed, and then the final suspend.
// CHECK: [[RESUMETRYCONT]]:
// CHECK-NEXT: %[[RESUMETHREWLOAD:.+]] = load i1, i1* %[[RESUMETHREW]]
// CHECK-NEXT: br i1 %[[RESUMETHREWLOAD]], label %[[RESUMEDCONT:.+]], label %[[RESUMEDBODY:.+]]
// CHECK: [[RESUMEDBODY]]:
// CHECK: invoke void @_ZN4task12promise_type11return_voidEv
// CHECK-NEXT: to label %[[REDUMEDBODYCONT:.+]] unwind label
// CHECK: [[REDUMEDBODYCONT]]:
// CHECK-NEXT: br label %[[COROFINAL:.+]]
// CHECK: [[RESUMEDCONT]]:
// CHECK-NEXT: br label %[[COROFINAL]]
// CHECK: [[COROFINAL]]:
// CHECK-NEXT: invoke void @_ZN4task12promise_type13final_suspendEv
co_return;
}

View File

@ -48,6 +48,8 @@ coro_t f() {
// CHECK: [[CATCHRETDEST]]:
// CHECK-NEXT: br label %[[TRYCONT:.+]]
// CHECK: [[TRYCONT]]:
// CHECK-NEXT: br label %[[RESUMECONT:.+]]
// CHECK: [[RESUMECONT]]:
// CHECK-NEXT: br label %[[COROFIN:.+]]
// CHECK: [[COROFIN]]:
// CHECK-NEXT: invoke void @"?final_suspend@promise_type@coro_t@@QEAA?AUsuspend_never@coroutines_v1@experimental@std@@XZ"(
@ -67,6 +69,8 @@ coro_t f() {
// CHECK-LPAD: [[CATCHRETDEST]]:
// CHECK-LPAD-NEXT: br label %[[TRYCONT:.+]]
// CHECK-LPAD: [[TRYCONT]]:
// CHECK-LPAD: br label %[[RESUMECONT:.+]]
// CHECK-LPAD: [[RESUMECONT]]:
// CHECK-LPAD-NEXT: br label %[[COROFIN:.+]]
// CHECK-LPAD: [[COROFIN]]:
// CHECK-LPAD-NEXT: invoke void @_ZN6coro_t12promise_type13final_suspendEv(