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

via cfe-commits cfe-commits at lists.llvm.org
Mon Apr 6 14:37:49 PDT 2026


================
@@ -110,6 +109,66 @@ 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);
----------------
Andres-Salamanca wrote:

 Yes, that’s exactly what I’m doing  I’m moving the instructions generated by
 `cgf.emitStmt(deallocate, /*useCurrentScope=*/true)` into the `if` body.

 I’m making a couple of assumptions here:
 First, that the deallocation AST always includes a call to `coro.free`. This comes from Sema (`makeNewAndDeleteExpr`)https://github.com/llvm/llvm-project/blob/8a11fe97a299b8b384195d52f317e198bb3a27d4/clang/lib/Sema/SemaCoroutine.cpp#L1650-L1660, so it’s not user-dependent.

 Second, I’m assuming everything is emitted into a single block. The reason is that this AST node is fully constructed in Sema and doesn’t introduce any control flow it’s just a call to `coro.free` followed by a delete call  so CodeGen ends up emitting it linearly.

 Based on that, I just move everything after the `coro.free` into the `if`.
 Do you think this assumption is reasonable here?

https://github.com/llvm/llvm-project/pull/189281


More information about the cfe-commits mailing list