[clang] [CIR] Implement flattening for cleanup scopes with multiple exits (PR #180627)

Henrich Lauko via cfe-commits cfe-commits at lists.llvm.org
Tue Feb 10 02:03:39 PST 2026


================
@@ -725,21 +725,105 @@ class CIRTernaryOpFlattening : public mlir::OpRewritePattern<cir::TernaryOp> {
   }
 };
 
+// Get or create the cleanup destination slot for a function. This slot is
+// shared across all cleanup scopes in the function to track which exit path
+// to take after running cleanup code when there are multiple exits.
+static cir::AllocaOp getOrCreateCleanupDestSlot(cir::FuncOp funcOp,
+                                                mlir::PatternRewriter &rewriter,
+                                                mlir::Location loc) {
+  mlir::Block &entryBlock = funcOp.getBody().front();
+
+  // Look for an existing cleanup dest slot in the entry block.
+  for (auto &op : entryBlock) {
+    if (auto existingAlloca = dyn_cast<cir::AllocaOp>(&op)) {
+      if (existingAlloca.getName() == "__cleanup_dest_slot")
+        return existingAlloca;
+    }
+  }
+
+  // Create a new cleanup dest slot at the start of the entry block.
+  mlir::OpBuilder::InsertionGuard guard(rewriter);
+  rewriter.setInsertionPointToStart(&entryBlock);
+  cir::IntType s32Type =
+      cir::IntType::get(rewriter.getContext(), 32, /*isSigned=*/true);
+  cir::PointerType ptrToS32Type = cir::PointerType::get(s32Type);
+  return cir::AllocaOp::create(rewriter, loc, ptrToS32Type, s32Type,
+                               "__cleanup_dest_slot",
+                               /*alignment=*/rewriter.getI64IntegerAttr(4));
+}
+
 class CIRCleanupScopeOpFlattening
     : public mlir::OpRewritePattern<cir::CleanupScopeOp> {
 public:
   using OpRewritePattern<cir::CleanupScopeOp>::OpRewritePattern;
 
-  // Flatten a cleanup scope with a single exit destination.
-  // The body region's exit branches to the cleanup block, the cleanup block
-  // branches to a cleanup exit block whose contents depend on the type of
-  // operation that exited the body region. Yield becomes a branch to the
-  // block after the cleanup scope, break and continue are preserved
-  // for later lowering by enclosing switch or loop. Return is preserved as is.
   mlir::LogicalResult
-  flattenSimpleCleanup(cir::CleanupScopeOp cleanupOp, mlir::Operation *exitOp,
+  createExitTerminator(mlir::Operation *exitOp, mlir::Location loc,
+                       mlir::Block *continueBlock,
                        mlir::PatternRewriter &rewriter) const {
+    return llvm::TypeSwitch<mlir::Operation *, mlir::LogicalResult>(exitOp)
+        .Case<cir::YieldOp>([&](auto) {
+          // Yield becomes a branch to continue block.
+          cir::BrOp::create(rewriter, loc, continueBlock);
+          return mlir::success();
+        })
+        .Case<cir::BreakOp>([&](auto) {
+          // Break is preserved for later lowering by enclosing switch/loop.
+          cir::BreakOp::create(rewriter, loc);
+          return mlir::success();
+        })
+        .Case<cir::ContinueOp>([&](auto) {
+          // Continue is preserved for later lowering by enclosing loop.
+          cir::ContinueOp::create(rewriter, loc);
+          return mlir::success();
+        })
+        .Case<cir::ReturnOp>([&](auto returnOp) {
----------------
xlauko wrote:

`ReturnOp` case copies the original return's operands into a dispatch block.

In multi-exit, the dispatch block is reachable only through the cleanup merge point, meaning the body block defining the return value does not dominate the dispatch block.

Don't we need to do something similar to Clang's `CGCleanup.cpp`, i.e., store the return value to a retval alloca before entering cleanup, load it back in the dispatch block?

```
int foo() {
    SomeClass s;
    if (cond) return 42;  // return-with-value through cleanup
    return 0;
}
```



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


More information about the cfe-commits mailing list