r331519 - [Coroutines] Catch exceptions in await_resume

Brian Gesiak via cfe-commits cfe-commits at lists.llvm.org
Fri May 4 07:02:38 PDT 2018


Author: modocache
Date: Fri May  4 07:02:37 2018
New Revision: 331519

URL: http://llvm.org/viewvc/llvm-project?rev=331519&view=rev
Log:
[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

Added:
    cfe/trunk/test/CodeGenCoroutines/coro-await-resume-eh.cpp
Modified:
    cfe/trunk/lib/CodeGen/CGCoroutine.cpp
    cfe/trunk/test/CodeGenCoroutines/coro-unhandled-exception.cpp

Modified: cfe/trunk/lib/CodeGen/CGCoroutine.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/CodeGen/CGCoroutine.cpp?rev=331519&r1=331518&r2=331519&view=diff
==============================================================================
--- cfe/trunk/lib/CodeGen/CGCoroutine.cpp (original)
+++ cfe/trunk/lib/CodeGen/CGCoroutine.cpp Fri May  4 07:02:37 2018
@@ -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 emitSuspendExpress
 
   // 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(
     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());

Added: cfe/trunk/test/CodeGenCoroutines/coro-await-resume-eh.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/CodeGenCoroutines/coro-await-resume-eh.cpp?rev=331519&view=auto
==============================================================================
--- cfe/trunk/test/CodeGenCoroutines/coro-await-resume-eh.cpp (added)
+++ cfe/trunk/test/CodeGenCoroutines/coro-await-resume-eh.cpp Fri May  4 07:02:37 2018
@@ -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;
+}

Modified: cfe/trunk/test/CodeGenCoroutines/coro-unhandled-exception.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/CodeGenCoroutines/coro-unhandled-exception.cpp?rev=331519&r1=331518&r2=331519&view=diff
==============================================================================
--- cfe/trunk/test/CodeGenCoroutines/coro-unhandled-exception.cpp (original)
+++ cfe/trunk/test/CodeGenCoroutines/coro-unhandled-exception.cpp Fri May  4 07:02:37 2018
@@ -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 at promise_type@coro_t@@QEAA?AUsuspend_never at coroutines_v1@experimental at 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(




More information about the cfe-commits mailing list