https://github.com/xlauko updated 
https://github.com/llvm/llvm-project/pull/231449

>From 174be027152561ef72c4fd9ab257414202bbd63f Mon Sep 17 00:00:00 2001
From: Henrich Lauko <[email protected]>
Date: Tue, 13 Oct 2026 10:40:32 +0000
Subject: [PATCH] [CIR] Fix cir.try successor regions

`TryOp::getSuccessorRegions` sent every exit from the try region back to the
parent, so nothing led from the try region to the handlers. A `cir.resume`
ending the try region, left there once FlattenCFG has flattened an inner
cleanup scope, unwinds into a handler, and `CIRTryOpFlattening` branches it
to the handler dispatch block. Now every exit from the try region can enter
the handlers, and a `cir.resume` leaves the op only when there are none. The
parent keeps its edges to the handlers. An unwind out of a call in the try
region is not a region branch point, so these edges stand in for it, and
without them -remove-dead-values empties a handler that is reached only that
way.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp       |  30 +++--
 .../CIR/Transforms/remove-dead-values-try.cir |  40 ++++++
 clang/unittests/CIR/ControlFlowTest.cpp       | 115 +++++++++++++++++-
 3 files changed, 173 insertions(+), 12 deletions(-)
 create mode 100644 clang/test/CIR/Transforms/remove-dead-values-try.cir

diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 15f5ccaef130b7..1012827cdcc6e8 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -807,7 +807,7 @@ 
cir::ConditionOp::getMutableSuccessorOperands(RegionSuccessor point) {
 
 MutableOperandRange
 cir::ResumeOp::getMutableSuccessorOperands(RegionSuccessor point) {
-  // The eh_token operand is not forwarded to the parent region.
+  // The eh_token operand is not modeled as forwarded to any successor.
   return MutableOperandRange(getOperation(), 0, 0);
 }
 
@@ -5147,18 +5147,30 @@ LogicalResult cir::TypeInfoAttr::verify(
 void cir::TryOp::getSuccessorRegions(
     mlir::RegionBranchPoint point,
     llvm::SmallVectorImpl<mlir::RegionSuccessor> &regions) {
-  // The `try` and the `catchers` region branch back to the parent operation.
-  if (!point.isParent()) {
-    regions.emplace_back(getOperation());
-    return;
+  if (point.isParent()) {
+    regions.emplace_back(&getTryRegion());
+  } else {
+    // A handler exit leaves the operation.
+    mlir::Operation *term = point.getTerminatorPredecessorOrNull();
+    if (term->getParentRegion() != &getTryRegion()) {
+      regions.emplace_back(getOperation());
+      return;
+    }
+    // A cir.resume ending the try region unwinds into a handler, so it leaves
+    // the operation only when there is none. Every other exit can leave it.
+    if (!isa<cir::ResumeOp>(term) || getHandlerRegions().empty())
+      regions.emplace_back(getOperation());
   }
 
-  regions.push_back(mlir::RegionSuccessor(&getTryRegion()));
-
+  // Most handlers are entered by an unwind out of a call in the try region,
+  // which is not a branch point, so the handlers are successors of the parent
+  // and of every exit from the try region to stand in for it.
+  //
   // TODO(CIR): If we know a target function never throws a specific type, we
   // can remove the catch handler.
-  for (mlir::Region &handlerRegion : this->getHandlerRegions())
-    regions.push_back(mlir::RegionSuccessor(&handlerRegion));
+  regions.reserve(regions.size() + getHandlerRegions().size());
+  for (mlir::Region &handlerRegion : getHandlerRegions())
+    regions.emplace_back(&handlerRegion);
 }
 
 /// Verify that each cir.init_catch_param in \p handlerRegion, outside any
diff --git a/clang/test/CIR/Transforms/remove-dead-values-try.cir 
b/clang/test/CIR/Transforms/remove-dead-values-try.cir
new file mode 100644
index 00000000000000..446f0355183b04
--- /dev/null
+++ b/clang/test/CIR/Transforms/remove-dead-values-try.cir
@@ -0,0 +1,40 @@
+// RUN: cir-opt %s -remove-dead-values -o - | FileCheck %s
+
+// The handler runs only when @may_throw throws, which is not a region branch
+// point. The handler region must still count as reachable, or generic passes
+// such as -remove-dead-values erase its contents.
+
+!s32i = !cir.int<s, 32>
+!void = !cir.void
+
+module {
+  cir.func private @may_throw()
+
+  cir.func @catch_all(%p : !cir.ptr<!s32i>) {
+    cir.scope {
+      cir.try {
+        cir.call @may_throw() : () -> ()
+        cir.yield
+      } catch all (%eh : !cir.eh_token) {
+        %ct, %exn = cir.begin_catch %eh : !cir.eh_token -> (!cir.catch_token, 
!cir.ptr<!void>)
+        %c = cir.const #cir.int<7> : !s32i
+        cir.store %c, %p : !s32i, !cir.ptr<!s32i>
+        cir.end_catch %ct : !cir.catch_token
+        cir.yield
+      }
+    }
+    cir.return
+  }
+
+  // CHECK-LABEL: cir.func @catch_all
+  // CHECK:         cir.try {
+  // CHECK-NEXT:      cir.call @may_throw() : () -> ()
+  // CHECK-NEXT:      cir.yield
+  // CHECK-NEXT:    } catch all (%[[EH:.*]]: !cir.eh_token) {
+  // CHECK-NEXT:      %[[CT:.*]], %{{.*}} = cir.begin_catch %[[EH]]
+  // CHECK-NEXT:      %[[C:.*]] = cir.const #cir.int<7> : !s32i
+  // CHECK-NEXT:      cir.store %[[C]], %{{.*}} : !s32i, !cir.ptr<!s32i>
+  // CHECK-NEXT:      cir.end_catch %[[CT]] : !cir.catch_token
+  // CHECK-NEXT:      cir.yield
+  // CHECK-NEXT:    }
+}
diff --git a/clang/unittests/CIR/ControlFlowTest.cpp 
b/clang/unittests/CIR/ControlFlowTest.cpp
index a83d1098a17cbc..92fa33b16708ac 100644
--- a/clang/unittests/CIR/ControlFlowTest.cpp
+++ b/clang/unittests/CIR/ControlFlowTest.cpp
@@ -733,15 +733,124 @@ TEST_F(CIRControlFlowTest, TryOpWithCatchAll) {
   MutableArrayRef<Region> handlerRegions = tryOp.getHandlerRegions();
   ASSERT_EQ(handlerRegions.size(), 1u);
 
+  // A throw from the middle of the body is not a branch point, so the handler
+  // is a successor of both the parent and the body's cir.yield.
   expectSuccessors(tryOp, RegionBranchPoint::parent(),
                    {&tryRegion, &handlerRegions[0]});
-  expectTerminatorSuccessors(tryRegion, {nullptr});
+  expectTerminatorSuccessors(tryRegion, {&handlerRegions[0], nullptr});
   expectTerminatorSuccessors(handlerRegions[0], {nullptr});
 
   EXPECT_FALSE(asRegionBranch(tryOp).hasLoop());
 
-  // TODO: TryOp::getSuccessorInputs returns empty for handler regions that
-  // have block arguments, so verifyControlFlowInterfaceConsistency fails.
+  verifyControlFlowInterfaceConsistency(tryOp);
+}
+
+TEST_F(CIRControlFlowTest, TryOpResumeInBody) {
+  // A cir.resume left behind in the body by an already-flattened inner
+  // cleanup scope unwinds into the handlers.
+  OwningOpRef<ModuleOp> module = parse(R"CIR(
+    !u8i = !cir.int<u, 8>
+    !void = !cir.void
+    cir.func @f() {
+      cir.scope {
+        cir.try {
+          %tok = cir.eh.initiate cleanup : !cir.eh_token
+          cir.resume %tok : !cir.eh_token
+        } catch [type #cir.global_view<@_ZTIi> : !cir.ptr<!u8i>]
+            (%eh : !cir.eh_token) {
+          %ct, %exn = cir.begin_catch %eh
+            : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!void>)
+          cir.end_catch %ct : !cir.catch_token
+          cir.yield
+        } unwind (%eh : !cir.eh_token) {
+          cir.resume %eh : !cir.eh_token
+        }
+      }
+      cir.return
+    }
+  )CIR");
+  auto tryOp = findFirstOp<cir::TryOp>(*module);
+
+  Region &tryRegion = tryOp.getTryRegion();
+  MutableArrayRef<Region> handlerRegions = tryOp.getHandlerRegions();
+  ASSERT_EQ(handlerRegions.size(), 2u);
+
+  expectSuccessors(tryOp, RegionBranchPoint::parent(),
+                   {&tryRegion, &handlerRegions[0], &handlerRegions[1]});
+  expectTerminatorSuccessors(tryRegion,
+                             {&handlerRegions[0], &handlerRegions[1]});
+  // A cir.resume in the unwind handler leaves the op like any handler exit.
+  expectTerminatorSuccessors(handlerRegions[0], {nullptr});
+  expectTerminatorSuccessors(handlerRegions[1], {nullptr});
+
+  EXPECT_FALSE(asRegionBranch(tryOp).hasLoop());
+
+  verifyControlFlowInterfaceConsistency(tryOp);
+}
+
+TEST_F(CIRControlFlowTest, TryOpMultiBlockBody) {
+  // One block leaves the try body normally and another unwinds. Only the
+  // normal exit can leave the op.
+  OwningOpRef<ModuleOp> module = parse(R"CIR(
+    !void = !cir.void
+    cir.func @f() {
+      cir.scope {
+        cir.try {
+          %cond = cir.const #cir.bool<true> : !cir.bool
+          cir.brcond %cond ^bb_normal, ^bb_unwind
+        ^bb_normal:
+          cir.yield
+        ^bb_unwind:
+          %tok = cir.eh.initiate cleanup : !cir.eh_token
+          cir.resume %tok : !cir.eh_token
+        } catch all (%eh : !cir.eh_token) {
+          %ct, %exn = cir.begin_catch %eh
+            : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!void>)
+          cir.end_catch %ct : !cir.catch_token
+          cir.yield
+        }
+      }
+      cir.return
+    }
+  )CIR");
+  auto tryOp = findFirstOp<cir::TryOp>(*module);
+  Region &tryRegion = tryOp.getTryRegion();
+  Region &handler = tryOp.getHandlerRegions()[0];
+
+  SmallVector<RegionBranchTerminatorOpInterface> bodyTerms =
+      getExitTerminators(tryRegion);
+  ASSERT_EQ(bodyTerms.size(), 2u);
+  EXPECT_TRUE(isa<cir::YieldOp>(*bodyTerms[0]));
+  EXPECT_TRUE(isa<cir::ResumeOp>(*bodyTerms[1]));
+
+  expectSuccessors(tryOp, RegionBranchPoint(bodyTerms[0]), {&handler, 
nullptr});
+  expectSuccessors(tryOp, RegionBranchPoint(bodyTerms[1]), {&handler});
+  expectTerminatorSuccessors(handler, {nullptr});
+
+  EXPECT_FALSE(asRegionBranch(tryOp).hasLoop());
+
+  verifyControlFlowInterfaceConsistency(tryOp);
+}
+
+TEST_F(CIRControlFlowTest, TryOpResumeInBodyWithoutHandlers) {
+  OwningOpRef<ModuleOp> module = parse(R"CIR(
+    cir.func @f() {
+      cir.scope {
+        cir.try {
+          %tok = cir.eh.initiate cleanup : !cir.eh_token
+          cir.resume %tok : !cir.eh_token
+        }
+      }
+      cir.return
+    }
+  )CIR");
+  auto tryOp = findFirstOp<cir::TryOp>(*module);
+
+  // With no handler to unwind into, the cir.resume leaves the op.
+  expectSuccessors(tryOp, RegionBranchPoint::parent(), 
{&tryOp.getTryRegion()});
+  expectTerminatorSuccessors(tryOp.getTryRegion(), {nullptr});
+
+  verifyControlFlowInterfaceConsistency(tryOp);
 }
 
 TEST_F(CIRControlFlowTest, CoroutineOp) {

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

Reply via email to