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
