[clang] [CIR][EH] Add dialect elements for dynamic exception specification (PR #223503)
Andy Kaylor via cfe-commits
cfe-commits at lists.llvm.org
Mon Sep 14 15:49:28 PDT 2026
https://github.com/andykaylor updated https://github.com/llvm/llvm-project/pull/223503
>From 6dc8fd4ef426cf7b66fb0b0564f0411aeb0bb11f Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Mon, 14 Sep 2026 12:21:59 -0700
Subject: [PATCH 1/2] [CIR][EH] Add dialect elements for dynamic exception
specification
This change introduces the CIR dialect elements that will be needed to
implement dynamic exception specification handling. At this point
nothing generates any of these elements, and they are not handled in CFG
flattening or EH ABI lowering. Those will be implemented in follow-up
changes.
See clang/docs/CIR/CleanupAndEHDesign.md for the design of this feature.
Assisted-by: Cursor / various models
---
.../CIR/Dialect/IR/CIRAttrConstraints.td | 15 +-
.../include/clang/CIR/Dialect/IR/CIRAttrs.td | 33 ++++
clang/include/clang/CIR/Dialect/IR/CIROps.td | 108 +++++++++-
clang/lib/CIR/Dialect/IR/CIRAttrs.cpp | 44 +++--
clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 147 +++++++++++++-
.../CIR/Dialect/Transforms/EHABILowering.cpp | 28 ++-
.../lib/CIR/Dialect/Transforms/FlattenCFG.cpp | 18 ++
clang/test/CIR/IR/invalid-try-catch.cir | 184 +++++++++++++++++-
clang/test/CIR/IR/try-catch.cir | 150 ++++++++++++++
.../eh-abi-lowering-eh-spec-nyi.cir | 62 ++++++
.../CIR/Transforms/flatten-eh-spec-nyi.cir | 37 ++++
11 files changed, 801 insertions(+), 25 deletions(-)
create mode 100644 clang/test/CIR/Transforms/eh-abi-lowering-eh-spec-nyi.cir
create mode 100644 clang/test/CIR/Transforms/flatten-eh-spec-nyi.cir
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrConstraints.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrConstraints.td
index 3cb21dd2f4fc7..0f0585beb0bed 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrConstraints.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrConstraints.td
@@ -48,6 +48,12 @@ def CIR_AnyCatchAllAttr
def CIR_AnyUnwindAttr
: CIR_AttrConstraint<"::cir::UnwindAttr", "unwind attribute">;
+def CIR_AnyEhFilterAttr
+ : CIR_AttrConstraint<"::cir::EhFilterAttr", "eh filter attribute">;
+
+def CIR_AnyEhUnexpectedAttr
+ : CIR_AttrConstraint<"::cir::EhUnexpectedAttr", "eh unexpected attribute">;
+
//===----------------------------------------------------------------------===//
// GlobalViewAttr constraints
//===----------------------------------------------------------------------===//
@@ -62,8 +68,10 @@ def CIR_AnyIntOrGlobalViewAttr
}
def CIR_TryHandlerAttr
- : AnyAttrOf<[CIR_AnyGlobalViewAttr, CIR_AnyCatchAllAttr, CIR_AnyUnwindAttr],
- "catch all or unwind or global view attribute"> {
+ : AnyAttrOf<[CIR_AnyGlobalViewAttr, CIR_AnyCatchAllAttr, CIR_AnyUnwindAttr,
+ CIR_AnyEhFilterAttr, CIR_AnyEhUnexpectedAttr],
+ "catch all, unwind, eh filter, eh unexpected or global view "
+ "attribute"> {
string cppType = "::mlir::TypedAttr";
}
@@ -99,6 +107,7 @@ def CIR_StringArrayAttrOrNull
: CIR_TypedArrayAttrOrNullBase<StrAttr, "string array attribute or null">;
def CIR_TryHandlerArrayAttr : CIR_TypedArrayAttrBase<CIR_TryHandlerAttr,
- "catch all or unwind or global view array attribute">;
+ "catch all, unwind, eh filter, eh unexpected or global view array "
+ "attribute">;
#endif // CLANG_CIR_DIALECT_IR_CIRATTRCONSTRAINTS_TD
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index f263cd30eb458..c5f19592d4592 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -1872,6 +1872,39 @@ def CIR_UnwindAttr : CIR_UnitAttr<"Unwind", "unwind"> {
let canHaveIllegalCXXABIType = 0;
}
+//===----------------------------------------------------------------------===//
+// EhFilterAttr & EhUnexpectedAttr
+//===----------------------------------------------------------------------===//
+
+def CIR_EhFilterAttr : CIR_Attr<"EhFilter", "eh_filter"> {
+ let summary = "Dynamic exception specification filter handler";
+ let description = [{
+ Identifies the handler of a `cir.try` operation that is reached when the
+ in-flight exception is permitted by the dynamic exception specification of
+ the enclosing function. The attribute carries the list of type info symbols
+ naming the permitted types. An empty list represents `throw()`, which
+ permits nothing.
+ }];
+
+ let parameters = (ins "mlir::ArrayAttr":$permitted_types);
+ let assemblyFormat = [{ `<` $permitted_types `>` }];
+ let genVerifyDecl = 1;
+ let canHaveIllegalCXXABIType = 0;
+}
+
+// Represents the region reached when an exception violates the dynamic
+// exception specification of the enclosing function.
+def CIR_EhUnexpectedAttr : CIR_UnitAttr<"EhUnexpected", "eh_unexpected"> {
+ let summary = "Dynamic exception specification unexpected exception handler";
+ let description = [{
+ Identifies the handler of a `cir.try` operation that is reached when the
+ in-flight exception is not permitted by the dynamic exception specification
+ of the enclosing function.
+ }];
+
+ let canHaveIllegalCXXABIType = 0;
+}
+
//===----------------------------------------------------------------------===//
// CIR_BlockAddrInfoAttr
//===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index f03c591fe9597..bd2cbd05b0949 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -8252,6 +8252,23 @@ def CIR_TryOp : CIR_RegionBranchOpBase<"try", [
into the body of the cleanup scope. This ensures that all paths out of the
catch handler will execute the end_catch operation.
+ A `cir.try` operation may instead have a `filter` handler paired with an
+ `unexpected` handler, which together implement the dynamic exception
+ specification of the enclosing function. The `filter` handler lists the
+ permitted type info symbols and holds the path taken when the in-flight
+ exception is permitted, which is a `cir.resume` that continues unwinding
+ to the caller. The `unexpected` handler holds the path taken when the
+ specification is violated, which is a `cir.eh.unexpected`. An empty
+ permitted type list represents `throw()`, which permits nothing, so the
+ filter handler region holds a `cir.unreachable` instead. Like `unwind`,
+ and unlike a catch handler, neither region begins with `cir.begin_catch`.
+
+ The two handlers must appear together, with the filter first, and must be
+ the only handlers on the operation. The try operation created for an
+ exception specification wraps the entire function body, and each try
+ statement written in the source becomes a separate `cir.try` operation
+ nested inside it.
+
Example:
```
@@ -8273,6 +8290,20 @@ def CIR_TryOp : CIR_RegionBranchOpBase<"try", [
cir.resume %eh_token : !cir.eh_token
}
```
+
+ Example of a `void f() throw(int)` exception specification:
+
+ ```
+ cir.try {
+ cir.call exception @function() : () -> ()
+ 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
+ }
+ ```
}];
let arguments = (ins
@@ -8372,6 +8403,15 @@ def CIR_EhInflightOp : CIR_Op<"eh.inflight_exception"> {
alongside typed catches, the landing pad will contain both the typed catch
clauses and a trailing `catch ptr null`.
+ The `filter_type_list` attribute lists the type info symbols permitted by
+ the dynamic exception specification of the enclosing function. It lowers to
+ the `filter` clause of the LLVM IR landingpad instruction. An empty list is
+ distinct from an absent one: it represents `throw()` and lowers to a
+ zero-length filter clause, which the personality routine treats as
+ permitting nothing. Since the exception specification encloses the whole
+ function body, the filter clause always comes after any catch clauses, and
+ it terminates the clause list in the same way that `catch_all` does.
+
Example:
```
@@ -8379,17 +8419,21 @@ def CIR_EhInflightOp : CIR_Op<"eh.inflight_exception"> {
%exception_ptr, %type_id = cir.eh.inflight_exception [@_ZTIi, @_ZTIPKc]
%exception_ptr, %type_id = cir.eh.inflight_exception cleanup
%exception_ptr, %type_id = cir.eh.inflight_exception catch_all [@_ZTIi]
+ %exception_ptr, %type_id = cir.eh.inflight_exception filter [@_ZTIi]
+ %exception_ptr, %type_id = cir.eh.inflight_exception [@_ZTIi] filter []
``
}];
let arguments = (ins UnitAttr:$cleanup,
UnitAttr:$catch_all,
- OptionalAttr<FlatSymbolRefArrayAttr>:$catch_type_list);
+ OptionalAttr<FlatSymbolRefArrayAttr>:$catch_type_list,
+ OptionalAttr<FlatSymbolRefArrayAttr>:$filter_type_list);
let results = (outs CIR_VoidPtrType:$exception_ptr, CIR_UInt32:$type_id);
let assemblyFormat = [{
(`cleanup` $cleanup^)?
(`catch_all` $catch_all^)?
($catch_type_list^)?
+ (`filter` $filter_type_list^)?
attr-dict
}];
}
@@ -8550,6 +8594,49 @@ def CIR_EhTerminateOp : CIR_Op<"eh.terminate", [
let hasLLVMLowering = false;
}
+//===----------------------------------------------------------------------===//
+// Exception related: EhUnexpectedOp
+//===----------------------------------------------------------------------===//
+
+def CIR_EhUnexpectedOp : CIR_Op<"eh.unexpected", [
+ Terminator
+]> {
+ let summary = "Handle an exception that violates an exception specification";
+ let description = [{
+ `cir.eh.unexpected` signals that the in-flight exception violated the
+ dynamic exception specification of the enclosing function, and that
+ `std::unexpected()` must be called. The C++ standard requires this when an
+ exception that the specification does not permit tries to escape the
+ function.
+
+ This operation takes a `!cir.eh_token` and acts as a terminator. In
+ high-level CIR it is the only operation in the `unexpected` handler region
+ of a `cir.try` operation, where the token comes from the handler region's
+ block argument. After CFG flattening the token comes from a
+ `cir.eh.initiate` operation.
+
+ Like `cir.eh.terminate`, this operation is ABI-agnostic. During EH ABI
+ lowering it is replaced with target-specific code. For the Itanium ABI it
+ becomes a call to `__cxa_call_unexpected` with the exception pointer,
+ followed by an unreachable operation.
+
+ Example:
+
+ ```
+ ^unexpected:
+ %eh_token = cir.eh.initiate : !cir.eh_token
+ cir.eh.unexpected %eh_token : !cir.eh_token
+ ```
+ }];
+
+ let arguments = (ins CIR_EhTokenType:$eh_token);
+ let assemblyFormat = [{
+ $eh_token `:` type($eh_token) attr-dict
+ }];
+
+ let hasLLVMLowering = false;
+}
+
//===----------------------------------------------------------------------===//
// Flattened EH Operations: EhDispatchOp
//===----------------------------------------------------------------------===//
@@ -8575,6 +8662,17 @@ def CIR_EhDispatchOp : CIR_Op<"eh.dispatch", [
another type handler. The `unwind` handler is used when no handler is
matched.
+ A handler may also be a `filter`, which carries the list of type info
+ symbols permitted by the dynamic exception specification of the enclosing
+ function. A `filter` handler has the *opposite* polarity from a `catch`
+ handler: its destination is taken when the in-flight exception is **not**
+ one of the listed types. The polarity comes from the Itanium personality
+ routine, which reports a filter failure by selecting the filter clause of
+ the landing pad. A `filter` handler therefore does not take the place of
+ the default destination, and a dispatch that has one always also has an
+ `unwind` handler, whose destination is taken when the exception is
+ permitted. A dispatch has at most one `filter` handler.
+
Example:
```
@@ -8588,6 +8686,12 @@ def CIR_EhDispatchOp : CIR_Op<"eh.dispatch", [
catch (#cir.global_view<type @_ZTIi> : !cir.ptr<!u8i>) : ^catch_int,
unwind : ^continue_unwind
]
+
+ cir.eh.dispatch %eh_token : !cir.eh_token [
+ filter(#cir.global_view<@_ZTIi> : !cir.ptr<!u8i>)
+ : ^specification_violated,
+ unwind : ^exception_permitted
+ ]
```
}];
@@ -8602,6 +8706,8 @@ def CIR_EhDispatchOp : CIR_Op<"eh.dispatch", [
VariadicSuccessor<AnySuccessor>:$catch_destinations
);
+ let hasVerifier = 1;
+
let assemblyFormat = [{
$eh_token `:` type($eh_token)
custom<EhDispatchDestinations>($catch_types, $catch_destinations,
diff --git a/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
index 64191971f0a81..05408c8b2f2e9 100644
--- a/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
@@ -893,24 +893,24 @@ std::string DynamicCastInfoAttr::getAlias() const {
return alias;
}
+// TODO: Give type_info a distinct CIR type so we can verify that a
+// FlatSymbolRefAttr actually names a type_info global.
+static bool isRttiPtr(mlir::Type ty) {
+ auto ptrTy = mlir::dyn_cast<cir::PointerType>(ty);
+ if (!ptrTy)
+ return false;
+
+ auto pointeeIntTy = mlir::dyn_cast<cir::IntType>(ptrTy.getPointee());
+ if (!pointeeIntTy)
+ return false;
+
+ return pointeeIntTy.isUnsigned() && pointeeIntTy.getWidth() == 8;
+}
+
LogicalResult DynamicCastInfoAttr::verify(
function_ref<InFlightDiagnostic()> emitError, cir::GlobalViewAttr srcRtti,
cir::GlobalViewAttr destRtti, mlir::FlatSymbolRefAttr runtimeFunc,
mlir::FlatSymbolRefAttr badCastFunc, cir::IntAttr offsetHint) {
- auto isRttiPtr = [](mlir::Type ty) {
- // RTTI pointers are !cir.ptr<!u8i>.
-
- auto ptrTy = mlir::dyn_cast<cir::PointerType>(ty);
- if (!ptrTy)
- return false;
-
- auto pointeeIntTy = mlir::dyn_cast<cir::IntType>(ptrTy.getPointee());
- if (!pointeeIntTy)
- return false;
-
- return pointeeIntTy.isUnsigned() && pointeeIntTy.getWidth() == 8;
- };
-
if (!isRttiPtr(srcRtti.getType()))
return emitError() << "srcRtti must be an RTTI pointer";
@@ -920,6 +920,22 @@ LogicalResult DynamicCastInfoAttr::verify(
return success();
}
+//===----------------------------------------------------------------------===//
+// EhFilterAttr definitions
+//===----------------------------------------------------------------------===//
+
+LogicalResult EhFilterAttr::verify(function_ref<InFlightDiagnostic()> emitError,
+ mlir::ArrayAttr permittedTypes) {
+ for (mlir::Attribute typeAttr : permittedTypes) {
+ auto rtti = mlir::dyn_cast<cir::GlobalViewAttr>(typeAttr);
+ if (!rtti || !isRttiPtr(rtti.getType()))
+ return emitError() << "permitted type list must contain only type info "
+ "symbols";
+ }
+
+ return success();
+}
+
//===----------------------------------------------------------------------===//
// RecordLayout lookup
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index d7b1551b2e7ed..4c71046b6c4d8 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -4501,6 +4501,22 @@ LogicalResult cir::TryOp::verify() {
return emitOpError(
"number of handler regions and handler types must match");
+ // A filter handler and an unexpected handler together implement a dynamic
+ // exception specification. The filter try operation wraps the whole function
+ // body and exists only to check the specification, so the two must appear
+ // together and must stand alone. A catch-all in the same list would make the
+ // filter unreachable, and the filter handler already provides the
+ // continue-unwinding path that an unwind handler would supply.
+ if (llvm::any_of(handlerTypes, [](mlir::Attribute typeAttr) {
+ return mlir::isa<cir::EhFilterAttr, cir::EhUnexpectedAttr>(typeAttr);
+ })) {
+ if (handlerTypes.size() != 2 ||
+ !mlir::isa<cir::EhFilterAttr>(handlerTypes[0]) ||
+ !mlir::isa<cir::EhUnexpectedAttr>(handlerTypes[1]))
+ return emitOpError("a filter handler must be followed by an unexpected "
+ "handler, and the two must be the only handlers");
+ }
+
for (const auto &[typeAttr, handlerRegion] :
llvm::zip(handlerTypes, handlerRegions)) {
// Verify that handler regions have a !cir.eh_token block argument.
@@ -4510,8 +4526,10 @@ LogicalResult cir::TryOp::verify() {
return emitOpError(
"handler region must have a single '!cir.eh_token' argument");
- // The unwind region does not require a cir.begin_catch.
- if (mlir::isa<cir::UnwindAttr>(typeAttr))
+ // The unwind, filter and unexpected regions do not require a
+ // cir.begin_catch. None of them catches the exception.
+ if (mlir::isa<cir::UnwindAttr, cir::EhFilterAttr, cir::EhUnexpectedAttr>(
+ typeAttr))
continue;
// Nothing may run in a catch handler before cir.begin_catch, so it has to
@@ -4573,6 +4591,14 @@ printTryHandlerRegions(mlir::OpAsmPrinter &printer, cir::TryOp op,
printer << "catch all ";
} else if (mlir::isa<cir::UnwindAttr>(typeAttr)) {
printer << "unwind ";
+ } else if (auto filterAttr = mlir::dyn_cast<cir::EhFilterAttr>(typeAttr)) {
+ printer << "filter [";
+ llvm::interleaveComma(
+ filterAttr.getPermittedTypes(), printer,
+ [&](mlir::Attribute sym) { printer.printAttribute(sym); });
+ printer << "] ";
+ } else if (mlir::isa<cir::EhUnexpectedAttr>(typeAttr)) {
+ printer << "unexpected ";
} else {
printer << "catch [type ";
printer.printAttribute(typeAttr);
@@ -4670,6 +4696,45 @@ static mlir::ParseResult parseTryHandlerRegions(
return mlir::failure();
}
+ // A filter handler carries the type info symbols permitted by the enclosing
+ // function's dynamic exception specification. TryOp::verify enforces that it
+ // is paired with an unexpected handler and that the two stand alone.
+ if (parser.parseOptionalKeyword("filter").succeeded()) {
+ mlir::SMLoc filterLoc = parser.getCurrentLocation();
+ llvm::SmallVector<mlir::Attribute, 4> permittedTypes;
+ auto parsePermittedType = [&]() -> mlir::ParseResult {
+ mlir::SMLoc typeLoc = parser.getCurrentLocation();
+ mlir::Attribute rtti;
+ if (parser.parseAttribute(rtti).failed())
+ return mlir::failure();
+ if (!mlir::isa<cir::GlobalViewAttr>(rtti))
+ return parser.emitError(typeLoc, "expected a type info symbol naming a "
+ "permitted exception type");
+ permittedTypes.push_back(rtti);
+ return mlir::success();
+ };
+ if (parser
+ .parseCommaSeparatedList(mlir::OpAsmParser::Delimiter::Square,
+ parsePermittedType)
+ .failed())
+ return mlir::failure();
+
+ auto filterAttr = cir::EhFilterAttr::getChecked(
+ [&]() { return parser.emitError(filterLoc); }, parser.getContext(),
+ parser.getBuilder().getArrayAttr(permittedTypes));
+ if (!filterAttr)
+ return mlir::failure();
+ catcherAttrs.push_back(filterAttr);
+ if (parseCheckedCatcherRegion().failed())
+ return mlir::failure();
+ }
+
+ if (parser.parseOptionalKeyword("unexpected").succeeded()) {
+ catcherAttrs.push_back(cir::EhUnexpectedAttr::get(parser.getContext()));
+ if (parseCheckedCatcherRegion().failed())
+ return mlir::failure();
+ }
+
if (parser.parseOptionalKeyword("unwind").succeeded()) {
if (hasCatchAll)
return parser.emitError(parser.getCurrentLocation(),
@@ -4797,6 +4862,31 @@ LogicalResult cir::ConstructCatchParamOp::verifySymbolUses(
// EhDispatchOp
//===----------------------------------------------------------------------===//
+LogicalResult cir::EhDispatchOp::verify() {
+ mlir::ArrayAttr handlerTypes = getCatchTypesAttr();
+ if (!handlerTypes)
+ return success();
+
+ bool hasFilter = false;
+ for (mlir::Attribute typeAttr : handlerTypes) {
+ if (!mlir::isa<cir::EhFilterAttr>(typeAttr))
+ continue;
+ if (hasFilter)
+ return emitOpError("can't have more than one 'filter' handler");
+ hasFilter = true;
+ }
+
+ // Unlike the other handlers, a filter does not take the place of the default
+ // destination. Its destination is taken when the exception is not permitted
+ // by the specification, and the default 'unwind' destination is taken when
+ // it is. A catch-all default would leave the filter unreachable.
+ if (hasFilter && getDefaultIsCatchAll())
+ return emitOpError(
+ "'filter' handler requires an 'unwind' default destination");
+
+ return success();
+}
+
static ParseResult
parseEhDispatchDestinations(OpAsmParser &parser, mlir::ArrayAttr &catchTypes,
SmallVectorImpl<Block *> &catchDestinations,
@@ -4848,6 +4938,47 @@ parseEhDispatchDestinations(OpAsmParser &parser, mlir::ArrayAttr &catchTypes,
return success();
}
+ // A filter handler carries the type info symbols permitted by the
+ // enclosing function's dynamic exception specification. Its destination is
+ // taken when the exception is *not* one of those types.
+ if (succeeded(parser.parseOptionalKeyword("filter"))) {
+ SMLoc filterLoc = parser.getCurrentLocation();
+ SmallVector<Attribute, 4> permittedTypes;
+ auto parsePermittedType = [&]() -> ParseResult {
+ mlir::SMLoc typeLoc = parser.getCurrentLocation();
+ mlir::Attribute rtti;
+ if (parser.parseAttribute(rtti).failed())
+ return failure();
+ if (!mlir::isa<cir::GlobalViewAttr>(rtti))
+ return parser.emitError(typeLoc,
+ "expected a type info symbol naming a "
+ "permitted exception type");
+ permittedTypes.push_back(rtti);
+ return success();
+ };
+ if (parser
+ .parseCommaSeparatedList(OpAsmParser::Delimiter::Paren,
+ parsePermittedType)
+ .failed())
+ return failure();
+
+ auto filterAttr = cir::EhFilterAttr::getChecked(
+ [&]() { return parser.emitError(filterLoc); }, parser.getContext(),
+ parser.getBuilder().getArrayAttr(permittedTypes));
+ if (!filterAttr)
+ return failure();
+ handlerTypes.push_back(filterAttr);
+
+ if (parser.parseColon().failed())
+ return failure();
+
+ Block *dest;
+ if (parser.parseSuccessor(dest).failed())
+ return failure();
+ catchDestinations.push_back(dest);
+ return success();
+ }
+
// Otherwise, expect 'catch(<attr> : <type>) : ^block'.
// The 'catch(...)' wrapper allows the attribute to include its type
// without conflicting with the ':' used for the block destination.
@@ -4910,8 +5041,16 @@ static void printEhDispatchDestinations(OpAsmPrinter &p, cir::EhDispatchOp op,
llvm::interleave(
llvm::zip(catchTypes, catchDestinations),
[&](auto i) {
- p << " catch(";
- p.printAttribute(std::get<0>(i));
+ mlir::Attribute typeAttr = std::get<0>(i);
+ if (auto filterAttr = mlir::dyn_cast<cir::EhFilterAttr>(typeAttr)) {
+ p << " filter(";
+ llvm::interleaveComma(
+ filterAttr.getPermittedTypes(), p,
+ [&](mlir::Attribute sym) { p.printAttribute(sym); });
+ } else {
+ p << " catch(";
+ p.printAttribute(typeAttr);
+ }
p << ") : ";
p.printSuccessor(std::get<1>(i));
},
diff --git a/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp b/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp
index 71609356141a4..6035f6c09af75 100644
--- a/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp
@@ -291,7 +291,8 @@ mlir::Block *ItaniumEHLowering::buildTerminateBlock(cir::FuncOp funcOp,
mlir::Block *terminateBlock = builder.createBlock(&body, body.end());
auto inflight = cir::EhInflightOp::create(
builder, loc, /*cleanup=*/false, /*catch_all=*/true,
- /*catch_type_list=*/mlir::ArrayAttr{});
+ /*catch_type_list=*/mlir::ArrayAttr{},
+ /*filter_type_list=*/mlir::ArrayAttr{});
auto terminateCall = cir::CallOp::create(
builder, loc, mlir::FlatSymbolRefAttr::get(clangCallTerminateFunc),
voidType, mlir::ValueRange{inflight.getExceptionPtr()});
@@ -512,7 +513,8 @@ mlir::LogicalResult ItaniumEHLowering::lowerEhInitiate(
auto inflightOp = cir::EhInflightOp::create(
builder, initiateOp.getLoc(),
/*cleanup=*/initiateOp.getCleanup() || reachesCleanup,
- /*catch_all=*/catchAll, catchTypeList);
+ /*catch_all=*/catchAll, catchTypeList,
+ /*filter_type_list=*/mlir::ArrayAttr{});
ehTokenMap[rootToken] = {inflightOp.getExceptionPtr(),
inflightOp.getTypeId()};
@@ -1034,6 +1036,28 @@ void CIREHABILoweringPass::runOnOperation() {
lowering = std::make_unique<ItaniumEHLowering>(mod);
}
+ // Dynamic exception specifications are not lowered yet. Diagnose them before
+ // the lowering runs, which would otherwise treat a filter handler as a typed
+ // catch handler.
+ if (mod.walk([&](mlir::Operation *op) {
+ if (auto dispatch = mlir::dyn_cast<cir::EhDispatchOp>(op)) {
+ mlir::ArrayAttr handlerTypes = dispatch.getCatchTypesAttr();
+ if (!handlerTypes ||
+ llvm::none_of(handlerTypes, [](mlir::Attribute typeAttr) {
+ return mlir::isa<cir::EhFilterAttr>(typeAttr);
+ }))
+ return mlir::WalkResult::advance();
+ dispatch.emitError("NYI: EH ABI lowering of a 'filter' handler");
+ return mlir::WalkResult::interrupt();
+ } else if (mlir::isa<cir::EhUnexpectedOp>(op)) {
+ op->emitError("NYI: EH ABI lowering of 'cir.eh.unexpected'");
+ return mlir::WalkResult::interrupt();
+ }
+ return mlir::WalkResult::advance();
+ })
+ .wasInterrupted())
+ return signalPassFailure();
+
if (mlir::failed(lowering->run()))
return signalPassFailure();
diff --git a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
index eda0b59cc2367..33160b56651c6 100644
--- a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
@@ -2036,6 +2036,24 @@ 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/IR/invalid-try-catch.cir b/clang/test/CIR/IR/invalid-try-catch.cir
index 50da221af2c52..1f81a4ff811da 100644
--- a/clang/test/CIR/IR/invalid-try-catch.cir
+++ b/clang/test/CIR/IR/invalid-try-catch.cir
@@ -22,7 +22,7 @@ module {
cir.func dso_local @invalid_catch_rtti_type() {
cir.scope {
- // expected-error @below {{'cir.try' op attribute 'handler_types' failed to satisfy constraint: catch all or unwind or global view array attribute}}
+ // expected-error @below {{'cir.try' op attribute 'handler_types' failed to satisfy constraint: catch all, unwind, eh filter, eh unexpected or global view array attribute}}
cir.try {
cir.yield
} catch [type #cir.undef] (%eh_token : !cir.eh_token) {
@@ -329,3 +329,185 @@ cir.func dso_local @invalid_unwind_with_catch_all() {
}
}
+
+// -----
+
+!u8i = !cir.int<u, 8>
+
+cir.func private @_Z8externalv()
+
+// A filter handler must be paired with an unexpected handler.
+cir.func @filter_without_unexpected() {
+ // expected-error at +1 {{a filter handler must be followed by an unexpected handler, and the two must be the only handlers}}
+ 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
+ }
+ cir.return
+}
+
+// -----
+
+cir.func private @_Z8externalv()
+
+// An unexpected handler must be preceded by a filter handler.
+cir.func @unexpected_without_filter() {
+ // expected-error at +1 {{a filter handler must be followed by an unexpected handler, and the two must be the only handlers}}
+ cir.try {
+ cir.call @_Z8externalv() : () -> ()
+ cir.yield
+ } unexpected (%eh_token : !cir.eh_token) {
+ cir.eh.unexpected %eh_token : !cir.eh_token
+ }
+ cir.return
+}
+
+// -----
+
+!u8i = !cir.int<u, 8>
+!void = !cir.void
+
+cir.func private @_Z8externalv()
+
+// The filter try operation exists only to check the specification. A try
+// statement written in the source becomes a separate, nested cir.try.
+cir.func @filter_with_catch() {
+ // expected-error at +1 {{a filter handler must be followed by an unexpected handler, and the two must be the only handlers}}
+ cir.try {
+ cir.call @_Z8externalv() : () -> ()
+ cir.yield
+ } catch [type #cir.global_view<@_ZTIl> : !cir.ptr<!u8i>]
+ (%eh_token : !cir.eh_token) {
+ %catch_token, %exn_ptr = cir.begin_catch %eh_token
+ : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!void>)
+ cir.end_catch %catch_token : !cir.catch_token
+ cir.yield
+ } filter [#cir.global_view<@_ZTIi> : !cir.ptr<!u8i>] (%eh_token.1 : !cir.eh_token) {
+ cir.resume %eh_token.1 : !cir.eh_token
+ } unexpected (%eh_token.2 : !cir.eh_token) {
+ cir.eh.unexpected %eh_token.2 : !cir.eh_token
+ }
+ cir.return
+}
+
+// -----
+
+!u8i = !cir.int<u, 8>
+
+cir.func private @_Z8externalv()
+
+// The filter handler already provides the continue-unwinding path that an
+// unwind handler would supply.
+cir.func @filter_with_unwind() {
+ // expected-error at +1 {{a filter handler must be followed by an unexpected handler, and the two must be the only handlers}}
+ 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
+ } unwind (%eh_token.2 : !cir.eh_token) {
+ cir.resume %eh_token.2 : !cir.eh_token
+ }
+ cir.return
+}
+
+// -----
+
+cir.func private @_Z8externalv()
+
+// The permitted type list holds type info symbols.
+cir.func @filter_bad_permitted_type() {
+ cir.try {
+ cir.call @_Z8externalv() : () -> ()
+ cir.yield
+ // expected-error at +1 {{expected a type info symbol naming a permitted exception type}}
+ } filter [42 : i32] (%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
+}
+
+// -----
+
+!u8i = !cir.int<u, 8>
+!u32i = !cir.int<u, 32>
+
+cir.func private @_Z8externalv()
+
+// Type info symbols are GlobalViewAttrs typed as RTTI pointers.
+cir.func @filter_non_rtti_ptr() {
+ cir.try {
+ cir.call @_Z8externalv() : () -> ()
+ cir.yield
+ // expected-error at +1 {{permitted type list must contain only type info symbols}}
+ } filter [#cir.global_view<@_ZTIi> : !cir.ptr<!u32i>] (%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
+}
+
+// -----
+
+!u8i = !cir.int<u, 8>
+
+cir.func private @_Z8externalv()
+
+// A filter clause does not take the place of the default destination, so it
+// cannot be combined with a catch-all default.
+cir.func @dispatch_filter_with_catch_all() {
+ cir.try_call @_Z8externalv() ^bb1, ^bb2 : () -> ()
+^bb1:
+ cir.br ^bb5
+^bb2:
+ %0 = cir.eh.initiate : !cir.eh_token
+ cir.br ^bb3(%0 : !cir.eh_token)
+^bb3(%eh_token: !cir.eh_token):
+ // expected-error at +1 {{'filter' handler requires an 'unwind' default destination}}
+ cir.eh.dispatch %eh_token : !cir.eh_token [
+ filter(#cir.global_view<@_ZTIi> : !cir.ptr<!u8i>) : ^bb4,
+ catch_all : ^bb6
+ ]
+^bb4(%eh_token.1: !cir.eh_token):
+ cir.eh.unexpected %eh_token.1 : !cir.eh_token
+^bb5:
+ cir.return
+^bb6(%eh_token.2: !cir.eh_token):
+ cir.br ^bb5
+}
+
+// -----
+
+!u8i = !cir.int<u, 8>
+
+cir.func private @_Z8externalv()
+
+// A dispatch has at most one filter clause.
+cir.func @dispatch_two_filters() {
+ cir.try_call @_Z8externalv() ^bb1, ^bb2 : () -> ()
+^bb1:
+ cir.br ^bb5
+^bb2:
+ %0 = cir.eh.initiate : !cir.eh_token
+ cir.br ^bb3(%0 : !cir.eh_token)
+^bb3(%eh_token: !cir.eh_token):
+ // expected-error at +1 {{can't have more than one 'filter' handler}}
+ cir.eh.dispatch %eh_token : !cir.eh_token [
+ filter(#cir.global_view<@_ZTIi> : !cir.ptr<!u8i>) : ^bb4,
+ filter(#cir.global_view<@_ZTIf> : !cir.ptr<!u8i>) : ^bb4,
+ unwind : ^bb6
+ ]
+^bb4(%eh_token.1: !cir.eh_token):
+ cir.eh.unexpected %eh_token.1 : !cir.eh_token
+^bb5:
+ cir.return
+^bb6(%eh_token.2: !cir.eh_token):
+ cir.resume %eh_token.2 : !cir.eh_token
+}
diff --git a/clang/test/CIR/IR/try-catch.cir b/clang/test/CIR/IR/try-catch.cir
index 203ec340d7f6e..d63ea3d25d28c 100644
--- a/clang/test/CIR/IR/try-catch.cir
+++ b/clang/test/CIR/IR/try-catch.cir
@@ -7,6 +7,7 @@
module {
cir.global "private" constant external @_ZTIi : !cir.ptr<!u8i>
+cir.global "private" constant external @_ZTIf : !cir.ptr<!u8i>
cir.global "private" constant external @_ZTIPKc : !cir.ptr<!u8i>
cir.func dso_local @empty_try_block_with_catch_all() {
@@ -147,4 +148,153 @@ cir.func @empty_try_block_with_catch_unwind_contains_resume() {
// CHECK: cir.return
// CHECK: }
+cir.func private @_Z8externalv()
+
+// void target() throw(int)
+cir.func @filter_one_type() {
+ 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_one_type
+// CHECK: cir.try {
+// CHECK: cir.call @_Z8externalv() : () -> ()
+// CHECK: cir.yield
+// CHECK: } filter [#cir.global_view<@_ZTIi> : !cir.ptr<!u8i>] (%[[TOK:.*]]: !cir.eh_token) {
+// CHECK: cir.resume %[[TOK]] : !cir.eh_token
+// CHECK: } unexpected (%[[UTOK:.*]]: !cir.eh_token) {
+// CHECK: cir.eh.unexpected %[[UTOK]] : !cir.eh_token
+// CHECK: }
+
+cir.func @filter_several_types() {
+ cir.try {
+ cir.call @_Z8externalv() : () -> ()
+ cir.yield
+ } filter [#cir.global_view<@_ZTIi> : !cir.ptr<!u8i>,
+ #cir.global_view<@_ZTIf> : !cir.ptr<!u8i>,
+ #cir.global_view<@_ZTIPKc> : !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_several_types
+// CHECK: } filter [#cir.global_view<@_ZTIi> : !cir.ptr<!u8i>, #cir.global_view<@_ZTIf> : !cir.ptr<!u8i>, #cir.global_view<@_ZTIPKc> : !cir.ptr<!u8i>] (%{{.*}}: !cir.eh_token) {
+
+cir.func @filter_no_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_no_types
+// CHECK: } filter [] (%{{.*}}: !cir.eh_token) {
+// CHECK: cir.unreachable
+// CHECK: } unexpected (%[[UTOK:.*]]: !cir.eh_token) {
+// CHECK: cir.eh.unexpected %[[UTOK]] : !cir.eh_token
+
+cir.func @flat_dispatch_filter() {
+ cir.try_call @_Z8externalv() ^bb1, ^bb2 : () -> ()
+^bb1:
+ cir.br ^bb5
+^bb2:
+ %0 = cir.eh.initiate : !cir.eh_token
+ cir.br ^bb3(%0 : !cir.eh_token)
+^bb3(%eh_token: !cir.eh_token):
+ cir.eh.dispatch %eh_token : !cir.eh_token [
+ filter(#cir.global_view<@_ZTIi> : !cir.ptr<!u8i>) : ^bb4,
+ unwind : ^bb6
+ ]
+^bb4(%eh_token.1: !cir.eh_token):
+ cir.eh.unexpected %eh_token.1 : !cir.eh_token
+^bb5:
+ cir.return
+^bb6(%eh_token.2: !cir.eh_token):
+ cir.resume %eh_token.2 : !cir.eh_token
+}
+
+// CHECK-LABEL: cir.func{{.*}} @flat_dispatch_filter
+// CHECK: cir.eh.dispatch %{{.*}} : !cir.eh_token [
+// CHECK: filter(#cir.global_view<@_ZTIi> : !cir.ptr<!u8i>) : ^[[VIOLATED:.*]],
+// CHECK: unwind : ^[[PERMITTED:.*]]
+// CHECK: ]
+// CHECK: ^[[VIOLATED]](%[[UTOK:.*]]: !cir.eh_token):
+// CHECK: cir.eh.unexpected %[[UTOK]] : !cir.eh_token
+
+cir.func @flat_dispatch_filter_types() {
+ cir.try_call @_Z8externalv() ^bb1, ^bb2 : () -> ()
+^bb1:
+ cir.br ^bb5
+^bb2:
+ %0 = cir.eh.initiate : !cir.eh_token
+ cir.br ^bb3(%0 : !cir.eh_token)
+^bb3(%eh_token: !cir.eh_token):
+ cir.eh.dispatch %eh_token : !cir.eh_token [
+ catch(#cir.global_view<@_ZTIl> : !cir.ptr<!u8i>) : ^bb7,
+ filter(#cir.global_view<@_ZTIi> : !cir.ptr<!u8i>,
+ #cir.global_view<@_ZTIf> : !cir.ptr<!u8i>) : ^bb4,
+ unwind : ^bb6
+ ]
+^bb4(%eh_token.1: !cir.eh_token):
+ cir.eh.unexpected %eh_token.1 : !cir.eh_token
+^bb5:
+ cir.return
+^bb6(%eh_token.2: !cir.eh_token):
+ cir.resume %eh_token.2 : !cir.eh_token
+^bb7(%eh_token.3: !cir.eh_token):
+ cir.br ^bb5
+}
+
+// CHECK-LABEL: cir.func{{.*}} @flat_dispatch_filter_types
+// CHECK: catch(#cir.global_view<@_ZTIl> : !cir.ptr<!u8i>) : ^{{.*}},
+// CHECK: filter(#cir.global_view<@_ZTIi> : !cir.ptr<!u8i>, #cir.global_view<@_ZTIf> : !cir.ptr<!u8i>) : ^{{.*}},
+// CHECK: unwind : ^{{.*}}
+
+// The landing pad accumulates the clauses of every dispatch the exception can
+// reach, so a catch clause and a filter clause may appear together.
+cir.func @inflight_filter() {
+ cir.try_call @_Z8externalv() ^bb1, ^bb2 : () -> ()
+^bb1:
+ cir.br ^bb3
+^bb2:
+ %exception_ptr, %type_id = cir.eh.inflight_exception filter [@_ZTIi]
+ cir.br ^bb3
+^bb3:
+ cir.return
+}
+
+// CHECK-LABEL: cir.func{{.*}} @inflight_filter
+// CHECK: %{{.*}}, %{{.*}} = cir.eh.inflight_exception filter [@_ZTIi]
+
+cir.func @inflight_catch_and_empty_filter() {
+ cir.try_call @_Z8externalv() ^bb1, ^bb2 : () -> ()
+^bb1:
+ cir.br ^bb3
+^bb2:
+ %exception_ptr, %type_id =
+ cir.eh.inflight_exception cleanup [@_ZTIl] filter []
+ cir.br ^bb3
+^bb3:
+ cir.return
+}
+
+// CHECK-LABEL: cir.func{{.*}} @inflight_catch_and_empty_filter
+// CHECK: cir.eh.inflight_exception cleanup [@_ZTIl] filter []
+
}
diff --git a/clang/test/CIR/Transforms/eh-abi-lowering-eh-spec-nyi.cir b/clang/test/CIR/Transforms/eh-abi-lowering-eh-spec-nyi.cir
new file mode 100644
index 0000000000000..f7904d55defce
--- /dev/null
+++ b/clang/test/CIR/Transforms/eh-abi-lowering-eh-spec-nyi.cir
@@ -0,0 +1,62 @@
+// RUN: cir-opt %s -cir-eh-abi-lowering -split-input-file -verify-diagnostics
+
+!u8i = !cir.int<u, 8>
+
+module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
+
+cir.func private @_Z8externalv()
+
+cir.func @dispatch_with_filter() {
+ cir.try_call @_Z8externalv() ^bb1, ^bb2 : () -> ()
+^bb1:
+ cir.br ^bb5
+^bb2:
+ %0 = cir.eh.initiate : !cir.eh_token
+ cir.br ^bb3(%0 : !cir.eh_token)
+^bb3(%eh_token: !cir.eh_token):
+ // expected-error at +1 {{NYI: EH ABI lowering of a 'filter' handler}}
+ cir.eh.dispatch %eh_token : !cir.eh_token [
+ filter(#cir.global_view<@_ZTIi> : !cir.ptr<!u8i>) : ^bb4,
+ unwind : ^bb6
+ ]
+^bb4(%eh_token.1: !cir.eh_token):
+ // expected-error at +1 {{NYI: EH ABI lowering of 'cir.eh.unexpected'}}
+ cir.eh.unexpected %eh_token.1 : !cir.eh_token
+^bb5:
+ cir.return
+^bb6(%eh_token.2: !cir.eh_token):
+ cir.resume %eh_token.2 : !cir.eh_token
+}
+
+}
+
+// -----
+
+module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
+
+cir.func private @_Z8externalv()
+
+// An empty filter list still needs lowering support.
+cir.func @dispatch_with_empty_filter() {
+ cir.try_call @_Z8externalv() ^bb1, ^bb2 : () -> ()
+^bb1:
+ cir.br ^bb5
+^bb2:
+ %0 = cir.eh.initiate : !cir.eh_token
+ cir.br ^bb3(%0 : !cir.eh_token)
+^bb3(%eh_token: !cir.eh_token):
+ // expected-error at +1 {{NYI: EH ABI lowering of a 'filter' handler}}
+ cir.eh.dispatch %eh_token : !cir.eh_token [
+ filter() : ^bb4,
+ unwind : ^bb6
+ ]
+^bb4(%eh_token.1: !cir.eh_token):
+ // expected-error at +1 {{NYI: EH ABI lowering of 'cir.eh.unexpected'}}
+ cir.eh.unexpected %eh_token.1 : !cir.eh_token
+^bb5:
+ cir.return
+^bb6(%eh_token.2: !cir.eh_token):
+ cir.unreachable
+}
+
+}
diff --git a/clang/test/CIR/Transforms/flatten-eh-spec-nyi.cir b/clang/test/CIR/Transforms/flatten-eh-spec-nyi.cir
new file mode 100644
index 0000000000000..41476e32e7de0
--- /dev/null
+++ b/clang/test/CIR/Transforms/flatten-eh-spec-nyi.cir
@@ -0,0 +1,37 @@
+// 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 at +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 at +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
+}
>From f9a1df0a90d994c771a88bdba665aa9c28bfebce Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Mon, 14 Sep 2026 15:48:49 -0700
Subject: [PATCH 2/2] Address review feedback and fix test
---
.../CIR/Dialect/Transforms/EHABILowering.cpp | 5 ++--
.../eh-abi-lowering-eh-spec-nyi.cir | 23 +++++++++++++++++--
2 files changed, 23 insertions(+), 5 deletions(-)
diff --git a/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp b/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp
index 6035f6c09af75..e545b8021daa1 100644
--- a/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp
@@ -511,9 +511,8 @@ mlir::LogicalResult ItaniumEHLowering::lowerEhInitiate(
builder.setInsertionPoint(initiateOp);
auto inflightOp = cir::EhInflightOp::create(
- builder, initiateOp.getLoc(),
- /*cleanup=*/initiateOp.getCleanup() || reachesCleanup,
- /*catch_all=*/catchAll, catchTypeList,
+ builder, initiateOp.getLoc(), initiateOp.getCleanup() || reachesCleanup,
+ catchAll, catchTypeList,
/*filter_type_list=*/mlir::ArrayAttr{});
ehTokenMap[rootToken] = {inflightOp.getExceptionPtr(),
diff --git a/clang/test/CIR/Transforms/eh-abi-lowering-eh-spec-nyi.cir b/clang/test/CIR/Transforms/eh-abi-lowering-eh-spec-nyi.cir
index f7904d55defce..670559c419477 100644
--- a/clang/test/CIR/Transforms/eh-abi-lowering-eh-spec-nyi.cir
+++ b/clang/test/CIR/Transforms/eh-abi-lowering-eh-spec-nyi.cir
@@ -20,7 +20,6 @@ cir.func @dispatch_with_filter() {
unwind : ^bb6
]
^bb4(%eh_token.1: !cir.eh_token):
- // expected-error at +1 {{NYI: EH ABI lowering of 'cir.eh.unexpected'}}
cir.eh.unexpected %eh_token.1 : !cir.eh_token
^bb5:
cir.return
@@ -51,7 +50,6 @@ cir.func @dispatch_with_empty_filter() {
unwind : ^bb6
]
^bb4(%eh_token.1: !cir.eh_token):
- // expected-error at +1 {{NYI: EH ABI lowering of 'cir.eh.unexpected'}}
cir.eh.unexpected %eh_token.1 : !cir.eh_token
^bb5:
cir.return
@@ -60,3 +58,24 @@ cir.func @dispatch_with_empty_filter() {
}
}
+
+// -----
+
+module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
+
+cir.func private @_Z8externalv()
+
+// The dispatch of a dynamic exception specification is diagnosed before its
+// 'cir.eh.unexpected' is reached, so the diagnostic for the operation itself
+// needs a function that has no 'filter' handler.
+cir.func @unexpected_without_dispatch() {
+ cir.try_call @_Z8externalv() ^bb1, ^bb2 : () -> ()
+^bb1:
+ cir.return
+^bb2:
+ %0 = cir.eh.initiate : !cir.eh_token
+ // expected-error at +1 {{NYI: EH ABI lowering of 'cir.eh.unexpected'}}
+ cir.eh.unexpected %0 : !cir.eh_token
+}
+
+}
More information about the cfe-commits
mailing list