[clang] 19f0770 - [Coroutine][Sema] Cleanup temporaries as early as possible

Xun Li via cfe-commits cfe-commits at lists.llvm.org
Tue Nov 10 13:27:52 PST 2020


Author: Xun Li
Date: 2020-11-10T13:27:42-08:00
New Revision: 19f07709234304b0214f5352750e85cacfda4b36

URL: https://github.com/llvm/llvm-project/commit/19f07709234304b0214f5352750e85cacfda4b36
DIFF: https://github.com/llvm/llvm-project/commit/19f07709234304b0214f5352750e85cacfda4b36.diff

LOG: [Coroutine][Sema] Cleanup temporaries as early as possible

The original bug was discovered in T75057860. Clang front-end emits an AST that looks like this for an co_await expression:
|- ExprWithCleanups
  |- -CoawaitExpr
    |- -MaterializeTemporaryExpr ... Awaiter
      ...
    |- -CXXMemberCallExpr ... .await_ready
      ...
    |- -CallExpr ... __builtin_coro_resume
      ...
    |- -CXXMemberCallExpr ... .await_resume
      ...

ExprWithCleanups is responsible for cleaning up (including calling dtors) for the temporaries generated in the wrapping expression).
In the above structure, the __builtin_coro_resume part (which corresponds to the code for the suspend case in the co_await with symmetric transfer), the pseudocode looks like this:
  __builtin_coro_resume(
   awaiter.await_suspend(
     from_address(
       __builtin_coro_frame())).address());

One of the temporaries that's generated as part of this code is the coroutine handle returned from awaiter.await_suspend() call. The call returns a handle  which is a prvalue (since it's a returned value on the fly). In order to call the address() method on it, it needs to be converted into an xvalue. Hence a materialized temp is created to hold it. This temp will need to be cleaned up eventually. Now, since all cleanups happen at the end of the entire co_await expression, which is after the <coro.suspend> suspension point, the compiler will think that such a temp needs to live across suspensions, and need to be put on the coroutine frame, even though it's only used temporarily just to call address() method.
Such a phenomena not only unnecessarily increases the frame size, but can lead to ASAN failures, if the coroutine was already destroyed as part of the await_suspend() call. This is because if the coroutine was already destroyed, the frame no longer exists, and one can not store anything into it. But if the temporary object is considered to need to live on the frame, it will be stored into the frame after await_suspend() returns.

A fix attempt was done in https://reviews.llvm.org/D87470. Unfortunately it is incorrect. The reason is that cleanups in Clang works more like linearly than nested. There is one current state indicating whether it needs cleanup, and an ExprWithCleanups resets that state. This means that an ExprWithCleanups must be capable of cleaning up all temporaries created  in the wrapping expression, otherwise there will be dangling temporaries cleaned up at the wrong place.
I eventually found a walk-around (https://reviews.llvm.org/D89066) that doesn't break any existing tests while fixing the issue. But it targets the final co_await only. If we ever have a co_await that's not on the final awaiter and the frame gets destroyed after suspend, we are in trouble. Hence we need a proper fix.

This patch is the proper fix. It does the folllowing things to fully resolve the issue:
1. The AST has to be generated in the order according to their nesting relationship. We should not generate AST out of order because then the code generator would incorrectly track the state of temporaries and when a cleanup is needed. So the code in buildCoawaitCalls is reorganized so that we will be generating the AST for each coawait member call in order along with their child AST.
2. await_ready() call is wrapped with an ExprWithCleanups so that temporaries in it gets cleaned up as early as possible to avoid living across suspension.
3. await_suspend() call is wrapped with an ExprWithCleanups if it's not a symmetric transfer. In the case of a symmetric transfer, in order to maintain the musttail call contract, the ExprWithCleanups is wraaped before the resume call.
4. In the end, we mark again that it needs a cleanup, so that the entire CoawaitExpr will be wrapped with a ExprWithCleanups which will clean up the Awaiter object associated with the await expression.

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

Added: 
    clang/test/AST/coroutine-locals-cleanup.cpp
    clang/test/CodeGenCoroutines/coro-symmetric-transfer-01.cpp
    clang/test/CodeGenCoroutines/coro-symmetric-transfer-02.cpp

Modified: 
    clang/lib/Sema/SemaCoroutine.cpp
    clang/test/AST/Inputs/std-coroutine.h

Removed: 
    clang/test/CodeGenCoroutines/coro-symmetric-transfer.cpp


################################################################################
diff  --git a/clang/lib/Sema/SemaCoroutine.cpp b/clang/lib/Sema/SemaCoroutine.cpp
index 5582c728aa2d..ce4e55873734 100644
--- a/clang/lib/Sema/SemaCoroutine.cpp
+++ b/clang/lib/Sema/SemaCoroutine.cpp
@@ -398,51 +398,55 @@ static Expr *maybeTailCall(Sema &S, QualType RetType, Expr *E,
            diag::warn_coroutine_handle_address_invalid_return_type)
         << JustAddress->getType();
 
-  // After the await_suspend call on the awaiter, the coroutine may have
-  // been destroyed. In that case, we can not store anything to the frame
-  // from this point on. Hence here we wrap it immediately with a cleanup. This
-  // could have applied to all await_suspend calls. However doing so causes
-  // alive objects being destructed for reasons that need further
-  // investigations. Here we walk-around it temporarily by only doing it after
-  // the suspend call on the final awaiter (indicated by IsImplicit) where it's
-  // most common to happen.
-  // TODO: Properly clean up the temps generated by await_suspend calls.
-  if (IsImplicit)
-    JustAddress = S.MaybeCreateExprWithCleanups(JustAddress);
+  // Clean up temporary objects so that they don't live across suspension points
+  // unnecessarily. We choose to clean up before the call to
+  // __builtin_coro_resume so that the cleanup code are not inserted in-between
+  // the resume call and return instruction, which would interfere with the
+  // musttail call contract.
+  JustAddress = S.MaybeCreateExprWithCleanups(JustAddress);
   return buildBuiltinCall(S, Loc, Builtin::BI__builtin_coro_resume,
                           JustAddress);
 }
 
 /// Build calls to await_ready, await_suspend, and await_resume for a co_await
 /// expression.
+/// The generated AST tries to clean up temporary objects as early as
+/// possible so that they don't live across suspension points if possible.
+/// Having temporary objects living across suspension points unnecessarily can
+/// lead to large frame size, and also lead to memory corruptions if the
+/// coroutine frame is destroyed after coming back from suspension. This is done
+/// by wrapping both the await_ready call and the await_suspend call with
+/// ExprWithCleanups. In the end of this function, we also need to explicitly
+/// set cleanup state so that the CoawaitExpr is also wrapped with an
+/// ExprWithCleanups to clean up the awaiter associated with the co_await
+/// expression.
 static ReadySuspendResumeResult buildCoawaitCalls(Sema &S, VarDecl *CoroPromise,
                                                   SourceLocation Loc, Expr *E,
                                                   bool IsImplicit) {
   OpaqueValueExpr *Operand = new (S.Context)
       OpaqueValueExpr(Loc, E->getType(), VK_LValue, E->getObjectKind(), E);
 
-  // Assume invalid until we see otherwise.
-  ReadySuspendResumeResult Calls = {{}, Operand, /*IsInvalid=*/true};
-
-  ExprResult CoroHandleRes = buildCoroutineHandle(S, CoroPromise->getType(), Loc);
-  if (CoroHandleRes.isInvalid())
-    return Calls;
-  Expr *CoroHandle = CoroHandleRes.get();
+  // Assume valid until we see otherwise.
+  // Further operations are responsible for setting IsInalid to true.
+  ReadySuspendResumeResult Calls = {{}, Operand, /*IsInvalid=*/false};
 
-  const StringRef Funcs[] = {"await_ready", "await_suspend", "await_resume"};
-  MultiExprArg Args[] = {None, CoroHandle, None};
-  for (size_t I = 0, N = llvm::array_lengthof(Funcs); I != N; ++I) {
-    ExprResult Result = buildMemberCall(S, Operand, Loc, Funcs[I], Args[I]);
-    if (Result.isInvalid())
-      return Calls;
-    Calls.Results[I] = Result.get();
-  }
+  using ACT = ReadySuspendResumeResult::AwaitCallType;
 
-  // Assume the calls are valid; all further checking should make them invalid.
-  Calls.IsInvalid = false;
+  auto BuildSubExpr = [&](ACT CallType, StringRef Func,
+                          MultiExprArg Arg) -> Expr * {
+    ExprResult Result = buildMemberCall(S, Operand, Loc, Func, Arg);
+    if (Result.isInvalid()) {
+      Calls.IsInvalid = true;
+      return nullptr;
+    }
+    Calls.Results[CallType] = Result.get();
+    return Result.get();
+  };
 
-  using ACT = ReadySuspendResumeResult::AwaitCallType;
-  CallExpr *AwaitReady = cast<CallExpr>(Calls.Results[ACT::ACT_Ready]);
+  CallExpr *AwaitReady =
+      cast_or_null<CallExpr>(BuildSubExpr(ACT::ACT_Ready, "await_ready", None));
+  if (!AwaitReady)
+    return Calls;
   if (!AwaitReady->getType()->isDependentType()) {
     // [expr.await]p3 [...]
     // — await-ready is the expression e.await_ready(), contextually converted
@@ -454,10 +458,21 @@ static ReadySuspendResumeResult buildCoawaitCalls(Sema &S, VarDecl *CoroPromise,
       S.Diag(Loc, diag::note_coroutine_promise_call_implicitly_required)
           << AwaitReady->getDirectCallee() << E->getSourceRange();
       Calls.IsInvalid = true;
-    }
-    Calls.Results[ACT::ACT_Ready] = Conv.get();
+    } else
+      Calls.Results[ACT::ACT_Ready] = S.MaybeCreateExprWithCleanups(Conv.get());
+  }
+
+  ExprResult CoroHandleRes =
+      buildCoroutineHandle(S, CoroPromise->getType(), Loc);
+  if (CoroHandleRes.isInvalid()) {
+    Calls.IsInvalid = true;
+    return Calls;
   }
-  CallExpr *AwaitSuspend = cast<CallExpr>(Calls.Results[ACT::ACT_Suspend]);
+  Expr *CoroHandle = CoroHandleRes.get();
+  CallExpr *AwaitSuspend = cast_or_null<CallExpr>(
+      BuildSubExpr(ACT::ACT_Suspend, "await_suspend", CoroHandle));
+  if (!AwaitSuspend)
+    return Calls;
   if (!AwaitSuspend->getType()->isDependentType()) {
     // [expr.await]p3 [...]
     //   - await-suspend is the expression e.await_suspend(h), which shall be
@@ -468,6 +483,11 @@ static ReadySuspendResumeResult buildCoawaitCalls(Sema &S, VarDecl *CoroPromise,
     // Experimental support for coroutine_handle returning await_suspend.
     if (Expr *TailCallSuspend =
             maybeTailCall(S, RetType, AwaitSuspend, Loc, IsImplicit))
+      // Note that we don't wrap the expression with ExprWithCleanups here
+      // because that might interfere with tailcall contract (e.g. inserting
+      // clean up instructions in-between tailcall and return). Instead
+      // ExprWithCleanups is wrapped within maybeTailCall() prior to the resume
+      // call.
       Calls.Results[ACT::ACT_Suspend] = TailCallSuspend;
     else {
       // non-class prvalues always have cv-unqualified types
@@ -479,10 +499,17 @@ static ReadySuspendResumeResult buildCoawaitCalls(Sema &S, VarDecl *CoroPromise,
         S.Diag(Loc, diag::note_coroutine_promise_call_implicitly_required)
             << AwaitSuspend->getDirectCallee();
         Calls.IsInvalid = true;
-      }
+      } else
+        Calls.Results[ACT::ACT_Suspend] =
+            S.MaybeCreateExprWithCleanups(AwaitSuspend);
     }
   }
 
+  BuildSubExpr(ACT::ACT_Resume, "await_resume", None);
+
+  // Make sure the awaiter object gets a chance to be cleaned up.
+  S.Cleanup.setExprNeedsCleanups(true);
+
   return Calls;
 }
 

diff  --git a/clang/test/AST/Inputs/std-coroutine.h b/clang/test/AST/Inputs/std-coroutine.h
index c1be9a19b810..5a1498f00494 100644
--- a/clang/test/AST/Inputs/std-coroutine.h
+++ b/clang/test/AST/Inputs/std-coroutine.h
@@ -5,18 +5,54 @@
 namespace std {
 namespace experimental {
 
-template <class Ret, typename... T>
-struct coroutine_traits { using promise_type = typename Ret::promise_type; };
+template <typename R, typename...> struct coroutine_traits {
+  using promise_type = typename R::promise_type;
+};
+
+template <typename Promise = void> struct coroutine_handle;
+
+template <> struct coroutine_handle<void> {
+  static coroutine_handle from_address(void *addr) noexcept {
+    coroutine_handle me;
+    me.ptr = addr;
+    return me;
+  }
+  void operator()() { resume(); }
+  void *address() const noexcept { return ptr; }
+  void resume() const { __builtin_coro_resume(ptr); }
+  void destroy() const { __builtin_coro_destroy(ptr); }
+  bool done() const { return __builtin_coro_done(ptr); }
+  coroutine_handle &operator=(decltype(nullptr)) {
+    ptr = nullptr;
+    return *this;
+  }
+  coroutine_handle(decltype(nullptr)) : ptr(nullptr) {}
+  coroutine_handle() : ptr(nullptr) {}
+  //  void reset() { ptr = nullptr; } // add to P0057?
+  explicit operator bool() const { return ptr; }
 
-template <class Promise = void>
-struct coroutine_handle {
-  static coroutine_handle from_address(void *) noexcept;
+protected:
+  void *ptr;
 };
-template <>
-struct coroutine_handle<void> {
-  template <class PromiseType>
-  coroutine_handle(coroutine_handle<PromiseType>) noexcept;
-  static coroutine_handle from_address(void *);
+
+template <typename Promise> struct coroutine_handle : coroutine_handle<> {
+  using coroutine_handle<>::operator=;
+
+  static coroutine_handle from_address(void *addr) noexcept {
+    coroutine_handle me;
+    me.ptr = addr;
+    return me;
+  }
+
+  Promise &promise() const {
+    return *reinterpret_cast<Promise *>(
+        __builtin_coro_promise(ptr, alignof(Promise), false));
+  }
+  static coroutine_handle from_promise(Promise &promise) {
+    coroutine_handle p;
+    p.ptr = __builtin_coro_promise(&promise, alignof(Promise), true);
+    return p;
+  }
 };
 
 struct suspend_always {

diff  --git a/clang/test/AST/coroutine-locals-cleanup.cpp b/clang/test/AST/coroutine-locals-cleanup.cpp
new file mode 100644
index 000000000000..5e993ad323f8
--- /dev/null
+++ b/clang/test/AST/coroutine-locals-cleanup.cpp
@@ -0,0 +1,107 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++14 -fsyntax-only -ast-dump %s | FileCheck %s
+
+#include "Inputs/std-coroutine.h"
+
+using namespace std::experimental;
+
+struct Task {
+  struct promise_type {
+    Task get_return_object() noexcept {
+      return Task{coroutine_handle<promise_type>::from_promise(*this)};
+    }
+
+    void return_void() noexcept {}
+
+    struct final_awaiter {
+      bool await_ready() noexcept { return false; }
+      coroutine_handle<> await_suspend(coroutine_handle<promise_type> h) noexcept {
+        h.destroy();
+        return {};
+      }
+      void await_resume() noexcept {}
+    };
+
+    void unhandled_exception() noexcept {}
+
+    final_awaiter final_suspend() noexcept { return {}; }
+
+    suspend_always initial_suspend() noexcept { return {}; }
+
+    template <typename Awaitable>
+    auto await_transform(Awaitable &&awaitable) {
+      return awaitable.co_viaIfAsync();
+    }
+  };
+
+  using handle_t = coroutine_handle<promise_type>;
+
+  class Awaiter {
+  public:
+    explicit Awaiter(handle_t coro) noexcept;
+    Awaiter(Awaiter &&other) noexcept;
+    Awaiter(const Awaiter &) = delete;
+    ~Awaiter();
+
+    bool await_ready() noexcept { return false; }
+    handle_t await_suspend(coroutine_handle<> continuation) noexcept;
+    void await_resume();
+
+  private:
+    handle_t coro_;
+  };
+
+  Task(handle_t coro) noexcept : coro_(coro) {}
+
+  handle_t coro_;
+
+  Task(const Task &t) = delete;
+  Task(Task &&t) noexcept;
+  ~Task();
+  Task &operator=(Task t) noexcept;
+
+  Awaiter co_viaIfAsync();
+};
+
+static Task foo() {
+  co_return;
+}
+
+Task bar() {
+  auto mode = 2;
+  switch (mode) {
+  case 1:
+    co_await foo();
+    break;
+  case 2:
+    co_await foo();
+    break;
+  default:
+    break;
+  }
+}
+
+// CHECK-LABEL: FunctionDecl {{.*}} bar 'Task ()'
+// CHECK:         SwitchStmt
+// CHECK:           CaseStmt
+// CHECK:             ExprWithCleanups {{.*}} 'void'
+// CHECK-NEXT:          CoawaitExpr
+// CHECK-NEXT:            MaterializeTemporaryExpr {{.*}} 'Task::Awaiter':'Task::Awaiter'
+// CHECK:                 ExprWithCleanups {{.*}} 'bool'
+// CHECK-NEXT:              CXXMemberCallExpr {{.*}} 'bool'
+// CHECK-NEXT:                MemberExpr {{.*}} .await_ready
+// CHECK:                 CallExpr {{.*}} 'void'
+// CHECK-NEXT:              ImplicitCastExpr {{.*}} 'void (*)(void *)'
+// CHECK-NEXT:                DeclRefExpr {{.*}} '__builtin_coro_resume' 'void (void *)'
+// CHECK-NEXT:              ExprWithCleanups {{.*}} 'void *'
+
+// CHECK:           CaseStmt
+// CHECK:             ExprWithCleanups {{.*}} 'void'
+// CHECK-NEXT:          CoawaitExpr
+// CHECK-NEXT:            MaterializeTemporaryExpr {{.*}} 'Task::Awaiter':'Task::Awaiter'
+// CHECK:                 ExprWithCleanups {{.*}} 'bool'
+// CHECK-NEXT:              CXXMemberCallExpr {{.*}} 'bool'
+// CHECK-NEXT:                MemberExpr {{.*}} .await_ready
+// CHECK:                 CallExpr {{.*}} 'void'
+// CHECK-NEXT:              ImplicitCastExpr {{.*}} 'void (*)(void *)'
+// CHECK-NEXT:                DeclRefExpr {{.*}} '__builtin_coro_resume' 'void (void *)'
+// CHECK-NEXT:              ExprWithCleanups {{.*}} 'void *'

diff  --git a/clang/test/CodeGenCoroutines/coro-symmetric-transfer.cpp b/clang/test/CodeGenCoroutines/coro-symmetric-transfer-01.cpp
similarity index 94%
rename from clang/test/CodeGenCoroutines/coro-symmetric-transfer.cpp
rename to clang/test/CodeGenCoroutines/coro-symmetric-transfer-01.cpp
index 4f841a918bcf..f459286122f1 100644
--- a/clang/test/CodeGenCoroutines/coro-symmetric-transfer.cpp
+++ b/clang/test/CodeGenCoroutines/coro-symmetric-transfer-01.cpp
@@ -48,7 +48,7 @@ detached_task foo() {
   co_return;
 }
 
-// check that the lifetime of the coroutine handle used to obtain the address is contained within single basic block.
+// check that the lifetime of the coroutine handle used to obtain the address is contained within single basic block, and hence does not live across suspension points.
 // CHECK-LABEL: final.suspend:
 // CHECK:         %[[PTR1:.+]] = bitcast %"struct.std::experimental::coroutines_v1::coroutine_handle.0"* %[[ADDR_TMP:.+]] to i8*
 // CHECK-NEXT:    call void @llvm.lifetime.start.p0i8(i64 8, i8* %[[PTR1]])

diff  --git a/clang/test/CodeGenCoroutines/coro-symmetric-transfer-02.cpp b/clang/test/CodeGenCoroutines/coro-symmetric-transfer-02.cpp
new file mode 100644
index 000000000000..e47423f6c6e0
--- /dev/null
+++ b/clang/test/CodeGenCoroutines/coro-symmetric-transfer-02.cpp
@@ -0,0 +1,126 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++14 -O1 -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s
+
+#include "Inputs/coroutine.h"
+
+namespace coro = std::experimental::coroutines_v1;
+
+struct Task {
+  struct promise_type {
+    Task get_return_object() noexcept {
+      return Task{coro::coroutine_handle<promise_type>::from_promise(*this)};
+    }
+
+    void return_void() noexcept {}
+
+    struct final_awaiter {
+      bool await_ready() noexcept { return false; }
+      coro::coroutine_handle<> await_suspend(coro::coroutine_handle<promise_type> h) noexcept {
+        h.destroy();
+        return {};
+      }
+      void await_resume() noexcept {}
+    };
+
+    void unhandled_exception() noexcept {}
+
+    final_awaiter final_suspend() noexcept { return {}; }
+
+    coro::suspend_always initial_suspend() noexcept { return {}; }
+
+    template <typename Awaitable>
+    auto await_transform(Awaitable &&awaitable) {
+      return awaitable.co_viaIfAsync();
+    }
+  };
+
+  using handle_t = coro::coroutine_handle<promise_type>;
+
+  class Awaiter {
+  public:
+    explicit Awaiter(handle_t coro) noexcept;
+    Awaiter(Awaiter &&other) noexcept;
+    Awaiter(const Awaiter &) = delete;
+    ~Awaiter();
+
+    bool await_ready() noexcept { return false; }
+    handle_t await_suspend(coro::coroutine_handle<> continuation) noexcept;
+    void await_resume();
+
+  private:
+    handle_t coro_;
+  };
+
+  Task(handle_t coro) noexcept : coro_(coro) {}
+
+  handle_t coro_;
+
+  Task(const Task &t) = delete;
+  Task(Task &&t) noexcept;
+  ~Task();
+  Task &operator=(Task t) noexcept;
+
+  Awaiter co_viaIfAsync();
+};
+
+static Task foo() {
+  co_return;
+}
+
+Task bar() {
+  auto mode = 2;
+  switch (mode) {
+  case 1:
+    co_await foo();
+    break;
+  case 2:
+    co_await foo();
+    break;
+  default:
+    break;
+  }
+}
+
+// CHECK-LABEL: define void @_Z3barv
+// CHECK:         %[[MODE:.+]] = load i32, i32* %mode
+// CHECK-NEXT:    switch i32 %[[MODE]], label %{{.+}} [
+// CHECK-NEXT:      i32 1, label %[[CASE1:.+]]
+// CHECK-NEXT:      i32 2, label %[[CASE2:.+]]
+// CHECK-NEXT:    ]
+
+// CHECK:       [[CASE1]]:
+// CHECK:         br i1 %{{.+}}, label %[[CASE1_AWAIT_READY:.+]], label %[[CASE1_AWAIT_SUSPEND:.+]]
+// CHECK:       [[CASE1_AWAIT_SUSPEND]]:
+// CHECK-NEXT:    %{{.+}} = call token @llvm.coro.save(i8* null)
+// CHECK-NEXT:    %[[HANDLE11:.+]] = bitcast %"struct.std::experimental::coroutines_v1::coroutine_handle"* %[[TMP1:.+]] to i8*
+// CHECK-NEXT:    call void @llvm.lifetime.start.p0i8(i64 8, i8* %[[HANDLE11]])
+
+// CHECK:         %[[HANDLE12:.+]] = bitcast %"struct.std::experimental::coroutines_v1::coroutine_handle"* %[[TMP1]] to i8*
+// CHECK-NEXT:    call void @llvm.lifetime.end.p0i8(i64 8, i8* %[[HANDLE12]])
+// CHECK-NEXT:    call void @llvm.coro.resume
+// CHECK-NEXT:    %{{.+}} = call i8 @llvm.coro.suspend
+// CHECK-NEXT:    switch i8 %{{.+}}, label %coro.ret [
+// CHECK-NEXT:      i8 0, label %[[CASE1_AWAIT_READY]]
+// CHECK-NEXT:      i8 1, label %[[CASE1_AWAIT_CLEANUP:.+]]
+// CHECK-NEXT:    ]
+// CHECK:       [[CASE1_AWAIT_CLEANUP]]:
+// make sure that the awaiter eventually gets cleaned up.
+// CHECK:         call void @{{.+Awaiter.+}}
+
+// CHECK:       [[CASE2]]:
+// CHECK:         br i1 %{{.+}}, label %[[CASE2_AWAIT_READY:.+]], label %[[CASE2_AWAIT_SUSPEND:.+]]
+// CHECK:       [[CASE2_AWAIT_SUSPEND]]:
+// CHECK-NEXT:    %{{.+}} = call token @llvm.coro.save(i8* null)
+// CHECK-NEXT:    %[[HANDLE21:.+]] = bitcast %"struct.std::experimental::coroutines_v1::coroutine_handle"* %[[TMP2:.+]] to i8*
+// CHECK-NEXT:    call void @llvm.lifetime.start.p0i8(i64 8, i8* %[[HANDLE21]])
+
+// CHECK:         %[[HANDLE22:.+]] = bitcast %"struct.std::experimental::coroutines_v1::coroutine_handle"* %[[TMP2]] to i8*
+// CHECK-NEXT:    call void @llvm.lifetime.end.p0i8(i64 8, i8* %[[HANDLE22]])
+// CHECK-NEXT:    call void @llvm.coro.resume
+// CHECK-NEXT:    %{{.+}} = call i8 @llvm.coro.suspend
+// CHECK-NEXT:    switch i8 %{{.+}}, label %coro.ret [
+// CHECK-NEXT:      i8 0, label %[[CASE2_AWAIT_READY]]
+// CHECK-NEXT:      i8 1, label %[[CASE2_AWAIT_CLEANUP:.+]]
+// CHECK-NEXT:    ]
+// CHECK:       [[CASE2_AWAIT_CLEANUP]]:
+// make sure that the awaiter eventually gets cleaned up.
+// CHECK:         call void @{{.+Awaiter.+}}


        


More information about the cfe-commits mailing list