[clang] [CIR] Document dynamic exception specification design (PR #222451)

Andy Kaylor via cfe-commits cfe-commits at lists.llvm.org
Wed Sep 9 14:57:33 PDT 2026


================
@@ -1596,3 +1599,422 @@ case will be handled by the personality function, using tables that are
 generated from the `cir.catchpad` operations. Each catch handler simply
 continues to the normal continuation block (`^bb6`) using the
 `cir.catchret` operation.
+
+## Dynamic Exception Specifications
+
+A dynamic exception specification (`throw(T...)`, and `throw()` before
+C++17) constrains the set of exception types that a function is allowed
+to propagate to its caller. If an exception of any other type would
+escape the function, `std::unexpected()` must be called instead
+([except.spec]). Dynamic exception specifications were removed in C++17,
+so this representation is only produced for earlier language modes.
+Functions declared `noexcept`, and `throw()` in C++17 and later, are handled
+differently.
+
+Because the constraint applies to every exception that could escape the
+function, it is represented as an exception handler that encloses the
+entire function body. This section describes that representation used by the
+high-level CIR produced by CIR generation, the flattened form produced by `cir::FlattenCFG`, and the ABI-specific form produced by EH ABI lowering.
+
+### High-level CIR representation
+
+A function with a dynamic exception specification has its entire body
+wrapped in a `cir.try` operation whose handler is a *filter* handler.
+A filter handler is identified by a `#cir.eh_filter` handler type
+attribute, which carries the list of type info symbols naming the
+permitted types.
+
+```mlir
+cir.try {
+  // function body
+  cir.yield
+} filter [@_ZTIi] (%eh_token : !cir.eh_token) {
+  cir.resume %eh_token : !cir.eh_token
+}
+```
+
+The `#cir.eh_filter` attribute occupies a slot in the try operation's
+handler type list, in the same way that a `#cir.global_view` catch type,
+`catch all`, or `unwind` does. Like `unwind`, and unlike a catch
+handler, a filter handler region does not begin with `cir.begin_catch`.
+A filter does not catch the exception. It only decides whether the
+exception is permitted to continue unwinding.
+
+The test that decides whether the in-flight exception matches the filter
+is implicit in the handler type, in the same way that the type test for
+a catch handler is implicit in its `#cir.global_view` handler type.
+Neither test is expressed in the handler region. Both are materialized
+during CFG flattening and ABI lowering. The body of a filter handler
+region therefore contains only the code that runs when the exception
+*is* permitted by the specification, which is a single `cir.resume`
+operation to continue unwinding to the caller.
+
+A `cir.try` operation may have at most one filter handler, it must be
+the last handler in the handler list, and it may not be combined with a
+`catch all` handler, because a catch-all consumes every exception and
+nothing could reach the filter. The try operation created for an
+exception specification always has the filter as its only handler. A
+function-try-block on a function that also has an exception
+specification produces a separate `cir.try` operation nested inside it.
+
+An empty type list represents `throw()` before C++17. No exception is
+permitted by such a specification, so there is no permitted path to
+describe and the handler region is terminated with `cir.unreachable`
+instead of `cir.resume`. This matches Clang's LLVM IR codegen, which
+generates no resume path at all for a function whose exception
+specification permits nothing.
+
+#### Example: Simple dynamic exception specification
+
+**C++**
+
+```c++
+void external();
+
+void target() throw(int) {
+  external();
+}
+
+void target2() throw() {
+  external();
+}
+```
+
+**CIR**
+
+```mlir
+cir.func @_Z6targetv() personality(@__gxx_personality_v0) {
+  cir.try {
+    cir.call @_Z8externalv() : () -> ()
+    cir.yield
+  } filter [@_ZTIi] (%eh_token : !cir.eh_token) {
+    cir.resume %eh_token : !cir.eh_token
----------------
andykaylor wrote:

Yeah, I thought about having another region to explicitly represent the unexpected exception case. That would be more readable. The way I have it here the unexpected exception handling is implied by the presence of the filter.

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


More information about the cfe-commits mailing list