ChuanqiXu created this revision.
ChuanqiXu added reviewers: rjmccall, lxfind, junparser, ychen, aaron.ballman.
ChuanqiXu added projects: clang, LLVM.
Herald added a subscriber: hiraditya.
ChuanqiXu requested review of this revision.
Herald added subscribers: llvm-commits, cfe-commits, jdoerfert.

[[dcl.fct.def.coroutine]/p14](https://eel.is/c++draft/dcl.fct.def.coroutine#14) 
says:

> If the evaluation of the expression promise.unhandled_­exception() exits via 
> an exception, the coroutine is considered suspended at the final suspend 
> point.

However, this is not implemented in clang. We could observe this from: 
https://godbolt.org/z/Edr59d5Y6.

This patch tries to implement [dcl.fct.def.coroutine]/p14 by rewrite the call 
to `promise.unhandled_exception()` into:

  C++
  try {
      promise.unhandled_exception();
  } catch(...) {
      __builtin_coro_mark_done();
      throw;
  }

And this patch introduces another coroutine intrinsic: `llvm.coro.mark.done()`. 
This would mark the coroutine done (suspended at the final suspend). 
Previously, this is done by the call to `llvm.coro.suspend(frame, 
/*IsFinal=*/true)`. There are two reasons why I choose to create a new 
intrinsic:

- The semantic isn't correct if we try to use `llvm.coro.suspend(, 
/*IsFinal=*/true)`. First, there is nothing to suspend. Second, 
`llvm.coro.suspend` has more semantics than `llvm.coro.mark.done`. So it seems 
inappropriate to reuse `llvm.coro.suspend`.
- The implementation would be more simple and robust if we use new intrinsics. 
There are codes treating `llvm.coro.suspend` specially. It is necessary to 
construct the structure  of the coroutine. And it would be complicated if we 
reuse `llvm.coro.suspend(, /*IsFinal*/true)` in an inappropriate place.

After this patch, the behavior of this example would be the same with GCC: 
https://godbolt.org/z/rh86xKf85.

Test Plan: check-all,  https://godbolt.org/z/rh86xKf85 and an internal 
coroutine library


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D115219

Files:
  clang/docs/LanguageExtensions.rst
  clang/include/clang/Basic/Builtins.def
  clang/lib/CodeGen/CGBuiltin.cpp
  clang/lib/Sema/SemaCoroutine.cpp
  clang/test/CodeGenCoroutines/coro-await-resume-eh-exp-namespace.cpp
  clang/test/CodeGenCoroutines/coro-await-resume-eh.cpp
  clang/test/CodeGenCoroutines/coro-throw-from-unhandled_exception.cpp
  llvm/docs/Coroutines.rst
  llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
  llvm/include/llvm/IR/Intrinsics.td
  llvm/lib/Transforms/Coroutines/CoroInstr.h
  llvm/lib/Transforms/Coroutines/CoroInternal.h
  llvm/lib/Transforms/Coroutines/CoroSplit.cpp
  llvm/lib/Transforms/Coroutines/Coroutines.cpp
  llvm/test/Transforms/Coroutines/coro-mark-done.ll

Index: llvm/test/Transforms/Coroutines/coro-mark-done.ll
===================================================================
--- /dev/null
+++ llvm/test/Transforms/Coroutines/coro-mark-done.ll
@@ -0,0 +1,68 @@
+; Check that the @llvm.coro.mark.done could be lowered correctly
+; RUN: opt < %s -passes='cgscc(coro-split),simplifycfg,early-cse' -S | FileCheck %s
+
+define i8* @f(i1 %val) "coroutine.presplit"="1" personality i32 3 {
+entry:
+  %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
+  %hdl = call i8* @llvm.coro.begin(token %id, i8* null)
+  call void @print(i32 0)
+  br i1 %val, label %resume, label %susp
+
+susp:
+  %0 = call i8 @llvm.coro.suspend(token none, i1 false)
+  switch i8 %0, label %suspend [i8 0, label %resume
+                                i8 1, label %suspend]
+resume:
+  invoke void @print(i32 1) to label %suspend unwind label %lpad
+
+suspend:
+  call i1 @llvm.coro.end(i8* %hdl, i1 false)
+  ret i8* %hdl
+
+lpad:
+  %lpval = landingpad { i8*, i32 }
+     cleanup
+
+  call void @llvm.coro.mark.done()
+  %need.resume = call i1 @llvm.coro.end(i8* null, i1 true)
+  resume { i8*, i32 } %lpval
+}
+; CHECK-LABEL: define i8* @f(
+; CHECK: lpad:
+; CHECK: store void (%f.Frame*)* null, void (%f.Frame*)** %resume.addr, align 8
+; CHECK-NEXT: resume { i8*, i32 } %lpval
+
+; CHECK-LABEL: define internal fastcc void @f.resume
+; CHECK: lpad:
+; CHECK: %ResumeFn.addr = getelementptr inbounds %f.Frame, %f.Frame* %FramePtr, i32 0, i32 0
+; CHECK-NEXT: store void (%f.Frame*)* null, void (%f.Frame*)** %ResumeFn.addr, align 8
+; CHECK-NEXT: resume { i8*, i32 } %lpval
+
+; CHECK-LABEL: define internal fastcc void @f.destroy
+; CHECK: lpad:
+; CHECK: %ResumeFn.addr = getelementptr inbounds %f.Frame, %f.Frame* %FramePtr, i32 0, i32 0
+; CHECK-NEXT: store void (%f.Frame*)* null, void (%f.Frame*)** %ResumeFn.addr, align 8
+; CHECK-NEXT: resume { i8*, i32 } %lpval
+
+; CHECK-LABEL: define internal fastcc void @f.cleanup
+; CHECK: lpad:
+; CHECK: %ResumeFn.addr = getelementptr inbounds %f.Frame, %f.Frame* %FramePtr, i32 0, i32 0
+; CHECK-NEXT: store void (%f.Frame*)* null, void (%f.Frame*)** %ResumeFn.addr, align 8
+; CHECK-NEXT: resume { i8*, i32 } %lpval
+
+declare i8* @llvm.coro.free(token, i8*)
+declare i32 @llvm.coro.size.i32()
+declare i8  @llvm.coro.suspend(token, i1)
+declare void @llvm.coro.resume(i8*)
+declare void @llvm.coro.destroy(i8*)
+declare void @llvm.coro.mark.done()
+
+declare token @llvm.coro.id(i32, i8*, i8*, i8*)
+declare i8* @llvm.coro.alloc(token)
+declare i8* @llvm.coro.begin(token, i8*)
+declare i1 @llvm.coro.end(i8*, i1)
+
+declare noalias i8* @malloc(i32)
+declare void @print(i32)
+declare void @free(i8*)
+ 
\ No newline at end of file
Index: llvm/lib/Transforms/Coroutines/Coroutines.cpp
===================================================================
--- llvm/lib/Transforms/Coroutines/Coroutines.cpp
+++ llvm/lib/Transforms/Coroutines/Coroutines.cpp
@@ -301,6 +301,9 @@
         }
         break;
       }
+      case Intrinsic::coro_mark_done:
+        CoroMarkDones.push_back(cast<CoroMarkDoneInst>(II));
+        break;
       case Intrinsic::coro_begin: {
         auto CB = cast<CoroBeginInst>(II);
 
Index: llvm/lib/Transforms/Coroutines/CoroSplit.cpp
===================================================================
--- llvm/lib/Transforms/Coroutines/CoroSplit.cpp
+++ llvm/lib/Transforms/Coroutines/CoroSplit.cpp
@@ -1480,10 +1480,28 @@
   S.resize(N);
 }
 
+static void handleCoroMarkDone(Function &F, coro::Shape &Shape) {
+  IRBuilder<> Builder(F.getContext());
+  for (auto *CMD : Shape.CoroMarkDones) {
+    Builder.SetInsertPoint(CMD);
+    // Done state is represented by storing zero in ResumeFnAddr.
+    auto *GepIndex = Builder.CreateStructGEP(
+        Shape.FrameTy, Shape.FramePtr, coro::Shape::SwitchFieldIndex::Resume,
+        "ResumeFn.addr");
+    auto *NullPtr = ConstantPointerNull::get(cast<PointerType>(
+        Shape.FrameTy->getTypeAtIndex(coro::Shape::SwitchFieldIndex::Resume)));
+    Builder.CreateStore(NullPtr, GepIndex);
+    CMD->eraseFromParent();
+  }
+
+  Shape.CoroMarkDones.clear();
+}
+
 static void splitSwitchCoroutine(Function &F, coro::Shape &Shape,
                                  SmallVectorImpl<Function *> &Clones) {
   assert(Shape.ABI == coro::ABI::Switch);
 
+  handleCoroMarkDone(F, Shape);
   createResumeEntryBlock(F, Shape);
   auto ResumeClone = createClone(F, ".resume", Shape,
                                  CoroCloner::Kind::SwitchResume);
Index: llvm/lib/Transforms/Coroutines/CoroInternal.h
===================================================================
--- llvm/lib/Transforms/Coroutines/CoroInternal.h
+++ llvm/lib/Transforms/Coroutines/CoroInternal.h
@@ -101,6 +101,7 @@
   SmallVector<CoroSizeInst *, 2> CoroSizes;
   SmallVector<AnyCoroSuspendInst *, 4> CoroSuspends;
   SmallVector<CallInst*, 2> SwiftErrorOps;
+  SmallVector<CoroMarkDoneInst *, 1> CoroMarkDones;
 
   // Field indexes for special fields in the switch lowering.
   struct SwitchFieldIndex {
Index: llvm/lib/Transforms/Coroutines/CoroInstr.h
===================================================================
--- llvm/lib/Transforms/Coroutines/CoroInstr.h
+++ llvm/lib/Transforms/Coroutines/CoroInstr.h
@@ -599,6 +599,18 @@
   }
 };
 
+/// This represents the llvm.coro.mark.done instruction.
+class LLVM_LIBRARY_VISIBILITY CoroMarkDoneInst : public IntrinsicInst {
+public:
+  // Methods to support type inquiry through isa, cast, and dyn_cast:
+  static bool classof(const IntrinsicInst *I) {
+    return I->getIntrinsicID() == Intrinsic::coro_mark_done;
+  }
+  static bool classof(const Value *V) {
+    return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V));
+  }
+};
+
 class LLVM_LIBRARY_VISIBILITY AnyCoroEndInst : public IntrinsicInst {
   enum { FrameArg, UnwindArg };
 
Index: llvm/include/llvm/IR/Intrinsics.td
===================================================================
--- llvm/include/llvm/IR/Intrinsics.td
+++ llvm/include/llvm/IR/Intrinsics.td
@@ -1286,6 +1286,8 @@
                                [IntrNoMem, ReadNone<ArgIndex<0>>,
                                 ReadNone<ArgIndex<1>>]>;
 
+def int_coro_mark_done : Intrinsic<[], [], []>;
+
 // Coroutine Manipulation Intrinsics.
 
 def int_coro_resume : Intrinsic<[], [llvm_ptr_ty], [Throws]>;
Index: llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
===================================================================
--- llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
+++ llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
@@ -626,6 +626,7 @@
     case Intrinsic::coro_size:
     case Intrinsic::coro_suspend:
     case Intrinsic::coro_param:
+    case Intrinsic::coro_mark_done:
     case Intrinsic::coro_subfn_addr:
       // These intrinsics don't actually represent code after lowering.
       return 0;
Index: llvm/docs/Coroutines.rst
===================================================================
--- llvm/docs/Coroutines.rst
+++ llvm/docs/Coroutines.rst
@@ -1644,6 +1644,54 @@
 In a yield-once coroutine, it is undefined behavior if the coroutine
 executes a call to ``llvm.coro.suspend.retcon`` after resuming in any way.
 
+.. _coro.mark.done:
+
+'llvm.coro.mark.done' Intrinsic
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+::
+
+  declare void @llvm.coro.mark.done()
+
+Overview:
+"""""""""
+
+The '``llvm.coro.mark.done``' is used by a frontend to mark a coroutine suspended
+at the final suspend point. After '``llvm.coro.mark.done``' is called, `llvm.coro.done()`
+should return true. All of the '``llvm.coro.mark.done``' would be lowered in CoroSplit pass.
+This intrinsic is only supported for switched-resume coroutines.
+
+Arguments:
+""""""""""
+
+The '``llvm.coro.mark.done``' doesn't require any argument. The basic assumption here is that
+the coroutines in LLVM wouldn't be inlined before splitted. So there is no ambiguousity about the
+coroutine that the '``llvm.coro.mark.done``' refers to.
+
+Semantics:
+""""""""""
+
+After '``llvm.coro.mark.done``' is called in a coroutine function, the corresponding coroutine is
+considered suspend at final suspend point. In other words, it isn't allowed to resume the coroutine
+any more.
+
+Example:
+""""""""
+
+In C++'s specification, [dcl.fct.def.coroutine]/p14 says:
+>    If the evaluation of the expression promise.unhandled_­exception() exits via an exception,
+> the coroutine is considered suspended at the final suspend point.
+
+In this case, the frontend could generate following code:
+
+.. code-block:: c++
+
+  try {
+    promise.unhandled_­exception();
+  } catch (...) {
+    call void @llvm.coro.mark.done();
+    throw;
+  }
+
 .. _coro.param:
 
 'llvm.coro.param' Intrinsic
Index: clang/test/CodeGenCoroutines/coro-throw-from-unhandled_exception.cpp
===================================================================
--- /dev/null
+++ clang/test/CodeGenCoroutines/coro-throw-from-unhandled_exception.cpp
@@ -0,0 +1,43 @@
+// RUN: %clang_cc1 -std=c++20 \
+// 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"
+
+struct throwing_task {
+  struct promise_type {
+    auto get_return_object() { return throwing_task{}; }
+    auto initial_suspend() { return std::suspend_always{}; }
+    auto final_suspend() noexcept { return std::suspend_never{}; }
+    void return_void() {}
+    void unhandled_exception() { throw 43; }
+  };
+};
+
+throwing_task f() {
+    // CHECK: invoke void @_ZN13throwing_task12promise_type19unhandled_exceptionEv
+    // CHECK-NEXT: to label %{{.+}} unwind label %[[LPAD:.+]]
+    // CHECK: [[LPAD]]
+    // CHECK: br label %[[CATCH:.+]]
+    // CHECK: [[CATCH]]
+    // CHECK-NOT: br label
+    // CHECK: call void @llvm.coro.mark.done
+    // CHECK-NEXT: invoke void @__cxa_rethrow()
+    co_return;
+}
+
+struct nothrowing_task {
+  struct promise_type {
+    auto get_return_object() { return nothrowing_task{}; }
+    auto initial_suspend() { return std::suspend_always{}; }
+    auto final_suspend() noexcept { return std::suspend_never{}; }
+    void return_void() {}
+    void unhandled_exception() noexcept {}
+  };
+};
+
+nothrowing_task f2() {
+    // CHECK: call void @_ZN15nothrowing_task12promise_type19unhandled_exceptionEv
+    co_return;
+}
Index: clang/test/CodeGenCoroutines/coro-await-resume-eh.cpp
===================================================================
--- clang/test/CodeGenCoroutines/coro-await-resume-eh.cpp
+++ clang/test/CodeGenCoroutines/coro-await-resume-eh.cpp
@@ -51,6 +51,8 @@
   // CHECK: invoke void @_ZN13throwing_task12promise_type19unhandled_exceptionEv
   // CHECK-NEXT: to label %[[RESUMEENDCATCH:.+]] unwind label
   // CHECK: [[RESUMEENDCATCH]]:
+  // CHECK-NEXT: br label %[[TRY_CONT:.+]]
+  // CHECK: [[TRY_CONT]]
   // CHECK-NEXT: invoke void @__cxa_end_catch()
   // CHECK-NEXT: to label %[[RESUMEENDCATCHCONT:.+]] unwind label
   // CHECK: [[RESUMEENDCATCHCONT]]:
Index: clang/test/CodeGenCoroutines/coro-await-resume-eh-exp-namespace.cpp
===================================================================
--- clang/test/CodeGenCoroutines/coro-await-resume-eh-exp-namespace.cpp
+++ clang/test/CodeGenCoroutines/coro-await-resume-eh-exp-namespace.cpp
@@ -53,6 +53,8 @@
   // CHECK: invoke void @_ZN13throwing_task12promise_type19unhandled_exceptionEv
   // CHECK-NEXT: to label %[[RESUMEENDCATCH:.+]] unwind label
   // CHECK: [[RESUMEENDCATCH]]:
+  // CHECK-NEXT: br label %[[TRY_CONT:.+]]
+  // CHECK: [[TRY_CONT]]
   // CHECK-NEXT: invoke void @__cxa_end_catch()
   // CHECK-NEXT: to label %[[RESUMEENDCATCHCONT:.+]] unwind label
   // CHECK: [[RESUMEENDCATCHCONT]]:
Index: clang/lib/Sema/SemaCoroutine.cpp
===================================================================
--- clang/lib/Sema/SemaCoroutine.cpp
+++ clang/lib/Sema/SemaCoroutine.cpp
@@ -1476,7 +1476,43 @@
     return false;
   }
 
-  this->OnException = UnhandledException.get();
+  // [dcl.fct.def.coroutine]/p14
+  //   If the evaluation of the expression promise.unhandled_­exception() exits
+  //   via an exception, the coroutine is considered suspended at the final
+  //   suspend point.
+  //
+  // To implement this, it convert the call to promise.unhandled_exception() to:
+  // ```
+  //  try {
+  //    promise.unhandled_exception();
+  //  } catch (...) {
+  //    __builtin_coro_mark_done();
+  //    throw;                        // Re-throw the exception
+  //    thrown from promise.unhandled_­exception()
+  //  }
+  // ```
+  //
+  // The call to __builtin_coro_mark_done would mark current coroutine as
+  // done. In other words, the coroutine is considered suspended at the final
+  // suspend point.
+  auto *TryBody = CompoundStmt::Create(S.getASTContext(),
+                                       UnhandledException.get(), Loc, Loc);
+
+  Expr *MarkDone =
+      S.BuildBuiltinCallExpr(Loc, Builtin::BI__builtin_coro_mark_done, {});
+  ExprResult Rethrow =
+      S.BuildCXXThrow(Loc, nullptr, /*IsThrownVarInScope=*/false);
+  if (Rethrow.isInvalid())
+    return false;
+  auto *CatchBody = CompoundStmt::Create(S.getASTContext(),
+                                         {MarkDone, Rethrow.get()}, Loc, Loc);
+  if (!CatchBody)
+    return false;
+
+  auto *Catch = new (S.getASTContext()) CXXCatchStmt(Loc, nullptr, CatchBody);
+
+  this->OnException =
+      CXXTryStmt::Create(S.getASTContext(), Loc, TryBody, Catch);
   return true;
 }
 
Index: clang/lib/CodeGen/CGBuiltin.cpp
===================================================================
--- clang/lib/CodeGen/CGBuiltin.cpp
+++ clang/lib/CodeGen/CGBuiltin.cpp
@@ -4696,6 +4696,8 @@
     return EmitCoroutineIntrinsic(E, Intrinsic::coro_suspend);
   case Builtin::BI__builtin_coro_param:
     return EmitCoroutineIntrinsic(E, Intrinsic::coro_param);
+  case Builtin::BI__builtin_coro_mark_done:
+    return EmitCoroutineIntrinsic(E, Intrinsic::coro_mark_done);
 
   // OpenCL v2.0 s6.13.16.2, Built-in pipe read and write functions
   case Builtin::BIread_pipe:
Index: clang/include/clang/Basic/Builtins.def
===================================================================
--- clang/include/clang/Basic/Builtins.def
+++ clang/include/clang/Basic/Builtins.def
@@ -1611,6 +1611,7 @@
 LANGBUILTIN(__builtin_coro_begin, "v*v*", "n", COR_LANG)
 LANGBUILTIN(__builtin_coro_end, "bv*Ib", "n", COR_LANG)
 LANGBUILTIN(__builtin_coro_suspend, "cIb", "n", COR_LANG)
+LANGBUILTIN(__builtin_coro_mark_done, "v", "n", COR_LANG)
 LANGBUILTIN(__builtin_coro_param, "bv*v*", "n", COR_LANG)
 
 // OpenCL v2.0 s6.13.16, s9.17.3.5 - Pipe functions.
Index: clang/docs/LanguageExtensions.rst
===================================================================
--- clang/docs/LanguageExtensions.rst
+++ clang/docs/LanguageExtensions.rst
@@ -3007,6 +3007,7 @@
   void  *__builtin_coro_begin(void *memory)
   void   __builtin_coro_end(void *coro_frame, bool unwind)
   char   __builtin_coro_suspend(bool final)
+  void   __builtin_coro_mark_done()
   bool   __builtin_coro_param(void *original, void *copy)
 
 Note that there is no builtin matching the `llvm.coro.save` intrinsic. LLVM
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to