[clang] [CIR] Add coroutine cleanup handling and update co_return semantics (PR #189281)

via cfe-commits cfe-commits at lists.llvm.org
Sun Mar 29 16:34:17 PDT 2026


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

>From 191ff93a5f99b60a7128ed9c351609aa7fe85a7a Mon Sep 17 00:00:00 2001
From: Andres Salamanca <andrealebarbaritos at gmail.com>
Date: Sun, 29 Mar 2026 14:35:23 -0500
Subject: [PATCH 1/2] [CIR] Add coroutine cleanup handling and update co_return
 semantics

---
 clang/include/clang/CIR/Dialect/IR/CIROps.td |  61 +++
 clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp      |   1 +
 clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp    | 136 ++++-
 clang/lib/CIR/CodeGen/CIRGenFunction.cpp     |   4 -
 clang/lib/CIR/CodeGen/CIRGenFunction.h       |   2 +
 clang/lib/CIR/CodeGen/CIRGenModule.h         |   1 +
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp      |  42 ++
 clang/test/CIR/CodeGen/coro-task.cpp         | 542 ++++++++++++-------
 clang/test/CIR/IR/co-return.cir              |  21 +
 clang/test/CIR/IR/coro-body.cir              |  19 +
 clang/test/CIR/IR/invalid-co-return.cir      |   5 +
 clang/test/CIR/IR/invalid-coro-body.cir      |   7 +
 12 files changed, 627 insertions(+), 214 deletions(-)
 create mode 100644 clang/test/CIR/IR/co-return.cir
 create mode 100644 clang/test/CIR/IR/coro-body.cir
 create mode 100644 clang/test/CIR/IR/invalid-co-return.cir
 create mode 100644 clang/test/CIR/IR/invalid-coro-body.cir

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 329939dc1b2e9..e6f08545060ae 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -4120,6 +4120,67 @@ def CIR_AwaitOp : CIR_Op<"await",[
 
   let hasVerifier = 1;
 }
+//===----------------------------------------------------------------------===//
+// CoroBody
+//===----------------------------------------------------------------------===//
+def CIR_CoroBodyOp : CIR_Op<"coro.body", [
+  DeclareOpInterfaceMethods<RegionBranchOpInterface, ["getSuccessorInputs"]>,
+  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:$bodyRegion);
+
+  let skipDefaultBuilders = 1;
+
+  let builders = [
+    OpBuilder<(ins "BuilderCallbackRef":$bodyBuilder)>
+  ];
+
+  let assemblyFormat = [{
+    $bodyRegion attr-dict
+  }];
+
+  let hasLLVMLowering = false;
+  let hasVerifier = 1;
+}
+
+//===----------------------------------------------------------------------===//
+// CoReturnOp
+//===----------------------------------------------------------------------===//
+
+def CIR_CoReturnOp : CIR_Op<"co_return", [
+  NoMemoryEffect, Pure, Terminator
+]> {
+  let summary = "Coroutine return operation";
+  let description = [{
+    The `cir.co_return` operation models a coroutine return point inside a
+    `cir.coro.body` region.
+    This operation is expected to appear only within a `cir.coro.body` region.
+  }];
+
+  let assemblyFormat = "attr-dict ";
+
+  let hasVerifier = 1;
+
+  let hasLLVMLowering = false;
+}
+
 
 //===----------------------------------------------------------------------===//
 // CopyOp
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
index 06dfdcb7c8014..a96c93440cfe0 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -1237,6 +1237,7 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
     return emitCoroutineFrame();
   }
   case Builtin::BI__builtin_coro_free:
+    return RValue::get(emitCoroFreeBuiltin(e).getResult());
   case Builtin::BI__builtin_coro_size: {
     GlobalDecl gd{fd};
     mlir::Type ty = cgm.getTypes().getFunctionType(
diff --git a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
index 036f1b1cfe637..fa1f34e052742 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
@@ -33,17 +33,16 @@ struct clang::CIRGen::CGCoroData {
   // Stores the result of __builtin_coro_begin call.
   mlir::Value coroBegin = nullptr;
 
-  // Stores the insertion point for final suspend, this happens after the
-  // promise call (return_xxx promise member) but before a cir.br to the return
-  // block.
-  mlir::Operation *finalSuspendInsPoint;
-
   // How many co_return statements are in the coroutine. Used to decide whether
   // we need to add co_return; equivalent at the end of the user authored body.
   unsigned coreturnCount = 0;
 
   // The promise type's 'unhandled_exception' handler, if it defines one.
   Stmt *exceptionHandler = nullptr;
+
+  // Stores the last emitted coro.free for the deallocate expressions, we use it
+  // to wrap dealloc code with if(auto mem = coro.free) dealloc(mem).
+  cir::CallOp lastCoroFree = nullptr;
 };
 
 // Defining these here allows to keep CGCoroData private to this file.
@@ -110,6 +109,67 @@ 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();
+    mlir::Location loc = cgf.getLoc(deallocate->getSourceRange());
+    cir::CallOp coroFree = cgf.curCoro.data->lastCoroFree;
+
+    if (!coroFree) {
+      cgf.cgm.error(deallocate->getBeginLoc(),
+                    "Deallocation expressoin does not refer to coro.free");
+      return;
+    }
+
+    builder.setInsertionPointAfter(coroFree);
+    cir::ConstantOp nullPtrCst = builder.getNullPtr(cgf.voidPtrTy, loc);
+    auto cmp = cir::CmpOp::create(builder, loc, cir::CmpOpKind::ne,
+                                  coroFree.getResult(), nullPtrCst);
+
+    llvm::SmallVector<mlir::Operation *> opsToMove;
+    mlir::Block *block = cmp->getBlock();
+    mlir::Block::iterator it(cmp);
+
+    for (++it; it != block->end(); ++it) {
+      opsToMove.push_back(&*it);
+    }
+
+    auto ifOp =
+        cir::IfOp::create(builder, cgf.getLoc(deallocate->getSourceRange()),
+                          cmp.getResult(), /*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);
@@ -235,6 +295,28 @@ cir::CallOp CIRGenFunction::emitCoroEndBuiltinCall(mlir::Location loc,
       loc, fnOp, mlir::ValueRange{nullPtr, builder.getBool(false, loc)});
 }
 
+cir::CallOp CIRGenFunction::emitCoroFreeBuiltin(const CallExpr *e) {
+  mlir::Operation *builtin = cgm.getGlobalValue(cgm.builtinCoroFree);
+  mlir::Location loc = getLoc(e->getBeginLoc());
+  cir::FuncOp fnOp;
+  if (!builtin) {
+    fnOp = cgm.createCIRBuiltinFunction(
+        loc, cgm.builtinCoroFree,
+        cir::FuncType::get({uInt32Ty, voidPtrTy}, voidPtrTy),
+        /*fd=*/nullptr);
+    assert(fnOp && "should always succeed");
+  } else {
+    fnOp = cast<cir::FuncOp>(builtin);
+  }
+  cir::CallOp coroFree =
+      builder.createCallOp(loc, fnOp,
+                           mlir::ValueRange{curCoro.data->coroId.getResult(),
+                                            curCoro.data->coroBegin});
+
+  curCoro.data->lastCoroFree = coroFree;
+  return coroFree;
+}
+
 mlir::LogicalResult
 CIRGenFunction::emitCoroutineBody(const CoroutineBodyStmt &s) {
   mlir::Location openCurlyLoc = getLoc(s.getBeginLoc());
@@ -280,6 +362,8 @@ 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.
     llvm::ArrayRef<const Stmt *> paramMoves = s.getParamMoves();
@@ -326,11 +410,20 @@ CIRGenFunction::emitCoroutineBody(const CoroutineBodyStmt &s) {
 
     curCoro.data->currentAwaitKind = cir::AwaitKind::User;
 
-    // FIXME(cir): wrap emitBodyAndFallthrough with try/catch bits.
-    if (s.getExceptionHandler())
-      assert(!cir::MissingFeatures::coroutineExceptions());
-    if (emitBodyAndFallthrough(*this, s, s.getBody(), curLexScope).failed())
-      return mlir::failure();
+    mlir::OpBuilder::InsertPoint userBody;
+    cir::CoroBodyOp::create(builder, openCurlyLoc, /*scopeBuilder=*/
+                            [&](mlir::OpBuilder &b, mlir::Location loc) {
+                              userBody = b.saveInsertionPoint();
+                            });
+    {
+      mlir::OpBuilder::InsertionGuard guard(builder);
+      builder.restoreInsertionPoint(userBody);
+      // FIXME(cir): wrap emitBodyAndFallthrough with try/catch bits.
+      if (s.getExceptionHandler())
+        assert(!cir::MissingFeatures::coroutineExceptions());
+      if (emitBodyAndFallthrough(*this, s, s.getBody(), curLexScope).failed())
+        return mlir::failure();
+    }
 
     // Note that LLVM checks CanFallthrough by looking into the availability
     // of the insert block which is kinda brittle and unintuitive, seems to be
@@ -346,13 +439,26 @@ CIRGenFunction::emitCoroutineBody(const CoroutineBodyStmt &s) {
       curCoro.data->currentAwaitKind = cir::AwaitKind::Final;
       {
         mlir::OpBuilder::InsertionGuard guard(builder);
-        builder.setInsertionPoint(curCoro.data->finalSuspendInsPoint);
         if (emitStmt(s.getFinalSuspendStmt(), /*useCurrentScope=*/true)
                 .failed())
           return mlir::failure();
       }
     }
   }
+
+  emitCoroEndBuiltinCall(
+      openCurlyLoc, builder.getNullPtr(builder.getVoidPtrTy(), 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.
+    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();
 }
 
@@ -538,13 +644,7 @@ mlir::LogicalResult CIRGenFunction::emitCoreturnStmt(CoreturnStmt const &s) {
   // it. The actual return instruction is only inserted during current
   // scope cleanup handling.
   mlir::Location loc = getLoc(s.getSourceRange());
-  mlir::Block *retBlock = curLexScope->getOrCreateRetBlock(*this, loc);
-  curCoro.data->finalSuspendInsPoint =
-      cir::BrOp::create(builder, loc, retBlock);
-
-  // Insert the new block to continue codegen after branch to ret block,
-  // this will likely be an empty block.
-  builder.createBlock(builder.getBlock()->getParent());
+  cir::CoReturnOp::create(builder, loc);
 
   return mlir::success();
 }
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
index 58fe699eeff1f..0527e507c179c 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -380,10 +380,6 @@ cir::ReturnOp CIRGenFunction::LexicalScope::emitReturn(mlir::Location loc) {
   auto fn = dyn_cast<cir::FuncOp>(cgf.curFn);
   assert(fn && "emitReturn from non-function");
 
-  // If we are on a coroutine, add the coro_end builtin call.
-  if (fn.getCoroutine())
-    cgf.emitCoroEndBuiltinCall(loc,
-                               builder.getNullPtr(builder.getVoidPtrTy(), loc));
   if (!fn.getFunctionType().hasVoidReturn()) {
     // Load the value from `__retval` and return it via the `cir.return` op.
     auto value = cir::LoadOp::create(
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index d80c2d635a89f..b1dde0f449b9d 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -1510,6 +1510,8 @@ class CIRGenFunction : public CIRGenTypeCache {
   cir::CallOp emitCoroAllocBuiltinCall(mlir::Location loc);
   cir::CallOp emitCoroBeginBuiltinCall(mlir::Location loc,
                                        mlir::Value coroframeAddr);
+
+  cir::CallOp emitCoroFreeBuiltin(const CallExpr *e);
   RValue emitCoroutineFrame();
 
   void emitDestroy(Address addr, QualType type, Destroyer *destroyer);
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h
index 266510de84fd0..5061c4e752079 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -677,6 +677,7 @@ class CIRGenModule : public CIRGenTypeCache {
   static constexpr const char *builtinCoroAlloc = "__builtin_coro_alloc";
   static constexpr const char *builtinCoroBegin = "__builtin_coro_begin";
   static constexpr const char *builtinCoroEnd = "__builtin_coro_end";
+  static constexpr const char *builtinCoroFree = "__builtin_coro_free";
 
   /// Given a builtin id for a function like "__builtin_fabsf", return a
   /// Function* for "fabsf".
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index eb322d135a804..bb47012e960aa 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -2874,6 +2874,48 @@ LogicalResult cir::AwaitOp::verify() {
   return success();
 }
 
+LogicalResult cir::CoReturnOp::verify() {
+  if (!getOperation()->getParentOfType<CoroBodyOp>())
+    return emitOpError("must be inside a cir.coro.body");
+  return success();
+}
+
+//===----------------------------------------------------------------------===//
+// CoroBody
+//===----------------------------------------------------------------------===//
+
+void cir::CoroBodyOp::getSuccessorRegions(
+    mlir::RegionBranchPoint point, SmallVectorImpl<RegionSuccessor> &regions) {
+  if (!point.isParent()) {
+    regions.push_back(RegionSuccessor::parent());
+    return;
+  }
+
+  regions.push_back(RegionSuccessor(&getBodyRegion()));
+}
+
+mlir::ValueRange
+cir::CoroBodyOp::getSuccessorInputs(RegionSuccessor successor) {
+  return ValueRange();
+}
+
+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);
+}
+
 //===----------------------------------------------------------------------===//
 // CopyOp Definitions
 //===----------------------------------------------------------------------===//
diff --git a/clang/test/CIR/CodeGen/coro-task.cpp b/clang/test/CIR/CodeGen/coro-task.cpp
index b52f0f1871079..b54803f7cce84 100644
--- a/clang/test/CIR/CodeGen/coro-task.cpp
+++ b/clang/test/CIR/CodeGen/coro-task.cpp
@@ -196,8 +196,11 @@ VoidTask silly_task() {
 
 // Call promise.get_return_object() to retrieve the task object.
 
-// CIR: %[[RetObj:.*]] = cir.call @_ZN5folly4coro4TaskIvE12promise_type17get_return_objectEv(%[[VoidPromisseAddr]]) nothrow : {{.*}} -> ![[VoidTask]]
-// CIR: cir.store{{.*}} %[[RetObj]], %[[VoidTaskAddr]] : ![[VoidTask]]
+
+// CIR: cir.cleanup.scope {
+
+// 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]])
@@ -210,8 +213,8 @@ VoidTask silly_task() {
 // the suspend_always struct to use for cir.await. Note that we return by-value since we defer ABI lowering
 // to later passes, same is done elsewhere.
 
-// 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]])
 
@@ -221,9 +224,9 @@ VoidTask silly_task() {
 
 // First regions `ready` has a special cir.yield code to veto suspension.
 
-// CIR: cir.await(init, ready : {
-// CIR:   %[[ReadyVeto:.*]] = cir.call @_ZNSt14suspend_always11await_readyEv(%[[SuspendAlwaysAddr]])
-// CIR:   cir.condition(%[[ReadyVeto]])
+// CIR:   cir.await(init, ready : {
+// CIR:     %[[ReadyVeto:.*]] = cir.call @_ZNSt14suspend_always11await_readyEv(%[[SuspendAlwaysAddr]])
+// CIR:     cir.condition(%[[ReadyVeto]])
 
 // OGCG: %[[Tmp0:.*]] = call noundef zeroext i1 @_ZNSt14suspend_always11await_readyEv(ptr noundef nonnull align 1 dereferenceable(1) %[[SuspendAlwaysAddr]])
 // OGCG: br i1 %[[Tmp0]], label %init.ready, label %init.suspend
@@ -237,14 +240,14 @@ VoidTask silly_task() {
 //
 // FIXME: add veto support for non-void await_suspends.
 
-// CIR: }, suspend : {
-// CIR:   %[[FromAddrRes:.*]] = cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIvE12promise_typeEE12from_addressEPv(%[[CoroFrameAddr]])
-// CIR:   cir.store{{.*}} %[[FromAddrRes]], %[[CoroHandlePromiseAddr]] : ![[CoroHandlePromiseVoid]]
-// CIR:   %[[CoroHandlePromiseReload:.*]] = cir.load{{.*}} %[[CoroHandlePromiseAddr]]
-// CIR:   cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_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.yield
+// CIR:   }, suspend : {
+// CIR:     %[[FromAddrRes:.*]] = cir.call @_ZNSt16coroutine_handleIN5folly4coro4TaskIvE12promise_typeEE12from_addressEPv(%[[CoroFrameAddr]])
+// CIR:     cir.store{{.*}} %[[FromAddrRes]], %[[CoroHandlePromiseAddr]] : ![[CoroHandlePromiseVoid]]
+// CIR:     %[[CoroHandlePromiseReload:.*]] = cir.load{{.*}} %[[CoroHandlePromiseAddr]]
+// CIR:     cir.call @_ZNSt16coroutine_handleIvEC1IN5folly4coro4TaskIvE12promise_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.yield
 
 // OGCG: init.suspend:
 // OGCG:   %[[Save:.*]] = call token @llvm.coro.save(ptr null)
@@ -257,10 +260,10 @@ VoidTask silly_task() {
 
 // Third region `resume` handles coroutine resuming logic.
 
-// CIR: }, resume : {
-// CIR:   cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SuspendAlwaysAddr]])
-// CIR:   cir.yield
-// CIR: },)
+// CIR:   }, resume : {
+// CIR:     cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SuspendAlwaysAddr]])
+// CIR:     cir.yield
+// CIR:   },)
 
 // OGCG: init.ready:
 // OGCG:   call void @_ZNSt14suspend_always12await_resumeEv(ptr noundef nonnull align 1 dereferenceable(1) %[[SuspendAlwaysAddr]]
@@ -272,31 +275,63 @@ VoidTask silly_task() {
 // - The final suspend co_await
 // - Return
 
+// 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
-// CHECK: cir.call @_ZN5folly4coro4TaskIvE12promise_type11return_voidEv(%[[VoidPromisseAddr]])
+// 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]])
 
 // 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
 
 // OGCG: coro.final:
 // OGCG: final.suspend:
 // OGCG: final.ready:
 
+// Cleanup of coroutine frame.
+//
+// `__builtin_coro_free` returns the frame pointer or null.
+// 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.call @__builtin_coro_free(%[[CoroId]], %[[CoroFrameAddr]])
+// 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: }
+
+// OGCG: %[[FreeMem:.*]] = call ptr @llvm.coro.free(token %[[CoroId]], ptr %[[CoroFrameAddr]])
+// OGCG: %[[Cond:.*]] = icmp ne ptr %[[FreeMem]], null
+// OGCG: br i1 %[[Cond]], label %coro.free, label %after.coro.free
+
+// OGCG: coro.free:
+// OGCG:   %[[Size:.*]] = call i64 @llvm.coro.size.i64()
+// OGCG:   call void @_ZdlPvm(ptr {{.*}} %[[FreeMem]], i64 {{.*}} %[[Size]])
+// OGCG:   br label %after.coro.free
+// OGCG: after.coro.free:
+
 // Call builtin coro end and return
 
 // CIR: %[[CoroEndArg0:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!void>
@@ -326,38 +361,46 @@ folly::coro::Task<int> byRef(const std::string& s) {
 // CIR:    %[[CoroHandlePromiseAddr:.*]] = cir.alloca ![[CoroHandlePromiseInt]], {{.*}} ["agg.tmp1"] {alignment = 1 : i64}
 // CIR:    cir.store %[[ARG]], %[[AllocaParam]] : !cir.ptr<![[StdString]]>, {{.*}}
 
+// 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.yield
-// CIR:    }, resume : {
-// CIR:      cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SuspendAlwaysAddr]])
-// CIR:      cir.yield
-// CIR:    },)
-
-// can't fallthrough
+// 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.yield
+// 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:     }
+
 
 // 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:    }
 
 folly::coro::Task<void> silly_coro() {
   std::optional<folly::coro::Task<int>> task;
@@ -373,16 +416,22 @@ folly::coro::Task<void> silly_coro() {
 // check there are not multiple co_returns emitted.
 
 // CIR: cir.func coroutine {{.*}} @_Z10silly_corov() {{.*}} ![[VoidTask]]
-// CIR: cir.await(init, ready : {
-// CIR: }, suspend : {
-// CIR: }, resume : {
-// CIR: },)
-// CIR: cir.call @_ZN5folly4coro4TaskIvE12promise_type11return_voidEv
-// CIR-NOT: cir.call @_ZN5folly4coro4TaskIvE12promise_type11return_voidEv
-// CIR: cir.await(final, ready : {
-// CIR: }, suspend : {
-// CIR: }, resume : {
-// CIR: },)
+// 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: }
 
 folly::coro::Task<void> yield();
 folly::coro::Task<void> yield1() {
@@ -408,67 +457,74 @@ folly::coro::Task<void> yield1() {
 // CIR-DAG: %[[CH_VOID2:.*]] = cir.alloca ![[CoroHandleVoid]], {{.*}} ["agg.tmp5"]
 // CIR-DAG: %[[CH_PROM2:.*]] = cir.alloca ![[CoroHandlePromiseVoid]], {{.*}} ["agg.tmp6"]
 
+// CIR: cir.cleanup.scope {
 // 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.yield
-// 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.yield
+// CIR:   }, resume : {
+// CIR:     cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SUSP0]]){{.*}}
+// CIR:     cir.yield
+// CIR:   },)
 
 // yield_value + await(yield)
-// CIR: %[[YIELD_TASK:.*]] = cir.call @_Z5yieldv(){{.*}}
-// CIR: cir.store{{.*}} %[[YIELD_TASK]], %[[T_ADDR]]
-// CIR: cir.copy %[[T_ADDR]] to %[[AWAITER_COPY_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.yield
-// CIR: }, resume : {
-// CIR:   cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SUSP1]]){{.*}}
-// CIR:   cir.yield
-// CIR: },)
+// CIR:   cir.coro.body {
+// CIR:     %[[YIELD_TASK:.*]] = cir.call @_Z5yieldv(){{.*}}
+// CIR:     cir.store{{.*}} %[[YIELD_TASK]], %[[T_ADDR]]
+// CIR:     cir.copy %[[T_ADDR]] to %[[AWAITER_COPY_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.yield
+// 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_type11return_voidEv(%[[PROMISE]]){{.*}}
-// 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.yield
-// CIR: }, resume : {
-// CIR:   cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SUSP2]]){{.*}}
+// 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.yield
+// CIR:   }, resume : {
+// CIR:     cir.call @_ZNSt14suspend_always12await_resumeEv(%[[SUSP2]]){{.*}}
+// CIR:     cir.yield
+// CIR:   },)
 // CIR:   cir.yield
-// CIR: },)
+// CIR: } cleanup  normal {
+// CIR: }
 // CIR: = cir.call @__builtin_coro_end(%{{.*}}, %{{.*}}){{.*}}
 // CIR: %[[RETLOAD:.*]] = cir.load{{.*}} %[[RETVAL]]
 // CIR: cir.return %[[RETLOAD]]
@@ -485,34 +541,41 @@ folly::coro::Task<int> go1() {
 // CIR: cir.func coroutine {{.*}} @_Z3go1v() {{.*}} ![[IntTask]]
 // CIR: %[[IntTaskAddr:.*]] = cir.alloca ![[IntTask]], !cir.ptr<![[IntTask]]>, ["task", init]
 
-// CIR: cir.await(init, ready : {
-// CIR: }, suspend : {
-// CIR: }, resume : {
-// CIR: },)
+// CIR: cir.cleanup.scope {
+// CIR:   cir.await(init, ready : {
+// CIR:   }, suspend : {
+// CIR:   }, resume : {
+// CIR:   },)
 
+// CIR:   cir.coro.body {
 // The call to go(1) has its own scope due to full-expression rules.
-// CIR: cir.scope {
-// CIR:   %[[OneAddr:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["ref.tmp1", init] {alignment = 4 : i64}
-// 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.scope {
+// CIR:       %[[OneAddr:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["ref.tmp1", init] {alignment = 4 : i64}
+// 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:     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:   },)
+// 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.await(final, ready : {
-// CIR: }, suspend : {
-// CIR: }, resume : {
-// CIR: },)
-
 
 folly::coro::Task<int> go1_lambda() {
   auto task = []() -> folly::coro::Task<int> {
@@ -522,27 +585,45 @@ folly::coro::Task<int> go1_lambda() {
 }
 
 // CIR: cir.func coroutine {{.*}} @_ZZ10go1_lambdavENK3$_0clEv{{.*}} ![[IntTask]]
-// CIR: cir.await(init, ready : {
-// CIR: }, suspend : {
-// CIR: }, resume : {
-// CIR: },)
-// CIR: cir.await(final, ready : {
-// CIR: }, suspend : {
-// CIR: }, resume : {
-// CIR: },)
+// 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.await(init, ready : {
-// CIR: }, suspend : {
-// CIR: }, resume : {
-// CIR: },)
-// CIR: cir.await(user, ready : {
-// CIR: }, suspend : {
-// CIR: }, resume : {
-// CIR: },)
-// CIR: cir.await(final, ready : {
-// CIR: }, suspend : {
-// CIR: }, resume : {
-// CIR: },)
+// 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 {
 
 folly::coro::Task<int> go4() {
   auto* fn = +[](int const& i) -> folly::coro::Task<int> { co_return i; };
@@ -551,21 +632,28 @@ folly::coro::Task<int> go4() {
 }
 
 // CIR: cir.func coroutine{{.*}} @_ZZ3go4vENK3$_0clERKi(
-// CIR: cir.await(init, ready : {
-// CIR: }, suspend : {
-// CIR: }, resume : {
-// CIR: },)
-// CIR: cir.await(final, ready : {
-// CIR: }, suspend : {
-// CIR: }, resume : {
-// CIR: },)
+// 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: cir.func coroutine {{.*}} @_Z3go4v() {{.*}} ![[IntTask]]
 
-// CIR: cir.await(init, ready : {
-// CIR: }, suspend : {
-// CIR: }, resume : {
-// CIR: },)
+// 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})
@@ -576,24 +664,28 @@ folly::coro::Task<int> go4() {
 // 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]]>
+// 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:  cir.await(user, ready : {
+// CIR:    = cir.call @_ZN5folly4coro4TaskIiE11await_readyEv(%[[TASK_ADDR]])
+// CIR:    cir.condition(
+// CIR:  }, suspend : {
+// CIR:    cir.yield
+// CIR:  }, resume : {
+// CIR:    cir.yield
+// CIR:  },)
+// CIR:  cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi
+// CIR:  cir.co_return
 // CIR: }
 
-// CIR: cir.await(user, ready : {
-// CIR:   = cir.call @_ZN5folly4coro4TaskIiE11await_readyEv(%[[TASK_ADDR]])
-// CIR:   cir.condition(
-// CIR: }, suspend : {
-// CIR:   cir.yield
-// CIR: }, resume : {
-// CIR:   cir.yield
-// CIR: },)
-
-// CIR: cir.await(final, ready : {
-// CIR: }, suspend : {
-// CIR: }, resume : {
-// CIR: },)
+// CIR:  cir.await(final, ready : {
+// CIR:  }, suspend : {
+// CIR:  }, resume : {
+// CIR:  },)
+// CIR: } cleanup  normal {
 
 // OGCG: define {{.*}}__await_suspend_wrapper__init(ptr noundef nonnull %[[Awaiter:.*]], ptr noundef %[[Handle:.*]])
 // OGCG: entry:
@@ -609,3 +701,69 @@ folly::coro::Task<int> go4() {
 // OGCG:   call void @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE(ptr noundef nonnull align 1 dereferenceable(1) %[[AwaiterReload]])
 // OGCG:   ret void
 // OGCG: }
+
+folly::coro::Task<int> co_returns(int x) {
+  if (x < 0)
+    co_return -1;
+  else if (x == 1)
+   co_return -2;
+
+  co_await std::suspend_always();
+
+  co_return x * 2;
+}
+
+// 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:     }, suspend : {
+// CIR:     }, resume : {
+// CIR:     },)
+// CIR:     %[[X_LOAD:.*]] = cir.load {{.*}} %[[X:.*]]
+// CIR:     %[[TWO:.*]] = cir.const #cir.int<2>
+// CIR:     %[[RES:.*]] = cir.binop(mul, %[[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 {
+
+
+// OGCG: define {{.*}} @_Z10co_returnsi
+// OGCG: coro.init:
+// OGCG: init.suspend:
+// OGCG: init.ready:
+// OGCG: if.then:
+// OGCG:   call void @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi({{.*}} %[[promise:.*]], {{.*}} -1)
+// OGCG:   br label %coro.final
+// OGCG: if.else:
+// OGCG: if.then5:
+// OGCG:   call void @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi({{.*}} %[[promise:.*]], {{.*}} -2)
+// OGCG:   br label %coro.final
+// OGCG: await.suspend:
+// OGCG: await.ready:
+// OGCG: coro.final:
+// OGCG: final.suspend:
+// OGCG: final.ready:
diff --git a/clang/test/CIR/IR/co-return.cir b/clang/test/CIR/IR/co-return.cir
new file mode 100644
index 0000000000000..613399b142650
--- /dev/null
+++ b/clang/test/CIR/IR/co-return.cir
@@ -0,0 +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.yield
+    }, resume : {
+      cir.yield
+    },)
+    cir.co_return
+  }
+  cir.return
+}
+
+// CHECK: cir.func coroutine @coro_co_return
+// CHECK:   cir.coro.body {
+// CHECK:     cir.co_return
+// CHECK:   }
+
+
diff --git a/clang/test/CIR/IR/coro-body.cir b/clang/test/CIR/IR/coro-body.cir
new file mode 100644
index 0000000000000..1c0dae384691a
--- /dev/null
+++ b/clang/test/CIR/IR/coro-body.cir
@@ -0,0 +1,19 @@
+// 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.yield
+    }, resume : {
+      cir.yield
+    },)
+    cir.co_return
+  }
+  cir.return
+}
+
+// CHECK: cir.func coroutine @coro_body
+// CHECK:   cir.coro.body {
+// CHECK:   }
diff --git a/clang/test/CIR/IR/invalid-co-return.cir b/clang/test/CIR/IR/invalid-co-return.cir
new file mode 100644
index 0000000000000..6085bcfe3a408
--- /dev/null
+++ b/clang/test/CIR/IR/invalid-co-return.cir
@@ -0,0 +1,5 @@
+// RUN: cir-opt %s -verify-diagnostics -split-input-file
+
+cir.func @must_be_inside_coro_body() { 
+  cir.co_return // expected-error {{must be inside a cir.coro.body}}
+}
diff --git a/clang/test/CIR/IR/invalid-coro-body.cir b/clang/test/CIR/IR/invalid-coro-body.cir
new file mode 100644
index 0000000000000..c304030fd2040
--- /dev/null
+++ b/clang/test/CIR/IR/invalid-coro-body.cir
@@ -0,0 +1,7 @@
+// 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}}
+
+  }
+}

>From 7539b9a4b91b6a54deabc43ddef1fe25c86a9464 Mon Sep 17 00:00:00 2001
From: Andres Salamanca <andrealebarbaritos at gmail.com>
Date: Sun, 29 Mar 2026 18:33:01 -0500
Subject: [PATCH 2/2] Fix tests after rebase

---
 clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp | 3 +--
 clang/test/CIR/CodeGen/coro-task.cpp      | 4 ++--
 2 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
index fa1f34e052742..d141a0263ceae 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
@@ -151,9 +151,8 @@ struct CallCoroDelete final : public EHScopeStack::Cleanup {
     mlir::Block *block = cmp->getBlock();
     mlir::Block::iterator it(cmp);
 
-    for (++it; it != block->end(); ++it) {
+    for (++it; it != block->end(); ++it)
       opsToMove.push_back(&*it);
-    }
 
     auto ifOp =
         cir::IfOp::create(builder, cgf.getLoc(deallocate->getSourceRange()),
diff --git a/clang/test/CIR/CodeGen/coro-task.cpp b/clang/test/CIR/CodeGen/coro-task.cpp
index b54803f7cce84..653b975e2a643 100644
--- a/clang/test/CIR/CodeGen/coro-task.cpp
+++ b/clang/test/CIR/CodeGen/coro-task.cpp
@@ -314,7 +314,7 @@ VoidTask silly_task() {
 // CIR: } cleanup  normal {
 // CIR:   %[[FreeMem:.*]] = cir.call @__builtin_coro_free(%[[CoroId]], %[[CoroFrameAddr]])
 // CIR:   %[[NullPtr2:.*]] = cir.const #cir.ptr<null>
-// CIR:   %[[Cond:.*]] = cir.cmp(ne, %[[FreeMem]], %[[NullPtr2]])
+// CIR:   %[[Cond:.*]] = cir.cmp ne %[[FreeMem]], %[[NullPtr2]]
 // CIR:   cir.if %[[Cond]] {
 // CIR:     %[[Size:.*]] = cir.call @__builtin_coro_size()
 // CIR:     cir.call @_ZdlPvm(%[[FreeMem]], %[[Size]])
@@ -739,7 +739,7 @@ folly::coro::Task<int> co_returns(int x) {
 // CIR:     },)
 // CIR:     %[[X_LOAD:.*]] = cir.load {{.*}} %[[X:.*]]
 // CIR:     %[[TWO:.*]] = cir.const #cir.int<2>
-// CIR:     %[[RES:.*]] = cir.binop(mul, %[[X_LOAD]], %[[TWO]])
+// CIR:     %[[RES:.*]] = cir.mul nsw %[[X_LOAD]], %[[TWO]]
 // CIR:     cir.call @_ZN5folly4coro4TaskIiE12promise_type12return_valueEi(%[[PROMISE]], %[[RES]])
 // CIR:     cir.co_return
 // CIR:   }



More information about the cfe-commits mailing list