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

This change implements the CFG flattening for try operations with filter and 
unexpected exception handlers. The flattening dispatches unexpected exceptions 
to an operation that will later be lowered to target-specific IR to call 
std::unexpected, while exceptions that match the dynamic exception 
specification are routed to normal unwind handling.

Target-specific EH ABI lowering will be added in a follow-up change.

Assisted-by: Cursor / various models

>From 3681d6c75814d82b68ca78c82887556765c372c2 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <[email protected]>
Date: Tue, 15 Sep 2026 11:23:19 -0700
Subject: [PATCH] [CIR][EH] Implement flattening for dynamic exception
 specification

This change implements the CFG flattening for try operations with filter
and unexpected exception handlers. The flattening dispatches unexpected
exceptions to an operation that will later be lowered to target-specific
IR to call std::unexpected, while exceptions that match the dynamic
exception specification are routed to normal unwind handling.

Target-specific EH ABI lowering will be added in a follow-up change.

Assisted-by: Cursor / various models
---
 .../lib/CIR/Dialect/Transforms/FlattenCFG.cpp |  59 ++++----
 .../CIR/Transforms/flatten-eh-spec-nyi.cir    |  37 -----
 clang/test/CIR/Transforms/flatten-eh-spec.cir | 136 ++++++++++++++++++
 3 files changed, 170 insertions(+), 62 deletions(-)
 delete mode 100644 clang/test/CIR/Transforms/flatten-eh-spec-nyi.cir
 create mode 100644 clang/test/CIR/Transforms/flatten-eh-spec.cir

diff --git a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp 
b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
index 33160b56651c6..23ab086175178 100644
--- a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
@@ -1723,6 +1723,14 @@ class CIRTryOpFlattening : public 
mlir::OpRewritePattern<cir::TryOp> {
     mlir::Block *defaultDest = nullptr;
     bool defaultIsCatchAll = false;
 
+    // A filter try operation has exactly two handlers. The unexpected region
+    // is the destination of the filter clause (taken when the exception is
+    // *not* permitted). The filter region is the unwind destination (taken
+    // when it is).
+    cir::EhFilterAttr filterAttr;
+    mlir::Block *filterUnwindDest = nullptr;
+    mlir::Block *filterClauseDest = nullptr;
+
     for (auto [typeAttr, handlerBlock] :
          llvm::zip(handlerTypes, catchHandlerBlocks)) {
       if (mlir::isa<cir::CatchAllAttr>(typeAttr)) {
@@ -1733,6 +1741,13 @@ class CIRTryOpFlattening : public 
mlir::OpRewritePattern<cir::TryOp> {
         assert(!defaultDest && "multiple catch_all or unwind handlers");
         defaultDest = handlerBlock;
         defaultIsCatchAll = false;
+      } else if (auto ehFilter = mlir::dyn_cast<cir::EhFilterAttr>(typeAttr)) {
+        assert(!filterAttr && "multiple filter handlers");
+        filterAttr = ehFilter;
+        filterUnwindDest = handlerBlock;
+      } else if (mlir::isa<cir::EhUnexpectedAttr>(typeAttr)) {
+        assert(!filterClauseDest && "multiple unexpected handlers");
+        filterClauseDest = handlerBlock;
       } else {
         // This is a typed catch handler (GlobalViewAttr with type info).
         catchTypeAttrs.push_back(typeAttr);
@@ -1740,6 +1755,17 @@ class CIRTryOpFlattening : public 
mlir::OpRewritePattern<cir::TryOp> {
       }
     }
 
+    if (filterAttr) {
+      assert(filterUnwindDest && filterClauseDest &&
+             "filter handler requires an unexpected handler");
+      assert(!defaultDest &&
+             "filter handler cannot share a dispatch with catch_all or 
unwind");
+      catchTypeAttrs.push_back(filterAttr);
+      catchDests.push_back(filterClauseDest);
+      defaultDest = filterUnwindDest;
+      defaultIsCatchAll = false;
+    }
+
     assert(defaultDest && "dispatch must have a catch_all or unwind handler");
 
     mlir::ArrayAttr catchTypesArrayAttr;
@@ -1823,12 +1849,12 @@ class CIRTryOpFlattening : public 
mlir::OpRewritePattern<cir::TryOp> {
     return handlerEntry;
   }
 
-  // Flatten an unwind handler region. The unwind region just contains a
-  // cir.resume that continues unwinding. We inline it and leave the resume
-  // in place. If this try op is nested inside an EH cleanup or another try op,
-  // the enclosing op will rewrite the resume as a branch to its cleanup or
-  // dispatch block when it is flattened. Otherwise, the resume will unwind to
-  // the caller.
+  // Flatten an unwind, filter, or unexpected handler region. These regions
+  // do not catch the exception. They resume, are unreachable, or call
+  // cir.eh.unexpected. We inline them and leave the terminator in place. If
+  // this try op is nested inside an EH cleanup or another try op, the
+  // enclosing op will rewrite a resume as a branch to its cleanup or dispatch
+  // block when it is flattened. Otherwise, a resume will unwind to the caller.
   mlir::Block *flattenUnwindHandler(mlir::Region &unwindRegion,
                                     mlir::Location loc,
                                     mlir::Block *insertBefore,
@@ -1918,7 +1944,8 @@ class CIRTryOpFlattening : public 
mlir::OpRewritePattern<cir::TryOp> {
     for (const auto &[idx, typeAttr] : llvm::enumerate(handlerTypes)) {
       mlir::Region &handlerRegion = handlerRegions[idx];
 
-      if (mlir::isa<cir::UnwindAttr>(typeAttr)) {
+      if (mlir::isa<cir::UnwindAttr, cir::EhFilterAttr, cir::EhUnexpectedAttr>(
+              typeAttr)) {
         mlir::Block *unwindEntry =
             flattenUnwindHandler(handlerRegion, loc, continueBlock, rewriter);
         catchHandlerBlocks.push_back(unwindEntry);
@@ -2036,24 +2063,6 @@ class MLIRChangedListener final : public 
mlir::RewriterBase::Listener {
 } // namespace
 
 void CIRFlattenCFGPass::runOnOperation() {
-  // Flattening of dynamic exception specifications is not implemented yet.
-  // Diagnose it up front rather than from the rewrite pattern, which the
-  // driver below may run more than once for the same operation.
-  if (getOperation()
-          ->walk([&](cir::TryOp tryOp) {
-            mlir::ArrayAttr handlerTypes = tryOp.getHandlerTypesAttr();
-            if (!handlerTypes ||
-                llvm::none_of(handlerTypes, [](mlir::Attribute typeAttr) {
-                  return mlir::isa<cir::EhFilterAttr>(typeAttr);
-                }))
-              return mlir::WalkResult::advance();
-            tryOp.emitError(
-                "NYI: flattening of a dynamic exception specification 
handler");
-            return mlir::WalkResult::interrupt();
-          })
-          .wasInterrupted())
-    return signalPassFailure();
-
   RewritePatternSet patternList(&getContext());
   populateFlattenCFGPatterns(patternList);
   FrozenRewritePatternSet patterns(std::move(patternList));
diff --git a/clang/test/CIR/Transforms/flatten-eh-spec-nyi.cir 
b/clang/test/CIR/Transforms/flatten-eh-spec-nyi.cir
deleted file mode 100644
index 41476e32e7de0..0000000000000
--- a/clang/test/CIR/Transforms/flatten-eh-spec-nyi.cir
+++ /dev/null
@@ -1,37 +0,0 @@
-// RUN: cir-opt %s -cir-flatten-cfg -split-input-file -verify-diagnostics
-
-!u8i = !cir.int<u, 8>
-
-cir.func private @_Z8externalv()
-
-// void target() throw(int)
-cir.func @filter_with_types() {
-  // expected-error@+1 {{NYI: flattening of a dynamic exception specification 
handler}}
-  cir.try {
-    cir.call @_Z8externalv() : () -> ()
-    cir.yield
-  } filter [#cir.global_view<@_ZTIi> : !cir.ptr<!u8i>] (%eh_token : 
!cir.eh_token) {
-    cir.resume %eh_token : !cir.eh_token
-  } unexpected (%eh_token.1 : !cir.eh_token) {
-    cir.eh.unexpected %eh_token.1 : !cir.eh_token
-  }
-  cir.return
-}
-
-// -----
-
-cir.func private @_Z8externalv()
-
-// void target() throw()
-cir.func @filter_without_types() {
-  // expected-error@+1 {{NYI: flattening of a dynamic exception specification 
handler}}
-  cir.try {
-    cir.call @_Z8externalv() : () -> ()
-    cir.yield
-  } filter [] (%eh_token : !cir.eh_token) {
-    cir.unreachable
-  } unexpected (%eh_token.1 : !cir.eh_token) {
-    cir.eh.unexpected %eh_token.1 : !cir.eh_token
-  }
-  cir.return
-}
diff --git a/clang/test/CIR/Transforms/flatten-eh-spec.cir 
b/clang/test/CIR/Transforms/flatten-eh-spec.cir
new file mode 100644
index 0000000000000..14256c2bc3e2a
--- /dev/null
+++ b/clang/test/CIR/Transforms/flatten-eh-spec.cir
@@ -0,0 +1,136 @@
+// RUN: cir-opt %s -cir-flatten-cfg -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s
+
+!s32i = !cir.int<s, 32>
+!u8i = !cir.int<u, 8>
+!void = !cir.void
+
+cir.func private @_Z8externalv()
+cir.func private @_Z5innerv()
+
+// void target() throw(int)
+cir.func @filter_with_types() {
+  cir.try {
+    cir.call @_Z8externalv() : () -> ()
+    cir.yield
+  } filter [#cir.global_view<@_ZTIi> : !cir.ptr<!u8i>] (%eh_token : 
!cir.eh_token) {
+    cir.resume %eh_token : !cir.eh_token
+  } unexpected (%eh_token.1 : !cir.eh_token) {
+    cir.eh.unexpected %eh_token.1 : !cir.eh_token
+  }
+  cir.return
+}
+
+// CHECK-LABEL: cir.func @filter_with_types()
+// CHECK:         cir.br ^[[BODY:bb[0-9]+]]
+// CHECK:       ^[[BODY]]:
+// CHECK:         cir.try_call @_Z8externalv() ^[[NORMAL:bb[0-9]+]], 
^[[UNWIND:bb[0-9]+]]
+// CHECK:       ^[[NORMAL]]:
+// CHECK:         cir.br ^[[CONTINUE:bb[0-9]+]]
+// CHECK:       ^[[UNWIND]]:
+// CHECK:         %[[EH:.*]] = cir.eh.initiate : !cir.eh_token
+// CHECK:         cir.br ^[[DISPATCH:bb[0-9]+]](%[[EH]] : !cir.eh_token)
+// CHECK:       ^[[DISPATCH]](%[[DT:.*]]: !cir.eh_token):
+// CHECK:         cir.eh.dispatch %[[DT]] : !cir.eh_token [
+// CHECK:           filter(#cir.global_view<@_ZTIi> : !cir.ptr<!u8i>) : 
^[[VIOLATED:bb[0-9]+]],
+// CHECK:           unwind : ^[[PERMITTED:bb[0-9]+]]
+// CHECK:         ]
+// CHECK:       ^[[PERMITTED]](%[[PT:.*]]: !cir.eh_token):
+// CHECK:         cir.resume %[[PT]] : !cir.eh_token
+// CHECK:       ^[[VIOLATED]](%[[UT:.*]]: !cir.eh_token):
+// CHECK:         cir.eh.unexpected %[[UT]] : !cir.eh_token
+// CHECK:       ^[[CONTINUE]]:
+// CHECK:         cir.return
+
+// void target() throw()
+cir.func @filter_without_types() {
+  cir.try {
+    cir.call @_Z8externalv() : () -> ()
+    cir.yield
+  } filter [] (%eh_token : !cir.eh_token) {
+    cir.unreachable
+  } unexpected (%eh_token.1 : !cir.eh_token) {
+    cir.eh.unexpected %eh_token.1 : !cir.eh_token
+  }
+  cir.return
+}
+
+// CHECK-LABEL: cir.func @filter_without_types()
+// CHECK:         cir.try_call @_Z8externalv() ^[[NORMAL:bb[0-9]+]], 
^[[UNWIND:bb[0-9]+]]
+// CHECK:       ^[[NORMAL]]:
+// CHECK:         cir.br ^[[CONTINUE:bb[0-9]+]]
+// CHECK:       ^[[UNWIND]]:
+// CHECK:         %[[EH:.*]] = cir.eh.initiate : !cir.eh_token
+// CHECK:         cir.br ^[[DISPATCH:bb[0-9]+]](%[[EH]] : !cir.eh_token)
+// CHECK:       ^[[DISPATCH]](%[[DT:.*]]: !cir.eh_token):
+// CHECK:         cir.eh.dispatch %[[DT]] : !cir.eh_token [
+// CHECK:           filter() : ^[[VIOLATED:bb[0-9]+]],
+// CHECK:           unwind : ^[[PERMITTED:bb[0-9]+]]
+// CHECK:         ]
+// CHECK:       ^[[PERMITTED]](%{{.*}}: !cir.eh_token):
+// CHECK:         cir.unreachable
+// CHECK:       ^[[VIOLATED]](%[[UT:.*]]: !cir.eh_token):
+// CHECK:         cir.eh.unexpected %[[UT]] : !cir.eh_token
+// CHECK:       ^[[CONTINUE]]:
+// CHECK:         cir.return
+
+// void outer() throw() { try { inner(); } catch (int) {} }
+// The inner unwind resume is chained to the outer filter dispatch.
+cir.func @nested_try_in_filter() {
+  cir.try {
+    cir.try {
+      cir.call @_Z5innerv() : () -> ()
+      cir.yield
+    } catch [type #cir.global_view<@_ZTIi> : !cir.ptr<!u8i>]
+        (%eh_token : !cir.eh_token) {
+      %ct, %exn = cir.begin_catch %eh_token
+          : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!s32i>)
+      cir.end_catch %ct : !cir.catch_token
+      cir.yield
+    } unwind (%eh_token.1 : !cir.eh_token) {
+      cir.resume %eh_token.1 : !cir.eh_token
+    }
+    cir.yield
+  } filter [] (%eh_token.2 : !cir.eh_token) {
+    cir.unreachable
+  } unexpected (%eh_token.3 : !cir.eh_token) {
+    cir.eh.unexpected %eh_token.3 : !cir.eh_token
+  }
+  cir.return
+}
+
+// CHECK-LABEL: cir.func @nested_try_in_filter()
+// CHECK:         cir.br ^[[OUTER_BODY:bb[0-9]+]]
+// CHECK:       ^[[OUTER_BODY]]:
+// CHECK:         cir.br ^[[INNER_BODY:bb[0-9]+]]
+// CHECK:       ^[[INNER_BODY]]:
+// CHECK:         cir.try_call @_Z5innerv() ^[[INNER_NORMAL:bb[0-9]+]], 
^[[INNER_UNWIND:bb[0-9]+]]
+// CHECK:       ^[[INNER_NORMAL]]:
+// CHECK:         cir.br ^[[INNER_CONTINUE:bb[0-9]+]]
+// CHECK:       ^[[INNER_UNWIND]]:
+// CHECK:         %[[IEH:.*]] = cir.eh.initiate : !cir.eh_token
+// CHECK:         cir.br ^[[INNER_DISPATCH:bb[0-9]+]](%[[IEH]] : !cir.eh_token)
+// CHECK:       ^[[INNER_DISPATCH]](%[[ID_ET:.*]]: !cir.eh_token):
+// CHECK:         cir.eh.dispatch %[[ID_ET]] : !cir.eh_token [
+// CHECK:           catch(#cir.global_view<@_ZTIi> : !cir.ptr<!u8i>) : 
^[[INNER_CATCH:bb[0-9]+]],
+// CHECK:           unwind : ^[[INNER_UW:bb[0-9]+]]
+// CHECK:         ]
+// CHECK:       ^[[INNER_CATCH]](%[[IC_ET:.*]]: !cir.eh_token):
+// CHECK:         %{{.*}}, %{{.*}} = cir.begin_catch %[[IC_ET]]
+// CHECK:         cir.end_catch
+// CHECK:         cir.br ^[[INNER_CONTINUE]]
+// CHECK:       ^[[INNER_UW]](%[[IUW_ET:.*]]: !cir.eh_token):
+// CHECK:         cir.br ^[[OUTER_DISPATCH:bb[0-9]+]](%[[IUW_ET]] : 
!cir.eh_token)
+// CHECK:       ^[[INNER_CONTINUE]]:
+// CHECK:         cir.br ^[[OUTER_CONTINUE:bb[0-9]+]]
+// CHECK:       ^[[OUTER_DISPATCH]](%[[OD_ET:.*]]: !cir.eh_token):
+// CHECK:         cir.eh.dispatch %[[OD_ET]] : !cir.eh_token [
+// CHECK:           filter() : ^[[OUTER_VIOLATED:bb[0-9]+]],
+// CHECK:           unwind : ^[[OUTER_PERMITTED:bb[0-9]+]]
+// CHECK:         ]
+// CHECK:       ^[[OUTER_PERMITTED]](%{{.*}}: !cir.eh_token):
+// CHECK:         cir.unreachable
+// CHECK:       ^[[OUTER_VIOLATED]](%[[OVT:.*]]: !cir.eh_token):
+// CHECK:         cir.eh.unexpected %[[OVT]] : !cir.eh_token
+// CHECK:       ^[[OUTER_CONTINUE]]:
+// CHECK:         cir.return

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

Reply via email to