https://github.com/andykaylor created 
https://github.com/llvm/llvm-project/pull/191084

This change adds a canonicalizer for CleanupScopeOp that erases any cleanup 
scope with a trivial cleanup region, inlining the contents of the body region 
into the block in place of the cleanup scope op. It also erases any EH-only 
cleanup scope whose body region contains only a yield operation, dropping the 
cleanup region contents even if they were not trivial because the EH cleanup is 
not reachable in this case.

Assisted-by: Cursor / claude-4.6-opus-high

>From 64ccca1fc79e689ae6bd976faf17431ffc019bcf Mon Sep 17 00:00:00 2001
From: Andy Kaylor <[email protected]>
Date: Wed, 8 Apr 2026 16:29:58 -0700
Subject: [PATCH] [CIR] Add canonicalizer for CleanupScopeOp

This change adds a canonicalizer for CleanupScopeOp that erases any
cleanup scope with a trivial cleanup region, inlining the contents
of the body region into the block in place of the cleanup scope op. It
also erases any EH-only cleanup scope whose body region contains
only a yield operation, dropping the cleanup region contents even if
they were not trivial because the EH cleanup is not reachable in this
case.

Assisted-by: Cursor / claude-4.6-opus-high
---
 clang/include/clang/CIR/Dialect/IR/CIROps.td  |   1 +
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp       |  35 ++++
 .../Dialect/Transforms/CIRCanonicalize.cpp    |   3 +-
 .../Transforms/canonicalize-cleanup-scope.cir | 196 ++++++++++++++++++
 4 files changed, 234 insertions(+), 1 deletion(-)
 create mode 100644 clang/test/CIR/Transforms/canonicalize-cleanup-scope.cir

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 93ca0172b2a7f..b806800275c7b 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -1298,6 +1298,7 @@ def CIR_CleanupScopeOp : CIR_Op<"cleanup.scope", [
       "llvm::function_ref<void(mlir::OpBuilder &, 
mlir::Location)>":$cleanupBuilder)>
   ];
 
+  let hasCanonicalizeMethod = 1;
   let hasLLVMLowering = false;
 }
 
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index e99faf840c15a..d5e21c26fe9be 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -1449,6 +1449,41 @@ cir::CleanupScopeOp::getSuccessorInputs(RegionSuccessor 
successor) {
   return ValueRange();
 }
 
+LogicalResult cir::CleanupScopeOp::canonicalize(CleanupScopeOp op,
+                                                PatternRewriter &rewriter) {
+  auto isRegionTrivial = [](Region &region) {
+    if (!region.hasOneBlock())
+      return false;
+    Block &block = region.front();
+    return llvm::hasSingleElement(block) && isa<cir::YieldOp>(block.front());
+  };
+
+  Region &body = op.getBodyRegion();
+  Region &cleanup = op.getCleanupRegion();
+
+  // An EH-only cleanup scope with an empty body can never trigger its cleanup
+  // region — there are no operations in the body that could throw. Erase it.
+  if (op.getCleanupKind() == CleanupKind::EH && isRegionTrivial(body)) {
+    rewriter.eraseOp(op);
+    return success();
+  }
+
+  // A cleanup scope with a trivial cleanup region has no cleanup to perform.
+  // Inline the body into the parent block and erase the scope.
+  if (!isRegionTrivial(cleanup) || !body.hasOneBlock())
+    return failure();
+
+  Block &bodyBlock = body.front();
+  if (!isa<cir::YieldOp>(bodyBlock.getTerminator()))
+    return failure();
+
+  Operation *yield = bodyBlock.getTerminator();
+  rewriter.inlineBlockBefore(&bodyBlock, op);
+  rewriter.eraseOp(yield);
+  rewriter.eraseOp(op);
+  return success();
+}
+
 void cir::CleanupScopeOp::build(
     OpBuilder &builder, OperationState &result, CleanupKind cleanupKind,
     function_ref<void(OpBuilder &, Location)> bodyBuilder,
diff --git a/clang/lib/CIR/Dialect/Transforms/CIRCanonicalize.cpp 
b/clang/lib/CIR/Dialect/Transforms/CIRCanonicalize.cpp
index 333517956fe9c..da08f21977066 100644
--- a/clang/lib/CIR/Dialect/Transforms/CIRCanonicalize.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CIRCanonicalize.cpp
@@ -75,7 +75,8 @@ void CIRCanonicalizePass::runOnOperation() {
             ComplexCreateOp, ComplexImagOp, ComplexRealOp, VecCmpOp,
             VecCreateOp, VecExtractOp, VecShuffleOp, VecShuffleDynamicOp,
             VecTernaryOp, BitClrsbOp, BitClzOp, BitCtzOp, BitFfsOp, 
BitParityOp,
-            BitPopcountOp, BitReverseOp, ByteSwapOp, RotateOp, ConstantOp>(op))
+            BitPopcountOp, BitReverseOp, ByteSwapOp, RotateOp, ConstantOp,
+            CleanupScopeOp>(op))
       ops.push_back(op);
   });
 
diff --git a/clang/test/CIR/Transforms/canonicalize-cleanup-scope.cir 
b/clang/test/CIR/Transforms/canonicalize-cleanup-scope.cir
new file mode 100644
index 0000000000000..e6f127549fe13
--- /dev/null
+++ b/clang/test/CIR/Transforms/canonicalize-cleanup-scope.cir
@@ -0,0 +1,196 @@
+// RUN: cir-opt %s -cir-canonicalize -o - | FileCheck %s
+
+!s32i = !cir.int<s, 32>
+!rec_S = !cir.record<struct "S" {!s32i}>
+
+module {
+
+cir.func private @dtor(!cir.ptr<!rec_S>) attributes {nothrow}
+cir.func private @may_throw()
+cir.func private @side_effect()
+
+// EH cleanup with trivial body and trivial cleanup is erased (trivially dead).
+cir.func @eh_both_trivial_erased() {
+  cir.cleanup.scope {
+    cir.yield
+  } cleanup eh {
+    cir.yield
+  }
+  cir.return
+}
+// CHECK-LABEL: @eh_both_trivial_erased
+// CHECK-NEXT:   cir.return
+
+// EH cleanup with trivial body and non-trivial cleanup is erased: the body
+// has no throwing operations so the EH cleanup can never fire.
+cir.func @eh_empty_body_nontrivial_cleanup(%arg0: !cir.ptr<!rec_S>) {
+  cir.cleanup.scope {
+    cir.yield
+  } cleanup eh {
+    cir.call @dtor(%arg0) nothrow : (!cir.ptr<!rec_S>) -> ()
+    cir.yield
+  }
+  cir.return
+}
+// CHECK-LABEL: @eh_empty_body_nontrivial_cleanup
+// CHECK-NEXT:   cir.return
+
+// EH cleanup with non-empty body should NOT be erased.
+cir.func @eh_nonempty_body_kept(%arg0: !cir.ptr<!rec_S>) {
+  cir.cleanup.scope {
+    cir.call @may_throw() : () -> ()
+    cir.yield
+  } cleanup eh {
+    cir.call @dtor(%arg0) nothrow : (!cir.ptr<!rec_S>) -> ()
+    cir.yield
+  }
+  cir.return
+}
+// CHECK-LABEL: @eh_nonempty_body_kept
+// CHECK:        cir.cleanup.scope {
+// CHECK:          cir.call @may_throw()
+// CHECK:          cir.yield
+// CHECK:        } cleanup eh {
+// CHECK:          cir.call @dtor
+// CHECK:          cir.yield
+// CHECK:        }
+// CHECK:        cir.return
+
+// All cleanup with non-trivial cleanup should NOT be erased.
+cir.func @all_nonempty_cleanup_kept(%arg0: !cir.ptr<!rec_S>) {
+  cir.cleanup.scope {
+    cir.yield
+  } cleanup all {
+    cir.call @dtor(%arg0) nothrow : (!cir.ptr<!rec_S>) -> ()
+    cir.yield
+  }
+  cir.return
+}
+// CHECK-LABEL: @all_nonempty_cleanup_kept
+// CHECK:        cir.cleanup.scope {
+// CHECK:          cir.yield
+// CHECK:        } cleanup all {
+// CHECK:          cir.call @dtor
+// CHECK:          cir.yield
+// CHECK:        }
+// CHECK:        cir.return
+
+// Normal cleanup with non-trivial cleanup should NOT be erased.
+cir.func @normal_nonempty_cleanup_kept(%arg0: !cir.ptr<!rec_S>) {
+  cir.cleanup.scope {
+    cir.yield
+  } cleanup normal {
+    cir.call @dtor(%arg0) nothrow : (!cir.ptr<!rec_S>) -> ()
+    cir.yield
+  }
+  cir.return
+}
+// CHECK-LABEL: @normal_nonempty_cleanup_kept
+// CHECK:        cir.cleanup.scope {
+// CHECK:          cir.yield
+// CHECK:        } cleanup normal {
+// CHECK:          cir.call @dtor
+// CHECK:          cir.yield
+// CHECK:        }
+// CHECK:        cir.return
+
+// Trivial cleanup region: body is inlined and scope is erased.
+cir.func @trivial_cleanup_body_inlined() {
+  cir.cleanup.scope {
+    cir.call @side_effect() : () -> ()
+    cir.yield
+  } cleanup all {
+    cir.yield
+  }
+  cir.return
+}
+// CHECK-LABEL: @trivial_cleanup_body_inlined
+// CHECK-NEXT:   cir.call @side_effect()
+// CHECK-NEXT:   cir.return
+
+// Trivial cleanup region with normal kind: body inlined.
+cir.func @normal_trivial_cleanup_inlined() {
+  cir.cleanup.scope {
+    cir.call @side_effect() : () -> ()
+    cir.yield
+  } cleanup normal {
+    cir.yield
+  }
+  cir.return
+}
+// CHECK-LABEL: @normal_trivial_cleanup_inlined
+// CHECK-NEXT:   cir.call @side_effect()
+// CHECK-NEXT:   cir.return
+
+// Trivial cleanup with multiple body ops: all inlined in order.
+cir.func @trivial_cleanup_multi_ops(%arg0: !cir.ptr<!rec_S>) {
+  cir.cleanup.scope {
+    cir.call @may_throw() : () -> ()
+    cir.call @dtor(%arg0) nothrow : (!cir.ptr<!rec_S>) -> ()
+    cir.yield
+  } cleanup all {
+    cir.yield
+  }
+  cir.return
+}
+// CHECK-LABEL: @trivial_cleanup_multi_ops
+// CHECK-NEXT:   cir.call @may_throw()
+// CHECK-NEXT:   cir.call @dtor
+// CHECK-NEXT:   cir.return
+
+// Nested: inner EH with empty body erased, outer kept.
+cir.func @nested_inner_eh_erased(%arg0: !cir.ptr<!rec_S>) {
+  cir.cleanup.scope {
+    cir.call @may_throw() : () -> ()
+    cir.cleanup.scope {
+      cir.yield
+    } cleanup eh {
+      cir.call @dtor(%arg0) nothrow : (!cir.ptr<!rec_S>) -> ()
+      cir.yield
+    }
+    cir.yield
+  } cleanup eh {
+    cir.call @dtor(%arg0) nothrow : (!cir.ptr<!rec_S>) -> ()
+    cir.yield
+  }
+  cir.return
+}
+// CHECK-LABEL: @nested_inner_eh_erased
+// CHECK:        cir.cleanup.scope {
+// CHECK:          cir.call @may_throw()
+// CHECK-NOT:      cir.cleanup.scope
+// CHECK:          cir.yield
+// CHECK:        } cleanup eh {
+// CHECK:          cir.call @dtor
+// CHECK:          cir.yield
+// CHECK:        }
+// CHECK:        cir.return
+
+// Nested: inner has trivial cleanup, outer does not. Inner body inlined
+// into outer body; outer remains.
+cir.func @nested_trivial_cleanup_inlined(%arg0: !cir.ptr<!rec_S>) {
+  cir.cleanup.scope {
+    cir.cleanup.scope {
+      cir.call @may_throw() : () -> ()
+      cir.yield
+    } cleanup eh {
+      cir.yield
+    }
+    cir.yield
+  } cleanup all {
+    cir.call @dtor(%arg0) nothrow : (!cir.ptr<!rec_S>) -> ()
+    cir.yield
+  }
+  cir.return
+}
+// CHECK-LABEL: @nested_trivial_cleanup_inlined
+// CHECK:        cir.cleanup.scope {
+// CHECK-NEXT:     cir.call @may_throw()
+// CHECK-NEXT:     cir.yield
+// CHECK-NEXT:   } cleanup all {
+// CHECK:          cir.call @dtor
+// CHECK:          cir.yield
+// CHECK:        }
+// CHECK:        cir.return
+
+}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to