[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> ®ions) {
+ 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