[clang] [Clang][Coroutines] Improve GRO handling to better fit scopes and cleanups (PR #66706)

Bruno Cardoso Lopes via cfe-commits cfe-commits at lists.llvm.org
Tue Sep 19 17:51:54 PDT 2023


https://github.com/bcardosolopes updated https://github.com/llvm/llvm-project/pull/66706

>From f9d54b81d4c3c10c3dd26193d9bef52785826f21 Mon Sep 17 00:00:00 2001
From: Bruno Cardoso Lopes <bruno.cardoso at gmail.com>
Date: Fri, 15 Sep 2023 15:40:20 -0700
Subject: [PATCH 1/2] [Clang][Coroutines] Improve GRO handling to better fit
 scopes and cleanups

When dealing with short-circuiting coroutines (e.g. `expected`), the deferred
calls that resolve the `get_return_object` are currently being emitted *after*
we delete the coroutine frame.

This was caught by ASAN when using optimizations `-O1` and above:
optimizations after inlining would place the `__coro_gro` in the heap, and
subsequent delete of the coroframe followed by the conversion -> BOOM.

This patch fixes GRO placement into scopes and cleanups such that:

1. Emit the return block while within `CallCoroDelete` cleanup scope. This
makes the conversion call to happen right after `coro.end` but before deleting
the coroutine frame.

2. Change gro allocation to happen *after* `__promise` is allocated. The effect
is to bound `__coro_gro` within the lifetime of `__promise`, which should make
destruction of the conversion result to happen before we also delete the
coroframe.
---
 clang/lib/CodeGen/CGCoroutine.cpp             |  38 +--
 .../test/CodeGenCoroutines/coro-dest-slot.cpp |  18 +-
 .../test/CodeGenCoroutines/coro-expected.cpp  | 223 ++++++++++++++++++
 clang/test/CodeGenCoroutines/coro-gro.cpp     |  30 ++-
 4 files changed, 280 insertions(+), 29 deletions(-)
 create mode 100644 clang/test/CodeGenCoroutines/coro-expected.cpp

diff --git a/clang/lib/CodeGen/CGCoroutine.cpp b/clang/lib/CodeGen/CGCoroutine.cpp
index 448943084acedf3..741a8f9990c1ed7 100644
--- a/clang/lib/CodeGen/CGCoroutine.cpp
+++ b/clang/lib/CodeGen/CGCoroutine.cpp
@@ -657,8 +657,6 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
   CurCoro.Data->CoroBegin = CoroBegin;
 
   GetReturnObjectManager GroManager(*this, S);
-  GroManager.EmitGroAlloca();
-
   CurCoro.Data->CleanupJD = getJumpDestInCurrentScope(RetBB);
   {
     CGDebugInfo *DI = getDebugInfo();
@@ -696,6 +694,12 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
     // promise local variable was not emitted yet.
     CoroId->setArgOperand(1, PromiseAddrVoidPtr);
 
+    // Emit the alloca for ` __coro_gro`  *after* it's done for the promise.
+    // This guarantees that while looking at deferred results from calls to
+    // `get_return_object`, the lifetime of ` __coro_gro` is enclosed by the
+    // `__promise` lifetime, and cleanup order is properly respected.
+    GroManager.EmitGroAlloca();
+
     // Now we have the promise, initialize the GRO
     GroManager.EmitGroInit();
 
@@ -752,22 +756,22 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
       // We don't need FinalBB. Emit it to make sure the block is deleted.
       EmitBlock(FinalBB, /*IsFinished=*/true);
     }
-  }
 
-  EmitBlock(RetBB);
-  // Emit coro.end before getReturnStmt (and parameter destructors), since
-  // resume and destroy parts of the coroutine should not include them.
-  llvm::Function *CoroEnd = CGM.getIntrinsic(llvm::Intrinsic::coro_end);
-  Builder.CreateCall(CoroEnd,
-                     {NullPtr, Builder.getFalse(),
-                      llvm::ConstantTokenNone::get(CoroEnd->getContext())});
-
-  if (Stmt *Ret = S.getReturnStmt()) {
-    // Since we already emitted the return value above, so we shouldn't
-    // emit it again here.
-    if (GroManager.DirectEmit)
-      cast<ReturnStmt>(Ret)->setRetValue(nullptr);
-    EmitStmt(Ret);
+    EmitBlock(RetBB);
+    // Emit coro.end before getReturnStmt (and parameter destructors), since
+    // resume and destroy parts of the coroutine should not include them.
+    llvm::Function *CoroEnd = CGM.getIntrinsic(llvm::Intrinsic::coro_end);
+    Builder.CreateCall(CoroEnd,
+                       {NullPtr, Builder.getFalse(),
+                        llvm::ConstantTokenNone::get(CoroEnd->getContext())});
+
+    if (Stmt *Ret = S.getReturnStmt()) {
+      // Since we already emitted the return value above, so we shouldn't
+      // emit it again here.
+      if (GroManager.DirectEmit)
+        cast<ReturnStmt>(Ret)->setRetValue(nullptr);
+      EmitStmt(Ret);
+    }
   }
 
   // LLVM require the frontend to mark the coroutine.
diff --git a/clang/test/CodeGenCoroutines/coro-dest-slot.cpp b/clang/test/CodeGenCoroutines/coro-dest-slot.cpp
index d794c74cd52d61a..0d2416e7913da0d 100644
--- a/clang/test/CodeGenCoroutines/coro-dest-slot.cpp
+++ b/clang/test/CodeGenCoroutines/coro-dest-slot.cpp
@@ -17,6 +17,7 @@ struct coro {
 extern "C" coro f(int) { co_return; }
 // Verify that cleanup.dest.slot is eliminated in a coroutine.
 // CHECK-LABEL: f(
+// CHECK: %[[PROMISE:.+]] = alloca %"struct.coro::promise_type"
 // CHECK: %[[INIT_SUSPEND:.+]] = call i8 @llvm.coro.suspend(
 // CHECK-NEXT: switch i8 %[[INIT_SUSPEND]], label
 // CHECK-NEXT:   i8 0, label %[[INIT_READY:.+]]
@@ -32,9 +33,22 @@ extern "C" coro f(int) { co_return; }
 
 // CHECK: call void @_ZNSt13suspend_never12await_resumeEv(
 // CHECK: %[[CLEANUP_DEST1:.+]] = phi i32 [ 0, %[[FINAL_READY]] ], [ 2, %[[FINAL_CLEANUP]] ]
-// CHECK: %[[CLEANUP_DEST2:.+]] = phi i32 [ %[[CLEANUP_DEST0]], %{{.+}} ], [ %[[CLEANUP_DEST1]], %{{.+}} ], [ 0, %{{.+}} ]
+// CHECK: switch i32 %[[CLEANUP_DEST1]], label %[[CLEANUP_BB_2:.+]] [
+// CHECK:   i32 0, label %[[CLEANUP_CONT:.+]]
+// CHECK: ]
+
+// CHECK: [[CLEANUP_CONT]]:
+// CHECK:   br label %[[CORO_RET:.+]]
+
+// CHECK: [[CORO_RET]]:
+// CHECK:   call i1 @llvm.coro.end
+// CHECK:   br label %cleanup19
+
+// CHECK: [[CLEANUP_BB_2]]:
+// CHECK: %[[CLEANUP_DEST2:.+]] = phi i32 [ %[[CLEANUP_DEST0]], %{{.+}} ], [ 1, %[[CORO_RET]] ], [ %[[CLEANUP_DEST1]], %{{.+}} ]
+// CHECK: call void @llvm.lifetime.end.p0(i64 1, ptr %[[PROMISE]])
 // CHECK: call ptr @llvm.coro.free(
 // CHECK: switch i32 %[[CLEANUP_DEST2]], label %{{.+}} [
-// CHECK-NEXT: i32 0
 // CHECK-NEXT: i32 2
+// CHECK-NEXT: i32 1
 // CHECK-NEXT: ]
diff --git a/clang/test/CodeGenCoroutines/coro-expected.cpp b/clang/test/CodeGenCoroutines/coro-expected.cpp
new file mode 100644
index 000000000000000..5d0f7d1fc261f35
--- /dev/null
+++ b/clang/test/CodeGenCoroutines/coro-expected.cpp
@@ -0,0 +1,223 @@
+// RUN: %clang_cc1 -std=c++20 -triple=x86_64-unknown-linux-gnu -fcxx-exceptions -emit-llvm -o %t.ll %s -disable-llvm-passes
+// RUN: FileCheck --input-file=%t.ll %s
+
+// Verifies lifetime of __coro_gro variable w.r.t to the promise type,
+// more specifically make sure the coroutine frame isn't destroyed *before*
+// the conversion function that unwraps the promise proxy as part of
+// short-circuiting coroutines delayed behavior.
+
+#include "Inputs/coroutine.h"
+
+using namespace std;
+
+namespace folly {
+
+template <class Error>
+struct unexpected final {
+  Error error;
+
+  constexpr unexpected() = default;
+  constexpr unexpected(unexpected const&) = default;
+  unexpected& operator=(unexpected const&) = default;
+  constexpr /* implicit */ unexpected(Error err) : error(err) {}
+};
+
+template <class Value, class Error>
+class expected final {
+  enum class Which : unsigned char { empty, value, error };
+
+  Which which_;
+  Error error_;
+  Value value_;
+
+ public:
+  struct promise_type;
+
+  constexpr expected() noexcept : which_(Which::empty) {}
+  constexpr expected(expected const& that) = default;
+
+  expected(expected*& pointer) noexcept : which_(Which::empty) {
+    pointer = this;
+  }
+
+  constexpr /* implicit */ expected(Value val) noexcept
+      : which_(Which::value), value_(val) {}
+
+  /* implicit */ expected(Error) = delete;
+
+  constexpr /* implicit */ expected(unexpected<Error> err) noexcept
+      : which_(Which::error), error_(err.error) {}
+
+  expected& operator=(expected const& that) = default;
+
+  expected& operator=(Value val) noexcept {
+    which_ = Which::value;
+    value_ = val;
+    return *this;
+  }
+
+  expected& operator=(unexpected<Error> err) noexcept {
+    which_ = Which::error;
+    error_ = err.error;
+    return *this;
+  }
+
+  /* implicit */ expected& operator=(Error) = delete;
+
+  constexpr bool has_value() const noexcept {
+    return Which::value == this->which_;
+  }
+
+  constexpr bool has_error() const noexcept {
+    return Which::error == this->which_;
+  }
+
+  Value value() const {
+    require_value();
+    return value_;
+  }
+
+  Error error() const {
+    require_error();
+    return error_;
+  }
+
+ private:
+  void require_value() const {
+    if (!has_value()) {
+      // terminate
+    }
+  }
+
+  void require_error() const {
+    if (!has_error()) {
+      // terminate
+    }
+  }
+};
+
+template <typename Value, typename Error>
+struct expected<Value, Error>::promise_type {
+  struct return_object;
+
+  expected<Value, Error>* value_ = nullptr;
+
+  promise_type() = default;
+  promise_type(promise_type const&) = delete;
+  return_object get_return_object() noexcept {
+    return return_object{value_};
+  }
+  std::suspend_never initial_suspend() const noexcept {
+    return {};
+  }
+  std::suspend_never final_suspend() const noexcept {
+    return {};
+  }
+  void return_value(Value u) {
+    *value_ = u;
+  }
+  void unhandled_exception() {
+    throw;
+  }
+};
+
+template <typename Value, typename Error>
+struct expected<Value, Error>::promise_type::return_object {
+  expected<Value, Error> storage_;
+  expected<Value, Error>*& pointer_;
+
+  explicit return_object(expected<Value, Error>*& p) noexcept : pointer_{p} {
+    pointer_ = &storage_;
+  }
+  return_object(return_object const&) = delete;
+  ~return_object() {}
+
+  /* implicit */ operator expected<Value, Error>() {
+    return storage_; // deferred
+  }
+};
+
+template <typename Value, typename Error>
+struct expected_awaitable {
+  using promise_type = typename expected<Value, Error>::promise_type;
+
+  expected<Value, Error> o_;
+
+  explicit expected_awaitable(expected<Value, Error> o) : o_(o) {}
+
+  bool await_ready() const noexcept {
+    return o_.has_value();
+  }
+  Value await_resume() {
+    return o_.value();
+  }
+  void await_suspend(std::coroutine_handle<promise_type> h) {
+    *h.promise().value_ = unexpected<Error>(o_.error());
+    h.destroy();
+  }
+};
+
+template <typename Value, typename Error>
+expected_awaitable<Value, Error>
+/* implicit */ operator co_await(expected<Value, Error> o) {
+  return expected_awaitable<Value, Error>{o};
+}
+
+} // namespace folly
+
+struct err {};
+
+folly::expected<int, err> go(folly::expected<int, err> e) {
+  co_return co_await e;
+}
+
+int main() {
+  return go(0).value();
+}
+
+// CHECK: define {{.*}} @_Z2goN5folly8expectedIi3errEE
+
+// CHECK: %[[RetVal:.+]] = alloca %"class.folly::expected"
+// CHECK: %[[Promise:.+]] = alloca %"struct.folly::expected<int, err>::promise_type"
+// CHECK: %[[GroActive:.+]] = alloca i1
+// CHECK: %[[CoroGro:.+]] = alloca %"struct.folly::expected<int, err>::promise_type::return_object"
+
+// __promise lifetime should enclose __coro_gro's.
+
+// CHECK: call void @llvm.lifetime.start.p0(i64 8, ptr %[[Promise]])
+// CHECK: call void @_ZN5folly8expectedIi3errE12promise_typeC1Ev({{.*}} %[[Promise]])
+// CHECK: store i1 false, ptr %[[GroActive]]
+// CHECK: call void @llvm.lifetime.start.p0(i64 16, ptr %[[CoroGro]])
+// CHECK: call void @_ZN5folly8expectedIi3errE12promise_type17get_return_objectEv({{.*}} %[[CoroGro]],{{.*}} %[[Promise]])
+// CHECK: store i1 true, ptr %[[GroActive]]
+
+// Calls into `folly::expected<int, err>::promise_type::return_object::operator folly::expected<int, err>()` to unwrap
+// the delayed proxied promise.
+
+// CHECK: call i1 @llvm.coro.end
+// CHECK: %[[DelayedConv:.+]] = call i64 @_ZN5folly8expectedIi3errE12promise_type13return_objectcvS2_Ev(ptr noundef nonnull align 8 dereferenceable(16) %[[CoroGro:.+]])
+// CHECK: store i64 %[[DelayedConv]], ptr %[[RetVal]]
+// CHECK: br label %[[Cleanup0:.+]]
+
+// CHECK: [[Cleanup0]]:
+// CHECK:   %[[IsCleanupActive:.+]] = load i1, ptr %[[GroActive]]
+// CHECK:   br i1 %[[IsCleanupActive]], label %[[CleanupAction:.+]], label %[[CleanupDone:.+]]
+
+// Call into `folly::expected<int, err>::promise_type::return_object::~return_object()` while __promise is alive.
+
+// CHECK: [[CleanupAction]]:
+// CHECK:   call void @_ZN5folly8expectedIi3errE12promise_type13return_objectD1Ev(ptr noundef nonnull align 8 dereferenceable(16) %[[CoroGro]])
+// CHECK:   br label %[[CleanupDone]]
+
+// __promise lifetime should end after __coro_gro's.
+
+// CHECK: [[CleanupDone]]:
+// CHECK:   call void @llvm.lifetime.end.p0(i64 16, ptr %[[CoroGro]])
+// CHECK:   call void @llvm.lifetime.end.p0(i64 8, ptr %[[Promise]])
+// CHECK:   call ptr @llvm.coro.free
+// CHECK:   br i1 {{.*}}, label %[[CoroFree:.+]], {{.*}}
+
+// Delete coroutine frame.
+
+// CHECK: [[CoroFree]]:
+// CHECK: call void @_ZdlPv
\ No newline at end of file
diff --git a/clang/test/CodeGenCoroutines/coro-gro.cpp b/clang/test/CodeGenCoroutines/coro-gro.cpp
index b48b769950ae871..f7ed865931dc2ed 100644
--- a/clang/test/CodeGenCoroutines/coro-gro.cpp
+++ b/clang/test/CodeGenCoroutines/coro-gro.cpp
@@ -29,14 +29,21 @@ void doSomething() noexcept;
 // CHECK: define{{.*}} i32 @_Z1fv(
 int f() {
   // CHECK: %[[RetVal:.+]] = alloca i32
+  // CHECK: %[[Promise:.+]] = alloca %"struct.std::coroutine_traits<int>::promise_type"
   // CHECK: %[[GroActive:.+]] = alloca i1
+  // CHECK: %[[CoroGro:.+]] = alloca %struct.GroType
 
   // CHECK: %[[Size:.+]] = call i64 @llvm.coro.size.i64()
   // CHECK: call noalias noundef nonnull ptr @_Znwm(i64 noundef %[[Size]])
-  // CHECK: store i1 false, ptr %[[GroActive]]
+
+  // GRO lifetime should be bound within promise's lifetime.
+  //
+  // CHECK: call void @llvm.lifetime.start.p0(i64 1, ptr %[[Promise]])
   // CHECK: call void @_ZNSt16coroutine_traitsIiJEE12promise_typeC1Ev(
+  // CHECK: store i1 false, ptr %[[GroActive]]
+  // CHECK: call void @llvm.lifetime.start.p0(i64 1, ptr %[[CoroGro]])
   // CHECK: call void @_ZNSt16coroutine_traitsIiJEE12promise_type17get_return_objectEv(
-  // CHECK: store i1 true, ptr %[[GroActive]]
+  // CHECK: store i1 true, ptr %[[GroActive]], align 1
 
   Cleanup cleanup;
   doSomething();
@@ -46,12 +53,6 @@ int f() {
   // CHECK: call void @_ZNSt16coroutine_traitsIiJEE12promise_type11return_voidEv(
   // CHECK: call void @_ZN7CleanupD1Ev(
 
-  // Destroy promise and free the memory.
-
-  // CHECK: call void @_ZNSt16coroutine_traitsIiJEE12promise_typeD1Ev(
-  // CHECK: %[[Mem:.+]] = call ptr @llvm.coro.free(
-  // CHECK: call void @_ZdlPv(ptr noundef %[[Mem]])
-
   // Initialize retval from Gro and destroy Gro
   // Note this also tests delaying initialization when Gro and function return
   // types mismatch (see cwg2563).
@@ -63,9 +64,18 @@ int f() {
 
   // CHECK: [[CleanupGro]]:
   // CHECK:   call void @_ZN7GroTypeD1Ev(
-  // CHECK:   br label %[[Done]]
+  // CHECK:   br label %[[CleanupDone:.+]]
+
+  // Destroy promise and free the memory.
+
+  // GRO lifetime should be bound within promise's lifetime.
+  // CHECK: [[CleanupDone]]:
+  // CHECK: call void @llvm.lifetime.end.p0(i64 1, ptr %[[CoroGro]])
+  // CHECK: call void @_ZNSt16coroutine_traitsIiJEE12promise_typeD1Ev(
+  // CHECK: call void @llvm.lifetime.end.p0(i64 1, ptr %[[Promise]])
+  // CHECK: %[[Mem:.+]] = call ptr @llvm.coro.free(
+  // CHECK: call void @_ZdlPv(ptr noundef %[[Mem]])
 
-  // CHECK: [[Done]]:
   // CHECK:   %[[LoadRet:.+]] = load i32, ptr %[[RetVal]]
   // CHECK:   ret i32 %[[LoadRet]]
 }

>From 033f1b302cf44770eabebd321b77b01421065555 Mon Sep 17 00:00:00 2001
From: Bruno Cardoso Lopes <bruno.cardoso at gmail.com>
Date: Tue, 19 Sep 2023 17:39:49 -0700
Subject: [PATCH 2/2] [Clang][Coroutines] Only change cleanup order when GRO
 does emits an alloca

---
 clang/lib/CodeGen/CGCoroutine.cpp             | 63 ++++++++++++-------
 .../test/CodeGenCoroutines/coro-dest-slot.cpp | 17 +----
 2 files changed, 43 insertions(+), 37 deletions(-)

diff --git a/clang/lib/CodeGen/CGCoroutine.cpp b/clang/lib/CodeGen/CGCoroutine.cpp
index 741a8f9990c1ed7..d3683995517a398 100644
--- a/clang/lib/CodeGen/CGCoroutine.cpp
+++ b/clang/lib/CodeGen/CGCoroutine.cpp
@@ -512,21 +512,28 @@ struct GetReturnObjectManager {
     }();
   }
 
+  bool shouldEmitAlloca() {
+    if (DirectEmit)
+      return false;
+
+    if (!isa_and_nonnull<DeclStmt>(S.getResultDecl())) {
+      // If get_return_object returns void, no need to do an alloca.
+      return false;
+    }
+
+    return true;
+  }
+
   // The gro variable has to outlive coroutine frame and coroutine promise, but,
   // it can only be initialized after coroutine promise was created, thus, we
   // split its emission in two parts. EmitGroAlloca emits an alloca and sets up
   // cleanups. Later when coroutine promise is available we initialize the gro
   // and sets the flag that the cleanup is now active.
   void EmitGroAlloca() {
-    if (DirectEmit)
-      return;
-
-    auto *GroDeclStmt = dyn_cast_or_null<DeclStmt>(S.getResultDecl());
-    if (!GroDeclStmt) {
-      // If get_return_object returns void, no need to do an alloca.
+    if (!shouldEmitAlloca())
       return;
-    }
 
+    auto *GroDeclStmt = cast<DeclStmt>(S.getResultDecl());
     auto *GroVarDecl = cast<VarDecl>(GroDeclStmt->getSingleDecl());
 
     // Set GRO flag that it is not initialized yet
@@ -658,6 +665,25 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
 
   GetReturnObjectManager GroManager(*this, S);
   CurCoro.Data->CleanupJD = getJumpDestInCurrentScope(RetBB);
+  bool shouldGROEmitAlloca = GroManager.shouldEmitAlloca();
+  auto emitRetBlock = [&]() {
+    EmitBlock(RetBB);
+    // Emit coro.end before getReturnStmt (and parameter destructors), since
+    // resume and destroy parts of the coroutine should not include them.
+    llvm::Function *CoroEnd = CGM.getIntrinsic(llvm::Intrinsic::coro_end);
+    Builder.CreateCall(CoroEnd,
+                       {NullPtr, Builder.getFalse(),
+                        llvm::ConstantTokenNone::get(CoroEnd->getContext())});
+
+    if (Stmt *Ret = S.getReturnStmt()) {
+      // Since we already emitted the return value above, so we shouldn't
+      // emit it again here.
+      if (GroManager.DirectEmit)
+        cast<ReturnStmt>(Ret)->setRetValue(nullptr);
+      EmitStmt(Ret);
+    }
+  };
+
   {
     CGDebugInfo *DI = getDebugInfo();
     ParamReferenceReplacerRAII ParamReplacer(LocalDeclMap);
@@ -757,23 +783,16 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
       EmitBlock(FinalBB, /*IsFinished=*/true);
     }
 
-    EmitBlock(RetBB);
-    // Emit coro.end before getReturnStmt (and parameter destructors), since
-    // resume and destroy parts of the coroutine should not include them.
-    llvm::Function *CoroEnd = CGM.getIntrinsic(llvm::Intrinsic::coro_end);
-    Builder.CreateCall(CoroEnd,
-                       {NullPtr, Builder.getFalse(),
-                        llvm::ConstantTokenNone::get(CoroEnd->getContext())});
-
-    if (Stmt *Ret = S.getReturnStmt()) {
-      // Since we already emitted the return value above, so we shouldn't
-      // emit it again here.
-      if (GroManager.DirectEmit)
-        cast<ReturnStmt>(Ret)->setRetValue(nullptr);
-      EmitStmt(Ret);
-    }
+    // If an alloca is emitted for GRO handling, make sure the ret block
+    // is taken into account within the appropriate cleanup.
+    if (shouldGROEmitAlloca)
+      emitRetBlock();
   }
 
+  // No GRO alloca's, no need for to account for extra cleanups.
+  if (!shouldGROEmitAlloca)
+    emitRetBlock();
+
   // LLVM require the frontend to mark the coroutine.
   CurFn->setPresplitCoroutine();
 }
diff --git a/clang/test/CodeGenCoroutines/coro-dest-slot.cpp b/clang/test/CodeGenCoroutines/coro-dest-slot.cpp
index 0d2416e7913da0d..6239870285995f4 100644
--- a/clang/test/CodeGenCoroutines/coro-dest-slot.cpp
+++ b/clang/test/CodeGenCoroutines/coro-dest-slot.cpp
@@ -33,22 +33,9 @@ extern "C" coro f(int) { co_return; }
 
 // CHECK: call void @_ZNSt13suspend_never12await_resumeEv(
 // CHECK: %[[CLEANUP_DEST1:.+]] = phi i32 [ 0, %[[FINAL_READY]] ], [ 2, %[[FINAL_CLEANUP]] ]
-// CHECK: switch i32 %[[CLEANUP_DEST1]], label %[[CLEANUP_BB_2:.+]] [
-// CHECK:   i32 0, label %[[CLEANUP_CONT:.+]]
-// CHECK: ]
-
-// CHECK: [[CLEANUP_CONT]]:
-// CHECK:   br label %[[CORO_RET:.+]]
-
-// CHECK: [[CORO_RET]]:
-// CHECK:   call i1 @llvm.coro.end
-// CHECK:   br label %cleanup19
-
-// CHECK: [[CLEANUP_BB_2]]:
-// CHECK: %[[CLEANUP_DEST2:.+]] = phi i32 [ %[[CLEANUP_DEST0]], %{{.+}} ], [ 1, %[[CORO_RET]] ], [ %[[CLEANUP_DEST1]], %{{.+}} ]
-// CHECK: call void @llvm.lifetime.end.p0(i64 1, ptr %[[PROMISE]])
+// CHECK: %[[CLEANUP_DEST2:.+]] = phi i32 [ %[[CLEANUP_DEST0]], %{{.+}} ], [ %[[CLEANUP_DEST1]], %{{.+}} ], [ 0, %{{.+}} ]
 // CHECK: call ptr @llvm.coro.free(
 // CHECK: switch i32 %[[CLEANUP_DEST2]], label %{{.+}} [
+// CHECK-NEXT: i32 0
 // CHECK-NEXT: i32 2
-// CHECK-NEXT: i32 1
 // CHECK-NEXT: ]



More information about the cfe-commits mailing list