forked from OSchip/llvm-project
[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:
parent
af73d2bdd9
commit
ea9144e818
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue