diff --git a/clang/lib/CodeGen/CGCoroutine.cpp b/clang/lib/CodeGen/CGCoroutine.cpp index 74c158cbd399..bd1689688fbe 100644 --- a/clang/lib/CodeGen/CGCoroutine.cpp +++ b/clang/lib/CodeGen/CGCoroutine.cpp @@ -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(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()); diff --git a/clang/test/CodeGenCoroutines/coro-await-resume-eh.cpp b/clang/test/CodeGenCoroutines/coro-await-resume-eh.cpp new file mode 100644 index 000000000000..96a3a74a2d74 --- /dev/null +++ b/clang/test/CodeGenCoroutines/coro-await-resume-eh.cpp @@ -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; +} diff --git a/clang/test/CodeGenCoroutines/coro-unhandled-exception.cpp b/clang/test/CodeGenCoroutines/coro-unhandled-exception.cpp index 4ea00a5849b9..039f02352cfc 100644 --- a/clang/test/CodeGenCoroutines/coro-unhandled-exception.cpp +++ b/clang/test/CodeGenCoroutines/coro-unhandled-exception.cpp @@ -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(