[clang] [CIR] Add structured control flow for coroutine suspend points (PR #213191)

via cfe-commits cfe-commits at lists.llvm.org
Thu Sep 24 18:51:48 PDT 2026


https://github.com/Andres-Salamanca updated https://github.com/llvm/llvm-project/pull/213191

>From 35d092ef1f807b63a13c222dd8d925a19c8a221c Mon Sep 17 00:00:00 2001
From: Andres Salamanca <andrealebarbaritos at gmail.com>
Date: Mon, 13 Jul 2026 14:34:45 -0500
Subject: [PATCH 01/12] initial coro_suspend

---
 clang/include/clang/CIR/Dialect/IR/CIROps.td  | 36 +++++++++++++++++++
 clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp     |  4 ++-
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp       | 12 +++++++
 .../test/CIR/CodeGenCoroutines/coro-task.cpp  | 22 ++++++------
 clang/test/CIR/IR/await.cir                   |  6 ++--
 clang/test/CIR/IR/co-return.cir               |  3 +-
 clang/test/CIR/IR/coro-body.cir               |  3 +-
 clang/test/CIR/IR/func.cir                    |  3 +-
 clang/test/CIR/IR/invalid-await.cir           | 18 +++++++++-
 clang/test/CIR/IR/invalid-coro-body.cir       |  3 +-
 clang/test/CIR/IR/invalid-coro-suspend.cir    | 17 +++++++++
 11 files changed, 108 insertions(+), 19 deletions(-)
 create mode 100644 clang/test/CIR/IR/invalid-coro-suspend.cir

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index f6b524176a6559..ad4b09501fc388 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -5122,6 +5122,42 @@ def CIR_CoroDoneOp : CIR_CoroIntrinsicOp<"done", (ins CIR_VoidPtrType:$handle),
   let llvmOp = "CoroDoneOp";
 }
 
+//===----------------------------------------------------------------------===//
+// CoroSuspendPoint
+//===----------------------------------------------------------------------===//
+
+def CIR_CoroSuspendPoint : CIR_Op<"coro.suspend.point", [
+  Pure, Terminator, HasParent<"AwaitOp">
+]> {
+  let summary = "";
+  let description = [{
+  }];
+
+  let assemblyFormat = [{
+    attr-dict
+  }];
+
+  let hasLLVMLowering = false;
+}
+
+//===----------------------------------------------------------------------===//
+// CoroSuspendPointDest
+//===----------------------------------------------------------------------===//
+
+def CIR_CoroSuspendPointDest : CIR_Op<"coro.suspend.point.dest", [
+  Pure
+]> {
+  let summary = "";
+  let description = [{
+  }];
+
+  let assemblyFormat = [{
+    attr-dict
+  }];
+
+  let hasLLVMLowering = false;
+}
+
 //===----------------------------------------------------------------------===//
 // CopyOp
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
index 6f2c0e33ce1d96..b28881f0cd0f19 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
@@ -559,6 +559,8 @@ CIRGenFunction::emitCoroutineBody(const CoroutineBodyStmt &s) {
   auto tkNone = cir::TokenNoneOp::create(builder, openCurlyLoc);
   cir::CoroEndOp::create(builder, openCurlyLoc, nullHandler, noUnwind, tkNone);
 
+  cir::CoroSuspendPointDest::create(builder, openCurlyLoc);
+
   if (auto *ret = cast_or_null<ReturnStmt>(s.getReturnStmt())) {
     // Since we already emitted the return value above, so we shouldn't
     // emit it again here.
@@ -640,7 +642,7 @@ emitSuspendExpression(CIRGenFunction &cgf, CGCoroData &coro,
         }
 
         // Signals the parent that execution flows to next region.
-        cir::YieldOp::create(builder, loc);
+        cir::CoroSuspendPoint::create(builder,loc);
       },
       /*resumeBuilder=*/
       [&](mlir::OpBuilder &b, mlir::Location loc) {
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 6bbf338fcbda12..bba9dca93861a6 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -32,6 +32,7 @@
 #include "llvm/ADT/SetOperations.h"
 #include "llvm/ADT/SmallSet.h"
 #include "llvm/ADT/TypeSwitch.h"
+#include "llvm/Support/Casting.h"
 #include "llvm/Support/LogicalResult.h"
 
 using namespace mlir;
@@ -3134,6 +3135,7 @@ mlir::LogicalResult cir::FuncOp::verify() {
   if (!isDeclaration() && getCoroutine()) {
     bool foundAwait = false;
     int coroBodyCount = 0;
+    int coroSuspendPointDestCount = 0;
     this->walk([&](Operation *op) {
       if (auto await = dyn_cast<AwaitOp>(op)) {
         foundAwait = true;
@@ -3142,6 +3144,11 @@ mlir::LogicalResult cir::FuncOp::verify() {
         if (coroBodyCount > 1) {
           return mlir::WalkResult::interrupt();
         }
+      } else if (isa<CoroSuspendPointDest>(op)) {
+        coroSuspendPointDestCount++;
+        if (coroSuspendPointDestCount > 1) {
+          return mlir::WalkResult::interrupt();
+        }
       }
       return mlir::WalkResult::advance();
     });
@@ -3151,6 +3158,9 @@ mlir::LogicalResult cir::FuncOp::verify() {
     if (coroBodyCount != 1)
       return emitOpError()
              << "coroutine function must have exactly one cir.body op";
+    if (coroSuspendPointDestCount != 1)
+      return emitOpError()
+             << "coroutine function must have exactly one cir.coro.suspend.point.dest";
   }
 
   llvm::SmallSet<llvm::StringRef, 16> labels;
@@ -3548,6 +3558,8 @@ void cir::AwaitOp::getSuccessorRegions(
 LogicalResult cir::AwaitOp::verify() {
   if (!isa<ConditionOp>(this->getReady().back().getTerminator()))
     return emitOpError("ready region must end with cir.condition");
+  if (!isa<CoroSuspendPoint>(this->getSuspend().back().getTerminator()))
+    return emitOpError("ready region must end with cir.coro.suspend.point");
   return success();
 }
 
diff --git a/clang/test/CIR/CodeGenCoroutines/coro-task.cpp b/clang/test/CIR/CodeGenCoroutines/coro-task.cpp
index 56360d852b0779..3ea3b903d153a7 100644
--- a/clang/test/CIR/CodeGenCoroutines/coro-task.cpp
+++ b/clang/test/CIR/CodeGenCoroutines/coro-task.cpp
@@ -131,8 +131,8 @@ VoidTask silly_task() {
 // CIR-NEXT:     %[[CoroHandlePromiseReload:.*]] = cir.load{{.*}} %[[CoroHandlePromiseAddr]]
 // CIR-NEXT:     cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_typeEEES_IT_E(%[[CoroHandleVoidAddr]])
 // CIR-NEXT:     %[[CoroHandleVoidReload:.*]] = cir.load{{.*}} %[[CoroHandleVoidAddr]] : !cir.ptr<![[CoroHandleVoid]]>, ![[CoroHandleVoid]]
-// CIR-NEXT:     cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SuspendAlwaysAddr]])
-// CIR-NEXT:     cir.yield
+// CIR-NEXT:     cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SuspendAlwaysAddr]], %[[CoroHandleVoidReload]])
+// CIR-NEXT:     cir.coro.suspend.point
 
 // OGCG: init.suspend:
 // OGCG:   %[[Save:.*]] = call token @llvm.coro.save(ptr null)
@@ -268,8 +268,8 @@ folly::coro::Task<int> byRef(const std::string& s) {
 // CIR:       %[[CoroHandlePromiseReload:.*]] = cir.load{{.*}} %[[CoroHandlePromiseAddr]]
 // CIR:       cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIiE12promise_typeEEES_IT_E(%[[CoroHandleVoidAddr]])
 // CIR:       %[[CoroHandleVoidReload:.*]] = cir.load{{.*}} %[[CoroHandleVoidAddr]] : !cir.ptr<![[CoroHandleVoid]]>, ![[CoroHandleVoid]]
-// CIR:       cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SuspendAlwaysAddr]])
-// CIR:       cir.yield
+// CIR:       cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SuspendAlwaysAddr]], %[[CoroHandleVoidReload]])
+// CIR:       cir.coro.suspend.point
 // CIR:     }, resume : {
 // CIR:       cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SuspendAlwaysAddr]])
 // CIR:       cir.yield
@@ -361,8 +361,8 @@ folly::coro::Task<void> yield1() {
 // CIR:     %[[PROM_RELOAD0:.*]] = cir.load{{.*}} %[[CH_PROM0]]
 // CIR:     cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_typeEEES_IT_E(%[[CH_VOID0]]){{.*}}
 // CIR:     %[[VOID_RELOAD0:.*]] = cir.load{{.*}} %[[CH_VOID0]]
-// CIR:     cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SUSP0]]){{.*}}
-// CIR:     cir.yield
+// CIR:     cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SUSP0]], %[[VOID_RELOAD0]]){{.*}}
+// CIR:     cir.coro.suspend.point
 // CIR:   }, resume : {
 // CIR:     cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SUSP0]]){{.*}}
 // CIR:     cir.yield
@@ -387,8 +387,8 @@ folly::coro::Task<void> yield1() {
 // CIR:       %[[PROM_RELOAD1:.*]] = cir.load{{.*}} %[[CH_PROM1]]
 // CIR:       cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_typeEEES_IT_E(%[[CH_VOID1]]){{.*}}
 // CIR:       %[[VOID_RELOAD1:.*]] = cir.load{{.*}} %[[CH_VOID1]]
-// CIR:       cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SUSP1]]){{.*}}
-// CIR:       cir.yield
+// CIR:       cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SUSP1]], %[[VOID_RELOAD1]]){{.*}}
+// CIR:       cir.coro.suspend.point
 // CIR:     }, resume : {
 // CIR:       cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SUSP1]]){{.*}}
 // CIR:       cir.yield
@@ -411,8 +411,8 @@ folly::coro::Task<void> yield1() {
 // CIR:     %[[PROM_RELOAD2:.*]] = cir.load{{.*}} %[[CH_PROM2]]
 // CIR:     cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_typeEEES_IT_E(%[[CH_VOID2]]){{.*}}
 // CIR:     %[[VOID_RELOAD2:.*]] = cir.load{{.*}} %[[CH_VOID2]]
-// CIR:     cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SUSP2]]){{.*}}
-// CIR:     cir.yield
+// CIR:     cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SUSP2]], %[[VOID_RELOAD2]]){{.*}}
+// CIR:     cir.coro.suspend.point
 // CIR:   }, resume : {
 // CIR:     cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SUSP2]]){{.*}}
 // CIR:     cir.yield
@@ -550,7 +550,7 @@ folly::coro::Task<int> go4() {
 // CIR:    = cir.call @_ZN5folly4coro4TaskIiE11await_readyEv(%[[TASK_ADDR]])
 // CIR:    cir.condition(
 // CIR:  }, suspend : {
-// CIR:    cir.yield
+// CIR:    cir.coro.suspend.point
 // CIR:  }, resume : {
 // CIR:    cir.yield
 // CIR:  },)
diff --git a/clang/test/CIR/IR/await.cir b/clang/test/CIR/IR/await.cir
index 06b925bcc07b39..1305c4bb31e0f9 100644
--- a/clang/test/CIR/IR/await.cir
+++ b/clang/test/CIR/IR/await.cir
@@ -5,12 +5,13 @@ cir.func coroutine @checkPrintParse(%arg0 : !cir.bool) {
     cir.await(user, ready : {
       cir.condition(%arg0)
     }, suspend : {
-      cir.yield
+      cir.coro.suspend.point
     }, resume : {
       cir.yield
     },)
     cir.yield
   }
+  cir.coro.suspend.point.dest
   cir.return
 }
 
@@ -18,7 +19,8 @@ cir.func coroutine @checkPrintParse(%arg0 : !cir.bool) {
 // CHECK:  cir.await(user, ready : {
 // CHECK:    cir.condition(%arg0)
 // CHECK:  }, suspend : {
-// CHECK:    cir.yield
+// CHECK:    cir.coro.suspend.point
 // CHECK:  }, resume : {
 // CHECK:    cir.yield
 // CHECK:  },)
+// CHECK:  cir.coro.suspend.point.dest
diff --git a/clang/test/CIR/IR/co-return.cir b/clang/test/CIR/IR/co-return.cir
index 613399b1426505..d6c3be2da80ada 100644
--- a/clang/test/CIR/IR/co-return.cir
+++ b/clang/test/CIR/IR/co-return.cir
@@ -4,12 +4,13 @@ cir.func coroutine @coro_co_return(%arg0 : !cir.bool) {
     cir.await(user, ready : {
       cir.condition(%arg0)
     }, suspend : {
-      cir.yield
+      cir.coro.suspend.point
     }, resume : {
       cir.yield
     },)
     cir.co_return
   }
+  cir.coro.suspend.point.dest
   cir.return
 }
 
diff --git a/clang/test/CIR/IR/coro-body.cir b/clang/test/CIR/IR/coro-body.cir
index 1c0dae384691a3..b1560485303b1c 100644
--- a/clang/test/CIR/IR/coro-body.cir
+++ b/clang/test/CIR/IR/coro-body.cir
@@ -5,12 +5,13 @@ cir.func coroutine @coro_body(%arg0 : !cir.bool) {
     cir.await(user, ready : {
       cir.condition(%arg0)
     }, suspend : {
-      cir.yield
+      cir.coro.suspend.point
     }, resume : {
       cir.yield
     },)
     cir.co_return
   }
+  cir.coro.suspend.point.dest
   cir.return
 }
 
diff --git a/clang/test/CIR/IR/func.cir b/clang/test/CIR/IR/func.cir
index 591c397e24e8c8..8b9cc2c8947865 100644
--- a/clang/test/CIR/IR/func.cir
+++ b/clang/test/CIR/IR/func.cir
@@ -115,12 +115,13 @@ cir.func coroutine @coro() {
       %1 = cir.load align(1) %0 : !cir.ptr<!cir.bool>, !cir.bool
       cir.condition(%1)
     }, suspend : {
-      cir.yield
+      cir.coro.suspend.point
     }, resume : {
       cir.yield
     },)
     cir.yield
   }
+  cir.coro.suspend.point.dest
   cir.return
 }
 // CHECK: cir.func{{.*}} coroutine @coro()
diff --git a/clang/test/CIR/IR/invalid-await.cir b/clang/test/CIR/IR/invalid-await.cir
index fe0fd76769e44e..cc6d011718ad00 100644
--- a/clang/test/CIR/IR/invalid-await.cir
+++ b/clang/test/CIR/IR/invalid-await.cir
@@ -10,12 +10,28 @@ cir.func coroutine @missing_condition() {
     cir.await(user, ready : { // expected-error {{ready region must end with cir.condition}}
       cir.yield
     }, suspend : {
-      cir.yield
+      cir.coro.suspend.point
     }, resume : {
       cir.yield
     },)
   }
   cir.coro.body {
   }
+  cir.coro.suspend.point.dest
+  cir.return
+}
+
+cir.func coroutine @missing_suspend(%arg0 : !cir.bool) {
+  cir.coro.body {
+    cir.await(user, ready : { // expected-error {{ready region must end with cir.coro.suspend.point}}
+      cir.condition(%arg0)
+    }, suspend : {
+      cir.yield
+    }, resume : {
+      cir.yield
+    },)
+    cir.yield
+  }
+  cir.coro.suspend.point.dest
   cir.return
 }
diff --git a/clang/test/CIR/IR/invalid-coro-body.cir b/clang/test/CIR/IR/invalid-coro-body.cir
index e08c96192e5b58..f247220c7d73d2 100644
--- a/clang/test/CIR/IR/invalid-coro-body.cir
+++ b/clang/test/CIR/IR/invalid-coro-body.cir
@@ -11,11 +11,12 @@ cir.func coroutine @must_have_one_coro_body(%arg0 : !cir.bool) {  // expected-er
     cir.await(user, ready : {
       cir.condition(%arg0)
     }, suspend : {
-      cir.yield
+      cir.coro.suspend.point
     }, resume : {
       cir.yield
     },)
   }
   cir.coro.body {
   }
+  cir.coro.suspend.point.dest
 }
diff --git a/clang/test/CIR/IR/invalid-coro-suspend.cir b/clang/test/CIR/IR/invalid-coro-suspend.cir
new file mode 100644
index 00000000000000..3a45e22433a589
--- /dev/null
+++ b/clang/test/CIR/IR/invalid-coro-suspend.cir
@@ -0,0 +1,17 @@
+// RUN: cir-opt %s -verify-diagnostics -split-input-file
+
+
+cir.func coroutine @must_have_one_coro_suspend_dest(%arg0 : !cir.bool) {  // expected-error {{coroutine function must have exactly one cir.coro.suspend.point.dest}}
+  cir.coro.body {
+    cir.await(user, ready : {
+      cir.condition(%arg0)
+    }, suspend : {
+      cir.coro.suspend.point
+    }, resume : {
+      cir.yield
+    },)
+  }
+  cir.coro.suspend.point.dest
+  cir.coro.suspend.point.dest
+}
+

>From b7cfbe6542c80ad772d246e84a5ec9c7a12ac458 Mon Sep 17 00:00:00 2001
From: Andres Salamanca <andrealebarbaritos at gmail.com>
Date: Thu, 30 Jul 2026 21:15:54 -0500
Subject: [PATCH 02/12] [CIR] Add structured control flow for coroutine suspend
 points

---
 clang/include/clang/CIR/Dialect/IR/CIROps.td  |  78 +-
 clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp     |  31 +-
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp       |  82 +-
 .../test/CIR/CodeGenCoroutines/coro-task.cpp  | 702 +++++++++---------
 clang/test/CIR/IR/await.cir                   |  25 +-
 clang/test/CIR/IR/co-return.cir               |  26 +-
 clang/test/CIR/IR/coro-body.cir               |  26 +-
 clang/test/CIR/IR/coro-ret-point.cir          |  23 +
 clang/test/CIR/IR/func.cir                    |  26 +-
 clang/test/CIR/IR/invalid-await.cir           |  36 +-
 clang/test/CIR/IR/invalid-coro-body.cir       |   3 +-
 clang/test/CIR/IR/invalid-coro-ret-point.cir  |  55 ++
 clang/test/CIR/IR/invalid-coro-suspend.cir    |  30 +-
 13 files changed, 710 insertions(+), 433 deletions(-)
 create mode 100644 clang/test/CIR/IR/coro-ret-point.cir
 create mode 100644 clang/test/CIR/IR/invalid-coro-ret-point.cir

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index ad4b09501fc388..9916eb373afe74 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -1014,7 +1014,7 @@ def CIR_StoreOp : CIR_Op<"store", [
 
 defvar CIR_ReturnableScopes = [
   "FuncOp", "ScopeOp", "IfOp", "SwitchOp", "CaseOp", "CleanupScopeOp",
-  "DoWhileOp", "WhileOp", "ForOp", "TryOp"
+  "DoWhileOp", "WhileOp", "ForOp", "TryOp", "CoroRetPointOp"
 ];
 
 def CIR_ReturnOp : CIR_Op<"return", [
@@ -1164,7 +1164,7 @@ def CIR_ConditionOp : CIR_Op<"condition", [
 defvar CIR_YieldableScopes = [
   "ArrayCtor", "ArrayDtor", "AwaitOp", "CaseOp", "CleanupScopeOp", "CoroBodyOp",
   "DoWhileOp", "ForOp", "GlobalOp", "IfOp", "LocalInitOp", "ScopeOp", "SwitchOp",
-  "TernaryOp", "TryOp", "WhileOp"
+  "TernaryOp", "TryOp", "WhileOp", "CoroRetPointOp"
 ];
 
 def CIR_YieldOp : CIR_Op<"yield", [
@@ -5126,11 +5126,36 @@ def CIR_CoroDoneOp : CIR_CoroIntrinsicOp<"done", (ins CIR_VoidPtrType:$handle),
 // CoroSuspendPoint
 //===----------------------------------------------------------------------===//
 
-def CIR_CoroSuspendPoint : CIR_Op<"coro.suspend.point", [
-  Pure, Terminator, HasParent<"AwaitOp">
+def CIR_CoroSuspendPoint : CIR_Op<"coro.suspend_point", [
+  Pure, Terminator, HasParent<"AwaitOp">, HasAncestor<"CoroRetPointOp">
 ]> {
-  let summary = "";
+  let summary = "Marks the point where a coroutine actually suspends";
   let description = [{
+    Terminates the `suspend` region of a `cir.await` op, marking the exact
+    point where control returns to the caller/resumer if the coroutine
+    decides to suspend.
+
+    This op must appear inside the `suspend` region of a `cir.await`, and
+    that `cir.await` must in turn be nested within a `cir.coro.ret_point`.
+    During lowering to FlattenCFG, `cir.coro.suspend_point` becomes the
+    branch target that routes control to one of three destinations
+    depending on how the coroutine resumes: the resume block (normal
+    continuation), the cleanup/destroy block, or the ret/exit block that
+    hands control back to the caller.
+
+    Example:
+    ```mlir
+    cir.await(user, ready : {
+      ...
+      cir.condition(%ready)
+    }, suspend : {
+      ...
+      cir.coro.suspend_point
+    }, resume : {
+      ...
+      cir.yield
+    },)
+    ```
   }];
 
   let assemblyFormat = [{
@@ -5141,21 +5166,54 @@ def CIR_CoroSuspendPoint : CIR_Op<"coro.suspend.point", [
 }
 
 //===----------------------------------------------------------------------===//
-// CoroSuspendPointDest
+// CoroRetPoint
 //===----------------------------------------------------------------------===//
 
-def CIR_CoroSuspendPointDest : CIR_Op<"coro.suspend.point.dest", [
-  Pure
+def CIR_CoroRetPointOp : CIR_Op<"coro.ret_point", [
+  DeclareOpInterfaceMethods<RegionBranchOpInterface, ["getSuccessorInputs"]>,
+  NoRegionArguments
 ]> {
-  let summary = "";
+
+  let summary = "Marks the coroutine's structured suspend/exit region";
   let description = [{
+    Pairs a coroutine's body its normal execution, including any
+    `cir.await`s and cleanups with a single, shared exit destination
+    (`retRegion`) that every suspend point in the body jumps to.
+
+    Every `cir.coro.suspend_point` inside `bodyRegion` is guaranteed to have
+    `cir.coro.ret_point` as an ancestor, so all suspend paths converge on
+    the same `retRegion` regardless of where in the body they occur. This
+    gives the coroutine a single, well-defined place to run final teardown
+    (e.g. `__builtin_coro_end`) and return control to the caller.
+
+    Example:
+    ```mlir
+    cir.coro.ret_point {
+      // body: awaits, cleanups, etc.
+    }, ret : {
+      // shared jump target for any suspend point in the body
+      cir.coro.intrinsic.end(...)
+      cir.return
+    }
+    ```
   }];
 
+  let regions = (region AnyRegion:$bodyRegion,
+                        AnyRegion:$retRegion);
+   let skipDefaultBuilders = 1;
+
+  let builders = [
+    OpBuilder<(ins "BuilderCallbackRef":$bodyBuilder,
+      "BuilderCallbackRef":$retBuilder)>
+  ];
+
   let assemblyFormat = [{
+    $bodyRegion `,`
+    `ret` `:` $retRegion
     attr-dict
   }];
-
   let hasLLVMLowering = false;
+  let hasVerifier = 1;
 }
 
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
index b28881f0cd0f19..f9ee288e1f29ef 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
@@ -15,8 +15,6 @@
 #include "clang/AST/StmtCXX.h"
 #include "clang/AST/StmtVisitor.h"
 #include "clang/Basic/TargetInfo.h"
-#include "clang/CIR/Dialect/IR/CIRDialect.h"
-#include "clang/CIR/Dialect/IR/CIRTypes.h"
 #include "clang/CIR/MissingFeatures.h"
 
 using namespace clang;
@@ -424,10 +422,25 @@ CIRGenFunction::emitCoroutineBody(const CoroutineBodyStmt &s) {
           curCoro.data->coroId.getResult(),
           cir::LoadOp::create(builder, openCurlyLoc, allocaTy, storeAddr)});
 
+  cir::CoroRetPointOp coroRet = nullptr;
+  mlir::OpBuilder::InsertPoint coroRetRegion;
   {
     assert(!cir::MissingFeatures::generateDebugInfo());
     ParamReferenceReplacerRAII paramReplacer(localDeclMap);
     RunCleanupsScope resumeScope(*this);
+    mlir::OpBuilder::InsertPoint coroRetBody;
+    coroRet = cir::CoroRetPointOp::create(
+        builder, openCurlyLoc,
+        /*bodyBuilder=*/
+        [&](mlir::OpBuilder &b, mlir::Location) {
+          coroRetBody = b.saveInsertionPoint();
+        },
+        /*retBuilder=*/
+        [&](mlir::OpBuilder &b, mlir::Location) {
+          coroRetRegion = b.saveInsertionPoint();
+        });
+    mlir::OpBuilder::InsertionGuard guard(builder);
+    builder.restoreInsertionPoint(coroRetBody);
     ehStack.pushCleanup<CallCoroDelete>(NormalAndEHCleanup, s.getDeallocate());
     // Create mapping between parameters and copy-params for coroutine
     // function.
@@ -559,7 +572,15 @@ CIRGenFunction::emitCoroutineBody(const CoroutineBodyStmt &s) {
   auto tkNone = cir::TokenNoneOp::create(builder, openCurlyLoc);
   cir::CoroEndOp::create(builder, openCurlyLoc, nullHandler, noUnwind, tkNone);
 
-  cir::CoroSuspendPointDest::create(builder, openCurlyLoc);
+  mlir::Block &coroRetBodyBlock = coroRet.getBodyRegion().back();
+  {
+    mlir::OpBuilder::InsertionGuard guard(builder);
+    builder.setInsertionPointToEnd(&coroRetBodyBlock);
+    cir::YieldOp::create(builder, openCurlyLoc);
+  }
+
+  mlir::OpBuilder::InsertionGuard guard(builder);
+  builder.restoreInsertionPoint(coroRetRegion);
 
   if (auto *ret = cast_or_null<ReturnStmt>(s.getReturnStmt())) {
     // Since we already emitted the return value above, so we shouldn't
@@ -568,6 +589,10 @@ CIRGenFunction::emitCoroutineBody(const CoroutineBodyStmt &s) {
     ret->setRetValue(nullptr);
     if (emitStmt(ret, /*useCurrentScope=*/true).failed())
       return mlir::failure();
+    mlir::Block *block = builder.getInsertionBlock();
+    // emitReturnStmt() always creates a new insertion block after emitting the
+    // return. That block is unreachable in this case, so erase it.
+    block->erase();
     // Set the return value back. The code generator, as the AST **Consumer**,
     // shouldn't change the AST.
     ret->setRetValue(previousRetValue);
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index bba9dca93861a6..7ddc9636eab1bd 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -3135,7 +3135,7 @@ mlir::LogicalResult cir::FuncOp::verify() {
   if (!isDeclaration() && getCoroutine()) {
     bool foundAwait = false;
     int coroBodyCount = 0;
-    int coroSuspendPointDestCount = 0;
+    int coroRetPointCount = 0;
     this->walk([&](Operation *op) {
       if (auto await = dyn_cast<AwaitOp>(op)) {
         foundAwait = true;
@@ -3144,9 +3144,9 @@ mlir::LogicalResult cir::FuncOp::verify() {
         if (coroBodyCount > 1) {
           return mlir::WalkResult::interrupt();
         }
-      } else if (isa<CoroSuspendPointDest>(op)) {
-        coroSuspendPointDestCount++;
-        if (coroSuspendPointDestCount > 1) {
+      } else if (isa<CoroRetPointOp>(op)) {
+        coroRetPointCount++;
+        if (coroRetPointCount > 1) {
           return mlir::WalkResult::interrupt();
         }
       }
@@ -3158,9 +3158,9 @@ mlir::LogicalResult cir::FuncOp::verify() {
     if (coroBodyCount != 1)
       return emitOpError()
              << "coroutine function must have exactly one cir.body op";
-    if (coroSuspendPointDestCount != 1)
-      return emitOpError()
-             << "coroutine function must have exactly one cir.coro.suspend.point.dest";
+    if (coroRetPointCount != 1)
+      return emitOpError() << "coroutine function must have exactly one "
+                              "cir.coro.ret_point op";
   }
 
   llvm::SmallSet<llvm::StringRef, 16> labels;
@@ -3559,7 +3559,7 @@ LogicalResult cir::AwaitOp::verify() {
   if (!isa<ConditionOp>(this->getReady().back().getTerminator()))
     return emitOpError("ready region must end with cir.condition");
   if (!isa<CoroSuspendPoint>(this->getSuspend().back().getTerminator()))
-    return emitOpError("ready region must end with cir.coro.suspend.point");
+    return emitOpError("suspend region must end with cir.coro.suspend_point");
   return success();
 }
 
@@ -3594,6 +3594,72 @@ void cir::CoroBodyOp::build(OpBuilder &builder, OperationState &result,
   bodyBuilder(builder, result.location);
 }
 
+//===----------------------------------------------------------------------===//
+// CoroRetPoint
+//===----------------------------------------------------------------------===//
+
+void cir::CoroRetPointOp::build(OpBuilder &builder, OperationState &result,
+                                BuilderCallbackRef bodyBuilder,
+                                BuilderCallbackRef retBuilder) {
+  {
+    OpBuilder::InsertionGuard guard(builder);
+    Region *bodyRegion = result.addRegion();
+    builder.createBlock(bodyRegion);
+    bodyBuilder(builder, result.location);
+  }
+
+  {
+    OpBuilder::InsertionGuard guard(builder);
+    Region *retRegion = result.addRegion();
+    builder.createBlock(retRegion);
+    retBuilder(builder, result.location);
+  }
+}
+
+void cir::CoroRetPointOp::getSuccessorRegions(
+    mlir::RegionBranchPoint point, SmallVectorImpl<RegionSuccessor> &regions) {
+  if (!point.isParent()) {
+    regions.emplace_back(getOperation());
+    return;
+  }
+
+  regions.push_back(RegionSuccessor(&getBodyRegion()));
+  regions.push_back(RegionSuccessor(&getRetRegion()));
+}
+
+mlir::ValueRange
+cir::CoroRetPointOp::getSuccessorInputs(RegionSuccessor successor) {
+  return ValueRange();
+}
+
+LogicalResult cir::CoroRetPointOp::verify() {
+
+  // TODO: Should we verify that the return region contains a
+  // `cir.coro.intrinsic.end`? Coroutine semantics require `coro.end` to mark
+  // the end of access to the coroutine frame before the coroutine completes.
+  bool hasReturn = false;
+  this->getBodyRegion().walk<mlir::WalkOrder::PreOrder>(
+      [&](mlir::Operation *op) {
+        if (mlir::isa<cir::ReturnOp>(op)) {
+          hasReturn = true;
+          return WalkResult::interrupt();
+        }
+        return WalkResult::advance();
+      });
+
+  if (hasReturn)
+    return emitError()
+           << "body region must not contain 'cir.return' operations";
+
+  if (!mlir::isa<cir::YieldOp>(this->getBodyRegion().back().back()))
+    return emitError() << "body region must terminate with 'cir.yield'";
+
+  if (!mlir::isa<cir::ReturnOp>(this->getRetRegion().back().back()))
+    return emitError() << "return region must terminate with 'cir.return'";
+
+  return mlir::success();
+}
+
 //===----------------------------------------------------------------------===//
 // CopyOp Definitions
 //===----------------------------------------------------------------------===//
diff --git a/clang/test/CIR/CodeGenCoroutines/coro-task.cpp b/clang/test/CIR/CodeGenCoroutines/coro-task.cpp
index 3ea3b903d153a7..9853eb5c9fd4be 100644
--- a/clang/test/CIR/CodeGenCoroutines/coro-task.cpp
+++ b/clang/test/CIR/CodeGenCoroutines/coro-task.cpp
@@ -74,15 +74,11 @@ VoidTask silly_task() {
 
 // Call promise.get_return_object() to retrieve the task object.
 
+// CIR: cir.coro.ret_point {
+// CIR:   cir.cleanup.scope {
 
-// CIR: cir.cleanup.scope {
-
-// Empty records are ignored, so calls returning one return nothing and the
-// slot takes a poison placeholder.
-
-// CIR:   cir.call @_ZN5folly4coro4TaskIvE12promise_type17get_return_objectEv(%[[VoidPromisseAddr]]) nothrow : {{.*}} -> ()
-// CIR:   %[[RetObj:.*]] = cir.const #cir.poison : ![[VoidTask]]
-// CIR:   cir.store{{.*}} %[[RetObj]], %[[VoidTaskAddr]] : ![[VoidTask]]
+// CIR:     %[[RetObj:.*]] = cir.call @_ZN5folly4coro4TaskIvE12promise_type17get_return_objectEv(%[[VoidPromisseAddr]]) nothrow : {{.*}} -> ![[VoidTask]]
+// CIR:     cir.store{{.*}} %[[RetObj]], %[[VoidTaskAddr]] : ![[VoidTask]]
 
 // OGCG: call void @llvm.lifetime.start.p0(ptr %[[VoidPromisseAddr]])
 // OGCG: call void @_ZN5folly4coro4TaskIvE12promise_type17get_return_objectEv(ptr noundef nonnull align 1 dereferenceable(1) %[[VoidPromisseAddr]])
@@ -94,9 +90,8 @@ VoidTask silly_task() {
 // Effectively execute `coawait promise_type::initial_suspend()` by calling initial_suspend() and getting
 // the suspend_always struct to use for cir.await.
 
-// CIR:   cir.call @_ZN5folly4coro4TaskIvE12promise_type15initial_suspendEv(%[[VoidPromisseAddr]])
-// CIR:   %[[Tmp0:.*]] = cir.const #cir.poison : ![[SuspendAlways]]
-// CIR:   cir.store{{.*}} %[[Tmp0]], %[[SuspendAlwaysAddr]]
+// CIR:     %[[Tmp0:.*]] = cir.call @_ZN5folly4coro4TaskIvE12promise_type15initial_suspendEv(%[[VoidPromisseAddr]])
+// CIR:     cir.store{{.*}} %[[Tmp0:.*]], %[[SuspendAlwaysAddr]]
 
 // OGCG: call void @_ZN5folly4coro4TaskIvE12promise_type15initial_suspendEv(ptr noundef nonnull align 1 dereferenceable(1) %[[VoidPromisseAddr]])
 
@@ -160,22 +155,22 @@ VoidTask silly_task() {
 // - The final suspend co_await
 // - Return
 
-// CIR:   cir.coro.body {
+// CIR:     cir.coro.body {
 
 // The actual user written co_await
-// CIR:     cir.await(user, ready : {
-// CIR:     }, suspend : {
-// CIR:     }, resume : {
-// CIR:     },)
+// CIR:       cir.await(user, ready : {
+// CIR:       }, suspend : {
+// CIR:       }, resume : {
+// CIR:       },)
 
 // OGCG: cleanup.cont
 // OGCG: await.suspend:
 // OGCG: await.ready:
 
 // The promise call
-// CIR:     cir.call @_ZN5folly4coro4TaskIvE12promise_type11return_voidEv(%[[VoidPromisseAddr]])
-// CIR:     cir.co_return
-// CIR:   }
+// CIR:       cir.call @_ZN5folly4coro4TaskIvE12promise_type11return_voidEv(%[[VoidPromisseAddr]])
+// CIR:       cir.co_return
+// CIR:     }
 
 // OGCG: call void @_ZN5folly4coro4TaskIvE12promise_type11return_voidEv(ptr noundef nonnull align 1 dereferenceable(1) %[[VoidPromisseAddr]])
 
@@ -184,7 +179,7 @@ VoidTask silly_task() {
 // CIR:   }, suspend : {
 // CIR:   }, resume : {
 // CIR:   },)
-// CIR-NEXT:   cir.yield
+// CIR-NEXT:     cir.yield
 
 // OGCG: coro.final:
 // OGCG: final.suspend:
@@ -196,16 +191,17 @@ VoidTask silly_task() {
 // If null, no dynamic allocation happened, so nothing to free.
 // The `if` ensures we only call delete on non-null.
 
-// CIR-NEXT: } cleanup  normal {
-// CIR-NEXT:   %[[FreeMem:.*]] = cir.coro.intrinsic.free(%[[CoroId]], %[[CoroFrameAddr]]) : (token, !cir.ptr<!void>) -> !cir.ptr<!void>
-// CIR-NEXT:   %[[NullPtr2:.*]] = cir.const #cir.ptr<null>
-// CIR-NEXT:   %[[Cond:.*]] = cir.cmp ne %[[FreeMem]], %[[NullPtr2]]
-// CIR-NEXT:   cir.if %[[Cond]] {
-// CIR-NEXT:     %[[Size:.*]] = cir.coro.intrinsic.size()
-// CIR-NEXT:     cir.call @_ZdlPvm(%[[FreeMem]], %[[Size]]){{.*}}
-// CIR-NEXT:   }
-// CIR-NEXT:   cir.yield
-// CIR-NEXT: }
+// CIR:   } cleanup  normal {
+// CIR:     %[[FreeMem:.*]] = cir.coro.intrinsic.free(%[[CoroId]], %[[CoroFrameAddr]]) : (token, !cir.ptr<!void>) -> !cir.ptr<!void>
+// CIR:     %[[NullPtr2:.*]] = cir.const #cir.ptr<null>
+// CIR:     %[[Cond:.*]] = cir.cmp ne %[[FreeMem]], %[[NullPtr2]]
+// CIR:     cir.if %[[Cond]] {
+// CIR:       %[[Size:.*]] = cir.call @__builtin_coro_size()
+// CIR:       cir.call @_ZdlPvm(%[[FreeMem]], %[[Size]])
+// CIR:     }
+// CIR:     cir.yield
+// CIR:   }
+// CIR: cir.yield
 
 // OGCG: %[[FreeMem:.*]] = call ptr @llvm.coro.free(token %[[CoroId]], ptr %[[CoroFrameAddr]])
 // OGCG: %[[Cond:.*]] = icmp ne ptr %[[FreeMem]], null
@@ -219,14 +215,17 @@ VoidTask silly_task() {
 
 // Call builtin coro end and return
 
+// CIR: }, ret : {
 // CIR-NEXT: %[[CoroEndArg0:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!void>
 // CIR-NEXT: %[[CoroEndArg1:.*]] = cir.const #false
 // CIR-NEXT: %[[TK_NONE:.*]] = cir.token.none
 // CIR-NEXT: cir.coro.intrinsic.end(%[[CoroEndArg0]], %[[CoroEndArg1]], %[[TK_NONE]]) : (!cir.ptr<!void>, !cir.bool, token){{.*}}
-
-// CIR: %[[Tmp1:.*]] = cir.load{{.*}} %[[VoidTaskAddr]]
-// CIR: cir.return
-// CIR: }
+// CIR:      %[[Tmp1:.*]] = cir.load{{.*}} %[[VoidTaskAddr]]
+// CIR:      cir.return %[[Tmp1]]
+// CIR:   }
+// CIRL }
+// CIR: cir.trap
+// CIR:}
 
 // OGCG: coro.ret:
 // OGCG:   call void @llvm.coro.end(ptr null, i1 false, token none)
@@ -247,49 +246,49 @@ folly::coro::Task<int> byRef(const std::string& s) {
 // CIR:    %[[CoroHandlePromiseAddr:.*]] = cir.alloca "agg.tmp1" {{.*}} : !cir.ptr<![[CoroHandlePromiseInt]]>
 // CIR:    cir.store %[[ARG]], %[[AllocaParam]] : !cir.ptr<![[StdString]]>, {{.*}}
 
-// CIR:    cir.cleanup.scope {
+// CIR:    cir.coro.ret_point {
+// CIR:     cir.cleanup.scope {
 // Call promise.get_return_object() to retrieve the task object.
 
-// CIR:     %[[LOAD:.*]] = cir.load %[[AllocaParam]] : !cir.ptr<!cir.ptr<![[StdString]]>>, !cir.ptr<![[StdString]]>
-// CIR:     cir.store {{.*}} %[[LOAD]], %[[AllocaFnUse]] : !cir.ptr<![[StdString]]>, !cir.ptr<!cir.ptr<![[StdString]]>>
-// CIR:     cir.call @_ZN5folly4coro4TaskIiE12promise_type17get_return_objectEv(%[[IntPromisseAddr]]) nothrow : {{.*}} -> ()
-// CIR:     %[[RetObj:.*]] = cir.const #cir.poison : ![[IntTask]]
-// CIR:     cir.store {{.*}} %[[RetObj]], %[[IntTaskAddr]] : ![[IntTask]]
-// CIR:     cir.call @_ZN5folly4coro4TaskIiE12promise_type15initial_suspendEv(%[[IntPromisseAddr]])
-// CIR:     %[[Tmp0:.*]] = cir.const #cir.poison : ![[SuspendAlways]]
-// CIR:     cir.store{{.*}} %[[Tmp0]], %[[SuspendAlwaysAddr]]
-// CIR:     cir.await(init, ready : {
-// CIR:       %[[TmpCallRes:.*]] = cir.call @_ZNSt14suspend_always11await_readyEv(%[[SuspendAlwaysAddr]])
-// CIR:       cir.condition(%[[TmpCallRes]])
-// CIR:     }, suspend : {
-// CIR:       cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIiE12promise_typeEE12from_addressEPv(%[[CoroFrameAddr:.*]])
-// CIR:       %[[FromAddrRes:.*]] = cir.const #cir.poison : ![[CoroHandlePromiseInt]]
-// CIR:       cir.store{{.*}} %[[FromAddrRes]], %[[CoroHandlePromiseAddr]] : ![[CoroHandlePromiseInt]]
-// CIR:       %[[CoroHandlePromiseReload:.*]] = cir.load{{.*}} %[[CoroHandlePromiseAddr]]
-// CIR:       cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIiE12promise_typeEEES_IT_E(%[[CoroHandleVoidAddr]])
-// CIR:       %[[CoroHandleVoidReload:.*]] = cir.load{{.*}} %[[CoroHandleVoidAddr]] : !cir.ptr<![[CoroHandleVoid]]>, ![[CoroHandleVoid]]
-// CIR:       cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SuspendAlwaysAddr]], %[[CoroHandleVoidReload]])
-// CIR:       cir.coro.suspend.point
-// CIR:     }, resume : {
-// CIR:       cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SuspendAlwaysAddr]])
-// CIR:       cir.yield
-// CIR:     },)
-// CIR:     cir.coro.body {
+// CIR:       %[[LOAD:.*]] = cir.load %[[AllocaParam]] : !cir.ptr<!cir.ptr<![[StdString]]>>, !cir.ptr<![[StdString]]>
+// CIR:       cir.store {{.*}} %[[LOAD]], %[[AllocaFnUse]] : !cir.ptr<![[StdString]]>, !cir.ptr<!cir.ptr<![[StdString]]>>
+// CIR:       %[[RetObj:.*]] = cir.call @_ZN5folly4coro4TaskIiE12promise_type17get_return_objectEv(%[[IntPromisseAddr]]) nothrow : {{.*}} -> ![[IntTask]]
+// CIR:       cir.store {{.*}} %[[RetObj]], %[[IntTaskAddr]] : ![[IntTask]]
+// CIR:       %[[Tmp0:.*]] = cir.call @_ZN5folly4coro4TaskIiE12promise_type15initial_suspendEv(%[[IntPromisseAddr]])
+// CIR:       cir.store{{.*}} %[[Tmp0]], %[[SuspendAlwaysAddr]]
+// CIR:       cir.await(init, ready : {
+// CIR:         %[[TmpCallRes:.*]] = cir.call @_ZNSt14suspend_always11await_readyEv(%[[SuspendAlwaysAddr]])
+// CIR:         cir.condition(%[[TmpCallRes]])
+// CIR:       }, suspend : {
+// CIR:         %[[FromAddrRes:.*]] = cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIiE12promise_typeEE12from_addressEPv(%[[CoroFrameAddr:.*]])
+// CIR:         cir.store{{.*}} %[[FromAddrRes]], %[[CoroHandlePromiseAddr]] : ![[CoroHandlePromiseInt]]
+// CIR:         %[[CoroHandlePromiseReload:.*]] = cir.load{{.*}} %[[CoroHandlePromiseAddr]]
+// CIR:         cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIiE12promise_typeEEES_IT_E(%[[CoroHandleVoidAddr]], %[[CoroHandlePromiseReload]])
+// CIR:         %[[CoroHandleVoidReload:.*]] = cir.load{{.*}} %[[CoroHandleVoidAddr]] : !cir.ptr<![[CoroHandleVoid]]>, ![[CoroHandleVoid]]
+// CIR:         cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SuspendAlwaysAddr]], %[[CoroHandleVoidReload]])
+// CIR:         cir.coro.suspend_point
+// CIR:       }, resume : {
+// CIR:         cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SuspendAlwaysAddr]])
+// CIR:         cir.yield
+// CIR:       },)
+// CIR:       cir.coro.body {
               // can't fallthrough
-// CIR-NOT:   cir.await(user
-// CIR:       cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[IntPromisseAddr]], %[[STRING_SIZE:.*]])
-//CIR:        cir.co_return
-// CIR:     }
+// CIR-NOT:     cir.await(user
+// CIR:         cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[IntPromisseAddr]], %[[STRING_SIZE:.*]])
+//CIR:          cir.co_return
+// CIR:       }
 
 
 // The final suspend co_await
-// CIR:     cir.await(final, ready : {
-// CIR:     }, suspend : {
-// CIR:     }, resume : {
-// CIR:     },)
+// CIR:      cir.await(final, ready : {
+// CIR:      }, suspend : {
+// CIR:      }, resume : {
+// CIR:      },)
+// CIR:      cir.yield
+// CIR:     } cleanup  normal {
+// CIR:     }
 // CIR:     cir.yield
-// CIR:    } cleanup  normal {
-// CIR:    }
+// CIR:    }, ret : {
 
 folly::coro::Task<void> silly_coro() {
   std::optional<folly::coro::Task<int>> task;
@@ -304,23 +303,25 @@ folly::coro::Task<void> silly_coro() {
 // Make sure we properly handle OnFallthrough coro body sub stmt and
 // check there are not multiple co_returns emitted.
 
-// CIR: cir.func coroutine {{.*}} @_Z10silly_corov() attributes
-// CIR: cir.cleanup.scope {
-// CIR:   cir.await(init, ready : {
-// CIR:   }, suspend : {
-// CIR:   }, resume : {
-// CIR:   },)
-// CIR:   cir.coro.body {
-// CIR:     cir.call @_ZN5folly4coro4TaskIvE12promise_type11return_voidEv
-// CIR:     cir.co_return
+// CIR: cir.func coroutine {{.*}} @_Z10silly_corov() {{.*}} ![[VoidTask]]
+// CIR:  cir.coro.ret_point {
+// CIR:   cir.cleanup.scope {
+// CIR:     cir.await(init, ready : {
+// CIR:     }, suspend : {
+// CIR:     }, resume : {
+// CIR:     },)
+// CIR:     cir.coro.body {
+// CIR:       cir.call @_ZN5folly4coro4TaskIvE12promise_type11return_voidEv
+// CIR:       cir.co_return
+// CIR:     }
+// CIR:     cir.await(final, ready : {
+// CIR:     }, suspend : {
+// CIR:     }, resume : {
+// CIR:     },)
+// CIR:     cir.yield
+// CIR:   } cleanup  normal {
 // CIR:   }
-// CIR:   cir.await(final, ready : {
-// CIR:   }, suspend : {
-// CIR:   }, resume : {
-// CIR:   },)
-// CIR:   cir.yield
-// CIR: } cleanup  normal {
-// CIR: }
+// CIR:  }, ret : {
 
 folly::coro::Task<void> yield();
 folly::coro::Task<void> yield1() {
@@ -346,85 +347,81 @@ folly::coro::Task<void> yield1() {
 // CIR-DAG: %[[CH_VOID2:.*]] = cir.alloca "agg.tmp5" {{.*}} : !cir.ptr<![[CoroHandleVoid]]>
 // CIR-DAG: %[[CH_PROM2:.*]] = cir.alloca "agg.tmp6" {{.*}} : !cir.ptr<![[CoroHandlePromiseVoid]]>
 
-// CIR: cir.cleanup.scope {
+// CIR:  cir.coro.ret_point {
+// CIR:   cir.cleanup.scope {
 // initial_suspend + await(init)
-// CIR:   cir.call @_ZN5folly4coro4TaskIvE12promise_type15initial_suspendEv(%[[PROMISE]]){{.*}}
-// CIR:   %[[INIT_SUSP:.*]] = cir.const #cir.poison : ![[SuspendAlways]]
-// CIR:   cir.store{{.*}} %[[INIT_SUSP]], %[[SUSP0]]
-// CIR:   cir.await(init, ready : {
-// CIR:     %[[READY0:.*]] = cir.call @_ZNSt14suspend_always11await_readyEv(%[[SUSP0]]){{.*}}
-// CIR:     cir.condition(%[[READY0]])
-// CIR:   }, suspend : {
-// CIR:     cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIvE12promise_typeEE12from_addressEPv(%{{.*}}){{.*}}
-// CIR:     %[[FROMADDR0:.*]] = cir.const #cir.poison : ![[CoroHandlePromiseVoid]]
-// CIR:     cir.store{{.*}} %[[FROMADDR0]], %[[CH_PROM0]]
-// CIR:     %[[PROM_RELOAD0:.*]] = cir.load{{.*}} %[[CH_PROM0]]
-// CIR:     cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_typeEEES_IT_E(%[[CH_VOID0]]){{.*}}
-// CIR:     %[[VOID_RELOAD0:.*]] = cir.load{{.*}} %[[CH_VOID0]]
-// CIR:     cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SUSP0]], %[[VOID_RELOAD0]]){{.*}}
-// CIR:     cir.coro.suspend.point
-// CIR:   }, resume : {
-// CIR:     cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SUSP0]]){{.*}}
-// CIR:     cir.yield
-// CIR:   },)
-
-// yield_value + await(yield)
-// CIR:   cir.coro.body {
-// CIR:     cir.call @_Z5yieldv(){{.*}}
-// CIR:     %[[YIELD_TASK:.*]] = cir.const #cir.poison : ![[VoidTask]]
-// CIR:     cir.store{{.*}} %[[YIELD_TASK]], %[[T_ADDR]]
-// CIR:     %[[AWAITER:.*]] = cir.load{{.*}} %[[AWAITER_COPY_ADDR]]
-// CIR:     cir.call @_ZN5folly4coro4TaskIvE12promise_type11yield_valueES2_(%[[PROMISE]]){{.*}}
-// CIR:     %[[YIELD_SUSP:.*]] = cir.const #cir.poison : ![[SuspendAlways]]
-// CIR:     cir.store{{.*}} %[[YIELD_SUSP]], %[[SUSP1]]
-// CIR:     cir.await(yield, ready : {
-// CIR:       %[[READY1:.*]] = cir.call @_ZNSt14suspend_always11await_readyEv(%[[SUSP1]]){{.*}}
-// CIR:       cir.condition(%[[READY1]])
+// CIR:     %[[INIT_SUSP:.*]] = cir.call @_ZN5folly4coro4TaskIvE12promise_type15initial_suspendEv(%[[PROMISE]]){{.*}}
+// CIR:     cir.store{{.*}} %[[INIT_SUSP]], %[[SUSP0]]
+// CIR:     cir.await(init, ready : {
+// CIR:       %[[READY0:.*]] = cir.call @_ZNSt14suspend_always11await_readyEv(%[[SUSP0]]){{.*}}
+// CIR:       cir.condition(%[[READY0]])
 // CIR:     }, suspend : {
-// CIR:       cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIvE12promise_typeEE12from_addressEPv(%{{.*}}){{.*}}
-// CIR:       %[[FROMADDR1:.*]] = cir.const #cir.poison : ![[CoroHandlePromiseVoid]]
-// CIR:       cir.store{{.*}} %[[FROMADDR1]], %[[CH_PROM1]]
-// CIR:       %[[PROM_RELOAD1:.*]] = cir.load{{.*}} %[[CH_PROM1]]
-// CIR:       cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_typeEEES_IT_E(%[[CH_VOID1]]){{.*}}
-// CIR:       %[[VOID_RELOAD1:.*]] = cir.load{{.*}} %[[CH_VOID1]]
-// CIR:       cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SUSP1]], %[[VOID_RELOAD1]]){{.*}}
-// CIR:       cir.coro.suspend.point
+// CIR:       %[[FROMADDR0:.*]] = cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIvE12promise_typeEE12from_addressEPv(%{{.*}}){{.*}}
+// CIR:       cir.store{{.*}} %[[FROMADDR0]], %[[CH_PROM0]]
+// CIR:       %[[PROM_RELOAD0:.*]] = cir.load{{.*}} %[[CH_PROM0]]
+// CIR:       cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_typeEEES_IT_E(%[[CH_VOID0]], %[[PROM_RELOAD0]]){{.*}}
+// CIR:       %[[VOID_RELOAD0:.*]] = cir.load{{.*}} %[[CH_VOID0]]
+// CIR:       cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SUSP0]], %[[VOID_RELOAD0]]){{.*}}
+// CIR:       cir.coro.suspend_point
 // CIR:     }, resume : {
-// CIR:       cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SUSP1]]){{.*}}
+// CIR:       cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SUSP0]]){{.*}}
 // CIR:       cir.yield
 // CIR:     },)
-// CIR:     cir.call @_ZN5folly4coro4TaskIvE12promise_type11return_voidEv(%[[PROMISE]])
-// CIR:     cir.co_return
-// CIR:   }
+
+// yield_value + await(yield)
+// CIR:     cir.coro.body {
+// CIR:       %[[YIELD_TASK:.*]] = cir.call @_Z5yieldv(){{.*}}
+// CIR:       cir.store{{.*}} %[[YIELD_TASK]], %[[T_ADDR]]
+// CIR:       %[[AWAITER:.*]] = cir.load{{.*}} %[[AWAITER_COPY_ADDR]]
+// CIR:       %[[YIELD_SUSP:.*]] = cir.call @_ZN5folly4coro4TaskIvE12promise_type11yield_valueES2_(%[[PROMISE]], %[[AWAITER]]){{.*}}
+// CIR:       cir.store{{.*}} %[[YIELD_SUSP]], %[[SUSP1]]
+// CIR:       cir.await(yield, ready : {
+// CIR:         %[[READY1:.*]] = cir.call @_ZNSt14suspend_always11await_readyEv(%[[SUSP1]]){{.*}}
+// CIR:         cir.condition(%[[READY1]])
+// CIR:       }, suspend : {
+// CIR:         %[[FROMADDR1:.*]] = cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIvE12promise_typeEE12from_addressEPv(%{{.*}}){{.*}}
+// CIR:         cir.store{{.*}} %[[FROMADDR1]], %[[CH_PROM1]]
+// CIR:         %[[PROM_RELOAD1:.*]] = cir.load{{.*}} %[[CH_PROM1]]
+// CIR:         cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_typeEEES_IT_E(%[[CH_VOID1]], %[[PROM_RELOAD1]]){{.*}}
+// CIR:         %[[VOID_RELOAD1:.*]] = cir.load{{.*}} %[[CH_VOID1]]
+// CIR:         cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SUSP1]], %[[VOID_RELOAD1]]){{.*}}
+// CIR:         cir.coro.suspend_point
+// CIR:       }, resume : {
+// CIR:         cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SUSP1]]){{.*}}
+// CIR:         cir.yield
+// CIR:       },)
+// CIR:       cir.call @_ZN5folly4coro4TaskIvE12promise_type11return_voidEv(%[[PROMISE]])
+// CIR:       cir.co_return
+// CIR:     }
 
 // return_void + await(final)
-// CIR:   cir.call @_ZN5folly4coro4TaskIvE12promise_type13final_suspendEv(%[[PROMISE]]){{.*}}
-// CIR:   %[[FINAL_SUSP:.*]] = cir.const #cir.poison : ![[SuspendAlways]]
-// CIR:   cir.store{{.*}} %[[FINAL_SUSP]], %[[SUSP2]]
-// CIR:   cir.await(final, ready : {
-// CIR:     %[[READY2:.*]] = cir.call @_ZNSt14suspend_always11await_readyEv(%[[SUSP2]]){{.*}}
-// CIR:     cir.condition(%[[READY2]])
-// CIR:   }, suspend : {
-// CIR:     cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIvE12promise_typeEE12from_addressEPv(%{{.*}}){{.*}}
-// CIR:     %[[FROMADDR2:.*]] = cir.const #cir.poison : ![[CoroHandlePromiseVoid]]
-// CIR:     cir.store{{.*}} %[[FROMADDR2]], %[[CH_PROM2]]
-// CIR:     %[[PROM_RELOAD2:.*]] = cir.load{{.*}} %[[CH_PROM2]]
-// CIR:     cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_typeEEES_IT_E(%[[CH_VOID2]]){{.*}}
-// CIR:     %[[VOID_RELOAD2:.*]] = cir.load{{.*}} %[[CH_VOID2]]
-// CIR:     cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SUSP2]], %[[VOID_RELOAD2]]){{.*}}
-// CIR:     cir.coro.suspend.point
-// CIR:   }, resume : {
-// CIR:     cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SUSP2]]){{.*}}
-// CIR:     cir.yield
-// CIR:   },)
-// CIR:   cir.yield
-// CIR: } cleanup  normal {
-// CIR: }
-// CIR: cir.coro.intrinsic.end(%{{.*}}, %{{.*}}, %{{.*}}){{.*}}
-// CIR: %[[RETLOAD:.*]] = cir.load{{.*}} %[[RETVAL]]
-// CIR: cir.return
-// CIR: }
 
+// CIR:     %[[FINAL_SUSP:.*]] = cir.call @_ZN5folly4coro4TaskIvE12promise_type13final_suspendEv(%[[PROMISE]]){{.*}}
+// CIR:     cir.store{{.*}} %[[FINAL_SUSP]], %[[SUSP2]]
+// CIR:     cir.await(final, ready : {
+// CIR:       %[[READY2:.*]] = cir.call @_ZNSt14suspend_always11await_readyEv(%[[SUSP2]]){{.*}}
+// CIR:       cir.condition(%[[READY2]])
+// CIR:     }, suspend : {
+// CIR:       %[[FROMADDR2:.*]] = cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIvE12promise_typeEE12from_addressEPv(%{{.*}}){{.*}}
+// CIR:       cir.store{{.*}} %[[FROMADDR2]], %[[CH_PROM2]]
+// CIR:       %[[PROM_RELOAD2:.*]] = cir.load{{.*}} %[[CH_PROM2]]
+// CIR:       cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_typeEEES_IT_E(%[[CH_VOID2]], %[[PROM_RELOAD2]]){{.*}}
+// CIR:       %[[VOID_RELOAD2:.*]] = cir.load{{.*}} %[[CH_VOID2]]
+// CIR:       cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SUSP2]], %[[VOID_RELOAD2]]){{.*}}
+// CIR:       cir.coro.suspend_point
+// CIR:     }, resume : {
+// CIR:       cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SUSP2]]){{.*}}
+// CIR:       cir.yield
+// CIR:     },)
+// CIR:     cir.yield
+// CIR:   } cleanup  normal {
+// CIR:   }
+// CIR:  }, ret : {
+// CIR:   = cir.coro.intrinsic.end(%{{.*}}, %{{.*}})
+// CIR:   %[[RETLOAD:.*]] = cir.load{{.*}} %[[RETVAL]]
+// CIR:   cir.return %[[RETLOAD]]
+// CIR:  }
+// CIR:  cir.trap
 // CHECK: }
 
 folly::coro::Task<int> go(int const& val);
@@ -437,37 +434,39 @@ folly::coro::Task<int> go1() {
 // CIR: %[[IntTaskAddr:.*]] = cir.alloca "task" {{.*}} init : !cir.ptr<![[IntTask]]>
 // CIR: %[[OneAddr:.*]] = cir.alloca "ref.tmp1" align(4) init : !cir.ptr<!s32i>
 
-// CIR: cir.cleanup.scope {
-// CIR:   cir.await(init, ready : {
-// CIR:   }, suspend : {
-// CIR:   }, resume : {
-// CIR:   },)
+// CIR: cir.coro.ret_point {
+// CIR:   cir.cleanup.scope {
+// CIR:     cir.await(init, ready : {
+// CIR:     }, suspend : {
+// CIR:     }, resume : {
+// CIR:     },)
 
-// CIR:   cir.coro.body {
-// CIR:     %[[One:.*]] = cir.const #cir.int<1> : !s32i
-// CIR:     cir.store{{.*}} %[[One]], %[[OneAddr]] : !s32i, !cir.ptr<!s32i>
-// CIR:     cir.call @_Z2goRKi(%[[OneAddr]]) : (!cir.ptr<!s32i>{{.*}}) -> ()
-// CIR:     %[[IntTaskTmp:.*]] = cir.const #cir.poison : ![[IntTask]]
-// CIR:     cir.store{{.*}} %[[IntTaskTmp]], %[[IntTaskAddr]] : ![[IntTask]], !cir.ptr<![[IntTask]]>
+// CIR:     cir.coro.body {
+// CIR:       %[[One:.*]] = cir.const #cir.int<1> : !s32i
+// CIR:       cir.store{{.*}} %[[One]], %[[OneAddr]] : !s32i, !cir.ptr<!s32i>
+// CIR:       %[[IntTaskTmp:.*]] = cir.call @_Z2goRKi(%[[OneAddr]]) : (!cir.ptr<!s32i>{{.*}}) -> ![[IntTask]]
+// CIR:       cir.store{{.*}} %[[IntTaskTmp]], %[[IntTaskAddr]] : ![[IntTask]], !cir.ptr<![[IntTask]]>
 
-// CIR:     cir.await(user, ready : {
+// CIR:       cir.await(user, ready : {
+// CIR:       }, suspend : {
+// CIR:       }, resume : {
+// CIR:       %[[ResumeVal:.*]] = cir.call @_ZN5folly4coro4TaskIiE12await_resumeEv(%[[IntTaskAddr]])
+// CIR:       cir.store{{.*}} %[[ResumeVal]], %[[CoReturnValAddr:.*]] : !s32i, !cir.ptr<!s32i>
+// CIR:       },)
+// CIR:       %[[V:.*]] = cir.load{{.*}} %[[CoReturnValAddr]] : !cir.ptr<!s32i>, !s32i
+// CIR:       cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi({{.*}}, %[[V]])
+// CIR:       cir.co_return
+// CIR:     }
+
+// CIR:     cir.await(final, ready : {
 // CIR:     }, suspend : {
 // CIR:     }, resume : {
-// CIR:     %[[ResumeVal:.*]] = cir.call @_ZN5folly4coro4TaskIiE12await_resumeEv(%[[IntTaskAddr]])
-// CIR:     cir.store{{.*}} %[[ResumeVal]], %[[CoReturnValAddr:.*]] : !s32i, !cir.ptr<!s32i>
 // CIR:     },)
-// CIR:     %[[V:.*]] = cir.load{{.*}} %[[CoReturnValAddr]] : !cir.ptr<!s32i>, !s32i
-// CIR:     cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi({{.*}}, %[[V]])
-// CIR:     cir.co_return
+// CIR:     cir.yield
+// CIR:   } cleanup  normal {
 // CIR:   }
-
-// CIR:   cir.await(final, ready : {
-// CIR:   }, suspend : {
-// CIR:   }, resume : {
-// CIR:   },)
-// CIR:   cir.yield
-// CIR: } cleanup  normal {
-// CIR: }
+// CIR:  cir.yield
+// CIR: }, ret : {
 
 folly::coro::Task<int> go1_lambda() {
   auto task = []() -> folly::coro::Task<int> {
@@ -476,46 +475,74 @@ folly::coro::Task<int> go1_lambda() {
   co_return co_await task;
 }
 
-// CIR: cir.func coroutine {{.*}} @_Z10go1_lambdav() attributes
-// CIR: cir.cleanup.scope {
-// CIR:   cir.await(init, ready : {
-// CIR:   }, suspend : {
-// CIR:   }, resume : {
-// CIR:   },)
-// CIR:   cir.coro.body {
-// CIR:     cir.call @_ZZ10go1_lambdavENK3$_0clEv
-// CIR:     cir.await(user, ready : {
+// CIR: cir.func coroutine {{.*}} @_ZZ10go1_lambdavENK3$_0clEv{{.*}} ![[IntTask]]
+// CIR: cir.coro.ret_point {
+// CIR:   cir.cleanup.scope {
+// CIR:     cir.await(init, ready : {
 // CIR:     }, suspend : {
 // CIR:     }, resume : {
-// CIR:       %[[RESUME_RES:.*]] = cir.call @_ZN5folly4coro4TaskIiE12await_resumeEv(%[[TASK:.*]])
-// CIR:       cir.store %[[RESUME_RES]], %[[resume_rval:.*]] : !s32i, !cir.ptr<!s32i>
 // CIR:     },)
-// CIR:     %[[TMP1:.*]] = cir.load %[[resume_rval:.*]] : !cir.ptr<!s32i>
-// CIR:     cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE:.*]], %[[TMP1]])
-// CIR:     cir.co_return
-// CIR:   }
-// CIR:   cir.await(final, ready : {
-// CIR:   }, suspend : {
-// CIR:   }, resume : {
-// CIR:   },)
-// CIR: } cleanup  normal {
+// CIR:     cir.coro.body {
+// CIR:       %[[ONE:.*]] = cir.const #cir.int<1>
+// CIR:       cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE:.*]], %[[ONE]])
+// CIR:       cir.co_return
+// CIR:     }
+// CIR:     cir.await(final, ready : {
+// CIR:     }, suspend : {
+// CIR:     }, resume : {
+// CIR:     },)
+// CIR:   } cleanup  normal {
+// CIR: }, ret : {
 
-// CIR: cir.func coroutine {{.*}} @_ZZ10go1_lambdavENK3$_0clEv{{.*}}) attributes
-// CIR: cir.cleanup.scope {
-// CIR:   cir.await(init, ready : {
-// CIR:   }, suspend : {
-// CIR:   }, resume : {
-// CIR:   },)
-// CIR:   cir.coro.body {
-// CIR:     %[[ONE:.*]] = cir.const #cir.int<1>
-// CIR:     cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE:.*]], %[[ONE]])
-// CIR:     cir.co_return
-// CIR:   }
-// CIR:   cir.await(final, ready : {
-// CIR:   }, suspend : {
-// CIR:   }, resume : {
-// CIR:   },)
-// CIR: } cleanup  normal {
+// CIR: cir.func coroutine {{.*}} @_Z10go1_lambdav() {{.*}} ![[IntTask]]
+// CIR: cir.coro.ret_point {
+// CIR:   cir.cleanup.scope {
+// CIR:     cir.await(init, ready : {
+// CIR:     }, suspend : {
+// CIR:     }, resume : {
+// CIR:     },)
+// CIR:     cir.coro.body {
+// CIR:       cir.call @_ZZ10go1_lambdavENK3$_0clEv
+// CIR:       cir.await(user, ready : {
+// CIR:       }, suspend : {
+// CIR:       }, resume : {
+// CIR:         %[[RESUME_RES:.*]] = cir.call @_ZN5folly4coro4TaskIiE12await_resumeEv(%[[TASK:.*]])
+// CIR:         cir.store %[[RESUME_RES]], %[[resume_rval:.*]] : !s32i, !cir.ptr<!s32i>
+// CIR:       },)
+// CIR:       %[[TMP1:.*]] = cir.load %[[resume_rval:.*]] : !cir.ptr<!s32i>
+// CIR:       cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE:.*]], %[[TMP1]])
+// CIR:       cir.co_return
+// CIR:     }
+// CIR:     cir.await(final, ready : {
+// CIR:     }, suspend : {
+// CIR:     }, resume : {
+// CIR:     },)
+// CIR:   } cleanup  normal {
+// CIR: }, ret : {
+
+folly::coro::Task<int> go4() {
+  auto* fn = +[](int const& i) -> folly::coro::Task<int> { co_return i; };
+  auto task = fn(3);
+  co_return co_await std::move(task);
+}
+
+// CIR: cir.func coroutine{{.*}} @_ZZ3go4vENK3$_0clERKi(
+// CIR: cir.coro.ret_point {
+// CIR:   cir.cleanup.scope {
+// CIR:     cir.await(init, ready : {
+// CIR:     }, suspend : {
+// CIR:     }, resume : {
+// CIR:     },)
+// CIR:     cir.coro.body {
+// CIR:       cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE:.*]], %[[I:.*]])
+// CIR:       cir.co_return
+// CIR:     }
+// CIR:     cir.await(final, ready : {
+// CIR:     }, suspend : {
+// CIR:     }, resume : {
+// CIR:     },)
+// CIR:   } cleanup  normal {
+// CIR: }, ret : {
 
 folly::coro::Task<int> go4() {
   auto* fn = +[](int const& i) -> folly::coro::Task<int> { co_return i; };
@@ -525,44 +552,42 @@ folly::coro::Task<int> go4() {
 
 // CIR: cir.func coroutine {{.*}} @_Z3go4v() attributes
 
-// CIR: cir.cleanup.scope {
-// CIR:   cir.await(init, ready : {
-// CIR:   }, suspend : {
-// CIR:   }, resume : {
-// CIR:   },)
+// CIR: cir.coro.ret_point {
+// CIR:   cir.cleanup.scope {
+// CIR:     cir.await(init, ready : {
+// CIR:     }, suspend : {
+// CIR:     }, resume : {
+// CIR:     },)
 
 // Get the lambda invoker ptr via `lambda operator folly::coro::Task<int> (*)(int const&)()`
-// CIR: %[[INVOKER:.*]] = cir.call @_ZZ3go4vENK3$_0cvPFN5folly4coro4TaskIiEERKiEEv(%{{.*}}) nothrow : {{.*}} -> (!cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>> {llvm.noundef})
-// CIR: cir.store{{.*}} %[[INVOKER]], %[[FN_ADDR:.*]] : !cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>, !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>>
-// CIR: %[[FN:.*]] = cir.load{{.*}} %[[FN_ADDR]] : !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>>, !cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>
-// CIR: %[[THREE:.*]] = cir.const #cir.int<3> : !s32i
-// CIR: cir.store{{.*}} %[[THREE]], %[[ARG:.*]] : !s32i, !cir.ptr<!s32i>
-
-// Call invoker, which calls operator() indirectly. The callee is bitcast to
-// the lowered signature.
-
-// CIR: %[[FN_LOWERED:.*]] = cir.cast bitcast %[[FN]] : !cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>> -> !cir.ptr<!cir.func<(!cir.ptr<!s32i>)>>
-// CIR: cir.call %[[FN_LOWERED]](%[[ARG]]) : (!cir.ptr<!cir.func<(!cir.ptr<!s32i>)>>, !cir.ptr<!s32i> {{.*}}) -> ()
-// CIR: %[[CALLRES:.*]] = cir.const #cir.poison : ![[IntTask]]
-// CIR: cir.store{{.*}} %[[CALLRES]], %[[TASK_ADDR:.*]] : ![[IntTask]], !cir.ptr<![[IntTask]]>
-
-// CIR:  cir.await(user, ready : {
-// CIR:    = cir.call @_ZN5folly4coro4TaskIiE11await_readyEv(%[[TASK_ADDR]])
-// CIR:    cir.condition(
-// CIR:  }, suspend : {
-// CIR:    cir.coro.suspend.point
-// CIR:  }, resume : {
-// CIR:    cir.yield
-// CIR:  },)
-// CIR:  cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi
-// CIR:  cir.co_return
-// CIR: }
-
-// CIR:  cir.await(final, ready : {
-// CIR:  }, suspend : {
-// CIR:  }, resume : {
-// CIR:  },)
-// CIR: } cleanup  normal {
+// CIR:   %[[INVOKER:.*]] = cir.call @_ZZ3go4vENK3$_0cvPFN5folly4coro4TaskIiEERKiEEv(%{{.*}}) nothrow : {{.*}} -> (!cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>> {llvm.noundef})
+// CIR:   cir.store{{.*}} %[[INVOKER]], %[[FN_ADDR:.*]] : !cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>, !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>>
+// CIR:   %[[FN:.*]] = cir.load{{.*}} %[[FN_ADDR]] : !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>>, !cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>
+// CIR:   %[[THREE:.*]] = cir.const #cir.int<3> : !s32i
+// CIR:   cir.store{{.*}} %[[THREE]], %[[ARG:.*]] : !s32i, !cir.ptr<!s32i>
+
+// Call invoker, which calls operator() indirectly.
+// CIR:   %[[CALLRES:.*]] = cir.call %[[FN]](%[[ARG]]) : (!cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>, !cir.ptr<!s32i> {{.*}}) -> ![[IntTask]]
+// CIR:   cir.store{{.*}} %[[CALLRES]], %[[TASK_ADDR:.*]] : ![[IntTask]], !cir.ptr<![[IntTask]]>
+
+// CIR:    cir.await(user, ready : {
+// CIR:      = cir.call @_ZN5folly4coro4TaskIiE11await_readyEv(%[[TASK_ADDR]])
+// CIR:      cir.condition(
+// CIR:    }, suspend : {
+// CIR:      cir.coro.suspend_point
+// CIR:    }, resume : {
+// CIR:      cir.yield
+// CIR:    },)
+// CIR:    cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi
+// CIR:    cir.co_return
+// CIR:   }
+
+// CIR:    cir.await(final, ready : {
+// CIR:    }, suspend : {
+// CIR:    }, resume : {
+// CIR:    },)
+// CIR:   } cleanup  normal {
+// CIR: }, ret : {
 
 // OGCG: define {{.*}}__await_suspend_wrapper__init(ptr noundef nonnull %[[Awaiter:.*]], ptr noundef %[[Handle:.*]])
 // OGCG: entry:
@@ -591,41 +616,43 @@ folly::coro::Task<int> co_returns(int x) {
 }
 
 // CIR: cir.func coroutine {{.*}} @_Z10co_returnsi
-// CIR: cir.cleanup.scope {
-// CIR:   cir.await(init, ready : {
-// CIR:   }, suspend : {
-// CIR:   }, resume : {
-// CIR:   },)
-// CIR:   cir.coro.body {
-// CIR:      cir.scope {
-// CIR:        cir.if {{.*}} {
-// CIR:          %[[MINUS_ONE:.*]] = cir.const #cir.int<-1>
-// CIR:          cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE:.*]], %[[MINUS_ONE]])
-// CIR:          cir.co_return
-// CIR:        } else {
-// CIR:         cir.if {{.*}} {
-// CIR:           %[[MINUS_TWO:.*]] = cir.const #cir.int<-2>
-  // CIR:          cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE]], %[[MINUS_TWO]])
-  // CIR:          cir.co_return
-// CIR:           }
-// CIR:        }
-// CIR:      }
-// CIR:     cir.await(user, ready : {
+// CIR: cir.coro.ret_point {
+// CIR:   cir.cleanup.scope {
+// CIR:     cir.await(init, ready : {
 // CIR:     }, suspend : {
 // CIR:     }, resume : {
 // CIR:     },)
-// CIR:     %[[X_LOAD:.*]] = cir.load {{.*}} %[[X:.*]]
-// CIR:     %[[TWO:.*]] = cir.const #cir.int<2>
-// CIR:     %[[RES:.*]] = cir.mul nsw %[[X_LOAD]], %[[TWO]]
-// CIR:     cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE]], %[[RES]])
-// CIR:     cir.co_return
-// CIR:   }
-// CIR:  cir.await(final, ready : {
-// CIR:  }, suspend : {
-// CIR:  }, resume : {
-// CIR:  },)
-// CIR:  cir.yield
-// CIR: } cleanup  normal {
+// CIR:     cir.coro.body {
+// CIR:        cir.scope {
+// CIR:          cir.if {{.*}} {
+// CIR:            %[[MINUS_ONE:.*]] = cir.const #cir.int<-1>
+// CIR:            cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE:.*]], %[[MINUS_ONE]])
+// CIR:            cir.co_return
+// CIR:          } else {
+// CIR:           cir.if {{.*}} {
+// CIR:             %[[MINUS_TWO:.*]] = cir.const #cir.int<-2>
+// CIR:             cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE]], %[[MINUS_TWO]])
+// CIR:             cir.co_return
+// CIR:             }
+// CIR:          }
+// CIR:        }
+// CIR:       cir.await(user, ready : {
+// CIR:       }, suspend : {
+// CIR:       }, resume : {
+// CIR:       },)
+// CIR:       %[[X_LOAD:.*]] = cir.load {{.*}} %[[X:.*]]
+// CIR:       %[[TWO:.*]] = cir.const #cir.int<2>
+// CIR:       %[[RES:.*]] = cir.mul nsw %[[X_LOAD]], %[[TWO]]
+// CIR:       cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE]], %[[RES]])
+// CIR:       cir.co_return
+// CIR:     }
+// CIR:    cir.await(final, ready : {
+// CIR:    }, suspend : {
+// CIR:    }, resume : {
+// CIR:    },)
+// CIR:    cir.yield
+// CIR:   } cleanup  normal {
+// CIR: }, ret : {
 
 
 // OGCG: define {{.*}} @_Z10co_returnsi
@@ -675,40 +702,42 @@ folly::coro::Task<int> co_return_with_dtor(int flag) {
 // CIR: cir.func coroutine {{.*}} @_Z19co_return_with_dtori
 
 // CIR: %[[LOCAL:.*]] = cir.alloca "local" {{.*}} : !cir.ptr<!rec_HasDtor>
-// CIR: cir.cleanup.scope {
-// CIR:   cir.await(init, ready : {
-// CIR:   }, suspend : {
-// CIR:   }, resume : {
-// CIR:   },)
+// CIR: cir.coro.ret_point {
+// CIR:   cir.cleanup.scope {
+// CIR:     cir.await(init, ready : {
+// CIR:     }, suspend : {
+// CIR:     }, resume : {
+// CIR:     },)
 
-// CIR:   cir.coro.body {
-// CIR:     cir.cleanup.scope {
-// CIR:       cir.scope {
-// CIR:         %[[CAST_FLAG:.*]] = cir.cast int_to_bool %[[FLAG:.*]]
-// CIR:         cir.if %[[CAST_FLAG]] {
-// CIR:           %[[ONE:.*]] = cir.const #cir.int<1>
-// CIR:           cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[promise:.*]], %[[ONE]])
-// CIR:           cir.co_return
+// CIR:     cir.coro.body {
+// CIR:       cir.cleanup.scope {
+// CIR:         cir.scope {
+// CIR:           %[[CAST_FLAG:.*]] = cir.cast int_to_bool %[[FLAG:.*]]
+// CIR:           cir.if %[[CAST_FLAG]] {
+// CIR:             %[[ONE:.*]] = cir.const #cir.int<1>
+// CIR:             cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[promise:.*]], %[[ONE]])
+// CIR:             cir.co_return
+// CIR:           }
 // CIR:         }
+// CIR:         %[[TWO:.*]] = cir.const #cir.int<2>
+// CIR:         cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[promise]], %[[TWO]])
+// CIR:         cir.co_return
+// CIR:       } cleanup normal {
+// CIR:         cir.call @_ZN7HasDtorD1Ev(%[[LOCAL]])
+// CIR:         cir.yield
 // CIR:       }
-// CIR:       %[[TWO:.*]] = cir.const #cir.int<2>
-// CIR:       cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[promise]], %[[TWO]])
-// CIR:       cir.co_return
-// CIR:     } cleanup normal {
-// CIR:       cir.call @_ZN7HasDtorD1Ev(%[[LOCAL]])
 // CIR:       cir.yield
 // CIR:     }
+
+// CIR:     cir.await(final, ready : {
+// CIR:     }, suspend : {
+// CIR:     }, resume : {
+// CIR:     },)
+// CIR:     cir.yield
+// CIR:   } cleanup normal {
 // CIR:     cir.yield
 // CIR:   }
-
-// CIR:   cir.await(final, ready : {
-// CIR:   }, suspend : {
-// CIR:   }, resume : {
-// CIR:   },)
-// CIR:   cir.yield
-// CIR: } cleanup normal {
-// CIR:   cir.yield
-// CIR: }
+// CIR: }, ret : {
 
 // OGCG: define {{.*}} void @_Z19co_return_with_dtori
 // OGCG:   %[[LOCAL:.*]] = alloca %struct.HasDtor
@@ -740,35 +769,30 @@ folly::coro::Task<int __complex__> complex_co_await() noexcept {
 // CIR: %[[COMPLEX_ADDR:.*]] = cir.alloca "ref.tmp1" {{.*}} : !cir.ptr<!rec_folly3A3Acoro3A3ATask3C_Complex_int3E>
 // CIR: %[[RESUME_VAL_ADDR:.*]] = cir.alloca "__coawait_resume_rval" {{.*}} : !cir.ptr<!cir.complex<!s32i>>
 
-// CIR: cir.cleanup.scope {
-// CIR:   cir.await(init, ready : {
-// CIR:   }, suspend : {
-// CIR:   }, resume : {
-// CIR:   },)
+// CIR: cir.coro.ret_point {
+// CIR:   cir.cleanup.scope {
+// CIR:     cir.await(init, ready : {
+// CIR:     }, suspend : {
+// CIR:     }, resume : {
+// CIR:     },)
 
-// CIR:   cir.coro.body {
-// CIR:     cir.call @_Z9fetchDatav() nothrow : () -> ()
-// CIR:     %[[CALL:.*]] = cir.const #cir.poison : !rec_folly3A3Acoro3A3ATask3C_Complex_int3E
-// CIR:     cir.store {{.*}} %[[CALL]], %[[COMPLEX_ADDR]] : !rec_folly3A3Acoro3A3ATask3C_Complex_int3E, !cir.ptr<!rec_folly3A3Acoro3A3ATask3C_Complex_int3E>
+// CIR:     cir.coro.body {
+// CIR:       %[[CALL:.*]] = cir.call @_Z9fetchDatav() nothrow : () -> !rec_folly3A3Acoro3A3ATask3C_Complex_int3E
+// CIR:       cir.store {{.*}} %[[CALL]], %[[COMPLEX_ADDR]] : !rec_folly3A3Acoro3A3ATask3C_Complex_int3E, !cir.ptr<!rec_folly3A3Acoro3A3ATask3C_Complex_int3E>
+
+// CIR:         cir.await(user, ready : {
+// CIR:         }, suspend : {
+// CIR:         }, resume : {
+// CIR:           %[[RESUME_VAL:.*]] = cir.call @_ZN5folly4coro4TaskICiE12await_resumeEv(%[[COMPLEX_ADDR]]) : (!cir.ptr<!rec_folly3A3Acoro3A3ATask3C_Complex_int3E> {llvm.align = 1 : i64, llvm.dereferenceable = 1 : i64, llvm.nonnull, llvm.noundef}) -> (!cir.complex<!s32i> {llvm.noundef})
+// CIR:           cir.store %[[RESUME_VAL]], %[[RESUME_VAL_ADDR]] : !cir.complex<!s32i>, !cir.ptr<!cir.complex<!s32i>>
+// CIR:         },)
+// CIR:         %[[V:.*]] = cir.load %[[RESUME_VAL_ADDR]] : !cir.ptr<!cir.complex<!s32i>>, !cir.complex<!s32i>
+// CIR:         cir.yield
+// CIR:     }
 
-// CIR:       cir.await(user, ready : {
-// CIR:       }, suspend : {
-// CIR:       }, resume : {
-// _Complex int is one eightbyte in class INTEGER, so it returns as !u64i and
-// is reloaded through the coerce slot.
-
-// CIR:         %[[RESUME_VAL:.*]] = cir.call @_ZN5folly4coro4TaskICiE12await_resumeEv(%[[COMPLEX_ADDR]]) : (!cir.ptr<!rec_folly3A3Acoro3A3ATask3C_Complex_int3E> {llvm.align = 1 : i64, llvm.dereferenceable = 1 : i64, llvm.nonnull, llvm.noundef}) -> (!u64i {llvm.noundef})
-// CIR:         cir.store %[[RESUME_VAL]], %[[COERCE_ADDR]] : !u64i, !cir.ptr<!u64i>
-// CIR:         %[[COERCE_CAST:.*]] = cir.cast bitcast %[[COERCE_ADDR]] : !cir.ptr<!u64i> -> !cir.ptr<!cir.complex<!s32i>>
-// CIR:         %[[RESUME_COMPLEX:.*]] = cir.load %[[COERCE_CAST]] : !cir.ptr<!cir.complex<!s32i>>, !cir.complex<!s32i>
-// CIR:         cir.store %[[RESUME_COMPLEX]], %[[RESUME_VAL_ADDR]] : !cir.complex<!s32i>, !cir.ptr<!cir.complex<!s32i>>
-// CIR:       },)
-// CIR:       %[[V:.*]] = cir.load %[[RESUME_VAL_ADDR]] : !cir.ptr<!cir.complex<!s32i>>, !cir.complex<!s32i>
-// CIR:       cir.yield
+// CIR:   } cleanup  normal {
 // CIR:   }
-
-// CIR: } cleanup  normal {
-// CIR: }
+// CIR: }, ret : {
 
 // CIR: cir.func coroutine{{.*}} @_ZZ3go4vENK3$_0clERKi(
 // CIR: cir.cleanup.scope {
diff --git a/clang/test/CIR/IR/await.cir b/clang/test/CIR/IR/await.cir
index 1305c4bb31e0f9..9e6a71351c42e8 100644
--- a/clang/test/CIR/IR/await.cir
+++ b/clang/test/CIR/IR/await.cir
@@ -1,26 +1,29 @@
 // RUN: cir-opt %s --verify-roundtrip | FileCheck %s
 
 cir.func coroutine @checkPrintParse(%arg0 : !cir.bool) {
-  cir.coro.body {
-    cir.await(user, ready : {
-      cir.condition(%arg0)
-    }, suspend : {
-      cir.coro.suspend.point
-    }, resume : {
+  cir.coro.ret_point {
+    cir.coro.body {
+      cir.await(user, ready : {
+        cir.condition(%arg0)
+      }, suspend : {
+        cir.coro.suspend_point
+      }, resume : {
+        cir.yield
+      },)
       cir.yield
-    },)
+    }
     cir.yield
+  }, ret : {
+      cir.return
   }
-  cir.coro.suspend.point.dest
-  cir.return
+  cir.trap
 }
 
 // CHECK:  cir.func coroutine @checkPrintParse
 // CHECK:  cir.await(user, ready : {
 // CHECK:    cir.condition(%arg0)
 // CHECK:  }, suspend : {
-// CHECK:    cir.coro.suspend.point
+// CHECK:    cir.coro.suspend_point
 // CHECK:  }, resume : {
 // CHECK:    cir.yield
 // CHECK:  },)
-// CHECK:  cir.coro.suspend.point.dest
diff --git a/clang/test/CIR/IR/co-return.cir b/clang/test/CIR/IR/co-return.cir
index d6c3be2da80ada..833b6a27d80162 100644
--- a/clang/test/CIR/IR/co-return.cir
+++ b/clang/test/CIR/IR/co-return.cir
@@ -1,17 +1,21 @@
 // RUN: cir-opt %s --verify-roundtrip | FileCheck %s
 cir.func coroutine @coro_co_return(%arg0 : !cir.bool) {
-  cir.coro.body {
-    cir.await(user, ready : {
-      cir.condition(%arg0)
-    }, suspend : {
-      cir.coro.suspend.point
-    }, resume : {
-      cir.yield
-    },)
-    cir.co_return
+  cir.coro.ret_point {
+    cir.coro.body {
+      cir.await(user, ready : {
+        cir.condition(%arg0)
+      }, suspend : {
+        cir.coro.suspend_point
+      }, resume : {
+        cir.yield
+      },)
+      cir.co_return
+    }
+    cir.yield
+  }, ret : {
+      cir.return
   }
-  cir.coro.suspend.point.dest
-  cir.return
+  cir.trap
 }
 
 // CHECK: cir.func coroutine @coro_co_return
diff --git a/clang/test/CIR/IR/coro-body.cir b/clang/test/CIR/IR/coro-body.cir
index b1560485303b1c..30af9fc0e5d3cb 100644
--- a/clang/test/CIR/IR/coro-body.cir
+++ b/clang/test/CIR/IR/coro-body.cir
@@ -1,18 +1,22 @@
 // RUN: cir-opt %s --verify-roundtrip | FileCheck %s
 
 cir.func coroutine @coro_body(%arg0 : !cir.bool) {
-  cir.coro.body {
-    cir.await(user, ready : {
-      cir.condition(%arg0)
-    }, suspend : {
-      cir.coro.suspend.point
-    }, resume : {
-      cir.yield
-    },)
-    cir.co_return
+  cir.coro.ret_point {
+    cir.coro.body {
+      cir.await(user, ready : {
+        cir.condition(%arg0)
+      }, suspend : {
+        cir.coro.suspend_point
+      }, resume : {
+        cir.yield
+      },)
+      cir.co_return
+    }
+    cir.yield
+  }, ret : {
+      cir.return
   }
-  cir.coro.suspend.point.dest
-  cir.return
+  cir.trap
 }
 
 // CHECK: cir.func coroutine @coro_body
diff --git a/clang/test/CIR/IR/coro-ret-point.cir b/clang/test/CIR/IR/coro-ret-point.cir
new file mode 100644
index 00000000000000..44afdc6407931b
--- /dev/null
+++ b/clang/test/CIR/IR/coro-ret-point.cir
@@ -0,0 +1,23 @@
+// RUN: cir-opt %s --verify-roundtrip | FileCheck %s
+
+cir.func coroutine @coro_ret_point(%arg0 : !cir.bool) {
+  cir.coro.ret_point {
+    cir.coro.body {
+      cir.await(user, ready : {
+        cir.condition(%arg0)
+      }, suspend : {
+        cir.coro.suspend_point
+      }, resume : {
+        cir.yield
+      },)
+      cir.yield
+    }
+    cir.yield
+  }, ret : {
+      cir.return
+  }
+  cir.trap
+}
+
+// CHECK: cir.coro.ret_point {
+// CHECK: }, ret : {
diff --git a/clang/test/CIR/IR/func.cir b/clang/test/CIR/IR/func.cir
index 8b9cc2c8947865..e7420d56022dbe 100644
--- a/clang/test/CIR/IR/func.cir
+++ b/clang/test/CIR/IR/func.cir
@@ -109,20 +109,24 @@ cir.func @ullfunc() -> !u64i {
 // CHECK: }
 
 cir.func coroutine @coro() {
-  cir.coro.body {
-    cir.await(init, ready : {
-      %0 = cir.alloca "" align(1) : !cir.ptr<!cir.bool>
-      %1 = cir.load align(1) %0 : !cir.ptr<!cir.bool>, !cir.bool
-      cir.condition(%1)
-    }, suspend : {
-      cir.coro.suspend.point
-    }, resume : {
+  cir.coro.ret_point {
+    cir.coro.body {
+      cir.await(init, ready : {
+        %0 = cir.alloca "" align(1) : !cir.ptr<!cir.bool>
+        %1 = cir.load align(1) %0 : !cir.ptr<!cir.bool>, !cir.bool
+        cir.condition(%1)
+      }, suspend : {
+        cir.coro.suspend_point
+      }, resume : {
+        cir.yield
+      },)
       cir.yield
-    },)
+    }
     cir.yield
+  }, ret : {
+      cir.return
   }
-  cir.coro.suspend.point.dest
-  cir.return
+  cir.trap
 }
 // CHECK: cir.func{{.*}} coroutine @coro()
 
diff --git a/clang/test/CIR/IR/invalid-await.cir b/clang/test/CIR/IR/invalid-await.cir
index cc6d011718ad00..8e40e0886428fa 100644
--- a/clang/test/CIR/IR/invalid-await.cir
+++ b/clang/test/CIR/IR/invalid-await.cir
@@ -6,32 +6,38 @@ cir.func coroutine @bad_task() { // expected-error {{coroutine body must use at
 // -----
 
 cir.func coroutine @missing_condition() {
-  cir.scope {
+  cir.coro.ret_point {
     cir.await(user, ready : { // expected-error {{ready region must end with cir.condition}}
       cir.yield
     }, suspend : {
-      cir.coro.suspend.point
+      cir.coro.suspend_point
     }, resume : {
       cir.yield
     },)
+    cir.coro.body {
+    }
+    cir.yield
+  }, ret : {
+      cir.return
   }
-  cir.coro.body {
-  }
-  cir.coro.suspend.point.dest
-  cir.return
+  cir.trap
 }
 
 cir.func coroutine @missing_suspend(%arg0 : !cir.bool) {
-  cir.coro.body {
-    cir.await(user, ready : { // expected-error {{ready region must end with cir.coro.suspend.point}}
-      cir.condition(%arg0)
-    }, suspend : {
+  cir.coro.ret_point {
+    cir.coro.body {
+      cir.await(user, ready : { // expected-error {{suspend region must end with cir.coro.suspend_point}}
+        cir.condition(%arg0)
+      }, suspend : {
+        cir.yield
+      }, resume : {
+        cir.yield
+      },)
       cir.yield
-    }, resume : {
-      cir.yield
-    },)
+    }
     cir.yield
+  }, ret : {
+      cir.return
   }
-  cir.coro.suspend.point.dest
-  cir.return
+  cir.trap
 }
diff --git a/clang/test/CIR/IR/invalid-coro-body.cir b/clang/test/CIR/IR/invalid-coro-body.cir
index f247220c7d73d2..c72ec6e4450fbb 100644
--- a/clang/test/CIR/IR/invalid-coro-body.cir
+++ b/clang/test/CIR/IR/invalid-coro-body.cir
@@ -11,12 +11,11 @@ cir.func coroutine @must_have_one_coro_body(%arg0 : !cir.bool) {  // expected-er
     cir.await(user, ready : {
       cir.condition(%arg0)
     }, suspend : {
-      cir.coro.suspend.point
+      cir.coro.suspend_point
     }, resume : {
       cir.yield
     },)
   }
   cir.coro.body {
   }
-  cir.coro.suspend.point.dest
 }
diff --git a/clang/test/CIR/IR/invalid-coro-ret-point.cir b/clang/test/CIR/IR/invalid-coro-ret-point.cir
new file mode 100644
index 00000000000000..3e65f094ccb63b
--- /dev/null
+++ b/clang/test/CIR/IR/invalid-coro-ret-point.cir
@@ -0,0 +1,55 @@
+// RUN: cir-opt %s -verify-diagnostics -split-input-file
+
+cir.func coroutine @coro_ret_point_body_must_end_in_yield(%arg0 : !cir.bool) {
+  cir.coro.ret_point { // expected-error {{body region must terminate with 'cir.yield'}}
+    cir.coro.body {
+      cir.await(user, ready : {
+        cir.condition(%arg0)
+      }, suspend : {
+        cir.coro.suspend_point
+      }, resume : {
+        cir.yield
+      },)
+    }
+    cir.trap
+  }, ret : {
+      cir.return
+  }
+  cir.trap
+}
+
+cir.func coroutine @coro_ret_point_body_must_not_contain_return(%arg0 : !cir.bool) {
+  cir.coro.ret_point { // expected-error {{body region must not contain 'cir.return' operations}}
+    cir.coro.body {
+      cir.await(user, ready : {
+        cir.condition(%arg0)
+      }, suspend : {
+        cir.coro.suspend_point
+      }, resume : {
+        cir.yield
+      },)
+    }
+    cir.return
+  }, ret : {
+      cir.return
+  }
+  cir.trap
+}
+
+cir.func coroutine @coro_ret_point_return_region_must_end_in_return(%arg0 : !cir.bool) {
+  cir.coro.ret_point { // expected-error {{return region must terminate with 'cir.return'}}
+    cir.coro.body {
+      cir.await(user, ready : {
+        cir.condition(%arg0)
+      }, suspend : {
+        cir.coro.suspend_point
+      }, resume : {
+        cir.yield
+      },)
+    }
+    cir.yield
+  }, ret : {
+      cir.trap
+  }
+  cir.trap
+}
diff --git a/clang/test/CIR/IR/invalid-coro-suspend.cir b/clang/test/CIR/IR/invalid-coro-suspend.cir
index 3a45e22433a589..d03769ed663060 100644
--- a/clang/test/CIR/IR/invalid-coro-suspend.cir
+++ b/clang/test/CIR/IR/invalid-coro-suspend.cir
@@ -1,17 +1,23 @@
 // RUN: cir-opt %s -verify-diagnostics -split-input-file
 
 
-cir.func coroutine @must_have_one_coro_suspend_dest(%arg0 : !cir.bool) {  // expected-error {{coroutine function must have exactly one cir.coro.suspend.point.dest}}
-  cir.coro.body {
-    cir.await(user, ready : {
-      cir.condition(%arg0)
-    }, suspend : {
-      cir.coro.suspend.point
-    }, resume : {
-      cir.yield
-    },)
+cir.func coroutine @must_have_one_coro_ret_point(%arg0 : !cir.bool) {  // expected-error {{coroutine function must have exactly one cir.coro.ret_point op}}
+  cir.coro.ret_point {
+    cir.coro.body {
+      cir.await(user, ready : {
+        cir.condition(%arg0)
+      }, suspend : {
+        cir.coro.suspend_point
+      }, resume : {
+        cir.yield
+      },)
+    }
+  cir.yield
+  }, ret : {
+      cir.return
   }
-  cir.coro.suspend.point.dest
-  cir.coro.suspend.point.dest
+  cir.coro.ret_point {
+  }, ret : {
+  }
+  cir.trap
 }
-

>From 0ebe3080dfce680c7ac69ca2a79cf70ffa1fe3e4 Mon Sep 17 00:00:00 2001
From: Andres Salamanca <andrealebarbaritos at gmail.com>
Date: Thu, 30 Jul 2026 21:35:53 -0500
Subject: [PATCH 03/12] Fix formatting

---
 clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
index f9ee288e1f29ef..11890c77d549e5 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
@@ -667,7 +667,7 @@ emitSuspendExpression(CIRGenFunction &cgf, CGCoroData &coro,
         }
 
         // Signals the parent that execution flows to next region.
-        cir::CoroSuspendPoint::create(builder,loc);
+        cir::CoroSuspendPoint::create(builder, loc);
       },
       /*resumeBuilder=*/
       [&](mlir::OpBuilder &b, mlir::Location loc) {

>From 417c67f142c27d37e0f9808a1a6cba28365e65f8 Mon Sep 17 00:00:00 2001
From: Andres Salamanca <andrealebarbaritos at gmail.com>
Date: Tue, 4 Aug 2026 20:52:07 -0500
Subject: [PATCH 04/12] Fix after rebase.

---
 clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp      | 5 +++++
 clang/test/CIR/CodeGenCoroutines/coro-task.cpp | 2 +-
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
index 11890c77d549e5..7ea854b2c1afea 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
@@ -582,6 +582,11 @@ CIRGenFunction::emitCoroutineBody(const CoroutineBodyStmt &s) {
   mlir::OpBuilder::InsertionGuard guard(builder);
   builder.restoreInsertionPoint(coroRetRegion);
 
+  cir::CoroEndOp::create(
+      cgm.getBuilder(), openCurlyLoc,
+      mlir::ValueRange{builder.getNullPtr(builder.getVoidPtrTy(), openCurlyLoc),
+                       builder.getBool(false, openCurlyLoc)});
+
   if (auto *ret = cast_or_null<ReturnStmt>(s.getReturnStmt())) {
     // Since we already emitted the return value above, so we shouldn't
     // emit it again here.
diff --git a/clang/test/CIR/CodeGenCoroutines/coro-task.cpp b/clang/test/CIR/CodeGenCoroutines/coro-task.cpp
index 9853eb5c9fd4be..7879ea6655e633 100644
--- a/clang/test/CIR/CodeGenCoroutines/coro-task.cpp
+++ b/clang/test/CIR/CodeGenCoroutines/coro-task.cpp
@@ -196,7 +196,7 @@ VoidTask silly_task() {
 // CIR:     %[[NullPtr2:.*]] = cir.const #cir.ptr<null>
 // CIR:     %[[Cond:.*]] = cir.cmp ne %[[FreeMem]], %[[NullPtr2]]
 // CIR:     cir.if %[[Cond]] {
-// CIR:       %[[Size:.*]] = cir.call @__builtin_coro_size()
+// CIR:       %[[Size:.*]] = cir.coro.intrinsic.size()
 // CIR:       cir.call @_ZdlPvm(%[[FreeMem]], %[[Size]])
 // CIR:     }
 // CIR:     cir.yield

>From a4a17e831947ffb9757c6c3846f12a300991f2e0 Mon Sep 17 00:00:00 2001
From: Andres Salamanca <andrealebarbaritos at gmail.com>
Date: Tue, 11 Aug 2026 20:07:37 -0500
Subject: [PATCH 05/12] delete ret point

---
 clang/include/clang/CIR/Dialect/IR/CIROps.td | 58 +++++++-------
 clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp    | 34 --------
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp      | 82 +++++++-------------
 3 files changed, 53 insertions(+), 121 deletions(-)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 9916eb373afe74..eb91f539f772bb 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -1014,7 +1014,7 @@ def CIR_StoreOp : CIR_Op<"store", [
 
 defvar CIR_ReturnableScopes = [
   "FuncOp", "ScopeOp", "IfOp", "SwitchOp", "CaseOp", "CleanupScopeOp",
-  "DoWhileOp", "WhileOp", "ForOp", "TryOp", "CoroRetPointOp"
+  "DoWhileOp", "WhileOp", "ForOp", "TryOp"
 ];
 
 def CIR_ReturnOp : CIR_Op<"return", [
@@ -1164,7 +1164,7 @@ def CIR_ConditionOp : CIR_Op<"condition", [
 defvar CIR_YieldableScopes = [
   "ArrayCtor", "ArrayDtor", "AwaitOp", "CaseOp", "CleanupScopeOp", "CoroBodyOp",
   "DoWhileOp", "ForOp", "GlobalOp", "IfOp", "LocalInitOp", "ScopeOp", "SwitchOp",
-  "TernaryOp", "TryOp", "WhileOp", "CoroRetPointOp"
+  "TernaryOp", "TryOp", "WhileOp"
 ];
 
 def CIR_YieldOp : CIR_Op<"yield", [
@@ -5127,7 +5127,7 @@ def CIR_CoroDoneOp : CIR_CoroIntrinsicOp<"done", (ins CIR_VoidPtrType:$handle),
 //===----------------------------------------------------------------------===//
 
 def CIR_CoroSuspendPoint : CIR_Op<"coro.suspend_point", [
-  Pure, Terminator, HasParent<"AwaitOp">, HasAncestor<"CoroRetPointOp">
+  Pure, Terminator, HasParent<"AwaitOp">
 ]> {
   let summary = "Marks the point where a coroutine actually suspends";
   let description = [{
@@ -5166,56 +5166,50 @@ def CIR_CoroSuspendPoint : CIR_Op<"coro.suspend_point", [
 }
 
 //===----------------------------------------------------------------------===//
-// CoroRetPoint
+// CoroutineOp
 //===----------------------------------------------------------------------===//
 
-def CIR_CoroRetPointOp : CIR_Op<"coro.ret_point", [
+def CIR_CoroutineOp : CIR_Op<"coroutine", [
   DeclareOpInterfaceMethods<RegionBranchOpInterface, ["getSuccessorInputs"]>,
   NoRegionArguments
 ]> {
 
-  let summary = "Marks the coroutine's structured suspend/exit region";
+  let summary = "";
   let description = [{
-    Pairs a coroutine's body its normal execution, including any
-    `cir.await`s and cleanups with a single, shared exit destination
-    (`retRegion`) that every suspend point in the body jumps to.
 
-    Every `cir.coro.suspend_point` inside `bodyRegion` is guaranteed to have
-    `cir.coro.ret_point` as an ancestor, so all suspend paths converge on
-    the same `retRegion` regardless of where in the body they occur. This
-    gives the coroutine a single, well-defined place to run final teardown
-    (e.g. `__builtin_coro_end`) and return control to the caller.
-
-    Example:
-    ```mlir
-    cir.coro.ret_point {
-      // body: awaits, cleanups, etc.
-    }, ret : {
-      // shared jump target for any suspend point in the body
-      cir.coro.intrinsic.end(...)
-      cir.return
-    }
-    ```
   }];
 
-  let regions = (region AnyRegion:$bodyRegion,
-                        AnyRegion:$retRegion);
-   let skipDefaultBuilders = 1;
+// TODO: ver como funciona con cleanup
+  let regions = (region AnyRegion:$initialSuspend,
+                        AnyRegion:$body,
+                        AnyRegion:$finalSuspend,
+                        AnyRegion:$destroy,
+                        AnyRegion:$exit);
+
+  let skipDefaultBuilders = 1;
 
   let builders = [
-    OpBuilder<(ins "BuilderCallbackRef":$bodyBuilder,
-      "BuilderCallbackRef":$retBuilder)>
+    OpBuilder<(ins "BuilderCallbackRef":$initialSuspendBuilder,
+      "BuilderCallbackRef":$bodyBuilder,
+      "BuilderCallbackRef":$finalSuspendBuilder,
+      "BuilderCallbackRef":$destroyBuilder,
+      "BuilderCallbackRef":$exitBuilder)>
   ];
 
   let assemblyFormat = [{
-    $bodyRegion `,`
-    `ret` `:` $retRegion
+    `initialSuspend` `:` $initialSuspend `,`
+    `body` `:` $body `,`
+    `finalSuspend` `:` $finalSuspend `,`
+    `destroy` `:` $destroy `,`
+    `exit` `:` $exit
     attr-dict
   }];
+
   let hasLLVMLowering = false;
   let hasVerifier = 1;
 }
 
+
 //===----------------------------------------------------------------------===//
 // CopyOp
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
index 7ea854b2c1afea..976e3712ace6f8 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
@@ -422,25 +422,10 @@ CIRGenFunction::emitCoroutineBody(const CoroutineBodyStmt &s) {
           curCoro.data->coroId.getResult(),
           cir::LoadOp::create(builder, openCurlyLoc, allocaTy, storeAddr)});
 
-  cir::CoroRetPointOp coroRet = nullptr;
-  mlir::OpBuilder::InsertPoint coroRetRegion;
   {
     assert(!cir::MissingFeatures::generateDebugInfo());
     ParamReferenceReplacerRAII paramReplacer(localDeclMap);
     RunCleanupsScope resumeScope(*this);
-    mlir::OpBuilder::InsertPoint coroRetBody;
-    coroRet = cir::CoroRetPointOp::create(
-        builder, openCurlyLoc,
-        /*bodyBuilder=*/
-        [&](mlir::OpBuilder &b, mlir::Location) {
-          coroRetBody = b.saveInsertionPoint();
-        },
-        /*retBuilder=*/
-        [&](mlir::OpBuilder &b, mlir::Location) {
-          coroRetRegion = b.saveInsertionPoint();
-        });
-    mlir::OpBuilder::InsertionGuard guard(builder);
-    builder.restoreInsertionPoint(coroRetBody);
     ehStack.pushCleanup<CallCoroDelete>(NormalAndEHCleanup, s.getDeallocate());
     // Create mapping between parameters and copy-params for coroutine
     // function.
@@ -572,21 +557,6 @@ CIRGenFunction::emitCoroutineBody(const CoroutineBodyStmt &s) {
   auto tkNone = cir::TokenNoneOp::create(builder, openCurlyLoc);
   cir::CoroEndOp::create(builder, openCurlyLoc, nullHandler, noUnwind, tkNone);
 
-  mlir::Block &coroRetBodyBlock = coroRet.getBodyRegion().back();
-  {
-    mlir::OpBuilder::InsertionGuard guard(builder);
-    builder.setInsertionPointToEnd(&coroRetBodyBlock);
-    cir::YieldOp::create(builder, openCurlyLoc);
-  }
-
-  mlir::OpBuilder::InsertionGuard guard(builder);
-  builder.restoreInsertionPoint(coroRetRegion);
-
-  cir::CoroEndOp::create(
-      cgm.getBuilder(), openCurlyLoc,
-      mlir::ValueRange{builder.getNullPtr(builder.getVoidPtrTy(), openCurlyLoc),
-                       builder.getBool(false, openCurlyLoc)});
-
   if (auto *ret = cast_or_null<ReturnStmt>(s.getReturnStmt())) {
     // Since we already emitted the return value above, so we shouldn't
     // emit it again here.
@@ -594,10 +564,6 @@ CIRGenFunction::emitCoroutineBody(const CoroutineBodyStmt &s) {
     ret->setRetValue(nullptr);
     if (emitStmt(ret, /*useCurrentScope=*/true).failed())
       return mlir::failure();
-    mlir::Block *block = builder.getInsertionBlock();
-    // emitReturnStmt() always creates a new insertion block after emitting the
-    // return. That block is unreachable in this case, so erase it.
-    block->erase();
     // Set the return value back. The code generator, as the AST **Consumer**,
     // shouldn't change the AST.
     ret->setRetValue(previousRetValue);
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 7ddc9636eab1bd..33c32a626e47c8 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -3135,7 +3135,6 @@ mlir::LogicalResult cir::FuncOp::verify() {
   if (!isDeclaration() && getCoroutine()) {
     bool foundAwait = false;
     int coroBodyCount = 0;
-    int coroRetPointCount = 0;
     this->walk([&](Operation *op) {
       if (auto await = dyn_cast<AwaitOp>(op)) {
         foundAwait = true;
@@ -3144,11 +3143,6 @@ mlir::LogicalResult cir::FuncOp::verify() {
         if (coroBodyCount > 1) {
           return mlir::WalkResult::interrupt();
         }
-      } else if (isa<CoroRetPointOp>(op)) {
-        coroRetPointCount++;
-        if (coroRetPointCount > 1) {
-          return mlir::WalkResult::interrupt();
-        }
       }
       return mlir::WalkResult::advance();
     });
@@ -3158,9 +3152,6 @@ mlir::LogicalResult cir::FuncOp::verify() {
     if (coroBodyCount != 1)
       return emitOpError()
              << "coroutine function must have exactly one cir.body op";
-    if (coroRetPointCount != 1)
-      return emitOpError() << "coroutine function must have exactly one "
-                              "cir.coro.ret_point op";
   }
 
   llvm::SmallSet<llvm::StringRef, 16> labels;
@@ -3594,68 +3585,49 @@ void cir::CoroBodyOp::build(OpBuilder &builder, OperationState &result,
   bodyBuilder(builder, result.location);
 }
 
+
 //===----------------------------------------------------------------------===//
-// CoroRetPoint
+// CoroutineOp
 //===----------------------------------------------------------------------===//
 
-void cir::CoroRetPointOp::build(OpBuilder &builder, OperationState &result,
+void cir::CoroutineOp::build(OpBuilder &builder, OperationState &result,
+                                BuilderCallbackRef initialSuspendBuilder,
                                 BuilderCallbackRef bodyBuilder,
-                                BuilderCallbackRef retBuilder) {
-  {
-    OpBuilder::InsertionGuard guard(builder);
-    Region *bodyRegion = result.addRegion();
-    builder.createBlock(bodyRegion);
-    bodyBuilder(builder, result.location);
-  }
-
-  {
-    OpBuilder::InsertionGuard guard(builder);
-    Region *retRegion = result.addRegion();
-    builder.createBlock(retRegion);
-    retBuilder(builder, result.location);
-  }
-}
-
-void cir::CoroRetPointOp::getSuccessorRegions(
+                                BuilderCallbackRef finalSuspendBuilder,
+                                BuilderCallbackRef destroyBuilder,
+                                BuilderCallbackRef exitBuilder) {
+  //{
+  //  OpBuilder::InsertionGuard guard(builder);
+  //  Region *bodyRegion = result.addRegion();
+  //  builder.createBlock(bodyRegion);
+  //  bodyBuilder(builder, result.location);
+  //}
+
+  //{
+  //  OpBuilder::InsertionGuard guard(builder);
+  //  Region *retRegion = result.addRegion();
+  //  builder.createBlock(retRegion);
+  //  retBuilder(builder, result.location);
+  //}
+}
+
+void cir::CoroutineOp::getSuccessorRegions(
     mlir::RegionBranchPoint point, SmallVectorImpl<RegionSuccessor> &regions) {
   if (!point.isParent()) {
     regions.emplace_back(getOperation());
     return;
   }
 
-  regions.push_back(RegionSuccessor(&getBodyRegion()));
-  regions.push_back(RegionSuccessor(&getRetRegion()));
+  //regions.push_back(RegionSuccessor(&getBodyRegion()));
+  //regions.push_back(RegionSuccessor(&getRetRegion()));
 }
 
 mlir::ValueRange
-cir::CoroRetPointOp::getSuccessorInputs(RegionSuccessor successor) {
+cir::CoroutineOp::getSuccessorInputs(RegionSuccessor successor) {
   return ValueRange();
 }
 
-LogicalResult cir::CoroRetPointOp::verify() {
-
-  // TODO: Should we verify that the return region contains a
-  // `cir.coro.intrinsic.end`? Coroutine semantics require `coro.end` to mark
-  // the end of access to the coroutine frame before the coroutine completes.
-  bool hasReturn = false;
-  this->getBodyRegion().walk<mlir::WalkOrder::PreOrder>(
-      [&](mlir::Operation *op) {
-        if (mlir::isa<cir::ReturnOp>(op)) {
-          hasReturn = true;
-          return WalkResult::interrupt();
-        }
-        return WalkResult::advance();
-      });
-
-  if (hasReturn)
-    return emitError()
-           << "body region must not contain 'cir.return' operations";
-
-  if (!mlir::isa<cir::YieldOp>(this->getBodyRegion().back().back()))
-    return emitError() << "body region must terminate with 'cir.yield'";
-
-  if (!mlir::isa<cir::ReturnOp>(this->getRetRegion().back().back()))
-    return emitError() << "return region must terminate with 'cir.return'";
+LogicalResult cir::CoroutineOp::verify() {
 
   return mlir::success();
 }

>From a9971e23e244b66f9f29f0fb4e4650071be6c5b1 Mon Sep 17 00:00:00 2001
From: Andres Salamanca <andrealebarbaritos at gmail.com>
Date: Thu, 20 Aug 2026 21:54:51 -0500
Subject: [PATCH 06/12] [CIR] Add cir.coroutine and remove cir.coroBody

---
 clang/include/clang/CIR/Dialect/IR/CIROps.td  | 169 +++--
 clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp     | 303 ++++----
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp       | 242 ++++--
 .../CIR/CodeGenCoroutines/coro-exceptions.cpp |  10 +-
 .../test/CIR/CodeGenCoroutines/coro-task.cpp  | 711 +++++++++---------
 clang/test/CIR/IR/await.cir                   |  49 +-
 clang/test/CIR/IR/co-return.cir               |  36 +-
 clang/test/CIR/IR/coro-body.cir               |  24 -
 clang/test/CIR/IR/coro-ret-point.cir          |  23 -
 clang/test/CIR/IR/coroutine.cir               |  36 +
 clang/test/CIR/IR/func.cir                    |  31 +-
 clang/test/CIR/IR/invalid-await.cir           |  49 +-
 clang/test/CIR/IR/invalid-co-return.cir       |  28 +-
 clang/test/CIR/IR/invalid-coro-body.cir       |  21 -
 clang/test/CIR/IR/invalid-coro-ret-point.cir  |  55 --
 clang/test/CIR/IR/invalid-coro-suspend.cir    |  23 -
 clang/test/CIR/IR/invalid-coroutine.cir       | 289 +++++++
 17 files changed, 1244 insertions(+), 855 deletions(-)
 delete mode 100644 clang/test/CIR/IR/coro-body.cir
 delete mode 100644 clang/test/CIR/IR/coro-ret-point.cir
 create mode 100644 clang/test/CIR/IR/coroutine.cir
 delete mode 100644 clang/test/CIR/IR/invalid-coro-body.cir
 delete mode 100644 clang/test/CIR/IR/invalid-coro-ret-point.cir
 delete mode 100644 clang/test/CIR/IR/invalid-coro-suspend.cir
 create mode 100644 clang/test/CIR/IR/invalid-coroutine.cir

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index eb91f539f772bb..fccca08ee06ef0 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -1014,7 +1014,7 @@ def CIR_StoreOp : CIR_Op<"store", [
 
 defvar CIR_ReturnableScopes = [
   "FuncOp", "ScopeOp", "IfOp", "SwitchOp", "CaseOp", "CleanupScopeOp",
-  "DoWhileOp", "WhileOp", "ForOp", "TryOp"
+  "CoroutineOp", "DoWhileOp", "WhileOp", "ForOp", "TryOp"
 ];
 
 def CIR_ReturnOp : CIR_Op<"return", [
@@ -1162,7 +1162,7 @@ def CIR_ConditionOp : CIR_Op<"condition", [
 //===----------------------------------------------------------------------===//
 
 defvar CIR_YieldableScopes = [
-  "ArrayCtor", "ArrayDtor", "AwaitOp", "CaseOp", "CleanupScopeOp", "CoroBodyOp",
+  "ArrayCtor", "ArrayDtor", "AwaitOp", "CaseOp", "CleanupScopeOp", "CoroutineOp",
   "DoWhileOp", "ForOp", "GlobalOp", "IfOp", "LocalInitOp", "ScopeOp", "SwitchOp",
   "TernaryOp", "TryOp", "WhileOp"
 ];
@@ -4779,6 +4779,85 @@ def CIR_TryCallOp : CIR_CallOpBase<"try_call",[
   ];
 }
 
+//===----------------------------------------------------------------------===//
+// CoroutineOp
+//===----------------------------------------------------------------------===//
+
+def CIR_CoroutineOp : CIR_Op<"coroutine", [
+  DeclareOpInterfaceMethods<RegionBranchOpInterface, ["getSuccessorInputs"]>,
+  NoRegionArguments, RecursivelySpeculatable, AutomaticAllocationScope
+]> {
+
+  let summary = "Groups a coroutine's structured suspend/resume/destroy control flow";
+  let description = [{
+    `cir.coroutine` is an op consisting of seven regions: `initial_suspend`,
+    `body`, `final_suspend`, grp (TODO),`unwind` (TODO), `destroy`, and`exit`.
+
+    - `initial_suspend`: the coroutine ramp/setup region. Its `cir.await` marked
+      `init` is the ramp boundary: the coroutine frame and its associated state
+      are established before this point, and crossing the await transfers
+      execution to `body`.
+
+    - `body`: the user-written coroutine body, arbitrary CFG, zero or more
+      `cir.await`s at any nesting depth, terminated by `cir.co_return` or a
+      plain `cir.yield` if control simply falls off the end. This region is
+      a structural boundary separating the coroutine's setup and teardown
+      logic (initial suspend, final suspend, destroy) from the
+      user-provided statements of the original function body, including
+      `co_await` and `co_return` expressions. In particular,
+      `cir.co_return` operations inside this region mark coroutine exit
+      points and introduce structured control flow that transfers
+      execution to `final_suspend`.
+    - `final_suspend`: exactly one `cir.await`, marked `final`. Nothing
+      else. Corresponds to `co_await promise.final_suspend()`. Resuming
+      past this point is undefined behavior in valid programs, so its
+      `resume` edge is expected to be dead in practice, but is kept for
+      structural symmetry with the other two suspend regions.
+    - `gro`: TODO.
+    - `unwind`: TODO.
+    - `destroy`: destroys the coroutine frame (`coro.free` + `delete`).
+      Reached by an explicit `destroy()` call on a suspended handle, by
+      normal completion through `final_suspend`, or by an exception
+      escaping the coroutine. Falls through to `exit` except when reached
+      due to an escaping exception, where it continues propagating that
+      exception instead.
+    - `exit`: the shared return-to-caller path. Reached directly from any
+      suspend point that isn't being destroyed (the coroutine is just
+      pausing), and from `destroy` when it was entered via ordinary
+      dispatch. Holds `coro.end` + the actual `cir.return`.
+
+  }];
+
+  // TODO(cir): Add GRO and EH
+  let regions = (region AnyRegion:$initialSuspend,
+                        AnyRegion:$body,
+                        AnyRegion:$finalSuspend,
+                        AnyRegion:$destroy,
+                        AnyRegion:$exit);
+
+  let skipDefaultBuilders = 1;
+
+  let builders = [
+    OpBuilder<(ins "BuilderCallbackRef":$initialSuspendBuilder,
+      "BuilderCallbackRef":$bodyBuilder,
+      "BuilderCallbackRef":$finalSuspendBuilder,
+      "BuilderCallbackRef":$destroyBuilder,
+      "BuilderCallbackRef":$exitBuilder)>
+  ];
+
+  let assemblyFormat = [{
+    `initialSuspend` `:` $initialSuspend `,`
+    `body` `:` $body `,`
+    `finalSuspend` `:` $finalSuspend `,`
+    `destroy` `:` $destroy `,`
+    `exit` `:` $exit
+    attr-dict
+  }];
+
+  let hasLLVMLowering = false;
+  let hasVerifier = 1;
+}
+
 //===----------------------------------------------------------------------===//
 // AwaitOp
 //===----------------------------------------------------------------------===//
@@ -4793,7 +4872,7 @@ def CIR_AwaitKind : CIR_I32Enum<"AwaitKind", "await kind", [
 def CIR_AwaitKindAttr : CIR_EnumAttr<CIR_AwaitKind, "await">;
 
 def CIR_AwaitOp : CIR_RegionBranchOpBase<"await", [
-  RecursivelySpeculatable, NoRegionArguments
+  RecursivelySpeculatable, NoRegionArguments, HasAncestor<"CoroutineOp">
 ]> {
   let summary = "Wraps C++ co_await implicit logic";
   let description = [{
@@ -4874,6 +4953,7 @@ def CIR_AwaitOp : CIR_RegionBranchOpBase<"await", [
   let hasVerifier = 1;
   let hasLLVMLowering = false;
 }
+<<<<<<< HEAD
 //===----------------------------------------------------------------------===//
 // CoroBody
 //===----------------------------------------------------------------------===//
@@ -4912,13 +4992,15 @@ def CIR_CoroBodyOp : CIR_RegionBranchOpBase<"coro.body", [
   let hasLLVMLowering = false;
   let hasVerifier = 1;
 }
+=======
+>>>>>>> 56098cff2da3 ([CIR] Add cir.coroutine and remove cir.coroBody)
 
 //===----------------------------------------------------------------------===//
 // CoReturnOp
 //===----------------------------------------------------------------------===//
 
 def CIR_CoReturnOp : CIR_Op<"co_return", [
-  ReturnLike, Pure, Terminator, HasAncestor<"CoroBodyOp">
+  ReturnLike, Pure, Terminator, HasAncestor<"CoroutineOp">
 ]> {
   let summary = "Coroutine return operation";
   let description = [{
@@ -4932,6 +5014,7 @@ def CIR_CoReturnOp : CIR_Op<"co_return", [
     attr-dict
   }];
 
+  let hasVerifier = 1;
   let hasLLVMLowering = false;
 }
 
@@ -5131,85 +5214,29 @@ def CIR_CoroSuspendPoint : CIR_Op<"coro.suspend_point", [
 ]> {
   let summary = "Marks the point where a coroutine actually suspends";
   let description = [{
-    Terminates the `suspend` region of a `cir.await` op, marking the exact
-    point where control returns to the caller/resumer if the coroutine
-    decides to suspend.
+    Terminates the `suspend` region of a `cir.await`. It has no explicit
+    successors; the destination is determined by `CoroutineOp`
+    according to how the suspended coroutine is subsequently handled:
 
-    This op must appear inside the `suspend` region of a `cir.await`, and
-    that `cir.await` must in turn be nested within a `cir.coro.ret_point`.
-    During lowering to FlattenCFG, `cir.coro.suspend_point` becomes the
-    branch target that routes control to one of three destinations
-    depending on how the coroutine resumes: the resume block (normal
-    continuation), the cleanup/destroy block, or the ret/exit block that
-    hands control back to the caller.
+    - resume: continues execution through the `cir.await`'s `resume` region
+      when the coroutine handle is resumed.
 
-    Example:
-    ```mlir
-    cir.await(user, ready : {
-      ...
-      cir.condition(%ready)
-    }, suspend : {
-      ...
-      cir.coro.suspend_point
-    }, resume : {
-      ...
-      cir.yield
-    },)
-    ```
-  }];
+    - exit: transfers directly to the enclosing `cir.coroutine`'s `exit`
+      region when the coroutine remains suspended. The coroutine frame remains
+      alive and no cleanups are performed.
 
-  let assemblyFormat = [{
-    attr-dict
+    - destroy: transfers through the enclosing `cir.cleanup.scope`s to the
+      `cir.coroutine`'s `destroy` region when the coroutine handle is
+      destroyed.
   }];
 
-  let hasLLVMLowering = false;
-}
-
-//===----------------------------------------------------------------------===//
-// CoroutineOp
-//===----------------------------------------------------------------------===//
-
-def CIR_CoroutineOp : CIR_Op<"coroutine", [
-  DeclareOpInterfaceMethods<RegionBranchOpInterface, ["getSuccessorInputs"]>,
-  NoRegionArguments
-]> {
-
-  let summary = "";
-  let description = [{
-
-  }];
-
-// TODO: ver como funciona con cleanup
-  let regions = (region AnyRegion:$initialSuspend,
-                        AnyRegion:$body,
-                        AnyRegion:$finalSuspend,
-                        AnyRegion:$destroy,
-                        AnyRegion:$exit);
-
-  let skipDefaultBuilders = 1;
-
-  let builders = [
-    OpBuilder<(ins "BuilderCallbackRef":$initialSuspendBuilder,
-      "BuilderCallbackRef":$bodyBuilder,
-      "BuilderCallbackRef":$finalSuspendBuilder,
-      "BuilderCallbackRef":$destroyBuilder,
-      "BuilderCallbackRef":$exitBuilder)>
-  ];
-
   let assemblyFormat = [{
-    `initialSuspend` `:` $initialSuspend `,`
-    `body` `:` $body `,`
-    `finalSuspend` `:` $finalSuspend `,`
-    `destroy` `:` $destroy `,`
-    `exit` `:` $exit
     attr-dict
   }];
 
   let hasLLVMLowering = false;
-  let hasVerifier = 1;
 }
 
-
 //===----------------------------------------------------------------------===//
 // CopyOp
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
index 976e3712ace6f8..af6ee325706249 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
@@ -15,6 +15,7 @@
 #include "clang/AST/StmtCXX.h"
 #include "clang/AST/StmtVisitor.h"
 #include "clang/Basic/TargetInfo.h"
+#include "clang/CIR/Dialect/IR/CIRDialect.h"
 #include "clang/CIR/MissingFeatures.h"
 
 using namespace clang;
@@ -119,63 +120,6 @@ struct ParamReferenceReplacerRAII {
 };
 } // namespace
 
-namespace {
-// Make sure to call coro.delete on scope exit.
-struct CallCoroDelete final : public EHScopeStack::Cleanup {
-  Stmt *deallocate;
-
-  // Emit "if (coro.free(CoroId, CoroBegin)) Deallocate;"
-
-  // Note: That deallocation will be emitted twice: once for a normal exit and
-  // once for exceptional exit. This usage is safe because Deallocate does not
-  // contain any declarations. The SubStmtBuilder::makeNewAndDeleteExpr()
-  // builds a single call to a deallocation function which is safe to emit
-  // multiple times.
-  void emit(CIRGenFunction &cgf, Flags) override {
-    // Remember the current point, as we are going to emit deallocation code
-    // first to get to coro.free instruction that is an argument to a delete
-    // call.
-
-    if (cgf.emitStmt(deallocate, /*useCurrentScope=*/true).failed()) {
-      cgf.cgm.error(deallocate->getBeginLoc(),
-                    "failed to emit coroutine deallocation expression");
-      return;
-    }
-
-    CIRGenBuilderTy &builder = cgf.getBuilder();
-    cir::CoroFreeOp coroFree = cgf.curCoro.data->lastCoroFree;
-
-    if (!coroFree) {
-      cgf.cgm.error(deallocate->getBeginLoc(),
-                    "Deallocation expression does not refer to coro.free");
-      return;
-    }
-
-    builder.setInsertionPointAfter(coroFree);
-    mlir::Value isPtrNotNull = builder.createPtrIsNotNull(coroFree.getResult());
-
-    llvm::SmallVector<mlir::Operation *> opsToMove;
-    mlir::Block *block = builder.getInsertionBlock();
-    mlir::Block::iterator it(isPtrNotNull.getDefiningOp());
-
-    for (++it; it != block->end(); ++it)
-      opsToMove.push_back(&*it);
-
-    auto ifOp =
-        cir::IfOp::create(builder, cgf.getLoc(deallocate->getSourceRange()),
-                          isPtrNotNull, /*withElseRegion*/ false,
-                          [&](mlir::OpBuilder &builder, mlir::Location loc) {
-                            cir::YieldOp::create(builder, loc);
-                          });
-
-    mlir::Operation *yieldOp = ifOp.getThenRegion().back().getTerminator();
-    for (auto *op : opsToMove)
-      op->moveBefore(yieldOp);
-  }
-  explicit CallCoroDelete(Stmt *deallocStmt) : deallocate(deallocStmt) {}
-};
-} // namespace
-
 RValue CIRGenFunction::emitCoroutineFrame() {
   if (curCoro.data && curCoro.data->coroBegin) {
     return RValue::get(curCoro.data->coroBegin);
@@ -425,10 +369,9 @@ CIRGenFunction::emitCoroutineBody(const CoroutineBodyStmt &s) {
   {
     assert(!cir::MissingFeatures::generateDebugInfo());
     ParamReferenceReplacerRAII paramReplacer(localDeclMap);
-    RunCleanupsScope resumeScope(*this);
-    ehStack.pushCleanup<CallCoroDelete>(NormalAndEHCleanup, s.getDeallocate());
-    // Create mapping between parameters and copy-params for coroutine
-    // function.
+
+    //  Create mapping between parameters and copy-params for coroutine
+    //  function.
     llvm::ArrayRef<const Stmt *> paramMoves = s.getParamMoves();
     assert((paramMoves.size() == 0 || (paramMoves.size() == fnArgs.size())) &&
            "ParamMoves and FnArgs should be the same size for coroutine "
@@ -446,44 +389,42 @@ CIRGenFunction::emitCoroutineBody(const CoroutineBodyStmt &s) {
       paramReplacer.addCopy(cast<DeclStmt>(pm));
     }
 
-    if (emitStmt(s.getPromiseDeclStmt(), /*useCurrentScope=*/true).failed())
-      return mlir::failure();
-    // returnValue should be valid as long as the coroutine's return type
-    // is not void. The assertion could help us to reduce the check later.
-    assert(returnValue.isValid() == (bool)s.getReturnStmt());
-    // Now we have the promise, initialize the GRO.
-    // We need to emit `get_return_object` first. According to:
-    // [dcl.fct.def.coroutine]p7
-    // The call to get_return_­object is sequenced before the call to
-    // initial_suspend and is invoked at most once.
-    //
-    // So we couldn't emit return value when we emit return statment,
-    // otherwise the call to get_return_object wouldn't be in front
-    // of initial_suspend.
-    if (returnValue.isValid())
-      emitAnyExprToMem(s.getReturnValue(), returnValue,
-                       s.getReturnValue()->getType().getQualifiers(),
-                       /*isInit*/ true);
-
-    assert(!cir::MissingFeatures::ehCleanupScope());
-
-    curCoro.data->currentAwaitKind = cir::AwaitKind::Init;
-    curCoro.data->exceptionHandler = s.getExceptionHandler();
-
-    if (emitStmt(s.getInitSuspendStmt(), /*useCurrentScope=*/true).failed())
-      return mlir::failure();
-
-    curCoro.data->currentAwaitKind = cir::AwaitKind::User;
-
-    mlir::OpBuilder::InsertPoint userBody;
-    auto coroBodyOp =
-        cir::CoroBodyOp::create(builder, openCurlyLoc, /*scopeBuilder=*/
-                                [&](mlir::OpBuilder &b, mlir::Location loc) {
-                                  userBody = b.saveInsertionPoint();
-                                });
-    {
-      mlir::OpBuilder::InsertionGuard guard(builder);
-      builder.restoreInsertionPoint(userBody);
+    // Builds `initial_suspend`.
+    auto initialSuspendBuilder = [&]() -> mlir::LogicalResult {
+      if (emitStmt(s.getPromiseDeclStmt(), /*useCurrentScope=*/true).failed())
+        return mlir::failure();
+      // returnValue should be valid as long as the coroutine's return type
+      // is not void. The assertion could help us to reduce the check later.
+      assert(returnValue.isValid() == (bool)s.getReturnStmt());
+      // Now we have the promise, initialize the GRO.
+      // We need to emit `get_return_object` first. According to:
+      // [dcl.fct.def.coroutine]p7
+      // The call to get_return_­object is sequenced before the call to
+      // initial_suspend and is invoked at most once.
+      //
+      // So we couldn't emit return value when we emit return statment,
+      // otherwise the call to get_return_object wouldn't be in front
+      // of initial_suspend.
+      if (returnValue.isValid())
+        emitAnyExprToMem(s.getReturnValue(), returnValue,
+                         s.getReturnValue()->getType().getQualifiers(),
+                         /*isInit*/ true);
+
+      curCoro.data->currentAwaitKind = cir::AwaitKind::Init;
+      curCoro.data->exceptionHandler = s.getExceptionHandler();
+
+      if (emitStmt(s.getInitSuspendStmt(), /*useCurrentScope=*/true).failed())
+        return mlir::failure();
+
+      cir::YieldOp::create(builder, openCurlyLoc);
+      return mlir::success();
+    };
+
+    // Builds `body`: the user-written coroutine code, including its
+    // implicit `try { ... } catch (...) { unhandled_exception(); }`.
+    auto bodyBuilder = [&]() -> mlir::LogicalResult {
+      curCoro.data->currentAwaitKind = cir::AwaitKind::User;
+
       if (curCoro.data->exceptionHandler) {
         // This bit of code is supposed to do:
         //
@@ -521,54 +462,142 @@ CIRGenFunction::emitCoroutineBody(const CoroutineBodyStmt &s) {
                      .failed()) {
         return mlir::failure();
       }
-    }
 
-    mlir::Block &coroBodyBlock = coroBodyOp.getBody().back();
-    if (!coroBodyBlock.mightHaveTerminator()) {
-      mlir::OpBuilder::InsertionGuard guard(builder);
-      builder.setInsertionPointToEnd(&coroBodyBlock);
+      return mlir::success();
+    };
+
+    // Builds `final_suspend`: only emitted at all if the body can actually
+    // reach it (an explicit co_return, or falling off the end).
+    auto finalSuspendBuilder = [&]() -> mlir::LogicalResult {
+      // Note that LLVM checks CanFallthrough by looking into the availability
+      // of the insert block which is kinda brittle and unintuitive, seems to be
+      // related with how landing pads are handled.
+      //
+      // CIRGen handles this by checking pre-existing co_returns in the current
+      // scope instead.
+      //
+      // From LLVM IR Gen: const bool CanFallthrough = Builder.GetInsertBlock();
+      const bool canFallthrough = curLexScope->hasCoreturn();
+      const bool hasCoreturns = curCoro.data->coreturnCount > 0;
+      if (canFallthrough || hasCoreturns) {
+        curCoro.data->currentAwaitKind = cir::AwaitKind::Final;
+        if (emitStmt(s.getFinalSuspendStmt(), /*useCurrentScope=*/true)
+                .failed())
+          return mlir::failure();
+      }
       cir::YieldOp::create(builder, openCurlyLoc);
-    }
+      return mlir::success();
+    };
+
+    // Emit "if (coro.free(CoroId, CoroBegin)) Deallocate;"
+    auto destroyBuilder = [&]() -> mlir::LogicalResult {
+      Stmt *deallocate = s.getDeallocate();
+      if (emitStmt(deallocate, /*useCurrentScope=*/true).failed()) {
+        cgm.error(deallocate->getBeginLoc(),
+                  "failed to emit coroutine deallocation expression");
+        return mlir::failure();
+      }
+
+      cir::CoroFreeOp coroFree = curCoro.data->lastCoroFree;
 
-    // Note that LLVM checks CanFallthrough by looking into the availability
-    // of the insert block which is kinda brittle and unintuitive, seems to be
-    // related with how landing pads are handled.
-    //
-    // CIRGen handles this by checking pre-existing co_returns in the current
-    // scope instead.
-    //
-    // From LLVM IR Gen: const bool CanFallthrough = Builder.GetInsertBlock();
-    const bool canFallthrough = curLexScope->hasCoreturn();
-    const bool hasCoreturns = curCoro.data->coreturnCount > 0;
-    if (canFallthrough || hasCoreturns) {
-      curCoro.data->currentAwaitKind = cir::AwaitKind::Final;
+      if (!coroFree) {
+        cgm.error(deallocate->getBeginLoc(),
+                  "Deallocation expression does not refer to coro.free");
+        return mlir::failure();
+      }
       {
         mlir::OpBuilder::InsertionGuard guard(builder);
-        if (emitStmt(s.getFinalSuspendStmt(), /*useCurrentScope=*/true)
-                .failed())
+        builder.setInsertionPointAfter(coroFree);
+        mlir::Value isPtrNotNull =
+            builder.createPtrIsNotNull(coroFree.getResult());
+
+        llvm::SmallVector<mlir::Operation *> opsToMove;
+        mlir::Block *block = builder.getInsertionBlock();
+        mlir::Block::iterator it(isPtrNotNull.getDefiningOp());
+
+        for (++it; it != block->end(); ++it)
+          opsToMove.push_back(&*it);
+
+        auto ifOp = cir::IfOp::create(
+            builder, getLoc(deallocate->getSourceRange()), isPtrNotNull,
+            /*withElseRegion*/ false,
+            [&](mlir::OpBuilder &builder, mlir::Location loc) {
+              cir::YieldOp::create(builder, loc);
+            });
+
+        mlir::Operation *yieldOp = ifOp.getThenRegion().back().getTerminator();
+        for (auto *op : opsToMove)
+          op->moveBefore(yieldOp);
+      }
+
+      cir::YieldOp::create(builder, openCurlyLoc);
+      return mlir::success();
+    };
+
+    // Builds `exit`: coro.end(/*unwind*/ false) followed by the actual return
+    // to the caller.
+    auto exitBuilder = [&]() {
+      cir::ConstantOp nullHandler =
+          builder.getNullPtr(builder.getVoidPtrTy(), openCurlyLoc);
+      cir::ConstantOp noUnwind = builder.getBool(false, openCurlyLoc);
+      auto tkNone = cir::TokenNoneOp::create(builder, openCurlyLoc);
+      cir::CoroEndOp::create(builder, openCurlyLoc, nullHandler, noUnwind,
+                             tkNone);
+
+      if (auto *ret = cast_or_null<ReturnStmt>(s.getReturnStmt())) {
+        // Since we already emitted the return value above, so we shouldn't
+        // emit it again here.
+        Expr *previousRetValue = ret->getRetValue();
+        ret->setRetValue(nullptr);
+        if (emitStmt(ret, /*useCurrentScope=*/true).failed())
           return mlir::failure();
+        mlir::Block *block = builder.getInsertionBlock();
+        // emitReturnStmt() always creates a new insertion block after emitting
+        // the return. That block is unreachable in this case, so erase it.
+        block->erase();
+        // Set the return value back. The code generator, as the AST
+        // **Consumer**, shouldn't change the AST.
+        ret->setRetValue(previousRetValue);
       }
+
+      return mlir::success();
+    };
+
+    cir::CoroutineOp coro = cir::CoroutineOp::create(
+        builder, openCurlyLoc,
+        /*initialSuspendBuilder=*/
+        [&](mlir::OpBuilder &b, mlir::Location loc) {
+          res = initialSuspendBuilder();
+        },
+        /*bodyBuilder=*/
+        [&](mlir::OpBuilder &b, mlir::Location loc) {
+          if (res.succeeded())
+            res = bodyBuilder();
+        },
+        /*finalSuspendBuilder=*/
+        [&](mlir::OpBuilder &b, mlir::Location loc) {
+          if (res.succeeded())
+            res = finalSuspendBuilder();
+        },
+        /*destroyBuilder=*/
+        [&](mlir::OpBuilder &b, mlir::Location loc) {
+          if (res.succeeded())
+            res = destroyBuilder();
+        },
+        /*exitBuilder=*/
+        [&](mlir::OpBuilder &b, mlir::Location loc) {
+          if (res.succeeded())
+            res = exitBuilder();
+        });
+
+    if (coro && !coro.getBody().back().mightHaveTerminator()) {
+      mlir::OpBuilder::InsertionGuard guard(builder);
+      builder.setInsertionPointAfter(&coro.getBody().back().back());
+      cir::YieldOp::create(builder, openCurlyLoc);
     }
   }
 
-  cir::ConstantOp nullHandler =
-      builder.getNullPtr(builder.getVoidPtrTy(), openCurlyLoc);
-  cir::ConstantOp noUnwind = builder.getBool(false, openCurlyLoc);
-  auto tkNone = cir::TokenNoneOp::create(builder, openCurlyLoc);
-  cir::CoroEndOp::create(builder, openCurlyLoc, nullHandler, noUnwind, tkNone);
-
-  if (auto *ret = cast_or_null<ReturnStmt>(s.getReturnStmt())) {
-    // Since we already emitted the return value above, so we shouldn't
-    // emit it again here.
-    Expr *previousRetValue = ret->getRetValue();
-    ret->setRetValue(nullptr);
-    if (emitStmt(ret, /*useCurrentScope=*/true).failed())
-      return mlir::failure();
-    // Set the return value back. The code generator, as the AST **Consumer**,
-    // shouldn't change the AST.
-    ret->setRetValue(previousRetValue);
-  }
-  return mlir::success();
+  return res;
 }
 
 static bool memberCallExpressionCanThrow(const Expr *e) {
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 33c32a626e47c8..8a24c0464d21d2 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -3133,25 +3133,19 @@ void cir::FuncOp::print(OpAsmPrinter &p) {
 mlir::LogicalResult cir::FuncOp::verify() {
 
   if (!isDeclaration() && getCoroutine()) {
-    bool foundAwait = false;
-    int coroBodyCount = 0;
+    int coroutineOpCount = 0;
     this->walk([&](Operation *op) {
-      if (auto await = dyn_cast<AwaitOp>(op)) {
-        foundAwait = true;
-      } else if (isa<CoroBodyOp>(op)) {
-        coroBodyCount++;
-        if (coroBodyCount > 1) {
+      if (isa<CoroutineOp>(op)) {
+        coroutineOpCount++;
+        if (coroutineOpCount > 1) {
           return mlir::WalkResult::interrupt();
         }
       }
       return mlir::WalkResult::advance();
     });
-    if (!foundAwait)
+    if (coroutineOpCount != 1)
       return emitOpError()
-             << "coroutine body must use at least one cir.await op";
-    if (coroBodyCount != 1)
-      return emitOpError()
-             << "coroutine function must have exactly one cir.body op";
+             << "coroutine function must have exactly one cir.coroutine op";
   }
 
   llvm::SmallSet<llvm::StringRef, 16> labels;
@@ -3555,71 +3549,124 @@ LogicalResult cir::AwaitOp::verify() {
 }
 
 //===----------------------------------------------------------------------===//
-// CoroBody
+// CoReturnOp
 //===----------------------------------------------------------------------===//
 
-void cir::CoroBodyOp::getSuccessorRegions(
-    mlir::RegionBranchPoint point, SmallVectorImpl<RegionSuccessor> &regions) {
-  if (!point.isParent()) {
-    regions.emplace_back(getOperation());
-    return;
+LogicalResult cir::CoReturnOp::verify() {
+  mlir::Operation *coRet = getOperation();
+  auto coroutine = coRet->getParentOfType<CoroutineOp>();
+  mlir::Region &coroBody = coroutine.getBody();
+  for (Operation *current = coRet; current; current = current->getParentOp()) {
+    if (current->getParentRegion() == &coroBody)
+      return success();
   }
 
-  regions.push_back(RegionSuccessor(&getBody()));
+  return emitOpError("must be inside the cir.coroutine body region");
 }
 
-LogicalResult cir::CoroBodyOp::verify() {
-  if (!getOperation()->getParentOfType<FuncOp>().getCoroutine())
-    return emitOpError("enclosing function must be a coroutine");
-  return success();
-}
-
-void cir::CoroBodyOp::build(OpBuilder &builder, OperationState &result,
-                            BuilderCallbackRef bodyBuilder) {
-  assert(bodyBuilder &&
-         "the builder callback for 'CoroBodyOp' must be present");
-  OpBuilder::InsertionGuard guard(builder);
-
-  Region *bodyRegion = result.addRegion();
-  builder.createBlock(bodyRegion);
-  bodyBuilder(builder, result.location);
-}
-
-
 //===----------------------------------------------------------------------===//
 // CoroutineOp
 //===----------------------------------------------------------------------===//
 
 void cir::CoroutineOp::build(OpBuilder &builder, OperationState &result,
-                                BuilderCallbackRef initialSuspendBuilder,
-                                BuilderCallbackRef bodyBuilder,
-                                BuilderCallbackRef finalSuspendBuilder,
-                                BuilderCallbackRef destroyBuilder,
-                                BuilderCallbackRef exitBuilder) {
-  //{
-  //  OpBuilder::InsertionGuard guard(builder);
-  //  Region *bodyRegion = result.addRegion();
-  //  builder.createBlock(bodyRegion);
-  //  bodyBuilder(builder, result.location);
-  //}
-
-  //{
-  //  OpBuilder::InsertionGuard guard(builder);
-  //  Region *retRegion = result.addRegion();
-  //  builder.createBlock(retRegion);
-  //  retBuilder(builder, result.location);
-  //}
+                             BuilderCallbackRef initialSuspendBuilder,
+                             BuilderCallbackRef bodyBuilder,
+                             BuilderCallbackRef finalSuspendBuilder,
+                             BuilderCallbackRef destroyBuilder,
+                             BuilderCallbackRef exitBuilder) {
+  {
+    OpBuilder::InsertionGuard guard(builder);
+    Region *initialRegion = result.addRegion();
+    builder.createBlock(initialRegion);
+    initialSuspendBuilder(builder, result.location);
+  }
+
+  {
+    OpBuilder::InsertionGuard guard(builder);
+    Region *bodyRegion = result.addRegion();
+    builder.createBlock(bodyRegion);
+    bodyBuilder(builder, result.location);
+  }
+
+  {
+    OpBuilder::InsertionGuard guard(builder);
+    Region *finalRegion = result.addRegion();
+    builder.createBlock(finalRegion);
+    finalSuspendBuilder(builder, result.location);
+  }
+
+  {
+    OpBuilder::InsertionGuard guard(builder);
+    Region *destroyRegion = result.addRegion();
+    builder.createBlock(destroyRegion);
+    destroyBuilder(builder, result.location);
+  }
+
+  {
+    OpBuilder::InsertionGuard guard(builder);
+    Region *exitRegion = result.addRegion();
+    builder.createBlock(exitRegion);
+    exitBuilder(builder, result.location);
+  }
 }
 
 void cir::CoroutineOp::getSuccessorRegions(
     mlir::RegionBranchPoint point, SmallVectorImpl<RegionSuccessor> &regions) {
-  if (!point.isParent()) {
-    regions.emplace_back(getOperation());
+  // The parent op always enters through initial_suspend get_return_object()
+  // (if present) and the init await always run first.
+  if (point.isParent()) {
+    regions.emplace_back(&getInitialSuspend());
+    return;
+  }
+
+  mlir::Region *parent =
+      point.getTerminatorPredecessorOrNull()->getParentRegion();
+
+  // initial_suspend either falls into body (resumed, or never actually
+  // suspended because await_ready() was true) or exits directly, a plain
+  // suspend here means nobody has resumed yet, so we just return to caller.
+  if (parent == &getInitialSuspend()) {
+    regions.emplace_back(&getBody());
+    regions.emplace_back(&getExit());
     return;
   }
 
-  //regions.push_back(RegionSuccessor(&getBodyRegion()));
-  //regions.push_back(RegionSuccessor(&getRetRegion()));
+  // body can fall through into final_suspend (co_return), exit directly
+  // from any suspend_point inside it that just suspends, or reach destroy,
+  // either from a suspend_point being destroyed, or from an exception
+  // escaping body's own catch-all.
+  if (parent == &getBody()) {
+    regions.emplace_back(&getFinalSuspend());
+    regions.emplace_back(&getExit());    // any suspend_point inside body
+    regions.emplace_back(&getDestroy()); // destroy() on any suspend inside body
+    return;
+  }
+
+  // final_suspend's only live edge in valid programs is destroy resuming
+  // past the final suspend is UB. The ready-immediately edge to exit is
+  // kept for structural symmetry with the other two suspend regions even
+  // though it's effectively dead.
+  if (parent == &getFinalSuspend()) {
+    regions.emplace_back(
+        &getDestroy());               // the only real exit from final_suspend
+    regions.emplace_back(&getExit()); // ready==true edge, rarely taken
+    return;
+  }
+
+  // destroy has two possible outcomes depending on why it was entered:
+  // ordinary destroy dispatch falls through to exit (normal return); an
+  // exception that reached destroy needs to keep propagating instead, i.e.
+  // leave the whole op rather than go through exit's cir.return.
+  if (parent == &getDestroy()) {
+    regions.emplace_back(&getExit());
+    regions.push_back(RegionSuccessor(getOperation())); // unwind out of the op
+    return;
+  }
+  // exit always terminates the op there's no region it loops back into.
+  if (parent == &getExit()) {
+    regions.emplace_back(getOperation());
+    return;
+  }
 }
 
 mlir::ValueRange
@@ -3628,6 +3675,83 @@ cir::CoroutineOp::getSuccessorInputs(RegionSuccessor successor) {
 }
 
 LogicalResult cir::CoroutineOp::verify() {
+  mlir::Region &initialSuspend = getInitialSuspend();
+  mlir::Region &body = getBody();
+  mlir::Region &finalSuspend = getFinalSuspend();
+  mlir::Region &destroy = getDestroy();
+  mlir::Region &exit = getExit();
+
+  // initial_suspend must contain exactly one 'init'-kind cir.await an
+  // optional prelude of ordinary statements (get_return_object() and
+  // similar) is allowed before it, but no *other* await may appear.
+  int initAwaitCount = 0;
+  bool hasInvalidAwait = false;
+
+  initialSuspend.walk([&](Operation *op) {
+    auto awaitOp = mlir::dyn_cast<AwaitOp>(op);
+    if (!awaitOp)
+      return mlir::WalkResult::advance();
+
+    if (awaitOp.getKind() == cir::AwaitKind::Init)
+      ++initAwaitCount;
+    else
+      hasInvalidAwait = true;
+
+    if (initAwaitCount > 1 || hasInvalidAwait)
+      return mlir::WalkResult::interrupt();
+
+    return mlir::WalkResult::advance();
+  });
+
+  if (hasInvalidAwait)
+    return emitOpError("'initial_suspend' must not contain any cir.await other "
+                       "than the 'init' one");
+  if (initAwaitCount != 1)
+    return emitOpError(
+        "must have exactly one 'init' cir.await in 'initial_suspend'");
+
+  // final_suspend must contain only one or zero 'final'-kind cir.await
+  int finalAwaitCount = 0;
+  hasInvalidAwait = false;
+
+  finalSuspend.walk([&](Operation *op) {
+    auto awaitOp = mlir::dyn_cast<AwaitOp>(op);
+    if (!awaitOp)
+      return mlir::WalkResult::advance();
+
+    if (awaitOp.getKind() == cir::AwaitKind::Final)
+      ++finalAwaitCount;
+    else
+      hasInvalidAwait = true;
+
+    if (finalAwaitCount > 1 || hasInvalidAwait)
+      return mlir::WalkResult::interrupt();
+
+    return mlir::WalkResult::advance();
+  });
+
+  if (hasInvalidAwait)
+    return emitOpError("'final_suspend' must not contain any cir.await other "
+                       "than the 'final' one");
+  if (finalAwaitCount > 1)
+    return emitOpError(
+        "must have one or zero 'final' cir.await in 'final_suspend'");
+
+  // Each region must end with the terminator its role requires:
+  // initial_suspend/final_suspend/destroy yield back into cir.coroutine's
+  // own control flow, body ends in a cir.co_return (or a plain yield if
+  // some path never reaches one), and exit actually returns from the
+  // function.
+  if (!isa<YieldOp>(initialSuspend.back().back()))
+    return emitOpError("'initial_suspend' must end with cir.yield");
+  if (!isa<YieldOp, CoReturnOp>(body.back().back()))
+    return emitOpError("'body' must end with cir.yield or cir.co_return");
+  if (!isa<YieldOp>(finalSuspend.back().back()))
+    return emitOpError("'final_suspend' must end with cir.yield");
+  if (!isa<YieldOp>(destroy.back().back()))
+    return emitOpError("'destroy' must end with cir.yield");
+  if (!isa<ReturnOp>(exit.back().back()))
+    return emitOpError("'exit' must end with cir.return");
 
   return mlir::success();
 }
diff --git a/clang/test/CIR/CodeGenCoroutines/coro-exceptions.cpp b/clang/test/CIR/CodeGenCoroutines/coro-exceptions.cpp
index 5edae916f8f9fb..b1b35ae058a506 100644
--- a/clang/test/CIR/CodeGenCoroutines/coro-exceptions.cpp
+++ b/clang/test/CIR/CodeGenCoroutines/coro-exceptions.cpp
@@ -11,13 +11,14 @@ TaskWithEH simple_eh_body() {
 }
 
 // CIR-LABEL: cir.func{{.*}} @_Z14simple_eh_bodyv
+// CIR: cir.coroutine initialSuspend : {
 // CIR: }, resume : {
 // Note no 'try'/'catch' here.
 // CIR-NEXT: cir.call @_ZNSt15suspend_nothrow12await_resumeEv
 // CIR-NEXT: cir.yield
 // CIR-NEXT: }
 
-// CIR: cir.coro.body {
+// CIR: }, body : {
 // CIR-NOT: cir.if
 // CIR:   cir.scope {
 // CIR:     cir.try {
@@ -35,8 +36,8 @@ TaskWithEH simple_eh_body() {
 // CIR:     }
 // CIR:   }
 // CIR:   cir.yield
-// CIR: }
 // Make sure that 'final' is outside of the above try/catch/etc.
+// CIR: }, finalSuspend : {
 // CIR: cir.call @_ZN10TaskWithEH12promise_type13final_suspendEv
 
 TaskThrowingInit throwing_init_suspend() {
@@ -45,6 +46,7 @@ TaskThrowingInit throwing_init_suspend() {
 
 // CIR-LABEL: cir.func{{.*}} @_Z21throwing_init_suspendv
 // CIR: %[[RESUME_FLAG:.*]] = cir.alloca "resume.eh" align(1) : !cir.ptr<!cir.bool>
+// CIR: cir.coroutine initialSuspend : {
 // CIR: }, resume : {
 // CIR:    %[[FALSE:.*]] = cir.const #false
 // CIR:    cir.store %[[FALSE]], %[[RESUME_FLAG]] : !cir.bool, !cir.ptr<!cir.bool>
@@ -69,7 +71,7 @@ TaskThrowingInit throwing_init_suspend() {
 // CIR:    cir.yield
 // CIR: }
 
-// CIR: cir.coro.body {
+// CIR: }, body : {
 // CIR:   %[[LOAD_RESUME_FLAG:.*]] = cir.load align(1) %[[RESUME_FLAG]] : !cir.ptr<!cir.bool>, !cir.bool
 // CIR:   cir.if %[[LOAD_RESUME_FLAG]] {
 // CIR:     cir.scope {
@@ -89,5 +91,5 @@ TaskThrowingInit throwing_init_suspend() {
 // CIR:     }
 // CIR:   }
 // CIR:   cir.yield
-// CIR: }
+// CIR: }, finalSuspend : {
 // CIR: cir.call @_ZN16TaskThrowingInit12promise_type13final_suspendEv
diff --git a/clang/test/CIR/CodeGenCoroutines/coro-task.cpp b/clang/test/CIR/CodeGenCoroutines/coro-task.cpp
index 7879ea6655e633..b4ee46b94287ac 100644
--- a/clang/test/CIR/CodeGenCoroutines/coro-task.cpp
+++ b/clang/test/CIR/CodeGenCoroutines/coro-task.cpp
@@ -74,11 +74,11 @@ VoidTask silly_task() {
 
 // Call promise.get_return_object() to retrieve the task object.
 
-// CIR: cir.coro.ret_point {
-// CIR:   cir.cleanup.scope {
 
-// CIR:     %[[RetObj:.*]] = cir.call @_ZN5folly4coro4TaskIvE12promise_type17get_return_objectEv(%[[VoidPromisseAddr]]) nothrow : {{.*}} -> ![[VoidTask]]
-// CIR:     cir.store{{.*}} %[[RetObj]], %[[VoidTaskAddr]] : ![[VoidTask]]
+// CIR: cir.coroutine initialSuspend : {
+
+// CIR:   %[[RetObj:.*]] = cir.call @_ZN5folly4coro4TaskIvE12promise_type17get_return_objectEv(%[[VoidPromisseAddr]]) nothrow : {{.*}} -> ![[VoidTask]]
+// CIR:   cir.store{{.*}} %[[RetObj]], %[[VoidTaskAddr]] : ![[VoidTask]]
 
 // OGCG: call void @llvm.lifetime.start.p0(ptr %[[VoidPromisseAddr]])
 // OGCG: call void @_ZN5folly4coro4TaskIvE12promise_type17get_return_objectEv(ptr noundef nonnull align 1 dereferenceable(1) %[[VoidPromisseAddr]])
@@ -90,8 +90,8 @@ VoidTask silly_task() {
 // Effectively execute `coawait promise_type::initial_suspend()` by calling initial_suspend() and getting
 // the suspend_always struct to use for cir.await.
 
-// CIR:     %[[Tmp0:.*]] = cir.call @_ZN5folly4coro4TaskIvE12promise_type15initial_suspendEv(%[[VoidPromisseAddr]])
-// CIR:     cir.store{{.*}} %[[Tmp0:.*]], %[[SuspendAlwaysAddr]]
+// CIR:   %[[Tmp0:.*]] = cir.call @_ZN5folly4coro4TaskIvE12promise_type15initial_suspendEv(%[[VoidPromisseAddr]])
+// CIR:   cir.store{{.*}} %[[Tmp0]], %[[SuspendAlwaysAddr]]
 
 // OGCG: call void @_ZN5folly4coro4TaskIvE12promise_type15initial_suspendEv(ptr noundef nonnull align 1 dereferenceable(1) %[[VoidPromisseAddr]])
 
@@ -127,7 +127,7 @@ VoidTask silly_task() {
 // CIR-NEXT:     cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_typeEEES_IT_E(%[[CoroHandleVoidAddr]])
 // CIR-NEXT:     %[[CoroHandleVoidReload:.*]] = cir.load{{.*}} %[[CoroHandleVoidAddr]] : !cir.ptr<![[CoroHandleVoid]]>, ![[CoroHandleVoid]]
 // CIR-NEXT:     cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SuspendAlwaysAddr]], %[[CoroHandleVoidReload]])
-// CIR-NEXT:     cir.coro.suspend.point
+// CIR-NEXT:     cir.coro.suspend_point
 
 // OGCG: init.suspend:
 // OGCG:   %[[Save:.*]] = call token @llvm.coro.save(ptr null)
@@ -144,6 +144,7 @@ VoidTask silly_task() {
 // CIR-NEXT:     cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SuspendAlwaysAddr]])
 // CIR-NEXT:     cir.yield
 // CIR-NEXT:   },)
+// CIR-NEXTY   cir.yield loc(#loc34)
 
 // OGCG: init.ready:
 // OGCG:   call void @_ZNSt14suspend_always12await_resumeEv(ptr noundef nonnull align 1 dereferenceable(1) %[[SuspendAlwaysAddr]]
@@ -154,23 +155,23 @@ VoidTask silly_task() {
 // - The promise call
 // - The final suspend co_await
 // - Return
-
-// CIR:     cir.coro.body {
+ 
+// CIR:   }, body : {
 
 // The actual user written co_await
-// CIR:       cir.await(user, ready : {
-// CIR:       }, suspend : {
-// CIR:       }, resume : {
-// CIR:       },)
+// CIR:     cir.await(user, ready : {
+// CIR:     }, suspend : {
+// CIR:     }, resume : {
+// CIR:     },)
 
 // OGCG: cleanup.cont
 // OGCG: await.suspend:
 // OGCG: await.ready:
 
 // The promise call
-// CIR:       cir.call @_ZN5folly4coro4TaskIvE12promise_type11return_voidEv(%[[VoidPromisseAddr]])
-// CIR:       cir.co_return
-// CIR:     }
+// CIR:     cir.call @_ZN5folly4coro4TaskIvE12promise_type11return_voidEv(%[[VoidPromisseAddr]])
+// CIR:     cir.co_return
+// CIR:   }, finalSuspend : {
 
 // OGCG: call void @_ZN5folly4coro4TaskIvE12promise_type11return_voidEv(ptr noundef nonnull align 1 dereferenceable(1) %[[VoidPromisseAddr]])
 
@@ -179,7 +180,7 @@ VoidTask silly_task() {
 // CIR:   }, suspend : {
 // CIR:   }, resume : {
 // CIR:   },)
-// CIR-NEXT:     cir.yield
+// CIR-NEXT:   cir.yield
 
 // OGCG: coro.final:
 // OGCG: final.suspend:
@@ -191,17 +192,17 @@ VoidTask silly_task() {
 // If null, no dynamic allocation happened, so nothing to free.
 // The `if` ensures we only call delete on non-null.
 
-// CIR:   } cleanup  normal {
-// CIR:     %[[FreeMem:.*]] = cir.coro.intrinsic.free(%[[CoroId]], %[[CoroFrameAddr]]) : (token, !cir.ptr<!void>) -> !cir.ptr<!void>
-// CIR:     %[[NullPtr2:.*]] = cir.const #cir.ptr<null>
-// CIR:     %[[Cond:.*]] = cir.cmp ne %[[FreeMem]], %[[NullPtr2]]
-// CIR:     cir.if %[[Cond]] {
-// CIR:       %[[Size:.*]] = cir.coro.intrinsic.size()
-// CIR:       cir.call @_ZdlPvm(%[[FreeMem]], %[[Size]])
-// CIR:     }
-// CIR:     cir.yield
-// CIR:   }
-// CIR: cir.yield
+// CIR-NEXT: }, destroy : {
+
+// CIR-NEXT:   %[[FreeMem:.*]] = cir.coro.intrinsic.free(%[[CoroId]], %[[CoroFrameAddr]]) : (token, !cir.ptr<!void>) -> !cir.ptr<!void>
+// CIR-NEXT:   %[[NullPtr2:.*]] = cir.const #cir.ptr<null>
+// CIR-NEXT:   %[[Cond:.*]] = cir.cmp ne %[[FreeMem]], %[[NullPtr2]]
+// CIR-NEXT:   cir.if %[[Cond]] {
+// CIR-NEXT:     %[[Size:.*]] = cir.coro.intrinsic.size()
+// CIR-NEXT:     cir.call @_ZdlPvm(%[[FreeMem]], %[[Size]]){{.*}}
+// CIR-NEXT:   }
+// CIR-NEXT:   cir.yield
+// CIR-NEXT: }, exit : {
 
 // OGCG: %[[FreeMem:.*]] = call ptr @llvm.coro.free(token %[[CoroId]], ptr %[[CoroFrameAddr]])
 // OGCG: %[[Cond:.*]] = icmp ne ptr %[[FreeMem]], null
@@ -215,17 +216,14 @@ VoidTask silly_task() {
 
 // Call builtin coro end and return
 
-// CIR: }, ret : {
 // CIR-NEXT: %[[CoroEndArg0:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!void>
 // CIR-NEXT: %[[CoroEndArg1:.*]] = cir.const #false
 // CIR-NEXT: %[[TK_NONE:.*]] = cir.token.none
 // CIR-NEXT: cir.coro.intrinsic.end(%[[CoroEndArg0]], %[[CoroEndArg1]], %[[TK_NONE]]) : (!cir.ptr<!void>, !cir.bool, token){{.*}}
-// CIR:      %[[Tmp1:.*]] = cir.load{{.*}} %[[VoidTaskAddr]]
-// CIR:      cir.return %[[Tmp1]]
-// CIR:   }
-// CIRL }
-// CIR: cir.trap
-// CIR:}
+
+// CIR: %[[Tmp1:.*]] = cir.load{{.*}} %[[VoidTaskAddr]]
+// CIR: cir.return %[[Tmp1]]
+// CIR: }
 
 // OGCG: coro.ret:
 // OGCG:   call void @llvm.coro.end(ptr null, i1 false, token none)
@@ -246,49 +244,47 @@ folly::coro::Task<int> byRef(const std::string& s) {
 // CIR:    %[[CoroHandlePromiseAddr:.*]] = cir.alloca "agg.tmp1" {{.*}} : !cir.ptr<![[CoroHandlePromiseInt]]>
 // CIR:    cir.store %[[ARG]], %[[AllocaParam]] : !cir.ptr<![[StdString]]>, {{.*}}
 
-// CIR:    cir.coro.ret_point {
-// CIR:     cir.cleanup.scope {
 // Call promise.get_return_object() to retrieve the task object.
 
-// CIR:       %[[LOAD:.*]] = cir.load %[[AllocaParam]] : !cir.ptr<!cir.ptr<![[StdString]]>>, !cir.ptr<![[StdString]]>
-// CIR:       cir.store {{.*}} %[[LOAD]], %[[AllocaFnUse]] : !cir.ptr<![[StdString]]>, !cir.ptr<!cir.ptr<![[StdString]]>>
-// CIR:       %[[RetObj:.*]] = cir.call @_ZN5folly4coro4TaskIiE12promise_type17get_return_objectEv(%[[IntPromisseAddr]]) nothrow : {{.*}} -> ![[IntTask]]
-// CIR:       cir.store {{.*}} %[[RetObj]], %[[IntTaskAddr]] : ![[IntTask]]
-// CIR:       %[[Tmp0:.*]] = cir.call @_ZN5folly4coro4TaskIiE12promise_type15initial_suspendEv(%[[IntPromisseAddr]])
-// CIR:       cir.store{{.*}} %[[Tmp0]], %[[SuspendAlwaysAddr]]
-// CIR:       cir.await(init, ready : {
-// CIR:         %[[TmpCallRes:.*]] = cir.call @_ZNSt14suspend_always11await_readyEv(%[[SuspendAlwaysAddr]])
-// CIR:         cir.condition(%[[TmpCallRes]])
-// CIR:       }, suspend : {
-// CIR:         %[[FromAddrRes:.*]] = cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIiE12promise_typeEE12from_addressEPv(%[[CoroFrameAddr:.*]])
-// CIR:         cir.store{{.*}} %[[FromAddrRes]], %[[CoroHandlePromiseAddr]] : ![[CoroHandlePromiseInt]]
-// CIR:         %[[CoroHandlePromiseReload:.*]] = cir.load{{.*}} %[[CoroHandlePromiseAddr]]
-// CIR:         cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIiE12promise_typeEEES_IT_E(%[[CoroHandleVoidAddr]], %[[CoroHandlePromiseReload]])
-// CIR:         %[[CoroHandleVoidReload:.*]] = cir.load{{.*}} %[[CoroHandleVoidAddr]] : !cir.ptr<![[CoroHandleVoid]]>, ![[CoroHandleVoid]]
-// CIR:         cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SuspendAlwaysAddr]], %[[CoroHandleVoidReload]])
-// CIR:         cir.coro.suspend_point
-// CIR:       }, resume : {
-// CIR:         cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SuspendAlwaysAddr]])
-// CIR:         cir.yield
-// CIR:       },)
-// CIR:       cir.coro.body {
-              // can't fallthrough
-// CIR-NOT:     cir.await(user
-// CIR:         cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[IntPromisseAddr]], %[[STRING_SIZE:.*]])
-//CIR:          cir.co_return
-// CIR:       }
+// CIR:     %[[LOAD:.*]] = cir.load %[[AllocaParam]] : !cir.ptr<!cir.ptr<![[StdString]]>>, !cir.ptr<![[StdString]]>
+// CIR:     cir.store {{.*}} %[[LOAD]], %[[AllocaFnUse]] : !cir.ptr<![[StdString]]>, !cir.ptr<!cir.ptr<![[StdString]]>>
 
+// CIR:    cir.coroutine initialSuspend : {
+// CIR:     %[[RetObj:.*]] = cir.call @_ZN5folly4coro4TaskIiE12promise_type17get_return_objectEv(%[[IntPromisseAddr]]) nothrow : {{.*}} -> ![[IntTask]]
+// CIR:     cir.store {{.*}} %[[RetObj]], %[[IntTaskAddr]] : ![[IntTask]]
+// CIR:     %[[Tmp0:.*]] = cir.call @_ZN5folly4coro4TaskIiE12promise_type15initial_suspendEv(%[[IntPromisseAddr]])
+// CIR:     cir.store{{.*}} %[[Tmp0]], %[[SuspendAlwaysAddr]]
+// CIR:     cir.await(init, ready : {
+// CIR:       %[[TmpCallRes:.*]] = cir.call @_ZNSt14suspend_always11await_readyEv(%[[SuspendAlwaysAddr]])
+// CIR:       cir.condition(%[[TmpCallRes]])
+// CIR:     }, suspend : {
+// CIR:       %[[FromAddrRes:.*]] = cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIiE12promise_typeEE12from_addressEPv(%[[CoroFrameAddr:.*]])
+// CIR:       cir.store{{.*}} %[[FromAddrRes]], %[[CoroHandlePromiseAddr]] : ![[CoroHandlePromiseInt]]
+// CIR:       %[[CoroHandlePromiseReload:.*]] = cir.load{{.*}} %[[CoroHandlePromiseAddr]]
+// CIR:       cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIiE12promise_typeEEES_IT_E(%[[CoroHandleVoidAddr]], %[[CoroHandlePromiseReload]])
+// CIR:       %[[CoroHandleVoidReload:.*]] = cir.load{{.*}} %[[CoroHandleVoidAddr]] : !cir.ptr<![[CoroHandleVoid]]>, ![[CoroHandleVoid]]
+// CIR:       cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SuspendAlwaysAddr]], %[[CoroHandleVoidReload]])
+// CIR:       cir.coro.suspend_point
+// CIR:     }, resume : {
+// CIR:       cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SuspendAlwaysAddr]])
+// CIR:       cir.yield
+// CIR:     },)
+// CIR:     cir.yield
+// CIR:     }, body : {
+              // can't fallthrough
+// CIR-NOT:   cir.await(user
+// CIR:       cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[IntPromisseAddr]], %[[STRING_SIZE:.*]])
+// CIR:        cir.co_return
+// CIR:     }, finalSuspend : {
 
 // The final suspend co_await
-// CIR:      cir.await(final, ready : {
-// CIR:      }, suspend : {
-// CIR:      }, resume : {
-// CIR:      },)
-// CIR:      cir.yield
-// CIR:     } cleanup  normal {
-// CIR:     }
+// CIR:     cir.await(final, ready : {
+// CIR:     }, suspend : {
+// CIR:     }, resume : {
+// CIR:     },)
 // CIR:     cir.yield
-// CIR:    }, ret : {
+// CIR:    }, destroy : {
+// CIR:    }, exit : {
 
 folly::coro::Task<void> silly_coro() {
   std::optional<folly::coro::Task<int>> task;
@@ -304,24 +300,22 @@ folly::coro::Task<void> silly_coro() {
 // check there are not multiple co_returns emitted.
 
 // CIR: cir.func coroutine {{.*}} @_Z10silly_corov() {{.*}} ![[VoidTask]]
-// CIR:  cir.coro.ret_point {
-// CIR:   cir.cleanup.scope {
-// CIR:     cir.await(init, ready : {
-// CIR:     }, suspend : {
-// CIR:     }, resume : {
-// CIR:     },)
-// CIR:     cir.coro.body {
-// CIR:       cir.call @_ZN5folly4coro4TaskIvE12promise_type11return_voidEv
-// CIR:       cir.co_return
-// CIR:     }
-// CIR:     cir.await(final, ready : {
-// CIR:     }, suspend : {
-// CIR:     }, resume : {
-// CIR:     },)
-// CIR:     cir.yield
-// CIR:   } cleanup  normal {
-// CIR:   }
-// CIR:  }, ret : {
+// CIR: cir.coroutine initialSuspend : {
+// CIR:   cir.await(init, ready : {
+// CIR:   }, suspend : {
+// CIR:   }, resume : {
+// CIR:   },)
+// CIR: }, body : {
+// CIR:   cir.call @_ZN5folly4coro4TaskIvE12promise_type11return_voidEv
+// CIR:   cir.co_return
+// CIR: }, finalSuspend : {
+// CIR:   cir.await(final, ready : {
+// CIR:   }, suspend : {
+// CIR:   }, resume : {
+// CIR:   },)
+// CIR:   cir.yield
+// CIR: }, destroy : {
+// CIR: }, exit : {
 
 folly::coro::Task<void> yield();
 folly::coro::Task<void> yield1() {
@@ -347,81 +341,80 @@ folly::coro::Task<void> yield1() {
 // CIR-DAG: %[[CH_VOID2:.*]] = cir.alloca "agg.tmp5" {{.*}} : !cir.ptr<![[CoroHandleVoid]]>
 // CIR-DAG: %[[CH_PROM2:.*]] = cir.alloca "agg.tmp6" {{.*}} : !cir.ptr<![[CoroHandlePromiseVoid]]>
 
-// CIR:  cir.coro.ret_point {
-// CIR:   cir.cleanup.scope {
+// CIR: cir.coroutine initialSuspend : {
 // initial_suspend + await(init)
-// CIR:     %[[INIT_SUSP:.*]] = cir.call @_ZN5folly4coro4TaskIvE12promise_type15initial_suspendEv(%[[PROMISE]]){{.*}}
-// CIR:     cir.store{{.*}} %[[INIT_SUSP]], %[[SUSP0]]
-// CIR:     cir.await(init, ready : {
-// CIR:       %[[READY0:.*]] = cir.call @_ZNSt14suspend_always11await_readyEv(%[[SUSP0]]){{.*}}
-// CIR:       cir.condition(%[[READY0]])
-// CIR:     }, suspend : {
-// CIR:       %[[FROMADDR0:.*]] = cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIvE12promise_typeEE12from_addressEPv(%{{.*}}){{.*}}
-// CIR:       cir.store{{.*}} %[[FROMADDR0]], %[[CH_PROM0]]
-// CIR:       %[[PROM_RELOAD0:.*]] = cir.load{{.*}} %[[CH_PROM0]]
-// CIR:       cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_typeEEES_IT_E(%[[CH_VOID0]], %[[PROM_RELOAD0]]){{.*}}
-// CIR:       %[[VOID_RELOAD0:.*]] = cir.load{{.*}} %[[CH_VOID0]]
-// CIR:       cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SUSP0]], %[[VOID_RELOAD0]]){{.*}}
-// CIR:       cir.coro.suspend_point
-// CIR:     }, resume : {
-// CIR:       cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SUSP0]]){{.*}}
-// CIR:       cir.yield
-// CIR:     },)
+// CIR:   %[[INIT_SUSP:.*]] = cir.call @_ZN5folly4coro4TaskIvE12promise_type15initial_suspendEv(%[[PROMISE]]){{.*}}
+// CIR:   cir.store{{.*}} %[[INIT_SUSP]], %[[SUSP0]]
+// CIR:   cir.await(init, ready : {
+// CIR:     %[[READY0:.*]] = cir.call @_ZNSt14suspend_always11await_readyEv(%[[SUSP0]]){{.*}}
+// CIR:     cir.condition(%[[READY0]])
+// CIR:   }, suspend : {
+// CIR:     %[[FROMADDR0:.*]] = cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIvE12promise_typeEE12from_addressEPv(%{{.*}}){{.*}}
+// CIR:     cir.store{{.*}} %[[FROMADDR0]], %[[CH_PROM0]]
+// CIR:     %[[PROM_RELOAD0:.*]] = cir.load{{.*}} %[[CH_PROM0]]
+// CIR:     cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_typeEEES_IT_E(%[[CH_VOID0]], %[[PROM_RELOAD0]]){{.*}}
+// CIR:     %[[VOID_RELOAD0:.*]] = cir.load{{.*}} %[[CH_VOID0]]
+// CIR:     cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SUSP0]], %[[VOID_RELOAD0]]){{.*}}
+// CIR:     cir.coro.suspend_point
+// CIR:   }, resume : {
+// CIR:     cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SUSP0]]){{.*}}
+// CIR:     cir.yield
+// CIR:   },)
+// CIR:   cir.yield
 
 // yield_value + await(yield)
-// CIR:     cir.coro.body {
-// CIR:       %[[YIELD_TASK:.*]] = cir.call @_Z5yieldv(){{.*}}
-// CIR:       cir.store{{.*}} %[[YIELD_TASK]], %[[T_ADDR]]
-// CIR:       %[[AWAITER:.*]] = cir.load{{.*}} %[[AWAITER_COPY_ADDR]]
-// CIR:       %[[YIELD_SUSP:.*]] = cir.call @_ZN5folly4coro4TaskIvE12promise_type11yield_valueES2_(%[[PROMISE]], %[[AWAITER]]){{.*}}
-// CIR:       cir.store{{.*}} %[[YIELD_SUSP]], %[[SUSP1]]
-// CIR:       cir.await(yield, ready : {
-// CIR:         %[[READY1:.*]] = cir.call @_ZNSt14suspend_always11await_readyEv(%[[SUSP1]]){{.*}}
-// CIR:         cir.condition(%[[READY1]])
-// CIR:       }, suspend : {
-// CIR:         %[[FROMADDR1:.*]] = cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIvE12promise_typeEE12from_addressEPv(%{{.*}}){{.*}}
-// CIR:         cir.store{{.*}} %[[FROMADDR1]], %[[CH_PROM1]]
-// CIR:         %[[PROM_RELOAD1:.*]] = cir.load{{.*}} %[[CH_PROM1]]
-// CIR:         cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_typeEEES_IT_E(%[[CH_VOID1]], %[[PROM_RELOAD1]]){{.*}}
-// CIR:         %[[VOID_RELOAD1:.*]] = cir.load{{.*}} %[[CH_VOID1]]
-// CIR:         cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SUSP1]], %[[VOID_RELOAD1]]){{.*}}
-// CIR:         cir.coro.suspend_point
-// CIR:       }, resume : {
-// CIR:         cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SUSP1]]){{.*}}
-// CIR:         cir.yield
-// CIR:       },)
-// CIR:       cir.call @_ZN5folly4coro4TaskIvE12promise_type11return_voidEv(%[[PROMISE]])
-// CIR:       cir.co_return
-// CIR:     }
+// CIR: }, body : {
+// CIR:    %[[YIELD_TASK:.*]] = cir.call @_Z5yieldv(){{.*}}
+// CIR:    cir.store{{.*}} %[[YIELD_TASK]], %[[T_ADDR]]
+// CIR:    %[[AWAITER:.*]] = cir.load{{.*}} %[[AWAITER_COPY_ADDR]]
+// CIR:    %[[YIELD_SUSP:.*]] = cir.call @_ZN5folly4coro4TaskIvE12promise_type11yield_valueES2_(%[[PROMISE]], %[[AWAITER]]){{.*}}
+// CIR:    cir.store{{.*}} %[[YIELD_SUSP]], %[[SUSP1]]
+// CIR:    cir.await(yield, ready : {
+// CIR:      %[[READY1:.*]] = cir.call @_ZNSt14suspend_always11await_readyEv(%[[SUSP1]]){{.*}}
+// CIR:      cir.condition(%[[READY1]])
+// CIR:    }, suspend : {
+// CIR:      %[[FROMADDR1:.*]] = cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIvE12promise_typeEE12from_addressEPv(%{{.*}}){{.*}}
+// CIR:      cir.store{{.*}} %[[FROMADDR1]], %[[CH_PROM1]]
+// CIR:      %[[PROM_RELOAD1:.*]] = cir.load{{.*}} %[[CH_PROM1]]
+// CIR:      cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_typeEEES_IT_E(%[[CH_VOID1]], %[[PROM_RELOAD1]]){{.*}}
+// CIR:      %[[VOID_RELOAD1:.*]] = cir.load{{.*}} %[[CH_VOID1]]
+// CIR:      cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SUSP1]], %[[VOID_RELOAD1]]){{.*}}
+// CIR:      cir.coro.suspend_point
+// CIR:    }, resume : {
+// CIR:      cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SUSP1]]){{.*}}
+// CIR:      cir.yield
+// CIR:    },)
+// CIR:    cir.call @_ZN5folly4coro4TaskIvE12promise_type11return_voidEv(%[[PROMISE]])
+// CIR:    cir.co_return
+// CIR: }, finalSuspend : {
 
 // return_void + await(final)
 
-// CIR:     %[[FINAL_SUSP:.*]] = cir.call @_ZN5folly4coro4TaskIvE12promise_type13final_suspendEv(%[[PROMISE]]){{.*}}
-// CIR:     cir.store{{.*}} %[[FINAL_SUSP]], %[[SUSP2]]
-// CIR:     cir.await(final, ready : {
-// CIR:       %[[READY2:.*]] = cir.call @_ZNSt14suspend_always11await_readyEv(%[[SUSP2]]){{.*}}
-// CIR:       cir.condition(%[[READY2]])
-// CIR:     }, suspend : {
-// CIR:       %[[FROMADDR2:.*]] = cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIvE12promise_typeEE12from_addressEPv(%{{.*}}){{.*}}
-// CIR:       cir.store{{.*}} %[[FROMADDR2]], %[[CH_PROM2]]
-// CIR:       %[[PROM_RELOAD2:.*]] = cir.load{{.*}} %[[CH_PROM2]]
-// CIR:       cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_typeEEES_IT_E(%[[CH_VOID2]], %[[PROM_RELOAD2]]){{.*}}
-// CIR:       %[[VOID_RELOAD2:.*]] = cir.load{{.*}} %[[CH_VOID2]]
-// CIR:       cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SUSP2]], %[[VOID_RELOAD2]]){{.*}}
-// CIR:       cir.coro.suspend_point
-// CIR:     }, resume : {
-// CIR:       cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SUSP2]]){{.*}}
-// CIR:       cir.yield
-// CIR:     },)
+// CIR:   %[[FINAL_SUSP:.*]] = cir.call @_ZN5folly4coro4TaskIvE12promise_type13final_suspendEv(%[[PROMISE]]){{.*}}
+// CIR:   cir.store{{.*}} %[[FINAL_SUSP]], %[[SUSP2]]
+// CIR:   cir.await(final, ready : {
+// CIR:     %[[READY2:.*]] = cir.call @_ZNSt14suspend_always11await_readyEv(%[[SUSP2]]){{.*}}
+// CIR:     cir.condition(%[[READY2]])
+// CIR:   }, suspend : {
+// CIR:     %[[FROMADDR2:.*]] = cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIvE12promise_typeEE12from_addressEPv(%{{.*}}){{.*}}
+// CIR:     cir.store{{.*}} %[[FROMADDR2]], %[[CH_PROM2]]
+// CIR:     %[[PROM_RELOAD2:.*]] = cir.load{{.*}} %[[CH_PROM2]]
+// CIR:     cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_typeEEES_IT_E(%[[CH_VOID2]], %[[PROM_RELOAD2]]){{.*}}
+// CIR:     %[[VOID_RELOAD2:.*]] = cir.load{{.*}} %[[CH_VOID2]]
+// CIR:     cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SUSP2]], %[[VOID_RELOAD2]]){{.*}}
+// CIR:     cir.coro.suspend_point
+// CIR:   }, resume : {
+// CIR:     cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SUSP2]]){{.*}}
 // CIR:     cir.yield
-// CIR:   } cleanup  normal {
-// CIR:   }
-// CIR:  }, ret : {
-// CIR:   = cir.coro.intrinsic.end(%{{.*}}, %{{.*}})
-// CIR:   %[[RETLOAD:.*]] = cir.load{{.*}} %[[RETVAL]]
-// CIR:   cir.return %[[RETLOAD]]
-// CIR:  }
-// CIR:  cir.trap
+// CIR:   },)
+// CIR:   cir.yield
+// CIR: }, destroy : {
+// CIR: }, exit : {
+// CIR: cir.coro.intrinsic.end(%{{.*}}, %{{.*}}, %{{.*}}){{.*}}
+// CIR: %[[RETLOAD:.*]] = cir.load{{.*}} %[[RETVAL]]
+// CIR: cir.return %[[RETLOAD]]
+// CIR: }
+
 // CHECK: }
 
 folly::coro::Task<int> go(int const& val);
@@ -434,39 +427,37 @@ folly::coro::Task<int> go1() {
 // CIR: %[[IntTaskAddr:.*]] = cir.alloca "task" {{.*}} init : !cir.ptr<![[IntTask]]>
 // CIR: %[[OneAddr:.*]] = cir.alloca "ref.tmp1" align(4) init : !cir.ptr<!s32i>
 
-// CIR: cir.coro.ret_point {
-// CIR:   cir.cleanup.scope {
-// CIR:     cir.await(init, ready : {
-// CIR:     }, suspend : {
-// CIR:     }, resume : {
-// CIR:     },)
+// CIR: cir.coroutine initialSuspend : {
+// CIR:   cir.await(init, ready : {
+// CIR:   }, suspend : {
+// CIR:   }, resume : {
+// CIR:   },)
+// CIR:   cir.yield
 
-// CIR:     cir.coro.body {
-// CIR:       %[[One:.*]] = cir.const #cir.int<1> : !s32i
-// CIR:       cir.store{{.*}} %[[One]], %[[OneAddr]] : !s32i, !cir.ptr<!s32i>
-// CIR:       %[[IntTaskTmp:.*]] = cir.call @_Z2goRKi(%[[OneAddr]]) : (!cir.ptr<!s32i>{{.*}}) -> ![[IntTask]]
-// CIR:       cir.store{{.*}} %[[IntTaskTmp]], %[[IntTaskAddr]] : ![[IntTask]], !cir.ptr<![[IntTask]]>
-
-// CIR:       cir.await(user, ready : {
-// CIR:       }, suspend : {
-// CIR:       }, resume : {
-// CIR:       %[[ResumeVal:.*]] = cir.call @_ZN5folly4coro4TaskIiE12await_resumeEv(%[[IntTaskAddr]])
-// CIR:       cir.store{{.*}} %[[ResumeVal]], %[[CoReturnValAddr:.*]] : !s32i, !cir.ptr<!s32i>
-// CIR:       },)
-// CIR:       %[[V:.*]] = cir.load{{.*}} %[[CoReturnValAddr]] : !cir.ptr<!s32i>, !s32i
-// CIR:       cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi({{.*}}, %[[V]])
-// CIR:       cir.co_return
-// CIR:     }
+// CIR: }, body : {
+// CIR:   %[[One:.*]] = cir.const #cir.int<1> : !s32i
+// CIR:   cir.store{{.*}} %[[One]], %[[OneAddr]] : !s32i, !cir.ptr<!s32i>
+// CIR:   %[[IntTaskTmp:.*]] = cir.call @_Z2goRKi(%[[OneAddr]]) : (!cir.ptr<!s32i>{{.*}}) -> ![[IntTask]]
+// CIR:   cir.store{{.*}} %[[IntTaskTmp]], %[[IntTaskAddr]] : ![[IntTask]], !cir.ptr<![[IntTask]]>
 
-// CIR:     cir.await(final, ready : {
-// CIR:     }, suspend : {
-// CIR:     }, resume : {
-// CIR:     },)
-// CIR:     cir.yield
-// CIR:   } cleanup  normal {
-// CIR:   }
+// CIR:   cir.await(user, ready : {
+// CIR:   }, suspend : {
+// CIR:   }, resume : {
+// CIR:   %[[ResumeVal:.*]] = cir.call @_ZN5folly4coro4TaskIiE12await_resumeEv(%[[IntTaskAddr]])
+// CIR:   cir.store{{.*}} %[[ResumeVal]], %[[CoReturnValAddr:.*]] : !s32i, !cir.ptr<!s32i>
+// CIR:   },)
+// CIR:   %[[V:.*]] = cir.load{{.*}} %[[CoReturnValAddr]] : !cir.ptr<!s32i>, !s32i
+// CIR:   cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi({{.*}}, %[[V]])
+// CIR:   cir.co_return
+
+// CIR: }, finalSuspend : {
+// CIR:  cir.await(final, ready : {
+// CIR:  }, suspend : {
+// CIR:  }, resume : {
+// CIR:  },)
 // CIR:  cir.yield
-// CIR: }, ret : {
+// CIR: }, destroy : {
+// CIR: }, exit : {
 
 folly::coro::Task<int> go1_lambda() {
   auto task = []() -> folly::coro::Task<int> {
@@ -476,49 +467,48 @@ folly::coro::Task<int> go1_lambda() {
 }
 
 // CIR: cir.func coroutine {{.*}} @_ZZ10go1_lambdavENK3$_0clEv{{.*}} ![[IntTask]]
-// CIR: cir.coro.ret_point {
-// CIR:   cir.cleanup.scope {
-// CIR:     cir.await(init, ready : {
-// CIR:     }, suspend : {
-// CIR:     }, resume : {
-// CIR:     },)
-// CIR:     cir.coro.body {
-// CIR:       %[[ONE:.*]] = cir.const #cir.int<1>
-// CIR:       cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE:.*]], %[[ONE]])
-// CIR:       cir.co_return
-// CIR:     }
-// CIR:     cir.await(final, ready : {
-// CIR:     }, suspend : {
-// CIR:     }, resume : {
-// CIR:     },)
-// CIR:   } cleanup  normal {
-// CIR: }, ret : {
+// CIR: cir.coroutine initialSuspend : {
+// CIR:   cir.await(init, ready : {
+// CIR:   }, suspend : {
+// CIR:   }, resume : {
+// CIR:   },)
+// CIR:   cir.yield
+// CIR: }, body : {
+// CIR:   %[[ONE:.*]] = cir.const #cir.int<1>
+// CIR:   cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE:.*]], %[[ONE]])
+// CIR:   cir.co_return
+// CIR: }, finalSuspend : {
+// CIR:   cir.await(final, ready : {
+// CIR:   }, suspend : {
+// CIR:   }, resume : {
+// CIR:   },)
+// CIR: }, destroy : {
 
 // CIR: cir.func coroutine {{.*}} @_Z10go1_lambdav() {{.*}} ![[IntTask]]
-// CIR: cir.coro.ret_point {
-// CIR:   cir.cleanup.scope {
-// CIR:     cir.await(init, ready : {
-// CIR:     }, suspend : {
-// CIR:     }, resume : {
-// CIR:     },)
-// CIR:     cir.coro.body {
-// CIR:       cir.call @_ZZ10go1_lambdavENK3$_0clEv
-// CIR:       cir.await(user, ready : {
-// CIR:       }, suspend : {
-// CIR:       }, resume : {
-// CIR:         %[[RESUME_RES:.*]] = cir.call @_ZN5folly4coro4TaskIiE12await_resumeEv(%[[TASK:.*]])
-// CIR:         cir.store %[[RESUME_RES]], %[[resume_rval:.*]] : !s32i, !cir.ptr<!s32i>
-// CIR:       },)
-// CIR:       %[[TMP1:.*]] = cir.load %[[resume_rval:.*]] : !cir.ptr<!s32i>
-// CIR:       cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE:.*]], %[[TMP1]])
-// CIR:       cir.co_return
-// CIR:     }
-// CIR:     cir.await(final, ready : {
-// CIR:     }, suspend : {
-// CIR:     }, resume : {
-// CIR:     },)
-// CIR:   } cleanup  normal {
-// CIR: }, ret : {
+// CIR: cir.coroutine initialSuspend : {
+// CIR:   cir.await(init, ready : {
+// CIR:   }, suspend : {
+// CIR:   }, resume : {
+// CIR:   },)
+// CIR:   cir.yield
+// CIR: }, body : {
+// CIR:   cir.call @_ZZ10go1_lambdavENK3$_0clEv
+// CIR:   cir.await(user, ready : {
+// CIR:   }, suspend : {
+// CIR:   }, resume : {
+// CIR:     %[[RESUME_RES:.*]] = cir.call @_ZN5folly4coro4TaskIiE12await_resumeEv(%[[TASK:.*]])
+// CIR:     cir.store %[[RESUME_RES]], %[[resume_rval:.*]] : !s32i, !cir.ptr<!s32i>
+// CIR:   },)
+// CIR:   %[[TMP1:.*]] = cir.load %[[resume_rval:.*]] : !cir.ptr<!s32i>
+// CIR:   cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE:.*]], %[[TMP1]])
+// CIR:   cir.co_return
+// CIR: }, finalSuspend : {
+// CIR:   cir.await(final, ready : {
+// CIR:   }, suspend : {
+// CIR:   }, resume : {
+// CIR:   },)
+// CIR:   cir.yield
+// CIR: }, destroy : {
 
 folly::coro::Task<int> go4() {
   auto* fn = +[](int const& i) -> folly::coro::Task<int> { co_return i; };
@@ -527,22 +517,21 @@ folly::coro::Task<int> go4() {
 }
 
 // CIR: cir.func coroutine{{.*}} @_ZZ3go4vENK3$_0clERKi(
-// CIR: cir.coro.ret_point {
-// CIR:   cir.cleanup.scope {
-// CIR:     cir.await(init, ready : {
-// CIR:     }, suspend : {
-// CIR:     }, resume : {
-// CIR:     },)
-// CIR:     cir.coro.body {
-// CIR:       cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE:.*]], %[[I:.*]])
-// CIR:       cir.co_return
-// CIR:     }
-// CIR:     cir.await(final, ready : {
-// CIR:     }, suspend : {
-// CIR:     }, resume : {
-// CIR:     },)
-// CIR:   } cleanup  normal {
-// CIR: }, ret : {
+// CIR: cir.coroutine initialSuspend : {
+// CIR:   cir.await(init, ready : {
+// CIR:   }, suspend : {
+// CIR:   }, resume : {
+// CIR:   },)
+// CIR:   cir.yield
+// CIR: }, body : {
+// CIR:   cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE:.*]], %[[I:.*]])
+// CIR:   cir.co_return
+// CIR: }, finalSuspend : {
+// CIR:   cir.await(final, ready : {
+// CIR:   }, suspend : {
+// CIR:   }, resume : {
+// CIR:   },)
+// CIR: }, destroy : {
 
 folly::coro::Task<int> go4() {
   auto* fn = +[](int const& i) -> folly::coro::Task<int> { co_return i; };
@@ -552,42 +541,40 @@ folly::coro::Task<int> go4() {
 
 // CIR: cir.func coroutine {{.*}} @_Z3go4v() attributes
 
-// CIR: cir.coro.ret_point {
-// CIR:   cir.cleanup.scope {
-// CIR:     cir.await(init, ready : {
-// CIR:     }, suspend : {
-// CIR:     }, resume : {
-// CIR:     },)
+// CIR: cir.coroutine initialSuspend : {
+// CIR:   cir.await(init, ready : {
+// CIR:   }, suspend : {
+// CIR:   }, resume : {
+// CIR:   },)
 
 // Get the lambda invoker ptr via `lambda operator folly::coro::Task<int> (*)(int const&)()`
-// CIR:   %[[INVOKER:.*]] = cir.call @_ZZ3go4vENK3$_0cvPFN5folly4coro4TaskIiEERKiEEv(%{{.*}}) nothrow : {{.*}} -> (!cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>> {llvm.noundef})
-// CIR:   cir.store{{.*}} %[[INVOKER]], %[[FN_ADDR:.*]] : !cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>, !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>>
-// CIR:   %[[FN:.*]] = cir.load{{.*}} %[[FN_ADDR]] : !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>>, !cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>
-// CIR:   %[[THREE:.*]] = cir.const #cir.int<3> : !s32i
-// CIR:   cir.store{{.*}} %[[THREE]], %[[ARG:.*]] : !s32i, !cir.ptr<!s32i>
+// CIR: %[[INVOKER:.*]] = cir.call @_ZZ3go4vENK3$_0cvPFN5folly4coro4TaskIiEERKiEEv(%{{.*}}) nothrow : {{.*}} -> (!cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>> {llvm.noundef})
+// CIR: cir.store{{.*}} %[[INVOKER]], %[[FN_ADDR:.*]] : !cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>, !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>>
+// CIR: %[[FN:.*]] = cir.load{{.*}} %[[FN_ADDR]] : !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>>, !cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>
+// CIR: %[[THREE:.*]] = cir.const #cir.int<3> : !s32i
+// CIR: cir.store{{.*}} %[[THREE]], %[[ARG:.*]] : !s32i, !cir.ptr<!s32i>
 
 // Call invoker, which calls operator() indirectly.
-// CIR:   %[[CALLRES:.*]] = cir.call %[[FN]](%[[ARG]]) : (!cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>, !cir.ptr<!s32i> {{.*}}) -> ![[IntTask]]
-// CIR:   cir.store{{.*}} %[[CALLRES]], %[[TASK_ADDR:.*]] : ![[IntTask]], !cir.ptr<![[IntTask]]>
-
-// CIR:    cir.await(user, ready : {
-// CIR:      = cir.call @_ZN5folly4coro4TaskIiE11await_readyEv(%[[TASK_ADDR]])
-// CIR:      cir.condition(
-// CIR:    }, suspend : {
-// CIR:      cir.coro.suspend_point
-// CIR:    }, resume : {
-// CIR:      cir.yield
-// CIR:    },)
-// CIR:    cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi
-// CIR:    cir.co_return
-// CIR:   }
+// CIR: %[[CALLRES:.*]] = cir.call %[[FN]](%[[ARG]]) : (!cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>, !cir.ptr<!s32i> {{.*}}) -> ![[IntTask]]
+// CIR: cir.store{{.*}} %[[CALLRES]], %[[TASK_ADDR:.*]] : ![[IntTask]], !cir.ptr<![[IntTask]]>
+
+// CIR:  cir.await(user, ready : {
+// CIR:    = cir.call @_ZN5folly4coro4TaskIiE11await_readyEv(%[[TASK_ADDR]])
+// CIR:    cir.condition(
+// CIR:  }, suspend : {
+// CIR:    cir.coro.suspend_point
+// CIR:  }, resume : {
+// CIR:    cir.yield
+// CIR:  },)
+// CIR:  cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi
+// CIR:  cir.co_return
+// CIR: }, finalSuspend : {
 
-// CIR:    cir.await(final, ready : {
-// CIR:    }, suspend : {
-// CIR:    }, resume : {
-// CIR:    },)
-// CIR:   } cleanup  normal {
-// CIR: }, ret : {
+// CIR:  cir.await(final, ready : {
+// CIR:  }, suspend : {
+// CIR:  }, resume : {
+// CIR:  },)
+// CIR: }, destroy : {
 
 // OGCG: define {{.*}}__await_suspend_wrapper__init(ptr noundef nonnull %[[Awaiter:.*]], ptr noundef %[[Handle:.*]])
 // OGCG: entry:
@@ -616,43 +603,42 @@ folly::coro::Task<int> co_returns(int x) {
 }
 
 // CIR: cir.func coroutine {{.*}} @_Z10co_returnsi
-// CIR: cir.coro.ret_point {
-// CIR:   cir.cleanup.scope {
-// CIR:     cir.await(init, ready : {
-// CIR:     }, suspend : {
-// CIR:     }, resume : {
-// CIR:     },)
-// CIR:     cir.coro.body {
-// CIR:        cir.scope {
-// CIR:          cir.if {{.*}} {
-// CIR:            %[[MINUS_ONE:.*]] = cir.const #cir.int<-1>
-// CIR:            cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE:.*]], %[[MINUS_ONE]])
-// CIR:            cir.co_return
-// CIR:          } else {
-// CIR:           cir.if {{.*}} {
-// CIR:             %[[MINUS_TWO:.*]] = cir.const #cir.int<-2>
-// CIR:             cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE]], %[[MINUS_TWO]])
-// CIR:             cir.co_return
-// CIR:             }
-// CIR:          }
-// CIR:        }
-// CIR:       cir.await(user, ready : {
-// CIR:       }, suspend : {
-// CIR:       }, resume : {
-// CIR:       },)
-// CIR:       %[[X_LOAD:.*]] = cir.load {{.*}} %[[X:.*]]
-// CIR:       %[[TWO:.*]] = cir.const #cir.int<2>
-// CIR:       %[[RES:.*]] = cir.mul nsw %[[X_LOAD]], %[[TWO]]
-// CIR:       cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE]], %[[RES]])
-// CIR:       cir.co_return
-// CIR:     }
-// CIR:    cir.await(final, ready : {
-// CIR:    }, suspend : {
-// CIR:    }, resume : {
-// CIR:    },)
-// CIR:    cir.yield
-// CIR:   } cleanup  normal {
-// CIR: }, ret : {
+// CIR: cir.coroutine initialSuspend : {
+// CIR:   cir.await(init, ready : {
+// CIR:   }, suspend : {
+// CIR:   }, resume : {
+// CIR:   },)
+// CIR:   cir.yield
+// CIR: }, body : {
+// CIR:    cir.scope {
+// CIR:      cir.if {{.*}} {
+// CIR:        %[[MINUS_ONE:.*]] = cir.const #cir.int<-1>
+// CIR:        cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE:.*]], %[[MINUS_ONE]])
+// CIR:        cir.co_return
+// CIR:      } else {
+// CIR:       cir.if {{.*}} {
+// CIR:         %[[MINUS_TWO:.*]] = cir.const #cir.int<-2>
+  // CIR         cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE]], %[[MINUS_TWO]])
+  // CIR         cir.co_return
+// CIR:         }
+// CIR:      }
+// CIR:    }
+// CIR:   cir.await(user, ready : {
+// CIR:   }, suspend : {
+// CIR:   }, resume : {
+// CIR:   },)
+// CIR:   %[[X_LOAD:.*]] = cir.load {{.*}} %[[X:.*]]
+// CIR:   %[[TWO:.*]] = cir.const #cir.int<2>
+// CIR:   %[[RES:.*]] = cir.mul nsw %[[X_LOAD]], %[[TWO]]
+// CIR:   cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE]], %[[RES]])
+// CIR:   cir.co_return
+// CIR: }, finalSuspend : {
+// CIR:  cir.await(final, ready : {
+// CIR:  }, suspend : {
+// CIR:  }, resume : {
+// CIR:  },)
+// CIR:  cir.yield
+// CIR: }, destroy : {
 
 
 // OGCG: define {{.*}} @_Z10co_returnsi
@@ -679,14 +665,14 @@ folly::coro::Task<int> implicit_yield_terminator(int x) {
     co_return 2;
 }
 // CIR: cir.func coroutine {{.*}} @_Z25implicit_yield_terminatori
-// CIR:   cir.coro.body {
+// CIR:   }, body : {
 // CIR:     cir.if %{{.*}} {
 // CIR:       cir.co_return
 // CIR:     } else {
 // CIR:       cir.co_return
 // CIR:     }
 // CIR:     cir.yield
-// CIR:   }
+// CIR:   }, finalSuspend : {
 
 struct HasDtor {
   ~ HasDtor();
@@ -702,42 +688,41 @@ folly::coro::Task<int> co_return_with_dtor(int flag) {
 // CIR: cir.func coroutine {{.*}} @_Z19co_return_with_dtori
 
 // CIR: %[[LOCAL:.*]] = cir.alloca "local" {{.*}} : !cir.ptr<!rec_HasDtor>
-// CIR: cir.coro.ret_point {
-// CIR:   cir.cleanup.scope {
-// CIR:     cir.await(init, ready : {
-// CIR:     }, suspend : {
-// CIR:     }, resume : {
-// CIR:     },)
+// CIR: cir.coroutine initialSuspend : {
+// CIR:   cir.await(init, ready : {
+// CIR:   }, suspend : {
+// CIR:   }, resume : {
+// CIR:   },)
+// CIR:   cir.yield
 
-// CIR:     cir.coro.body {
-// CIR:       cir.cleanup.scope {
-// CIR:         cir.scope {
-// CIR:           %[[CAST_FLAG:.*]] = cir.cast int_to_bool %[[FLAG:.*]]
-// CIR:           cir.if %[[CAST_FLAG]] {
-// CIR:             %[[ONE:.*]] = cir.const #cir.int<1>
-// CIR:             cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[promise:.*]], %[[ONE]])
-// CIR:             cir.co_return
-// CIR:           }
-// CIR:         }
-// CIR:         %[[TWO:.*]] = cir.const #cir.int<2>
-// CIR:         cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[promise]], %[[TWO]])
+// CIR: }, body : {
+// CIR:   cir.cleanup.scope {
+// CIR:     cir.scope {
+// CIR:       %[[CAST_FLAG:.*]] = cir.cast int_to_bool %[[FLAG:.*]]
+// CIR:       cir.if %[[CAST_FLAG]] {
+// CIR:         %[[ONE:.*]] = cir.const #cir.int<1>
+// CIR:         cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[promise:.*]], %[[ONE]])
 // CIR:         cir.co_return
-// CIR:       } cleanup normal {
-// CIR:         cir.call @_ZN7HasDtorD1Ev(%[[LOCAL]])
-// CIR:         cir.yield
 // CIR:       }
-// CIR:       cir.yield
 // CIR:     }
-
-// CIR:     cir.await(final, ready : {
-// CIR:     }, suspend : {
-// CIR:     }, resume : {
-// CIR:     },)
-// CIR:     cir.yield
+// CIR:     %[[TWO:.*]] = cir.const #cir.int<2>
+// CIR:     cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[promise]], %[[TWO]])
+// CIR:     cir.co_return
 // CIR:   } cleanup normal {
+// CIR:     cir.call @_ZN7HasDtorD1Ev(%[[LOCAL]])
 // CIR:     cir.yield
 // CIR:   }
-// CIR: }, ret : {
+// CIR:   cir.yield
+
+// CIR: }, finalSuspend : {
+// CIR:   cir.await(final, ready : {
+// CIR:   }, suspend : {
+// CIR:   }, resume : {
+// CIR:   },)
+// CIR:   cir.yield
+// CIR: }, destroy : {
+// CIR:   cir.yield
+// CIR: }
 
 // OGCG: define {{.*}} void @_Z19co_return_with_dtori
 // OGCG:   %[[LOCAL:.*]] = alloca %struct.HasDtor
@@ -769,30 +754,28 @@ folly::coro::Task<int __complex__> complex_co_await() noexcept {
 // CIR: %[[COMPLEX_ADDR:.*]] = cir.alloca "ref.tmp1" {{.*}} : !cir.ptr<!rec_folly3A3Acoro3A3ATask3C_Complex_int3E>
 // CIR: %[[RESUME_VAL_ADDR:.*]] = cir.alloca "__coawait_resume_rval" {{.*}} : !cir.ptr<!cir.complex<!s32i>>
 
-// CIR: cir.coro.ret_point {
-// CIR:   cir.cleanup.scope {
-// CIR:     cir.await(init, ready : {
+// CIR: cir.coroutine initialSuspend : {
+// CIR:   cir.await(init, ready : {
+// CIR:   }, suspend : {
+// CIR:   }, resume : {
+// CIR:   },)
+
+// CIR: }, body : {
+// CIR:   %[[CALL:.*]] = cir.call @_Z9fetchDatav() nothrow : () -> !rec_folly3A3Acoro3A3ATask3C_Complex_int3E
+// CIR:   cir.store {{.*}} %[[CALL]], %[[COMPLEX_ADDR]] : !rec_folly3A3Acoro3A3ATask3C_Complex_int3E, !cir.ptr<!rec_folly3A3Acoro3A3ATask3C_Complex_int3E>
+
+// CIR:     cir.await(user, ready : {
 // CIR:     }, suspend : {
 // CIR:     }, resume : {
+// CIR:       %[[RESUME_VAL:.*]] = cir.call @_ZN5folly4coro4TaskICiE12await_resumeEv(%[[COMPLEX_ADDR]]) : (!cir.ptr<!rec_folly3A3Acoro3A3ATask3C_Complex_int3E> {llvm.align = 1 : i64, llvm.dereferenceable = 1 : i64, llvm.nonnull, llvm.noundef}) -> (!cir.complex<!s32i> {llvm.noundef})
+// CIR:       cir.store %[[RESUME_VAL]], %[[RESUME_VAL_ADDR]] : !cir.complex<!s32i>, !cir.ptr<!cir.complex<!s32i>>
 // CIR:     },)
+// CIR:     %[[V:.*]] = cir.load %[[RESUME_VAL_ADDR]] : !cir.ptr<!cir.complex<!s32i>>, !cir.complex<!s32i>
+// CIR:     cir.yield
+// CIR: }, finalSuspend : {
 
-// CIR:     cir.coro.body {
-// CIR:       %[[CALL:.*]] = cir.call @_Z9fetchDatav() nothrow : () -> !rec_folly3A3Acoro3A3ATask3C_Complex_int3E
-// CIR:       cir.store {{.*}} %[[CALL]], %[[COMPLEX_ADDR]] : !rec_folly3A3Acoro3A3ATask3C_Complex_int3E, !cir.ptr<!rec_folly3A3Acoro3A3ATask3C_Complex_int3E>
-
-// CIR:         cir.await(user, ready : {
-// CIR:         }, suspend : {
-// CIR:         }, resume : {
-// CIR:           %[[RESUME_VAL:.*]] = cir.call @_ZN5folly4coro4TaskICiE12await_resumeEv(%[[COMPLEX_ADDR]]) : (!cir.ptr<!rec_folly3A3Acoro3A3ATask3C_Complex_int3E> {llvm.align = 1 : i64, llvm.dereferenceable = 1 : i64, llvm.nonnull, llvm.noundef}) -> (!cir.complex<!s32i> {llvm.noundef})
-// CIR:           cir.store %[[RESUME_VAL]], %[[RESUME_VAL_ADDR]] : !cir.complex<!s32i>, !cir.ptr<!cir.complex<!s32i>>
-// CIR:         },)
-// CIR:         %[[V:.*]] = cir.load %[[RESUME_VAL_ADDR]] : !cir.ptr<!cir.complex<!s32i>>, !cir.complex<!s32i>
-// CIR:         cir.yield
-// CIR:     }
-
-// CIR:   } cleanup  normal {
-// CIR:   }
-// CIR: }, ret : {
+// CIR: }, destroy : {
+// CIR: }, exit : {
 
 // CIR: cir.func coroutine{{.*}} @_ZZ3go4vENK3$_0clERKi(
 // CIR: cir.cleanup.scope {
diff --git a/clang/test/CIR/IR/await.cir b/clang/test/CIR/IR/await.cir
index 9e6a71351c42e8..b5d3df6175b9c2 100644
--- a/clang/test/CIR/IR/await.cir
+++ b/clang/test/CIR/IR/await.cir
@@ -1,29 +1,38 @@
 // RUN: cir-opt %s --verify-roundtrip | FileCheck %s
 
 cir.func coroutine @checkPrintParse(%arg0 : !cir.bool) {
-  cir.coro.ret_point {
-    cir.coro.body {
-      cir.await(user, ready : {
-        cir.condition(%arg0)
-      }, suspend : {
-        cir.coro.suspend_point
-      }, resume : {
-        cir.yield
-      },)
+  cir.coroutine initialSuspend : {
+    cir.await(init, ready : {
+      cir.condition(%arg0)
+    }, suspend : {
+      cir.coro.suspend_point
+    }, resume : {
       cir.yield
-    }
+    },)
     cir.yield
-  }, ret : {
-      cir.return
+  }, body : {
+    cir.await(user, ready : {
+      cir.condition(%arg0)
+    }, suspend : {
+      cir.coro.suspend_point
+    }, resume : {
+      cir.yield
+    },)
+    cir.yield
+  }, finalSuspend : {
+    cir.yield
+  }, destroy : {
+    cir.yield
+  }, exit : {
+    cir.return
   }
   cir.trap
 }
 
-// CHECK:  cir.func coroutine @checkPrintParse
-// CHECK:  cir.await(user, ready : {
-// CHECK:    cir.condition(%arg0)
-// CHECK:  }, suspend : {
-// CHECK:    cir.coro.suspend_point
-// CHECK:  }, resume : {
-// CHECK:    cir.yield
-// CHECK:  },)
+// CHECK: cir.await(user, ready : {
+// CHECK:   cir.condition(%arg0)
+// CHECK: }, suspend : {
+// CHECK:   cir.coro.suspend_point
+// CHECK: }, resume : {
+// CHECK:   cir.yield
+// CHECK: },)
diff --git a/clang/test/CIR/IR/co-return.cir b/clang/test/CIR/IR/co-return.cir
index 833b6a27d80162..c7d16d4699c610 100644
--- a/clang/test/CIR/IR/co-return.cir
+++ b/clang/test/CIR/IR/co-return.cir
@@ -1,26 +1,26 @@
 // RUN: cir-opt %s --verify-roundtrip | FileCheck %s
+
 cir.func coroutine @coro_co_return(%arg0 : !cir.bool) {
-  cir.coro.ret_point {
-    cir.coro.body {
-      cir.await(user, ready : {
-        cir.condition(%arg0)
-      }, suspend : {
-        cir.coro.suspend_point
-      }, resume : {
-        cir.yield
-      },)
-      cir.co_return
-    }
+  cir.coroutine initialSuspend : {
+    cir.await(init, ready : {
+      cir.condition(%arg0)
+    }, suspend : {
+      cir.coro.suspend_point
+    }, resume : {
+      cir.yield
+    },)
+    cir.yield
+  }, body : {
+    cir.co_return
+  }, finalSuspend : {
+    cir.yield
+  }, destroy : {
     cir.yield
-  }, ret : {
-      cir.return
+  }, exit : {
+    cir.return
   }
   cir.trap
 }
 
 // CHECK: cir.func coroutine @coro_co_return
-// CHECK:   cir.coro.body {
-// CHECK:     cir.co_return
-// CHECK:   }
-
-
+// CHECK: cir.co_return
diff --git a/clang/test/CIR/IR/coro-body.cir b/clang/test/CIR/IR/coro-body.cir
deleted file mode 100644
index 30af9fc0e5d3cb..00000000000000
--- a/clang/test/CIR/IR/coro-body.cir
+++ /dev/null
@@ -1,24 +0,0 @@
-// RUN: cir-opt %s --verify-roundtrip | FileCheck %s
-
-cir.func coroutine @coro_body(%arg0 : !cir.bool) {
-  cir.coro.ret_point {
-    cir.coro.body {
-      cir.await(user, ready : {
-        cir.condition(%arg0)
-      }, suspend : {
-        cir.coro.suspend_point
-      }, resume : {
-        cir.yield
-      },)
-      cir.co_return
-    }
-    cir.yield
-  }, ret : {
-      cir.return
-  }
-  cir.trap
-}
-
-// CHECK: cir.func coroutine @coro_body
-// CHECK:   cir.coro.body {
-// CHECK:   }
diff --git a/clang/test/CIR/IR/coro-ret-point.cir b/clang/test/CIR/IR/coro-ret-point.cir
deleted file mode 100644
index 44afdc6407931b..00000000000000
--- a/clang/test/CIR/IR/coro-ret-point.cir
+++ /dev/null
@@ -1,23 +0,0 @@
-// RUN: cir-opt %s --verify-roundtrip | FileCheck %s
-
-cir.func coroutine @coro_ret_point(%arg0 : !cir.bool) {
-  cir.coro.ret_point {
-    cir.coro.body {
-      cir.await(user, ready : {
-        cir.condition(%arg0)
-      }, suspend : {
-        cir.coro.suspend_point
-      }, resume : {
-        cir.yield
-      },)
-      cir.yield
-    }
-    cir.yield
-  }, ret : {
-      cir.return
-  }
-  cir.trap
-}
-
-// CHECK: cir.coro.ret_point {
-// CHECK: }, ret : {
diff --git a/clang/test/CIR/IR/coroutine.cir b/clang/test/CIR/IR/coroutine.cir
new file mode 100644
index 00000000000000..d9ef98936376a6
--- /dev/null
+++ b/clang/test/CIR/IR/coroutine.cir
@@ -0,0 +1,36 @@
+// RUN: cir-opt %s --verify-roundtrip | FileCheck %s
+
+cir.func coroutine @valid_coroutine(%arg0 : !cir.bool) {
+  cir.coroutine initialSuspend : {
+    cir.await(init, ready : {
+      cir.condition(%arg0)
+    }, suspend : {
+      cir.coro.suspend_point
+    }, resume : {
+      cir.yield
+    },)
+    cir.yield
+  }, body : {
+    cir.yield
+  }, finalSuspend : {
+    cir.yield
+  }, destroy : {
+    cir.yield
+  }, exit : {
+    cir.return
+  }
+  cir.trap
+}
+
+// CHECK: cir.func coroutine @valid_coroutine
+// CHECK: cir.coroutine initialSuspend : {
+// CHECK: cir.await(init
+// CHECK: }, body : {
+// CHECK: cir.yield
+// CHECK: }, finalSuspend : {
+// CHECK: cir.yield
+// CHECK: }, destroy : {
+// CHECK: cir.yield
+// CHECK: }, exit : {
+// CHECK: cir.return
+// CHECK: }
diff --git a/clang/test/CIR/IR/func.cir b/clang/test/CIR/IR/func.cir
index e7420d56022dbe..2070df469d142b 100644
--- a/clang/test/CIR/IR/func.cir
+++ b/clang/test/CIR/IR/func.cir
@@ -109,22 +109,25 @@ cir.func @ullfunc() -> !u64i {
 // CHECK: }
 
 cir.func coroutine @coro() {
-  cir.coro.ret_point {
-    cir.coro.body {
-      cir.await(init, ready : {
-        %0 = cir.alloca "" align(1) : !cir.ptr<!cir.bool>
-        %1 = cir.load align(1) %0 : !cir.ptr<!cir.bool>, !cir.bool
-        cir.condition(%1)
-      }, suspend : {
-        cir.coro.suspend_point
-      }, resume : {
-        cir.yield
-      },)
+  cir.coroutine initialSuspend : {
+    cir.await(init, ready : {
+      %0 = cir.alloca "" align(1) : !cir.ptr<!cir.bool>
+      %1 = cir.load align(1) %0 : !cir.ptr<!cir.bool>, !cir.bool
+      cir.condition(%1)
+    }, suspend : {
+      cir.coro.suspend_point
+    }, resume : {
       cir.yield
-    }
+    },)
+    cir.yield
+  }, body : {
+    cir.yield
+  }, finalSuspend : {
+    cir.yield
+  }, destroy : {
     cir.yield
-  }, ret : {
-      cir.return
+  }, exit : {
+    cir.return
   }
   cir.trap
 }
diff --git a/clang/test/CIR/IR/invalid-await.cir b/clang/test/CIR/IR/invalid-await.cir
index 8e40e0886428fa..813f10b65b519b 100644
--- a/clang/test/CIR/IR/invalid-await.cir
+++ b/clang/test/CIR/IR/invalid-await.cir
@@ -1,43 +1,52 @@
 // RUN: cir-opt %s -verify-diagnostics -split-input-file
-cir.func coroutine @bad_task() { // expected-error {{coroutine body must use at least one cir.await op}}
+cir.func coroutine @bad_task() { // expected-error {{coroutine function must have exactly one cir.coroutine op}}
   cir.return
 }
 
 // -----
 
 cir.func coroutine @missing_condition() {
-  cir.coro.ret_point {
-    cir.await(user, ready : { // expected-error {{ready region must end with cir.condition}}
+  cir.coroutine initialSuspend : {
+    cir.await(init, ready : { // expected-error {{ready region must end with cir.condition}}
       cir.yield
     }, suspend : {
       cir.coro.suspend_point
     }, resume : {
       cir.yield
     },)
-    cir.coro.body {
-    }
     cir.yield
-  }, ret : {
-      cir.return
+  }, body : {
+    cir.yield
+  }, finalSuspend : {
+    cir.yield
+  }, destroy : {
+    cir.yield
+  }, exit : {
+    cir.return
   }
   cir.trap
 }
 
-cir.func coroutine @missing_suspend(%arg0 : !cir.bool) {
-  cir.coro.ret_point {
-    cir.coro.body {
-      cir.await(user, ready : { // expected-error {{suspend region must end with cir.coro.suspend_point}}
-        cir.condition(%arg0)
-      }, suspend : {
-        cir.yield
-      }, resume : {
-        cir.yield
-      },)
+// -----
+
+cir.func coroutine @missing_suspend_point(%arg0 : !cir.bool) {
+  cir.coroutine initialSuspend : {
+    cir.await(init, ready : { // expected-error {{suspend region must end with cir.coro.suspend_point}}
+      cir.condition(%arg0)
+    }, suspend : { 
       cir.yield
-    }
+    }, resume : {
+      cir.yield
+    },)
+    cir.yield
+  }, body : {
+    cir.yield
+  }, finalSuspend : {
+    cir.yield
+  }, destroy : {
     cir.yield
-  }, ret : {
-      cir.return
+  }, exit : {
+    cir.return
   }
   cir.trap
 }
diff --git a/clang/test/CIR/IR/invalid-co-return.cir b/clang/test/CIR/IR/invalid-co-return.cir
index 115bfbac26d427..3f3cbac044a4e6 100644
--- a/clang/test/CIR/IR/invalid-co-return.cir
+++ b/clang/test/CIR/IR/invalid-co-return.cir
@@ -1,5 +1,29 @@
 // RUN: cir-opt %s -verify-diagnostics -split-input-file
 
-cir.func @must_be_inside_coro_body() { 
-  cir.co_return // expected-error {{expects ancestor op 'cir.coro.body'}}
+cir.func @must_be_inside_coroutine() {
+  cir.co_return // expected-error {{expects ancestor op 'cir.coroutine'}}
+}
+
+// -----
+
+cir.func coroutine @must_be_in_body(%arg0 : !cir.bool) {
+  cir.coroutine initialSuspend : {
+    cir.await(init, ready : {
+      cir.condition(%arg0)
+    }, suspend : {
+      cir.coro.suspend_point
+    }, resume : {
+      cir.co_return // expected-error {{must be inside the cir.coroutine body region}}
+    },)
+    cir.yield
+  }, body : {
+    cir.yield
+  }, finalSuspend : {
+    cir.yield
+  }, destroy : {
+    cir.yield
+  }, exit : {
+    cir.return
+  }
+  cir.trap
 }
diff --git a/clang/test/CIR/IR/invalid-coro-body.cir b/clang/test/CIR/IR/invalid-coro-body.cir
deleted file mode 100644
index c72ec6e4450fbb..00000000000000
--- a/clang/test/CIR/IR/invalid-coro-body.cir
+++ /dev/null
@@ -1,21 +0,0 @@
-// RUN: cir-opt %s -verify-diagnostics -split-input-file
-
-cir.func @must_be_inside_coroutine() { 
-  cir.coro.body { // expected-error {{enclosing function must be a coroutine}}
-
-  }
-}
-
-cir.func coroutine @must_have_one_coro_body(%arg0 : !cir.bool) {  // expected-error {{coroutine function must have exactly one cir.body op}}
-  cir.coro.body {
-    cir.await(user, ready : {
-      cir.condition(%arg0)
-    }, suspend : {
-      cir.coro.suspend_point
-    }, resume : {
-      cir.yield
-    },)
-  }
-  cir.coro.body {
-  }
-}
diff --git a/clang/test/CIR/IR/invalid-coro-ret-point.cir b/clang/test/CIR/IR/invalid-coro-ret-point.cir
deleted file mode 100644
index 3e65f094ccb63b..00000000000000
--- a/clang/test/CIR/IR/invalid-coro-ret-point.cir
+++ /dev/null
@@ -1,55 +0,0 @@
-// RUN: cir-opt %s -verify-diagnostics -split-input-file
-
-cir.func coroutine @coro_ret_point_body_must_end_in_yield(%arg0 : !cir.bool) {
-  cir.coro.ret_point { // expected-error {{body region must terminate with 'cir.yield'}}
-    cir.coro.body {
-      cir.await(user, ready : {
-        cir.condition(%arg0)
-      }, suspend : {
-        cir.coro.suspend_point
-      }, resume : {
-        cir.yield
-      },)
-    }
-    cir.trap
-  }, ret : {
-      cir.return
-  }
-  cir.trap
-}
-
-cir.func coroutine @coro_ret_point_body_must_not_contain_return(%arg0 : !cir.bool) {
-  cir.coro.ret_point { // expected-error {{body region must not contain 'cir.return' operations}}
-    cir.coro.body {
-      cir.await(user, ready : {
-        cir.condition(%arg0)
-      }, suspend : {
-        cir.coro.suspend_point
-      }, resume : {
-        cir.yield
-      },)
-    }
-    cir.return
-  }, ret : {
-      cir.return
-  }
-  cir.trap
-}
-
-cir.func coroutine @coro_ret_point_return_region_must_end_in_return(%arg0 : !cir.bool) {
-  cir.coro.ret_point { // expected-error {{return region must terminate with 'cir.return'}}
-    cir.coro.body {
-      cir.await(user, ready : {
-        cir.condition(%arg0)
-      }, suspend : {
-        cir.coro.suspend_point
-      }, resume : {
-        cir.yield
-      },)
-    }
-    cir.yield
-  }, ret : {
-      cir.trap
-  }
-  cir.trap
-}
diff --git a/clang/test/CIR/IR/invalid-coro-suspend.cir b/clang/test/CIR/IR/invalid-coro-suspend.cir
deleted file mode 100644
index d03769ed663060..00000000000000
--- a/clang/test/CIR/IR/invalid-coro-suspend.cir
+++ /dev/null
@@ -1,23 +0,0 @@
-// RUN: cir-opt %s -verify-diagnostics -split-input-file
-
-
-cir.func coroutine @must_have_one_coro_ret_point(%arg0 : !cir.bool) {  // expected-error {{coroutine function must have exactly one cir.coro.ret_point op}}
-  cir.coro.ret_point {
-    cir.coro.body {
-      cir.await(user, ready : {
-        cir.condition(%arg0)
-      }, suspend : {
-        cir.coro.suspend_point
-      }, resume : {
-        cir.yield
-      },)
-    }
-  cir.yield
-  }, ret : {
-      cir.return
-  }
-  cir.coro.ret_point {
-  }, ret : {
-  }
-  cir.trap
-}
diff --git a/clang/test/CIR/IR/invalid-coroutine.cir b/clang/test/CIR/IR/invalid-coroutine.cir
new file mode 100644
index 00000000000000..0ca25aed13e105
--- /dev/null
+++ b/clang/test/CIR/IR/invalid-coroutine.cir
@@ -0,0 +1,289 @@
+// RUN: cir-opt %s -verify-diagnostics -split-input-file
+
+cir.func coroutine @missing_initial_await() {
+  cir.coroutine initialSuspend : { // expected-error {{must have exactly one 'init' cir.await in 'initial_suspend'}}
+    cir.yield
+  }, body : {
+    cir.yield
+  }, finalSuspend : {
+    cir.yield
+  }, destroy : {
+    cir.yield
+  }, exit : {
+    cir.return
+  } 
+}
+
+// -----
+
+cir.func coroutine @multiple_initial_awaits(%arg0 : !cir.bool) {
+  cir.coroutine initialSuspend : { // expected-error {{must have exactly one 'init' cir.await in 'initial_suspend'}}
+    cir.await(init, ready : {
+      cir.condition(%arg0)
+    }, suspend : {
+      cir.coro.suspend_point
+    }, resume : {
+      cir.yield
+    },)
+    cir.await(init, ready : {
+      cir.condition(%arg0)
+    }, suspend : {
+      cir.coro.suspend_point
+    }, resume : {
+      cir.yield
+    },)
+    cir.yield
+  }, body : {
+    cir.yield
+  }, finalSuspend : {
+    cir.yield
+  }, destroy : {
+    cir.yield
+  }, exit : {
+    cir.return
+  }
+}
+
+// -----
+
+cir.func coroutine @wrong_initial_await_kind(%arg0 : !cir.bool) {
+  cir.coroutine initialSuspend : { // expected-error {{'initial_suspend' must not contain any cir.await other than the 'init' one}}
+    cir.await(user, ready : {
+      cir.condition(%arg0)
+    }, suspend : {
+      cir.coro.suspend_point
+    }, resume : {
+      cir.yield
+    },)
+    cir.yield
+  }, body : {
+    cir.yield
+  }, finalSuspend : {
+    cir.yield
+  }, destroy : {
+    cir.yield
+  }, exit : {
+    cir.return
+  }
+  cir.trap
+}
+
+// -----
+
+cir.func coroutine @initial_suspend_wrong_terminator(%arg0 : !cir.bool) {
+  cir.coroutine initialSuspend : { // expected-error {{'initial_suspend' must end with cir.yield}}
+    cir.await(init, ready : {
+      cir.condition(%arg0)
+    }, suspend : {
+      cir.coro.suspend_point
+    }, resume : {
+      cir.yield
+    },)
+    cir.return
+  }, body : {
+    cir.yield
+  }, finalSuspend : {
+    cir.yield
+  }, destroy : {
+    cir.yield
+  }, exit : {
+    cir.return
+  }
+  cir.trap
+}
+
+// -----
+
+cir.func coroutine @body_wrong_terminator(%arg0 : !cir.bool) {
+  cir.coroutine initialSuspend : { // expected-error {{'body' must end with cir.yield or cir.co_return}}
+    cir.await(init, ready : {
+      cir.condition(%arg0)
+    }, suspend : {
+      cir.coro.suspend_point
+    }, resume : {
+      cir.yield
+    },)
+    cir.yield
+  }, body : {
+    cir.return
+  }, finalSuspend : {
+    cir.yield
+  }, destroy : {
+    cir.yield
+  }, exit : {
+    cir.return
+  }
+  cir.trap
+}
+
+// -----
+
+cir.func coroutine @wrong_final_await_kind(%arg0 : !cir.bool) {
+  cir.coroutine initialSuspend : { // expected-error {{'final_suspend' must not contain any cir.await other than the 'final' one}}
+    cir.await(init, ready : {
+      cir.condition(%arg0)
+    }, suspend : {
+      cir.coro.suspend_point
+    }, resume : {
+      cir.yield
+    },)
+    cir.yield
+  }, body : {
+    cir.yield
+  }, finalSuspend : {
+    cir.await(user, ready : {
+      cir.condition(%arg0)
+    }, suspend : {
+      cir.coro.suspend_point
+    }, resume : {
+      cir.yield
+    },)
+    cir.yield
+  }, destroy : {
+    cir.yield
+  }, exit : {
+    cir.return
+  }
+  cir.trap
+}
+
+// -----
+
+cir.func coroutine @final_suspend_wrong_terminator(%arg0 : !cir.bool) {
+  cir.coroutine initialSuspend : { // expected-error {{'final_suspend' must end with cir.yield}}
+    cir.await(init, ready : {
+      cir.condition(%arg0)
+    }, suspend : {
+      cir.coro.suspend_point
+    }, resume : {
+      cir.yield
+    },)
+    cir.yield
+  }, body : {
+    cir.yield
+  }, finalSuspend : {
+    cir.return
+  }, destroy : {
+    cir.yield
+  }, exit : {
+    cir.return
+  }
+  cir.trap
+}
+
+// -----
+
+cir.func coroutine @destroy_wrong_terminator(%arg0 : !cir.bool) {
+  cir.coroutine initialSuspend : { // expected-error {{'destroy' must end with cir.yield}}
+    cir.await(init, ready : {
+      cir.condition(%arg0)
+    }, suspend : {
+      cir.coro.suspend_point
+    }, resume : {
+      cir.yield
+    },)
+    cir.yield
+  }, body : {
+    cir.yield
+  }, finalSuspend : {
+    cir.yield
+  }, destroy : {
+    cir.return
+  }, exit : {
+    cir.return
+  }
+  cir.trap
+}
+
+// -----
+
+cir.func coroutine @exit_wrong_terminator(%arg0 : !cir.bool) {
+  cir.coroutine initialSuspend : { // expected-error {{'exit' must end with cir.return}}
+    cir.await(init, ready : {
+      cir.condition(%arg0)
+    }, suspend : {
+      cir.coro.suspend_point
+    }, resume : {
+      cir.yield
+    },)
+    cir.yield
+  }, body : {
+    cir.yield
+  }, finalSuspend : {
+    cir.yield
+  }, destroy : {
+    cir.yield
+  }, exit : {
+    cir.yield
+  }
+  cir.trap
+}
+
+// -----
+
+cir.func coroutine @final_suspend_multiple_awaits(%arg0 : !cir.bool) {
+  cir.coroutine initialSuspend : { // expected-error {{must have one or zero 'final' cir.await in 'final_suspend'}}
+    cir.await(init, ready : {
+      cir.condition(%arg0)
+    }, suspend : {
+      cir.coro.suspend_point
+    }, resume : {
+      cir.yield
+    },)
+    cir.yield
+  }, body : {
+    cir.yield
+  }, finalSuspend : {
+    cir.await(final, ready : {
+      cir.condition(%arg0)
+    }, suspend : {
+      cir.coro.suspend_point
+    }, resume : {
+      cir.yield
+    },)
+    cir.await(final, ready : {
+      cir.condition(%arg0)
+    }, suspend : {
+      cir.coro.suspend_point
+    }, resume : {
+      cir.yield
+    },)
+    cir.yield
+  }, destroy : {
+    cir.yield
+  }, exit : {
+    cir.return
+  }
+  cir.trap
+}
+
+// -----
+
+cir.func coroutine @final_suspend_invalid_await(%arg0 : !cir.bool) {
+  cir.coroutine initialSuspend : { // expected-error {{'final_suspend' must not contain any cir.await other than the 'final' one}}
+    cir.await(init, ready : {
+      cir.condition(%arg0)
+    }, suspend : {
+      cir.coro.suspend_point
+    }, resume : {
+      cir.yield
+    },)
+    cir.yield
+  }, body : {
+    cir.yield
+  }, finalSuspend : {
+    cir.await(user, ready : {
+      cir.condition(%arg0)
+    }, suspend : {
+      cir.coro.suspend_point
+    }, resume : {
+      cir.yield
+    },)
+    cir.yield
+  }, destroy : {
+    cir.yield
+  }, exit : {
+    cir.return
+  }
+  cir.trap
+}

>From 6e3e958a883f2baa6a92a3de6307dc74ffa9b455 Mon Sep 17 00:00:00 2001
From: Andres Salamanca <andrealebarbaritos at gmail.com>
Date: Thu, 3 Sep 2026 21:23:16 -0500
Subject: [PATCH 07/12] Fix after rebase

---
 .../test/CIR/CodeGenCoroutines/coro-task.cpp  | 184 ++++++++----------
 1 file changed, 85 insertions(+), 99 deletions(-)

diff --git a/clang/test/CIR/CodeGenCoroutines/coro-task.cpp b/clang/test/CIR/CodeGenCoroutines/coro-task.cpp
index b4ee46b94287ac..cbdd177d037076 100644
--- a/clang/test/CIR/CodeGenCoroutines/coro-task.cpp
+++ b/clang/test/CIR/CodeGenCoroutines/coro-task.cpp
@@ -74,10 +74,13 @@ VoidTask silly_task() {
 
 // Call promise.get_return_object() to retrieve the task object.
 
+// Empty records are ignored, so calls returning one return nothing and the
+// slot takes a poison placeholder.
 
 // CIR: cir.coroutine initialSuspend : {
 
-// CIR:   %[[RetObj:.*]] = cir.call @_ZN5folly4coro4TaskIvE12promise_type17get_return_objectEv(%[[VoidPromisseAddr]]) nothrow : {{.*}} -> ![[VoidTask]]
+// CIR:   cir.call @_ZN5folly4coro4TaskIvE12promise_type17get_return_objectEv(%[[VoidPromisseAddr]]) nothrow : {{.*}} -> ()
+// CIR:   %[[RetObj:.*]] = cir.const #cir.poison : ![[VoidTask]]
 // CIR:   cir.store{{.*}} %[[RetObj]], %[[VoidTaskAddr]] : ![[VoidTask]]
 
 // OGCG: call void @llvm.lifetime.start.p0(ptr %[[VoidPromisseAddr]])
@@ -90,7 +93,8 @@ VoidTask silly_task() {
 // Effectively execute `coawait promise_type::initial_suspend()` by calling initial_suspend() and getting
 // the suspend_always struct to use for cir.await.
 
-// CIR:   %[[Tmp0:.*]] = cir.call @_ZN5folly4coro4TaskIvE12promise_type15initial_suspendEv(%[[VoidPromisseAddr]])
+// CIR:   cir.call @_ZN5folly4coro4TaskIvE12promise_type15initial_suspendEv(%[[VoidPromisseAddr]])
+// CIR:   %[[Tmp0:.*]] = cir.const #cir.poison : ![[SuspendAlways]]
 // CIR:   cir.store{{.*}} %[[Tmp0]], %[[SuspendAlwaysAddr]]
 
 // OGCG: call void @_ZN5folly4coro4TaskIvE12promise_type15initial_suspendEv(ptr noundef nonnull align 1 dereferenceable(1) %[[VoidPromisseAddr]])
@@ -126,7 +130,7 @@ VoidTask silly_task() {
 // CIR-NEXT:     %[[CoroHandlePromiseReload:.*]] = cir.load{{.*}} %[[CoroHandlePromiseAddr]]
 // CIR-NEXT:     cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_typeEEES_IT_E(%[[CoroHandleVoidAddr]])
 // CIR-NEXT:     %[[CoroHandleVoidReload:.*]] = cir.load{{.*}} %[[CoroHandleVoidAddr]] : !cir.ptr<![[CoroHandleVoid]]>, ![[CoroHandleVoid]]
-// CIR-NEXT:     cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SuspendAlwaysAddr]], %[[CoroHandleVoidReload]])
+// CIR-NEXT:     cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SuspendAlwaysAddr]])
 // CIR-NEXT:     cir.coro.suspend_point
 
 // OGCG: init.suspend:
@@ -155,7 +159,7 @@ VoidTask silly_task() {
 // - The promise call
 // - The final suspend co_await
 // - Return
- 
+
 // CIR:   }, body : {
 
 // The actual user written co_await
@@ -222,7 +226,7 @@ VoidTask silly_task() {
 // CIR-NEXT: cir.coro.intrinsic.end(%[[CoroEndArg0]], %[[CoroEndArg1]], %[[TK_NONE]]) : (!cir.ptr<!void>, !cir.bool, token){{.*}}
 
 // CIR: %[[Tmp1:.*]] = cir.load{{.*}} %[[VoidTaskAddr]]
-// CIR: cir.return %[[Tmp1]]
+// CIR: cir.return
 // CIR: }
 
 // OGCG: coro.ret:
@@ -250,20 +254,23 @@ folly::coro::Task<int> byRef(const std::string& s) {
 // CIR:     cir.store {{.*}} %[[LOAD]], %[[AllocaFnUse]] : !cir.ptr<![[StdString]]>, !cir.ptr<!cir.ptr<![[StdString]]>>
 
 // CIR:    cir.coroutine initialSuspend : {
-// CIR:     %[[RetObj:.*]] = cir.call @_ZN5folly4coro4TaskIiE12promise_type17get_return_objectEv(%[[IntPromisseAddr]]) nothrow : {{.*}} -> ![[IntTask]]
+// CIR:     cir.call @_ZN5folly4coro4TaskIiE12promise_type17get_return_objectEv(%[[IntPromisseAddr]]) nothrow : {{.*}} -> ()
+// CIR:     %[[RetObj:.*]] = cir.const #cir.poison : ![[IntTask]]
 // CIR:     cir.store {{.*}} %[[RetObj]], %[[IntTaskAddr]] : ![[IntTask]]
-// CIR:     %[[Tmp0:.*]] = cir.call @_ZN5folly4coro4TaskIiE12promise_type15initial_suspendEv(%[[IntPromisseAddr]])
+// CIR:     cir.call @_ZN5folly4coro4TaskIiE12promise_type15initial_suspendEv(%[[IntPromisseAddr]])
+// CIR:     %[[Tmp0:.*]] = cir.const #cir.poison : ![[SuspendAlways]]
 // CIR:     cir.store{{.*}} %[[Tmp0]], %[[SuspendAlwaysAddr]]
 // CIR:     cir.await(init, ready : {
 // CIR:       %[[TmpCallRes:.*]] = cir.call @_ZNSt14suspend_always11await_readyEv(%[[SuspendAlwaysAddr]])
 // CIR:       cir.condition(%[[TmpCallRes]])
 // CIR:     }, suspend : {
-// CIR:       %[[FromAddrRes:.*]] = cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIiE12promise_typeEE12from_addressEPv(%[[CoroFrameAddr:.*]])
+// CIR:       cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIiE12promise_typeEE12from_addressEPv(%[[CoroFrameAddr:.*]])
+// CIR:       %[[FromAddrRes:.*]] = cir.const #cir.poison : ![[CoroHandlePromiseInt]]
 // CIR:       cir.store{{.*}} %[[FromAddrRes]], %[[CoroHandlePromiseAddr]] : ![[CoroHandlePromiseInt]]
 // CIR:       %[[CoroHandlePromiseReload:.*]] = cir.load{{.*}} %[[CoroHandlePromiseAddr]]
-// CIR:       cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIiE12promise_typeEEES_IT_E(%[[CoroHandleVoidAddr]], %[[CoroHandlePromiseReload]])
+// CIR:       cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIiE12promise_typeEEES_IT_E(%[[CoroHandleVoidAddr]])
 // CIR:       %[[CoroHandleVoidReload:.*]] = cir.load{{.*}} %[[CoroHandleVoidAddr]] : !cir.ptr<![[CoroHandleVoid]]>, ![[CoroHandleVoid]]
-// CIR:       cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SuspendAlwaysAddr]], %[[CoroHandleVoidReload]])
+// CIR:       cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SuspendAlwaysAddr]])
 // CIR:       cir.coro.suspend_point
 // CIR:     }, resume : {
 // CIR:       cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SuspendAlwaysAddr]])
@@ -299,7 +306,7 @@ folly::coro::Task<void> silly_coro() {
 // Make sure we properly handle OnFallthrough coro body sub stmt and
 // check there are not multiple co_returns emitted.
 
-// CIR: cir.func coroutine {{.*}} @_Z10silly_corov() {{.*}} ![[VoidTask]]
+// CIR: cir.func coroutine {{.*}} @_Z10silly_corov() attributes
 // CIR: cir.coroutine initialSuspend : {
 // CIR:   cir.await(init, ready : {
 // CIR:   }, suspend : {
@@ -343,18 +350,20 @@ folly::coro::Task<void> yield1() {
 
 // CIR: cir.coroutine initialSuspend : {
 // initial_suspend + await(init)
-// CIR:   %[[INIT_SUSP:.*]] = cir.call @_ZN5folly4coro4TaskIvE12promise_type15initial_suspendEv(%[[PROMISE]]){{.*}}
+// CIR:   cir.call @_ZN5folly4coro4TaskIvE12promise_type15initial_suspendEv(%[[PROMISE]]){{.*}}
+// CIR:   %[[INIT_SUSP:.*]] = cir.const #cir.poison : ![[SuspendAlways]]
 // CIR:   cir.store{{.*}} %[[INIT_SUSP]], %[[SUSP0]]
 // CIR:   cir.await(init, ready : {
 // CIR:     %[[READY0:.*]] = cir.call @_ZNSt14suspend_always11await_readyEv(%[[SUSP0]]){{.*}}
 // CIR:     cir.condition(%[[READY0]])
 // CIR:   }, suspend : {
-// CIR:     %[[FROMADDR0:.*]] = cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIvE12promise_typeEE12from_addressEPv(%{{.*}}){{.*}}
+// CIR:     cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIvE12promise_typeEE12from_addressEPv(%{{.*}}){{.*}}
+// CIR:     %[[FROMADDR0:.*]] = cir.const #cir.poison : ![[CoroHandlePromiseVoid]]
 // CIR:     cir.store{{.*}} %[[FROMADDR0]], %[[CH_PROM0]]
 // CIR:     %[[PROM_RELOAD0:.*]] = cir.load{{.*}} %[[CH_PROM0]]
-// CIR:     cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_typeEEES_IT_E(%[[CH_VOID0]], %[[PROM_RELOAD0]]){{.*}}
+// CIR:     cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_typeEEES_IT_E(%[[CH_VOID0]]){{.*}}
 // CIR:     %[[VOID_RELOAD0:.*]] = cir.load{{.*}} %[[CH_VOID0]]
-// CIR:     cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SUSP0]], %[[VOID_RELOAD0]]){{.*}}
+// CIR:     cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SUSP0]]){{.*}}
 // CIR:     cir.coro.suspend_point
 // CIR:   }, resume : {
 // CIR:     cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SUSP0]]){{.*}}
@@ -364,21 +373,24 @@ folly::coro::Task<void> yield1() {
 
 // yield_value + await(yield)
 // CIR: }, body : {
-// CIR:    %[[YIELD_TASK:.*]] = cir.call @_Z5yieldv(){{.*}}
+// CIR:    cir.call @_Z5yieldv(){{.*}}
+// CIR:    %[[YIELD_TASK:.*]] = cir.const #cir.poison : ![[VoidTask]]
 // CIR:    cir.store{{.*}} %[[YIELD_TASK]], %[[T_ADDR]]
 // CIR:    %[[AWAITER:.*]] = cir.load{{.*}} %[[AWAITER_COPY_ADDR]]
-// CIR:    %[[YIELD_SUSP:.*]] = cir.call @_ZN5folly4coro4TaskIvE12promise_type11yield_valueES2_(%[[PROMISE]], %[[AWAITER]]){{.*}}
+// CIR:    cir.call @_ZN5folly4coro4TaskIvE12promise_type11yield_valueES2_(%[[PROMISE]]){{.*}}
+// CIR:    %[[YIELD_SUSP:.*]] = cir.const #cir.poison : ![[SuspendAlways]]
 // CIR:    cir.store{{.*}} %[[YIELD_SUSP]], %[[SUSP1]]
 // CIR:    cir.await(yield, ready : {
 // CIR:      %[[READY1:.*]] = cir.call @_ZNSt14suspend_always11await_readyEv(%[[SUSP1]]){{.*}}
 // CIR:      cir.condition(%[[READY1]])
 // CIR:    }, suspend : {
-// CIR:      %[[FROMADDR1:.*]] = cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIvE12promise_typeEE12from_addressEPv(%{{.*}}){{.*}}
+// CIR:      cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIvE12promise_typeEE12from_addressEPv(%{{.*}}){{.*}}
+// CIR:      %[[FROMADDR1:.*]] = cir.const #cir.poison : ![[CoroHandlePromiseVoid]]
 // CIR:      cir.store{{.*}} %[[FROMADDR1]], %[[CH_PROM1]]
 // CIR:      %[[PROM_RELOAD1:.*]] = cir.load{{.*}} %[[CH_PROM1]]
-// CIR:      cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_typeEEES_IT_E(%[[CH_VOID1]], %[[PROM_RELOAD1]]){{.*}}
+// CIR:      cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_typeEEES_IT_E(%[[CH_VOID1]]){{.*}}
 // CIR:      %[[VOID_RELOAD1:.*]] = cir.load{{.*}} %[[CH_VOID1]]
-// CIR:      cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SUSP1]], %[[VOID_RELOAD1]]){{.*}}
+// CIR:      cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SUSP1]]){{.*}}
 // CIR:      cir.coro.suspend_point
 // CIR:    }, resume : {
 // CIR:      cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SUSP1]]){{.*}}
@@ -389,19 +401,20 @@ folly::coro::Task<void> yield1() {
 // CIR: }, finalSuspend : {
 
 // return_void + await(final)
-
-// CIR:   %[[FINAL_SUSP:.*]] = cir.call @_ZN5folly4coro4TaskIvE12promise_type13final_suspendEv(%[[PROMISE]]){{.*}}
+// CIR:   cir.call @_ZN5folly4coro4TaskIvE12promise_type13final_suspendEv(%[[PROMISE]]){{.*}}
+// CIR:   %[[FINAL_SUSP:.*]] = cir.const #cir.poison : ![[SuspendAlways]]
 // CIR:   cir.store{{.*}} %[[FINAL_SUSP]], %[[SUSP2]]
 // CIR:   cir.await(final, ready : {
 // CIR:     %[[READY2:.*]] = cir.call @_ZNSt14suspend_always11await_readyEv(%[[SUSP2]]){{.*}}
 // CIR:     cir.condition(%[[READY2]])
 // CIR:   }, suspend : {
-// CIR:     %[[FROMADDR2:.*]] = cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIvE12promise_typeEE12from_addressEPv(%{{.*}}){{.*}}
+// CIR:     cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIvE12promise_typeEE12from_addressEPv(%{{.*}}){{.*}}
+// CIR:     %[[FROMADDR2:.*]] = cir.const #cir.poison : ![[CoroHandlePromiseVoid]]
 // CIR:     cir.store{{.*}} %[[FROMADDR2]], %[[CH_PROM2]]
 // CIR:     %[[PROM_RELOAD2:.*]] = cir.load{{.*}} %[[CH_PROM2]]
-// CIR:     cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_typeEEES_IT_E(%[[CH_VOID2]], %[[PROM_RELOAD2]]){{.*}}
+// CIR:     cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_typeEEES_IT_E(%[[CH_VOID2]]){{.*}}
 // CIR:     %[[VOID_RELOAD2:.*]] = cir.load{{.*}} %[[CH_VOID2]]
-// CIR:     cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SUSP2]], %[[VOID_RELOAD2]]){{.*}}
+// CIR:     cir.call @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(%[[SUSP2]]){{.*}}
 // CIR:     cir.coro.suspend_point
 // CIR:   }, resume : {
 // CIR:     cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SUSP2]]){{.*}}
@@ -412,7 +425,7 @@ folly::coro::Task<void> yield1() {
 // CIR: }, exit : {
 // CIR: cir.coro.intrinsic.end(%{{.*}}, %{{.*}}, %{{.*}}){{.*}}
 // CIR: %[[RETLOAD:.*]] = cir.load{{.*}} %[[RETVAL]]
-// CIR: cir.return %[[RETLOAD]]
+// CIR: cir.return
 // CIR: }
 
 // CHECK: }
@@ -437,7 +450,8 @@ folly::coro::Task<int> go1() {
 // CIR: }, body : {
 // CIR:   %[[One:.*]] = cir.const #cir.int<1> : !s32i
 // CIR:   cir.store{{.*}} %[[One]], %[[OneAddr]] : !s32i, !cir.ptr<!s32i>
-// CIR:   %[[IntTaskTmp:.*]] = cir.call @_Z2goRKi(%[[OneAddr]]) : (!cir.ptr<!s32i>{{.*}}) -> ![[IntTask]]
+// CIR:   cir.call @_Z2goRKi(%[[OneAddr]]) : (!cir.ptr<!s32i>{{.*}}) -> ()
+// CIR:   %[[IntTaskTmp:.*]] = cir.const #cir.poison : ![[IntTask]]
 // CIR:   cir.store{{.*}} %[[IntTaskTmp]], %[[IntTaskAddr]] : ![[IntTask]], !cir.ptr<![[IntTask]]>
 
 // CIR:   cir.await(user, ready : {
@@ -466,7 +480,7 @@ folly::coro::Task<int> go1_lambda() {
   co_return co_await task;
 }
 
-// CIR: cir.func coroutine {{.*}} @_ZZ10go1_lambdavENK3$_0clEv{{.*}} ![[IntTask]]
+// CIR: cir.func coroutine {{.*}} @_Z10go1_lambdav() attributes
 // CIR: cir.coroutine initialSuspend : {
 // CIR:   cir.await(init, ready : {
 // CIR:   }, suspend : {
@@ -474,9 +488,16 @@ folly::coro::Task<int> go1_lambda() {
 // CIR:   },)
 // CIR:   cir.yield
 // CIR: }, body : {
-// CIR:   %[[ONE:.*]] = cir.const #cir.int<1>
-// CIR:   cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE:.*]], %[[ONE]])
-// CIR:   cir.co_return
+// CIR:    cir.call @_ZZ10go1_lambdavENK3$_0clEv
+// CIR:    cir.await(user, ready : {
+// CIR:    }, suspend : {
+// CIR:    }, resume : {
+// CIR:      %[[RESUME_RES:.*]] = cir.call @_ZN5folly4coro4TaskIiE12await_resumeEv(%[[TASK:.*]])
+// CIR:      cir.store %[[RESUME_RES]], %[[resume_rval:.*]] : !s32i, !cir.ptr<!s32i>
+// CIR:    },)
+// CIR:    %[[TMP1:.*]] = cir.load %[[resume_rval:.*]] : !cir.ptr<!s32i>
+// CIR:    cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE:.*]], %[[TMP1]])
+// CIR:    cir.co_return
 // CIR: }, finalSuspend : {
 // CIR:   cir.await(final, ready : {
 // CIR:   }, suspend : {
@@ -484,7 +505,7 @@ folly::coro::Task<int> go1_lambda() {
 // CIR:   },)
 // CIR: }, destroy : {
 
-// CIR: cir.func coroutine {{.*}} @_Z10go1_lambdav() {{.*}} ![[IntTask]]
+// CIR: cir.func coroutine {{.*}} @_ZZ10go1_lambdavENK3$_0clEv{{.*}}) attributes
 // CIR: cir.coroutine initialSuspend : {
 // CIR:   cir.await(init, ready : {
 // CIR:   }, suspend : {
@@ -492,16 +513,9 @@ folly::coro::Task<int> go1_lambda() {
 // CIR:   },)
 // CIR:   cir.yield
 // CIR: }, body : {
-// CIR:   cir.call @_ZZ10go1_lambdavENK3$_0clEv
-// CIR:   cir.await(user, ready : {
-// CIR:   }, suspend : {
-// CIR:   }, resume : {
-// CIR:     %[[RESUME_RES:.*]] = cir.call @_ZN5folly4coro4TaskIiE12await_resumeEv(%[[TASK:.*]])
-// CIR:     cir.store %[[RESUME_RES]], %[[resume_rval:.*]] : !s32i, !cir.ptr<!s32i>
-// CIR:   },)
-// CIR:   %[[TMP1:.*]] = cir.load %[[resume_rval:.*]] : !cir.ptr<!s32i>
-// CIR:   cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE:.*]], %[[TMP1]])
-// CIR:   cir.co_return
+// CIR:    %[[ONE:.*]] = cir.const #cir.int<1>
+// CIR:    cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE:.*]], %[[ONE]])
+// CIR:    cir.co_return
 // CIR: }, finalSuspend : {
 // CIR:   cir.await(final, ready : {
 // CIR:   }, suspend : {
@@ -516,7 +530,7 @@ folly::coro::Task<int> go4() {
   co_return co_await std::move(task);
 }
 
-// CIR: cir.func coroutine{{.*}} @_ZZ3go4vENK3$_0clERKi(
+// CIR: cir.func coroutine {{.*}} @_Z3go4v() attributes
 // CIR: cir.coroutine initialSuspend : {
 // CIR:   cir.await(init, ready : {
 // CIR:   }, suspend : {
@@ -524,38 +538,20 @@ folly::coro::Task<int> go4() {
 // CIR:   },)
 // CIR:   cir.yield
 // CIR: }, body : {
-// CIR:   cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE:.*]], %[[I:.*]])
-// CIR:   cir.co_return
-// CIR: }, finalSuspend : {
-// CIR:   cir.await(final, ready : {
-// CIR:   }, suspend : {
-// CIR:   }, resume : {
-// CIR:   },)
-// CIR: }, destroy : {
-
-folly::coro::Task<int> go4() {
-  auto* fn = +[](int const& i) -> folly::coro::Task<int> { co_return i; };
-  auto task = fn(3);
-  co_return co_await std::move(task);
-}
-
-// CIR: cir.func coroutine {{.*}} @_Z3go4v() attributes
-
-// CIR: cir.coroutine initialSuspend : {
-// CIR:   cir.await(init, ready : {
-// CIR:   }, suspend : {
-// CIR:   }, resume : {
-// CIR:   },)
 
 // Get the lambda invoker ptr via `lambda operator folly::coro::Task<int> (*)(int const&)()`
-// CIR: %[[INVOKER:.*]] = cir.call @_ZZ3go4vENK3$_0cvPFN5folly4coro4TaskIiEERKiEEv(%{{.*}}) nothrow : {{.*}} -> (!cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>> {llvm.noundef})
-// CIR: cir.store{{.*}} %[[INVOKER]], %[[FN_ADDR:.*]] : !cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>, !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>>
-// CIR: %[[FN:.*]] = cir.load{{.*}} %[[FN_ADDR]] : !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>>, !cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>
-// CIR: %[[THREE:.*]] = cir.const #cir.int<3> : !s32i
-// CIR: cir.store{{.*}} %[[THREE]], %[[ARG:.*]] : !s32i, !cir.ptr<!s32i>
-
-// Call invoker, which calls operator() indirectly.
-// CIR: %[[CALLRES:.*]] = cir.call %[[FN]](%[[ARG]]) : (!cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>, !cir.ptr<!s32i> {{.*}}) -> ![[IntTask]]
+// CIR:  %[[INVOKER:.*]] = cir.call @_ZZ3go4vENK3$_0cvPFN5folly4coro4TaskIiEERKiEEv(%{{.*}}) nothrow : {{.*}} -> (!cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>> {llvm.noundef})
+// CIR:  cir.store{{.*}} %[[INVOKER]], %[[FN_ADDR:.*]] : !cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>, !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>>
+// CIR:  %[[FN:.*]] = cir.load{{.*}} %[[FN_ADDR]] : !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>>, !cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>>
+// CIR:  %[[THREE:.*]] = cir.const #cir.int<3> : !s32i
+// CIR:  cir.store{{.*}} %[[THREE]], %[[ARG:.*]] : !s32i, !cir.ptr<!s32i>
+
+// Call invoker, which calls operator() indirectly. The callee is bitcast to
+// the lowered signature.
+
+// CIR: %[[FN_LOWERED:.*]] = cir.cast bitcast %[[FN]] : !cir.ptr<!cir.func<(!cir.ptr<!s32i>) -> ![[IntTask]]>> -> !cir.ptr<!cir.func<(!cir.ptr<!s32i>)>>
+// CIR: cir.call %[[FN_LOWERED]](%[[ARG]]) : (!cir.ptr<!cir.func<(!cir.ptr<!s32i>)>>, !cir.ptr<!s32i> {{.*}}) -> ()
+// CIR: %[[CALLRES:.*]] = cir.const #cir.poison : ![[IntTask]]
 // CIR: cir.store{{.*}} %[[CALLRES]], %[[TASK_ADDR:.*]] : ![[IntTask]], !cir.ptr<![[IntTask]]>
 
 // CIR:  cir.await(user, ready : {
@@ -618,8 +614,8 @@ folly::coro::Task<int> co_returns(int x) {
 // CIR:      } else {
 // CIR:       cir.if {{.*}} {
 // CIR:         %[[MINUS_TWO:.*]] = cir.const #cir.int<-2>
-  // CIR         cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE]], %[[MINUS_TWO]])
-  // CIR         cir.co_return
+// CIR          cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE]], %[[MINUS_TWO]])
+// CIR          cir.co_return
 // CIR:         }
 // CIR:      }
 // CIR:    }
@@ -761,38 +757,28 @@ folly::coro::Task<int __complex__> complex_co_await() noexcept {
 // CIR:   },)
 
 // CIR: }, body : {
-// CIR:   %[[CALL:.*]] = cir.call @_Z9fetchDatav() nothrow : () -> !rec_folly3A3Acoro3A3ATask3C_Complex_int3E
-// CIR:   cir.store {{.*}} %[[CALL]], %[[COMPLEX_ADDR]] : !rec_folly3A3Acoro3A3ATask3C_Complex_int3E, !cir.ptr<!rec_folly3A3Acoro3A3ATask3C_Complex_int3E>
+// CIR:     cir.call @_Z9fetchDatav() nothrow : () -> ()
+// CIR:     %[[CALL:.*]] = cir.const #cir.poison : !rec_folly3A3Acoro3A3ATask3C_Complex_int3E
+// CIR:     cir.store {{.*}} %[[CALL]], %[[COMPLEX_ADDR]] : !rec_folly3A3Acoro3A3ATask3C_Complex_int3E, !cir.ptr<!rec_folly3A3Acoro3A3ATask3C_Complex_int3E>
 
 // CIR:     cir.await(user, ready : {
 // CIR:     }, suspend : {
 // CIR:     }, resume : {
-// CIR:       %[[RESUME_VAL:.*]] = cir.call @_ZN5folly4coro4TaskICiE12await_resumeEv(%[[COMPLEX_ADDR]]) : (!cir.ptr<!rec_folly3A3Acoro3A3ATask3C_Complex_int3E> {llvm.align = 1 : i64, llvm.dereferenceable = 1 : i64, llvm.nonnull, llvm.noundef}) -> (!cir.complex<!s32i> {llvm.noundef})
-// CIR:       cir.store %[[RESUME_VAL]], %[[RESUME_VAL_ADDR]] : !cir.complex<!s32i>, !cir.ptr<!cir.complex<!s32i>>
-// CIR:     },)
-// CIR:     %[[V:.*]] = cir.load %[[RESUME_VAL_ADDR]] : !cir.ptr<!cir.complex<!s32i>>, !cir.complex<!s32i>
-// CIR:     cir.yield
+// _Complex int is one eightbyte in class INTEGER, so it returns as !u64i and
+// is reloaded through the coerce slot.
+
+// CIR:         %[[RESUME_VAL:.*]] = cir.call @_ZN5folly4coro4TaskICiE12await_resumeEv(%[[COMPLEX_ADDR]]) : (!cir.ptr<!rec_folly3A3Acoro3A3ATask3C_Complex_int3E> {llvm.align = 1 : i64, llvm.dereferenceable = 1 : i64, llvm.nonnull, llvm.noundef}) -> (!u64i {llvm.noundef})
+// CIR:         cir.store %[[RESUME_VAL]], %[[COERCE_ADDR]] : !u64i, !cir.ptr<!u64i>
+// CIR:         %[[COERCE_CAST:.*]] = cir.cast bitcast %[[COERCE_ADDR]] : !cir.ptr<!u64i> -> !cir.ptr<!cir.complex<!s32i>>
+// CIR:         %[[RESUME_COMPLEX:.*]] = cir.load %[[COERCE_CAST]] : !cir.ptr<!cir.complex<!s32i>>, !cir.complex<!s32i>
+// CIR:         cir.store %[[RESUME_COMPLEX]], %[[RESUME_VAL_ADDR]] : !cir.complex<!s32i>, !cir.ptr<!cir.complex<!s32i>>
+// CIR:       },)
+// CIR:       %[[V:.*]] = cir.load %[[RESUME_VAL_ADDR]] : !cir.ptr<!cir.complex<!s32i>>, !cir.complex<!s32i>
+// CIR:       cir.yield
 // CIR: }, finalSuspend : {
-
 // CIR: }, destroy : {
 // CIR: }, exit : {
 
-// CIR: cir.func coroutine{{.*}} @_ZZ3go4vENK3$_0clERKi(
-// CIR: cir.cleanup.scope {
-// CIR:   cir.await(init, ready : {
-// CIR:   }, suspend : {
-// CIR:   }, resume : {
-// CIR:   },)
-// CIR:   cir.coro.body {
-// CIR:     cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE:.*]], %[[I:.*]])
-// CIR:     cir.co_return
-// CIR:   }
-// CIR:   cir.await(final, ready : {
-// CIR:   }, suspend : {
-// CIR:   }, resume : {
-// CIR:   },)
-// CIR: } cleanup  normal {
-
 // OGCG: define dso_local void @_Z16complex_co_awaitv()
 
 // OGCG: %[[RESUME_VAL_ADDR:.*]] = alloca { i32, i32 }, align 4

>From af118251d5b55b4a76c1c14709a0fd73d28d8f7d Mon Sep 17 00:00:00 2001
From: Andres Salamanca <andrealebarbaritos at gmail.com>
Date: Tue, 8 Sep 2026 17:20:21 -0500
Subject: [PATCH 08/12] Fix After Rebase

---
 clang/include/clang/CIR/Dialect/IR/CIROps.td | 41 --------------------
 1 file changed, 41 deletions(-)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index fccca08ee06ef0..c1d95c61345905 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -4953,47 +4953,6 @@ def CIR_AwaitOp : CIR_RegionBranchOpBase<"await", [
   let hasVerifier = 1;
   let hasLLVMLowering = false;
 }
-<<<<<<< HEAD
-//===----------------------------------------------------------------------===//
-// CoroBody
-//===----------------------------------------------------------------------===//
-def CIR_CoroBodyOp : CIR_RegionBranchOpBase<"coro.body", [
-  RecursivelySpeculatable, AutomaticAllocationScope, NoRegionArguments,
-  RecursiveMemoryEffects
-]> {
-  let summary = "Region containing the user-authored coroutine body";
-  let description = [{
-    The `cir.coro.body` operation models the region where the user-authored
-    coroutine code is emitted.
-
-    This operation serves as a structural boundary separating the coroutine
-    setup and teardown logic (e.g. initial suspend, final suspend, and cleanup)
-    from the user-provided statements inside the coroutine.
-
-    The body region contains the code corresponding to the original function
-    body, including `co_await` and `co_return` expressions. In particular,
-    `cir.co_return` operations inside this region mark coroutine exit points
-    and introduce structured control flow that transfers execution to the
-    final suspend point of the coroutine.
-  }];
-
-  let regions = (region AnyRegion:$body);
-
-  let skipDefaultBuilders = 1;
-
-  let builders = [
-    OpBuilder<(ins "BuilderCallbackRef":$bodyBuilder)>
-  ];
-
-  let assemblyFormat = [{
-    $body attr-dict
-  }];
-
-  let hasLLVMLowering = false;
-  let hasVerifier = 1;
-}
-=======
->>>>>>> 56098cff2da3 ([CIR] Add cir.coroutine and remove cir.coroBody)
 
 //===----------------------------------------------------------------------===//
 // CoReturnOp

>From 531c83239fd6471983ef1c92733b991c6e412936 Mon Sep 17 00:00:00 2001
From: Andres Salamanca <andrealebarbaritos at gmail.com>
Date: Mon, 21 Sep 2026 20:23:46 -0500
Subject: [PATCH 09/12] Update ControlFlowTest after rebase

---
 clang/unittests/CIR/ControlFlowTest.cpp | 95 ++++++++++++++++++++-----
 1 file changed, 79 insertions(+), 16 deletions(-)

diff --git a/clang/unittests/CIR/ControlFlowTest.cpp b/clang/unittests/CIR/ControlFlowTest.cpp
index d6b9fda5235e6d..5376d27c84608d 100644
--- a/clang/unittests/CIR/ControlFlowTest.cpp
+++ b/clang/unittests/CIR/ControlFlowTest.cpp
@@ -613,50 +613,113 @@ TEST_F(CIRControlFlowTest, TryOpWithCatchAll) {
   // have block arguments, so verifyControlFlowInterfaceConsistency fails.
 }
 
-TEST_F(CIRControlFlowTest, CoroBodyOp) {
-  // A coroutine body must contain at least one cir.await.
+TEST_F(CIRControlFlowTest, CoroutineOp) {
   OwningOpRef<ModuleOp> module = parse(R"CIR(
     cir.func coroutine @f(%arg0 : !cir.bool) {
-      cir.coro.body {
-        cir.await(user, ready : {
+      cir.coroutine initialSuspend : {
+        cir.await(init, ready : {
           cir.condition(%arg0)
         }, suspend : {
-          cir.yield
+          cir.coro.suspend_point
         }, resume : {
           cir.yield
         },)
         cir.yield
+      }, body : {
+        cir.yield
+      }, finalSuspend : {
+        cir.yield
+      }, destroy : {
+        cir.yield
+      }, exit : {
+        cir.return
       }
-      cir.return
+      cir.trap
     }
   )CIR");
-  auto coroBodyOp = findFirstOp<cir::CoroBodyOp>(*module);
+  auto coroOp = findFirstOp<cir::CoroutineOp>(*module);
 
-  expectSuccessors(coroBodyOp, RegionBranchPoint::parent(),
-                   {&coroBodyOp.getBody()});
-  expectTerminatorSuccessors(coroBodyOp.getBody(), {nullptr});
+  // The parent only ever enters through initial_suspend.
+  expectSuccessors(coroOp, RegionBranchPoint::parent(),
+                   {&coroOp.getInitialSuspend()});
 
-  RegionBranchOpInterface coroBranch = asRegionBranch(coroBodyOp);
+  // initial_suspend: falls into body once resumed, exits directly on a
+  // plain suspend, or reaches destroy on an explicit destroy() call.
+  RegionBranchTerminatorOpInterface initTerm =
+      getTerminator(coroOp.getInitialSuspend());
+  ASSERT_TRUE(initTerm);
+  expectSuccessors(
+      coroOp, RegionBranchPoint(initTerm),
+      {&coroOp.getBody(), &coroOp.getExit(), &coroOp.getDestroy()});
+  expectTerminatorSuccessors(
+      coroOp.getInitialSuspend(),
+      {&coroOp.getBody(), &coroOp.getExit(), &coroOp.getDestroy()});
+
+  // body: falls through to final_suspend, exits directly on a plain
+  // suspend, or reaches destroy
+  RegionBranchTerminatorOpInterface bodyTerm = getTerminator(coroOp.getBody());
+  ASSERT_TRUE(bodyTerm);
+  expectSuccessors(
+      coroOp, RegionBranchPoint(bodyTerm),
+      {&coroOp.getFinalSuspend(), &coroOp.getExit(), &coroOp.getDestroy()});
+  expectTerminatorSuccessors(
+      coroOp.getBody(),
+      {&coroOp.getFinalSuspend(), &coroOp.getExit(), &coroOp.getDestroy()});
+
+  // final_suspend: exits or destroy.
+  // Should always be destroy in practice, a real suspend after
+  // final_suspend is UB, so the exit edge is never actually taken by a
+  // valid program. Keeping exit here anyway for now.
+  RegionBranchTerminatorOpInterface finalTerm =
+      getTerminator(coroOp.getFinalSuspend());
+  ASSERT_TRUE(finalTerm);
+  expectSuccessors(coroOp, RegionBranchPoint(finalTerm),
+                   {&coroOp.getExit(), &coroOp.getDestroy()});
+  expectTerminatorSuccessors(coroOp.getFinalSuspend(),
+                             {&coroOp.getExit(), &coroOp.getDestroy()});
+
+  // destroy: ordinary dispatch falls through to exit;
+  RegionBranchTerminatorOpInterface destroyTerm =
+      getTerminator(coroOp.getDestroy());
+  ASSERT_TRUE(destroyTerm);
+  expectSuccessors(coroOp, RegionBranchPoint(destroyTerm), {&coroOp.getExit()});
+  expectTerminatorSuccessors(coroOp.getDestroy(), {&coroOp.getExit()});
+
+  // exit always terminates the op.
+  RegionBranchTerminatorOpInterface exitTerm = getTerminator(coroOp.getExit());
+  ASSERT_TRUE(exitTerm);
+  expectSuccessors(coroOp, RegionBranchPoint(exitTerm), {nullptr});
+  expectTerminatorSuccessors(coroOp.getExit(), {nullptr});
+
+  RegionBranchOpInterface coroBranch = asRegionBranch(coroOp);
   EXPECT_FALSE(coroBranch.isRepetitiveRegion(0));
   EXPECT_FALSE(coroBranch.hasLoop());
 
-  verifyControlFlowInterfaceConsistency(coroBodyOp);
+  verifyControlFlowInterfaceConsistency(coroOp);
 }
 
 TEST_F(CIRControlFlowTest, AwaitOp) {
   OwningOpRef<ModuleOp> module = parse(R"CIR(
     cir.func coroutine @f(%arg0 : !cir.bool) {
-      cir.coro.body {
-        cir.await(user, ready : {
+      cir.coroutine initialSuspend : {
+        cir.await(init, ready : {
           cir.condition(%arg0)
         }, suspend : {
-          cir.yield
+          cir.coro.suspend_point
         }, resume : {
           cir.yield
         },)
         cir.yield
+      }, body : {
+        cir.yield
+      }, finalSuspend : {
+        cir.yield
+      }, destroy : {
+        cir.yield
+      }, exit : {
+        cir.return
       }
-      cir.return
+      cir.trap
     }
   )CIR");
   auto awaitOp = findFirstOp<cir::AwaitOp>(*module);

>From 2f0104ad8c934c3d0799d867e6c05a568796cd6d Mon Sep 17 00:00:00 2001
From: Andres Salamanca <andrealebarbaritos at gmail.com>
Date: Mon, 21 Sep 2026 21:37:08 -0500
Subject: [PATCH 10/12] Address review comments

---
 clang/include/clang/CIR/Dialect/IR/CIROps.td |  5 ++++
 clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp    |  2 +-
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp      | 27 +++++++++++++++-----
 3 files changed, 26 insertions(+), 8 deletions(-)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index c1d95c61345905..85a8dc5d61a1da 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -4808,19 +4808,24 @@ def CIR_CoroutineOp : CIR_Op<"coroutine", [
       `cir.co_return` operations inside this region mark coroutine exit
       points and introduce structured control flow that transfers
       execution to `final_suspend`.
+
     - `final_suspend`: exactly one `cir.await`, marked `final`. Nothing
       else. Corresponds to `co_await promise.final_suspend()`. Resuming
       past this point is undefined behavior in valid programs, so its
       `resume` edge is expected to be dead in practice, but is kept for
       structural symmetry with the other two suspend regions.
+
     - `gro`: TODO.
+
     - `unwind`: TODO.
+
     - `destroy`: destroys the coroutine frame (`coro.free` + `delete`).
       Reached by an explicit `destroy()` call on a suspended handle, by
       normal completion through `final_suspend`, or by an exception
       escaping the coroutine. Falls through to `exit` except when reached
       due to an escaping exception, where it continues propagating that
       exception instead.
+
     - `exit`: the shared return-to-caller path. Reached directly from any
       suspend point that isn't being destroyed (the coroutine is just
       pausing), and from `destroy` when it was entered via ordinary
diff --git a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
index af6ee325706249..deb7f1d2bafcf9 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
@@ -590,7 +590,7 @@ CIRGenFunction::emitCoroutineBody(const CoroutineBodyStmt &s) {
             res = exitBuilder();
         });
 
-    if (coro && !coro.getBody().back().mightHaveTerminator()) {
+    if (coro && !coro.getBody().empty() && !coro.getBody().back().mightHaveTerminator()) {
       mlir::OpBuilder::InsertionGuard guard(builder);
       builder.setInsertionPointAfter(&coro.getBody().back().back());
       cir::YieldOp::create(builder, openCurlyLoc);
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 8a24c0464d21d2..a64977599019d6 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -3543,6 +3543,8 @@ void cir::AwaitOp::getSuccessorRegions(
 LogicalResult cir::AwaitOp::verify() {
   if (!isa<ConditionOp>(this->getReady().back().getTerminator()))
     return emitOpError("ready region must end with cir.condition");
+  if (this->getSuspend().empty())
+    return emitOpError("suspend region must not be empty");
   if (!isa<CoroSuspendPoint>(this->getSuspend().back().getTerminator()))
     return emitOpError("suspend region must end with cir.coro.suspend_point");
   return success();
@@ -3555,13 +3557,9 @@ LogicalResult cir::AwaitOp::verify() {
 LogicalResult cir::CoReturnOp::verify() {
   mlir::Operation *coRet = getOperation();
   auto coroutine = coRet->getParentOfType<CoroutineOp>();
-  mlir::Region &coroBody = coroutine.getBody();
-  for (Operation *current = coRet; current; current = current->getParentOp()) {
-    if (current->getParentRegion() == &coroBody)
-      return success();
-  }
-
-  return emitOpError("must be inside the cir.coroutine body region");
+  if (!coroutine.getBody().isAncestor(getOperation()->getParentRegion()))
+    return emitOpError("must be inside the cir.coroutine body region");
+  return success();
 }
 
 //===----------------------------------------------------------------------===//
@@ -3742,14 +3740,29 @@ LogicalResult cir::CoroutineOp::verify() {
   // own control flow, body ends in a cir.co_return (or a plain yield if
   // some path never reaches one), and exit actually returns from the
   // function.
+  if (initialSuspend.empty() || initialSuspend.back().empty())
+    return emitOpError("initial_suspend region must not be empty");
   if (!isa<YieldOp>(initialSuspend.back().back()))
     return emitOpError("'initial_suspend' must end with cir.yield");
+
+  if (body.empty() || body.back().empty())
+    return emitOpError("'body' region must not be empty");
   if (!isa<YieldOp, CoReturnOp>(body.back().back()))
     return emitOpError("'body' must end with cir.yield or cir.co_return");
+
+  if (finalSuspend.empty() || finalSuspend.back().empty())
+    return emitOpError("'final_suspend' region must not be empty");
   if (!isa<YieldOp>(finalSuspend.back().back()))
     return emitOpError("'final_suspend' must end with cir.yield");
+
+  if (destroy.empty() || destroy.back().empty())
+    return emitOpError("'destroy' region must not be empty");
   if (!isa<YieldOp>(destroy.back().back()))
     return emitOpError("'destroy' must end with cir.yield");
+
+
+  if (exit.empty() || exit.back().empty())
+    return emitOpError("'exit' region must not be empty");
   if (!isa<ReturnOp>(exit.back().back()))
     return emitOpError("'exit' must end with cir.return");
 

>From 768af970a58d7ee555caadb6604d1bbf161f6137 Mon Sep 17 00:00:00 2001
From: Andres Salamanca <andrealebarbaritos at gmail.com>
Date: Thu, 24 Sep 2026 18:30:29 -0500
Subject: [PATCH 11/12] Fix formatting

---
 clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp | 3 ++-
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp   | 1 -
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
index deb7f1d2bafcf9..dcff972ddd7d9c 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
@@ -590,7 +590,8 @@ CIRGenFunction::emitCoroutineBody(const CoroutineBodyStmt &s) {
             res = exitBuilder();
         });
 
-    if (coro && !coro.getBody().empty() && !coro.getBody().back().mightHaveTerminator()) {
+    if (coro && !coro.getBody().empty() &&
+        !coro.getBody().back().mightHaveTerminator()) {
       mlir::OpBuilder::InsertionGuard guard(builder);
       builder.setInsertionPointAfter(&coro.getBody().back().back());
       cir::YieldOp::create(builder, openCurlyLoc);
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index a64977599019d6..6653bbfc245b92 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -3760,7 +3760,6 @@ LogicalResult cir::CoroutineOp::verify() {
   if (!isa<YieldOp>(destroy.back().back()))
     return emitOpError("'destroy' must end with cir.yield");
 
-
   if (exit.empty() || exit.back().empty())
     return emitOpError("'exit' region must not be empty");
   if (!isa<ReturnOp>(exit.back().back()))

>From 234ec75939d11810b355612740492eafc50dc6b4 Mon Sep 17 00:00:00 2001
From: Andres Salamanca <andrealebarbaritos at gmail.com>
Date: Thu, 24 Sep 2026 20:50:54 -0500
Subject: [PATCH 12/12] Fix unittests

---
 clang/include/clang/CIR/Dialect/IR/CIROps.td |  3 +-
 clang/unittests/CIR/ControlFlowTest.cpp      | 35 +++++++++++---------
 2 files changed, 22 insertions(+), 16 deletions(-)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 85a8dc5d61a1da..11cd7a75687f78 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -5174,7 +5174,8 @@ def CIR_CoroDoneOp : CIR_CoroIntrinsicOp<"done", (ins CIR_VoidPtrType:$handle),
 //===----------------------------------------------------------------------===//
 
 def CIR_CoroSuspendPoint : CIR_Op<"coro.suspend_point", [
-  Pure, Terminator, HasParent<"AwaitOp">
+  Pure, Terminator, HasParent<"AwaitOp">,
+  DeclareOpInterfaceMethods<RegionBranchTerminatorOpInterface>
 ]> {
   let summary = "Marks the point where a coroutine actually suspends";
   let description = [{
diff --git a/clang/unittests/CIR/ControlFlowTest.cpp b/clang/unittests/CIR/ControlFlowTest.cpp
index 5376d27c84608d..47312d5e8843a1 100644
--- a/clang/unittests/CIR/ControlFlowTest.cpp
+++ b/clang/unittests/CIR/ControlFlowTest.cpp
@@ -648,12 +648,10 @@ TEST_F(CIRControlFlowTest, CoroutineOp) {
   RegionBranchTerminatorOpInterface initTerm =
       getTerminator(coroOp.getInitialSuspend());
   ASSERT_TRUE(initTerm);
-  expectSuccessors(
-      coroOp, RegionBranchPoint(initTerm),
-      {&coroOp.getBody(), &coroOp.getExit(), &coroOp.getDestroy()});
-  expectTerminatorSuccessors(
-      coroOp.getInitialSuspend(),
-      {&coroOp.getBody(), &coroOp.getExit(), &coroOp.getDestroy()});
+  expectSuccessors(coroOp, RegionBranchPoint(initTerm),
+                   {&coroOp.getBody(), &coroOp.getExit()});
+  expectTerminatorSuccessors(coroOp.getInitialSuspend(),
+                             {&coroOp.getBody(), &coroOp.getExit()});
 
   // body: falls through to final_suspend, exits directly on a plain
   // suspend, or reaches destroy
@@ -682,20 +680,27 @@ TEST_F(CIRControlFlowTest, CoroutineOp) {
   RegionBranchTerminatorOpInterface destroyTerm =
       getTerminator(coroOp.getDestroy());
   ASSERT_TRUE(destroyTerm);
-  expectSuccessors(coroOp, RegionBranchPoint(destroyTerm), {&coroOp.getExit()});
-  expectTerminatorSuccessors(coroOp.getDestroy(), {&coroOp.getExit()});
+  expectSuccessors(coroOp, RegionBranchPoint(destroyTerm),
+                   {&coroOp.getExit(), nullptr});
+  expectTerminatorSuccessors(coroOp.getDestroy(), {&coroOp.getExit(), nullptr});
 
+  // TODO: cir.return doesn't implement RegionBranchTerminatorOpInterface
   // exit always terminates the op.
-  RegionBranchTerminatorOpInterface exitTerm = getTerminator(coroOp.getExit());
-  ASSERT_TRUE(exitTerm);
-  expectSuccessors(coroOp, RegionBranchPoint(exitTerm), {nullptr});
-  expectTerminatorSuccessors(coroOp.getExit(), {nullptr});
+  // RegionBranchTerminatorOpInterface exitTerm =
+  // getTerminator(coroOp.getExit()); ASSERT_TRUE(exitTerm);
+  // expectSuccessors(coroOp, RegionBranchPoint(exitTerm), {nullptr});
+  // expectTerminatorSuccessors(coroOp.getExit(), {nullptr});
 
   RegionBranchOpInterface coroBranch = asRegionBranch(coroOp);
   EXPECT_FALSE(coroBranch.isRepetitiveRegion(0));
-  EXPECT_FALSE(coroBranch.hasLoop());
-
-  verifyControlFlowInterfaceConsistency(coroOp);
+  EXPECT_FALSE(coroBranch.isRepetitiveRegion(1));
+  EXPECT_FALSE(coroBranch.isRepetitiveRegion(2));
+  EXPECT_FALSE(coroBranch.isRepetitiveRegion(3));
+  EXPECT_FALSE(coroBranch.isRepetitiveRegion(4));
+  EXPECT_TRUE(coroBranch.hasLoop());
+
+  // TODO: cir.return doesn't implement RegionBranchTerminatorOpInterface
+  // verifyControlFlowInterfaceConsistency(coroOp);
 }
 
 TEST_F(CIRControlFlowTest, AwaitOp) {



More information about the cfe-commits mailing list