From 3af059e4bbc1ccd09170264670a38d3c36a932cd Mon Sep 17 00:00:00 2001 From: Amr Hesham Date: Fri, 10 Oct 2025 19:09:16 +0200 Subject: [PATCH 1/4] [CIR] Upstream CIR Dialect TryOp with Catch Attrs --- .../include/clang/CIR/Dialect/IR/CIRAttrs.td | 15 ++ clang/include/clang/CIR/Dialect/IR/CIROps.td | 63 ++++++++- clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 132 ++++++++++++++++++ clang/test/CIR/IR/try-catch.cir | 84 +++++++++++ 4 files changed, 292 insertions(+), 2 deletions(-) create mode 100644 clang/test/CIR/IR/try-catch.cir diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td index bb62223d9e152..38b53396bad31 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td @@ -960,4 +960,19 @@ def CIR_TypeInfoAttr : CIR_Attr<"TypeInfo", "typeinfo", [TypedAttrInterface]> { }]; } +//===----------------------------------------------------------------------===// +// CatchAllAttr & CatchUnwindAttr +//===----------------------------------------------------------------------===// + +// Represents the unwind region where unwind continues or +// the program std::terminate's. +def CIR_CatchUnwindAttr : CIR_UnitAttr<"CatchUnwind", "unwind"> { + let storageType = [{ CatchUnwind }]; +} + +// Represents the catch_all region. +def CIR_CatchAllAttr : CIR_UnitAttr<"CatchAll", "all"> { + let storageType = [{ CatchAllAttr }]; +} + #endif // CLANG_CIR_DIALECT_IR_CIRATTRS_TD diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 27fe0cc46d7cf..1171b6f820341 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -631,7 +631,7 @@ def CIR_StoreOp : CIR_Op<"store", [ defvar CIR_ReturnableScopes = [ "FuncOp", "ScopeOp", "IfOp", "SwitchOp", "CaseOp", - "DoWhileOp", "WhileOp", "ForOp" + "DoWhileOp", "WhileOp", "ForOp", "TryOp" ]; def CIR_ReturnOp : CIR_Op<"return", [ @@ -778,7 +778,7 @@ def CIR_ConditionOp : CIR_Op<"condition", [ defvar CIR_YieldableScopes = [ "ArrayCtor", "ArrayDtor", "CaseOp", "DoWhileOp", "ForOp", "GlobalOp", "IfOp", - "ScopeOp", "SwitchOp", "TernaryOp", "WhileOp" + "ScopeOp", "SwitchOp", "TernaryOp", "WhileOp", "TryOp" ]; def CIR_YieldOp : CIR_Op<"yield", [ @@ -4296,6 +4296,65 @@ def CIR_AllocExceptionOp : CIR_Op<"alloc.exception"> { }]; } +//===----------------------------------------------------------------------===// +// TryOp +//===----------------------------------------------------------------------===// + +def CIR_TryOp : CIR_Op<"try",[ + DeclareOpInterfaceMethods, + RecursivelySpeculatable, AutomaticAllocationScope, NoRegionArguments +]> { + let summary = "C++ try block"; + let description = [{ + Holds the lexical scope of `try {}`. Note that resources used on catch + clauses are usually allocated in the same parent as `cir.try`. + + `synthetic`: use `cir.try` to represent try/catches not originally + present in the source code (e.g. `g = new Class` under `-fexceptions`). + + `cleanup`: signal to targets (LLVM for now) that this try/catch, needs + to specially tag their landing pads as needing "cleanup". + + Example: + + ```mlir + %0 = cir.alloc.exception 16 -> !cir.ptr + %1 = cir.get_global @d2 : !cir.ptr + cir.try synthetic cleanup { + cir.call exception @_ZN7test2_DC1ERKS_(%0, %1) + : (!cir.ptr, !cir.ptr) -> () cleanup { + %2 = cir.cast bitcast %0 : !cir.ptr -> !cir.ptr + cir.free.exception %2 + cir.yield + } + ... + } + ``` + }]; + + let arguments = (ins UnitAttr:$synthetic, UnitAttr:$cleanup, + OptionalAttr:$catch_types); + let regions = (region AnyRegion:$try_region, + VariadicRegion:$catch_regions); + + let assemblyFormat = [{ + (`synthetic` $synthetic^)? + (`cleanup` $cleanup^)? + $try_region + custom($catch_regions, $catch_types) + attr-dict + }]; + + // Everything already covered elsewhere. + let builders = [OpBuilder<(ins + "llvm::function_ref":$tryBuilder, + "llvm::function_ref":$catchBuilder)>]; + + let hasLLVMLowering = false; +} + //===----------------------------------------------------------------------===// // Atomic operations //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index 5f88590c48d30..6e6eb4e5c9093 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -2878,6 +2878,138 @@ LogicalResult cir::TypeInfoAttr::verify( return success(); } +//===----------------------------------------------------------------------===// +// TryOp +//===----------------------------------------------------------------------===// + +void cir::TryOp::build( + OpBuilder &builder, OperationState &result, + function_ref tryBuilder, + function_ref catchBuilder) { + assert(tryBuilder && "expected builder callback for 'cir.try' body"); + assert(catchBuilder && "expected builder callback for 'catch' body"); + + OpBuilder::InsertionGuard guard(builder); + + // Try body region + Region *tryBodyRegion = result.addRegion(); + + // Create try body region and set insertion point + builder.createBlock(tryBodyRegion); + tryBuilder(builder, result.location); + catchBuilder(builder, result.location, result); +} + +void cir::TryOp::getSuccessorRegions( + mlir::RegionBranchPoint point, SmallVectorImpl ®ions) { + // If any index all the underlying regions branch back to the parent + // operation. + if (!point.isParent()) { + regions.push_back(RegionSuccessor()); + return; + } + + // If the condition isn't constant, both regions may be executed. + regions.push_back(RegionSuccessor(&getTryRegion())); + + // FIXME: optimize, ideas include: + // - If we know a target function never throws a specific type, we can + // remove the catch handler. + for (mlir::Region &r : this->getCatchRegions()) + regions.push_back(RegionSuccessor(&r)); +} + +static void printCatchRegions(OpAsmPrinter &printer, cir::TryOp op, + mlir::MutableArrayRef<::mlir::Region> regions, + mlir::ArrayAttr catchersAttr) { + if (!catchersAttr) + return; + + int currCatchIdx = 0; + printer << "catch ["; + llvm::interleaveComma(catchersAttr, printer, [&](const Attribute &a) { + if (mlir::isa(a)) { + printer.printAttribute(a); + printer << " "; + } else if (!a) { + printer << "all"; + } else { + printer << "type "; + printer.printAttribute(a); + printer << " "; + } + printer.printRegion(regions[currCatchIdx], /*printEntryBLockArgs=*/false, + /*printBlockTerminators=*/true); + currCatchIdx++; + }); + + printer << "]"; +} + +static ParseResult parseCatchRegions( + OpAsmParser &parser, + llvm::SmallVectorImpl> ®ions, + ::mlir::ArrayAttr &catchersAttr) { + if (parser.parseKeyword("catch").failed()) + return parser.emitError(parser.getCurrentLocation(), + "expected 'catch' keyword here"); + + auto parseAndCheckRegion = [&]() -> ParseResult { + // Parse region attached to catch + regions.emplace_back(new Region); + Region &currRegion = *regions.back(); + SMLoc parserLoc = parser.getCurrentLocation(); + if (parser.parseRegion(currRegion)) { + regions.clear(); + return failure(); + } + + if (currRegion.empty()) { + return parser.emitError(parser.getCurrentLocation(), + "catch region shall not be empty"); + } + + if (!(currRegion.back().mightHaveTerminator() && + currRegion.back().getTerminator())) + return parser.emitError( + parserLoc, "blocks are expected to be explicitly terminated"); + + return success(); + }; + + llvm::SmallVector catchList; + auto parseCatchEntry = [&]() -> ParseResult { + mlir::Attribute exceptionTypeInfo; + + if (parser.parseOptionalAttribute(exceptionTypeInfo).has_value()) { + catchList.push_back(exceptionTypeInfo); + } else { + ::llvm::StringRef attrStr; + if (parser.parseOptionalKeyword(&attrStr, {"all"}).succeeded()) { + // "all" keyword found, exceptionTypeInfo remains null + } else if (parser.parseOptionalKeyword("type").succeeded()) { + if (parser.parseAttribute(exceptionTypeInfo).failed()) + return parser.emitError(parser.getCurrentLocation(), + "expected valid RTTI info attribute"); + } else { + return parser.emitError(parser.getCurrentLocation(), + "expected attribute, 'all', or 'type' keyword"); + } + catchList.push_back(exceptionTypeInfo); + } + return parseAndCheckRegion(); + }; + + if (parser + .parseCommaSeparatedList(OpAsmParser::Delimiter::Square, + parseCatchEntry, " in catch list") + .failed()) + return failure(); + + catchersAttr = parser.getBuilder().getArrayAttr(catchList); + return ::mlir::success(); +} + //===----------------------------------------------------------------------===// // TableGen'd op method definitions //===----------------------------------------------------------------------===// diff --git a/clang/test/CIR/IR/try-catch.cir b/clang/test/CIR/IR/try-catch.cir new file mode 100644 index 0000000000000..7bc71ff84d4ae --- /dev/null +++ b/clang/test/CIR/IR/try-catch.cir @@ -0,0 +1,84 @@ +// RUN: cir-opt %s --verify-roundtrip | FileCheck %s + +!u8i = !cir.int + +module { + +cir.global "private" constant external @_ZTIi : !cir.ptr +cir.global "private" constant external @_ZTIPKc : !cir.ptr + +cir.func dso_local @empty_try_block_with_catch_all() { + cir.scope { + cir.try { + cir.yield + } catch [type #cir.all { + cir.yield + }] + } + cir.return +} + +// CHECK: cir.func dso_local @empty_try_block_with_catch_all() { +// CHECK: cir.scope { +// CHECK: cir.try { +// CHECK: cir.yield +// CHECK: } catch [type #cir.all { +// CHECK: cir.yield +// CHECK: }] +// CHECK: } +// CHECK: cir.return +// CHECK: } + +cir.func dso_local @empty_try_block_with_catch_unwind() { + cir.scope { + cir.try { + cir.yield + } catch [#cir.unwind { + cir.yield + }] + } + cir.return +} + +// CHECK: cir.func dso_local @empty_try_block_with_catch_unwind() { +// CHECK: cir.scope { +// CHECK: cir.try { +// CHECK: cir.yield +// CHECK: } catch [#cir.unwind { +// CHECK: cir.yield +// CHECK: }] +// CHECK: } +// CHECK: cir.return +// CHECK: } + +cir.func dso_local @empty_try_block_with_catch_ist() { + cir.scope { + cir.try { + cir.yield + } catch [type #cir.global_view<@_ZTIi> : !cir.ptr { + cir.yield + }, type #cir.global_view<@_ZTIPKc> : !cir.ptr { + cir.yield + }, #cir.unwind { + cir.yield + }] + } + cir.return +} + +// CHECK: cir.func dso_local @empty_try_block_with_catch_ist() { +// CHECK: cir.scope { +// CHECK: cir.try { +// CHECK: cir.yield +// CHECK: } catch [type #cir.global_view<@_ZTIi> : !cir.ptr { +// CHECK: cir.yield +// CHECK: }, type #cir.global_view<@_ZTIPKc> : !cir.ptr { +// CHECK: cir.yield +// CHECK: }, #cir.unwind { +// CHECK: cir.yield +// CHECK: }] +// CHECK: } +// CHECK: cir.return +// CHECK: } + +} From 720984fcd4e6936c82bc1cb98c599f66abc6ee65 Mon Sep 17 00:00:00 2001 From: Amr Hesham Date: Fri, 10 Oct 2025 21:14:15 +0200 Subject: [PATCH 2/4] Address code review comments --- clang/include/clang/CIR/Dialect/IR/CIROps.td | 20 +++++- clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 18 ----- clang/test/CIR/IR/invalid-try-catch.cir | 75 ++++++++++++++++++++ 3 files changed, 93 insertions(+), 20 deletions(-) create mode 100644 clang/test/CIR/IR/invalid-try-catch.cir diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 1171b6f820341..db832330aaae2 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -4346,11 +4346,27 @@ def CIR_TryOp : CIR_Op<"try",[ }]; // Everything already covered elsewhere. - let builders = [OpBuilder<(ins + let builders = [ + OpBuilder<(ins "llvm::function_ref":$tryBuilder, "llvm::function_ref":$catchBuilder)>]; + "mlir::OperationState &)>":$catchBuilder), + [{ + assert(tryBuilder && "expected builder callback for 'cir.try' body"); + assert(catchBuilder && "expected builder callback for 'catch' body"); + + OpBuilder::InsertionGuard guard($_builder); + + // Try body region + Region *tryBodyRegion = $_state.addRegion(); + + // Create try body region and set insertion point + $_builder.createBlock(tryBodyRegion); + tryBuilder($_builder, $_state.location); + catchBuilder($_builder, $_state.location, $_state); + }]> + ]; let hasLLVMLowering = false; } diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index 6e6eb4e5c9093..9f06d799a04ce 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -2882,24 +2882,6 @@ LogicalResult cir::TypeInfoAttr::verify( // TryOp //===----------------------------------------------------------------------===// -void cir::TryOp::build( - OpBuilder &builder, OperationState &result, - function_ref tryBuilder, - function_ref catchBuilder) { - assert(tryBuilder && "expected builder callback for 'cir.try' body"); - assert(catchBuilder && "expected builder callback for 'catch' body"); - - OpBuilder::InsertionGuard guard(builder); - - // Try body region - Region *tryBodyRegion = result.addRegion(); - - // Create try body region and set insertion point - builder.createBlock(tryBodyRegion); - tryBuilder(builder, result.location); - catchBuilder(builder, result.location, result); -} - void cir::TryOp::getSuccessorRegions( mlir::RegionBranchPoint point, SmallVectorImpl ®ions) { // If any index all the underlying regions branch back to the parent diff --git a/clang/test/CIR/IR/invalid-try-catch.cir b/clang/test/CIR/IR/invalid-try-catch.cir new file mode 100644 index 0000000000000..57863af13d20b --- /dev/null +++ b/clang/test/CIR/IR/invalid-try-catch.cir @@ -0,0 +1,75 @@ +// RUN: cir-opt %s -verify-diagnostics -split-input-file + +module { + +cir.func dso_local @invalid_catch_without_all_or_type() { + cir.scope { + cir.try { + cir.yield + // expected-error @below {{'cir.try' expected attribute, 'all', or 'type' keyword}} + } catch [invalid_keyword { + cir.yield + }] + } + cir.return +} + +} + +// ----- + +module { + +cir.func dso_local @invalid_catch_rtti_type() { + cir.scope { + cir.try { + cir.yield + // expected-error @below {{expected attribute value}} + // expected-error @below {{'cir.try' expected valid RTTI info attribute}} + } catch [type invalid_type { + cir.yield + }] + } + cir.return +} + +} + +// ----- + +module { + +cir.func dso_local @invalid_catch_empty_block() { + cir.scope { + cir.try { + cir.yield + } catch [type #cir.all { + // expected-error @below {{'cir.try' catch region shall not be empty}} + }] + } + cir.return +} + +} + +// ----- + +!s32i = !cir.int + +module { + +cir.func dso_local @invalid_catch_not_terminated() { + %a = cir.alloca !s32i, !cir.ptr, ["a", init] + cir.scope { + cir.try { + cir.yield + } + // expected-error @below {{'cir.try' blocks are expected to be explicitly terminated}} + catch [type #cir.all { + %tmp_a = cir.load %a : !cir.ptr, !s32i + }] + } + cir.return +} + +} From 43306db5c16b97575ba8b1261a7b51dee24d65e2 Mon Sep 17 00:00:00 2001 From: Amr Hesham Date: Sat, 11 Oct 2025 00:05:22 +0200 Subject: [PATCH 3/4] Address code review comments --- clang/include/clang/CIR/Dialect/IR/CIROps.td | 26 +++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index db832330aaae2..08bc34d419638 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -4310,24 +4310,26 @@ def CIR_TryOp : CIR_Op<"try",[ clauses are usually allocated in the same parent as `cir.try`. `synthetic`: use `cir.try` to represent try/catches not originally - present in the source code (e.g. `g = new Class` under `-fexceptions`). + present in the source code. For example, a synthetic `cir.try` region + is created around the constructor call when `operator new` is used + so that the memory allocated will be freed if the constructor throws + an exception. - `cleanup`: signal to targets (LLVM for now) that this try/catch, needs - to specially tag their landing pads as needing "cleanup". + `cleanup`: indicates that there are cleanups that must be performed + when exiting the try region via exception, even if the exception is not + caught. Example: ```mlir - %0 = cir.alloc.exception 16 -> !cir.ptr - %1 = cir.get_global @d2 : !cir.ptr - cir.try synthetic cleanup { - cir.call exception @_ZN7test2_DC1ERKS_(%0, %1) - : (!cir.ptr, !cir.ptr) -> () cleanup { - %2 = cir.cast bitcast %0 : !cir.ptr -> !cir.ptr - cir.free.exception %2 - cir.yield - } + cir.try { + cir.call exception @function() : () -> () + cir.yield + } catch [type #cir.global_view<@_ZTIPf> : !cir.ptr] { ... + cir.yield + } unwind { + cir.resume } ``` }]; From 523b1eeb171f996958107cfdc35b9c103f018961 Mon Sep 17 00:00:00 2001 From: Amr Hesham Date: Sat, 11 Oct 2025 13:25:55 +0200 Subject: [PATCH 4/4] TryOp & Catchers Parsers and printers --- .../CIR/Dialect/IR/CIRAttrConstraints.td | 30 +++- .../include/clang/CIR/Dialect/IR/CIRAttrs.td | 14 +- clang/include/clang/CIR/Dialect/IR/CIROps.td | 17 +- clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 152 +++++++++--------- clang/test/CIR/IR/invalid-try-catch.cir | 82 ++++++++-- clang/test/CIR/IR/try-catch.cir | 60 +++---- 6 files changed, 223 insertions(+), 132 deletions(-) diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrConstraints.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrConstraints.td index 8f72ff4d754ad..bab847ef621bf 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRAttrConstraints.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrConstraints.td @@ -38,14 +38,32 @@ def CIR_AnyIntOrFloatAttr : AnyAttrOf<[CIR_AnyIntAttr, CIR_AnyFPAttr], string cppType = "::mlir::TypedAttr"; } +//===----------------------------------------------------------------------===// +// Exceptions constraints +//===----------------------------------------------------------------------===// + +def CIR_AnyCatchAllAttr + : CIR_AttrConstraint<"::cir::CatchAllAttr", "catch all attribute">; + +def CIR_AnyUnwindAttr + : CIR_AttrConstraint<"::cir::UnwindAttr", "unwind attribute">; + //===----------------------------------------------------------------------===// // GlobalViewAttr constraints //===----------------------------------------------------------------------===// -def CIR_AnyGlobalViewAttr : CIR_AttrConstraint<"::cir::GlobalViewAttr", "GlobalView attribute">; +def CIR_AnyGlobalViewAttr + : CIR_AttrConstraint<"::cir::GlobalViewAttr", "GlobalView attribute">; -def CIR_AnyIntOrGlobalViewAttr : AnyAttrOf<[CIR_AnyIntAttr, CIR_AnyGlobalViewAttr], - "integer or global view attribute"> { +def CIR_AnyIntOrGlobalViewAttr + : AnyAttrOf<[CIR_AnyIntAttr, CIR_AnyGlobalViewAttr], + "integer or global view attribute"> { + string cppType = "::mlir::TypedAttr"; +} + +def CIR_AnyGlobalViewOrCatchAllOrUnwindAttr + : AnyAttrOf<[CIR_AnyGlobalViewAttr, CIR_AnyCatchAllAttr, CIR_AnyUnwindAttr], + "catch all or unwind or global view attribute"> { string cppType = "::mlir::TypedAttr"; } @@ -61,4 +79,8 @@ def CIR_IntOrGlobalViewArrayAttr : TypedArrayAttrBase; + +#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 38b53396bad31..f8916760c37a2 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td @@ -961,18 +961,18 @@ def CIR_TypeInfoAttr : CIR_Attr<"TypeInfo", "typeinfo", [TypedAttrInterface]> { } //===----------------------------------------------------------------------===// -// CatchAllAttr & CatchUnwindAttr +// CatchAllAttr & UnwindAttr //===----------------------------------------------------------------------===// -// Represents the unwind region where unwind continues or -// the program std::terminate's. -def CIR_CatchUnwindAttr : CIR_UnitAttr<"CatchUnwind", "unwind"> { - let storageType = [{ CatchUnwind }]; -} - // Represents the catch_all region. def CIR_CatchAllAttr : CIR_UnitAttr<"CatchAll", "all"> { let storageType = [{ CatchAllAttr }]; } +// Represents the unwind region where unwind continues or +// the program std::terminate's. +def CIR_UnwindAttr : CIR_UnitAttr<"Unwind", "unwind"> { + let storageType = [{ CatchUnwind }]; +} + #endif // CLANG_CIR_DIALECT_IR_CIRATTRS_TD diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 08bc34d419638..2c637c9cf4214 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -4334,10 +4334,16 @@ def CIR_TryOp : CIR_Op<"try",[ ``` }]; - let arguments = (ins UnitAttr:$synthetic, UnitAttr:$cleanup, - OptionalAttr:$catch_types); - let regions = (region AnyRegion:$try_region, - VariadicRegion:$catch_regions); + let arguments = (ins + UnitAttr:$synthetic, + UnitAttr:$cleanup, + CIR_GlobalViewOrCatchAllOrUnwindArrayAttr:$catch_types + ); + + let regions = (region + AnyRegion:$try_region, + VariadicRegion>:$catch_regions + ); let assemblyFormat = [{ (`synthetic` $synthetic^)? @@ -4347,7 +4353,6 @@ def CIR_TryOp : CIR_Op<"try",[ attr-dict }]; - // Everything already covered elsewhere. let builders = [ OpBuilder<(ins "llvm::function_ref ®ions) { - // If any index all the underlying regions branch back to the parent - // operation. + mlir::RegionBranchPoint point, + llvm::SmallVectorImpl ®ions) { + // The `try` and the `catchers` region branch back to the parent operation. if (!point.isParent()) { - regions.push_back(RegionSuccessor()); + regions.push_back(mlir::RegionSuccessor()); return; } - // If the condition isn't constant, both regions may be executed. - regions.push_back(RegionSuccessor(&getTryRegion())); + regions.push_back(mlir::RegionSuccessor(&getTryRegion())); - // FIXME: optimize, ideas include: - // - If we know a target function never throws a specific type, we can - // remove the catch handler. - for (mlir::Region &r : this->getCatchRegions()) - regions.push_back(RegionSuccessor(&r)); + // TODO(CIR): If we know a target function never throws a specific type, we + // can remove the catch handler. + for (mlir::Region ®ion : this->getCatchRegions()) + regions.push_back(mlir::RegionSuccessor(®ion)); } -static void printCatchRegions(OpAsmPrinter &printer, cir::TryOp op, - mlir::MutableArrayRef<::mlir::Region> regions, +static void printCatchRegions(mlir::OpAsmPrinter &printer, cir::TryOp op, + mlir::MutableArrayRef regions, mlir::ArrayAttr catchersAttr) { if (!catchersAttr) return; - int currCatchIdx = 0; - printer << "catch ["; - llvm::interleaveComma(catchersAttr, printer, [&](const Attribute &a) { - if (mlir::isa(a)) { - printer.printAttribute(a); + for (const auto [catcherIdx, catcherAttr] : llvm::enumerate(catchersAttr)) { + if (catcherIdx) printer << " "; - } else if (!a) { - printer << "all"; + + if (mlir::isa(catcherAttr)) { + printer << "catch all "; + } else if (mlir::isa(catcherAttr)) { + printer << "unwind "; } else { - printer << "type "; - printer.printAttribute(a); - printer << " "; + printer << "catch [type "; + printer.printAttribute(catcherAttr); + printer << "] "; } - printer.printRegion(regions[currCatchIdx], /*printEntryBLockArgs=*/false, - /*printBlockTerminators=*/true); - currCatchIdx++; - }); - printer << "]"; + printer.printRegion(regions[catcherIdx], /*printEntryBLockArgs=*/false, + /*printBlockTerminators=*/true); + } } -static ParseResult parseCatchRegions( - OpAsmParser &parser, - llvm::SmallVectorImpl> ®ions, - ::mlir::ArrayAttr &catchersAttr) { - if (parser.parseKeyword("catch").failed()) - return parser.emitError(parser.getCurrentLocation(), - "expected 'catch' keyword here"); +static mlir::ParseResult +parseCatchRegions(mlir::OpAsmParser &parser, + llvm::SmallVectorImpl> ®ions, + mlir::ArrayAttr &catchersAttr) { - auto parseAndCheckRegion = [&]() -> ParseResult { - // Parse region attached to catch - regions.emplace_back(new Region); - Region &currRegion = *regions.back(); - SMLoc parserLoc = parser.getCurrentLocation(); + auto parseCheckedCatcherRegion = [&]() -> mlir::ParseResult { + regions.emplace_back(new mlir::Region); + + mlir::Region &currRegion = *regions.back(); + mlir::SMLoc regionLoc = parser.getCurrentLocation(); if (parser.parseRegion(currRegion)) { regions.clear(); return failure(); } - if (currRegion.empty()) { - return parser.emitError(parser.getCurrentLocation(), - "catch region shall not be empty"); - } - - if (!(currRegion.back().mightHaveTerminator() && - currRegion.back().getTerminator())) + if (!currRegion.empty() && !(currRegion.back().mightHaveTerminator() && + currRegion.back().getTerminator())) return parser.emitError( - parserLoc, "blocks are expected to be explicitly terminated"); + regionLoc, "blocks are expected to be explicitly terminated"); return success(); }; - llvm::SmallVector catchList; - auto parseCatchEntry = [&]() -> ParseResult { - mlir::Attribute exceptionTypeInfo; + bool hasCatchAll = false; + llvm::SmallVector catcherAttrs; + while (parser.parseOptionalKeyword("catch").succeeded()) { + bool hasLSquare = parser.parseOptionalLSquare().succeeded(); - if (parser.parseOptionalAttribute(exceptionTypeInfo).has_value()) { - catchList.push_back(exceptionTypeInfo); - } else { - ::llvm::StringRef attrStr; - if (parser.parseOptionalKeyword(&attrStr, {"all"}).succeeded()) { - // "all" keyword found, exceptionTypeInfo remains null - } else if (parser.parseOptionalKeyword("type").succeeded()) { - if (parser.parseAttribute(exceptionTypeInfo).failed()) - return parser.emitError(parser.getCurrentLocation(), - "expected valid RTTI info attribute"); - } else { + llvm::StringRef attrStr; + if (parser.parseOptionalKeyword(&attrStr, {"all", "type"}).failed()) + return parser.emitError(parser.getCurrentLocation(), + "expected 'all' or 'type' keyword"); + + bool isCatchAll = attrStr == "all"; + if (isCatchAll) { + if (hasCatchAll) return parser.emitError(parser.getCurrentLocation(), - "expected attribute, 'all', or 'type' keyword"); - } - catchList.push_back(exceptionTypeInfo); + "can't have more than one catch all"); + hasCatchAll = true; } - return parseAndCheckRegion(); - }; - if (parser - .parseCommaSeparatedList(OpAsmParser::Delimiter::Square, - parseCatchEntry, " in catch list") - .failed()) - return failure(); + mlir::Attribute exceptionRTTIAttr; + if (!isCatchAll && parser.parseAttribute(exceptionRTTIAttr).failed()) + return parser.emitError(parser.getCurrentLocation(), + "expected valid RTTI info attribute"); + + catcherAttrs.push_back(isCatchAll + ? cir::CatchAllAttr::get(parser.getContext()) + : exceptionRTTIAttr); + + if (hasLSquare && isCatchAll) + return parser.emitError(parser.getCurrentLocation(), + "catch all dosen't need RTTI info attribute"); + + if (hasLSquare && parser.parseRSquare().failed()) + return parser.emitError(parser.getCurrentLocation(), + "expected `]` after RTTI info attribute"); + + if (parseCheckedCatcherRegion().failed()) + return mlir::failure(); + } - catchersAttr = parser.getBuilder().getArrayAttr(catchList); - return ::mlir::success(); + if (parser.parseOptionalKeyword("unwind").succeeded()) { + catcherAttrs.push_back(cir::UnwindAttr::get(parser.getContext())); + if (parseCheckedCatcherRegion().failed()) + return mlir::failure(); + } + + catchersAttr = parser.getBuilder().getArrayAttr(catcherAttrs); + return mlir::success(); } //===----------------------------------------------------------------------===// diff --git a/clang/test/CIR/IR/invalid-try-catch.cir b/clang/test/CIR/IR/invalid-try-catch.cir index 57863af13d20b..40eaa7b06e3c3 100644 --- a/clang/test/CIR/IR/invalid-try-catch.cir +++ b/clang/test/CIR/IR/invalid-try-catch.cir @@ -6,10 +6,10 @@ cir.func dso_local @invalid_catch_without_all_or_type() { cir.scope { cir.try { cir.yield - // expected-error @below {{'cir.try' expected attribute, 'all', or 'type' keyword}} + // expected-error @below {{'cir.try' expected 'all' or 'type' keyword}} } catch [invalid_keyword { cir.yield - }] + } } cir.return } @@ -22,13 +22,12 @@ module { cir.func dso_local @invalid_catch_rtti_type() { cir.scope { + // expected-error @below {{'cir.try' op attribute 'catch_types' failed to satisfy constraint: catch all or unwind or global view array attribute}} cir.try { cir.yield - // expected-error @below {{expected attribute value}} - // expected-error @below {{'cir.try' expected valid RTTI info attribute}} - } catch [type invalid_type { + } catch [type #cir.undef] { cir.yield - }] + } } cir.return } @@ -41,11 +40,11 @@ module { cir.func dso_local @invalid_catch_empty_block() { cir.scope { + // expected-error @below {{'cir.try' op region #1 ('catch_regions') failed to verify constraint: region with at least 1 blocks}} cir.try { cir.yield - } catch [type #cir.all { - // expected-error @below {{'cir.try' catch region shall not be empty}} - }] + } catch all { + } } cir.return } @@ -65,11 +64,72 @@ cir.func dso_local @invalid_catch_not_terminated() { cir.yield } // expected-error @below {{'cir.try' blocks are expected to be explicitly terminated}} - catch [type #cir.all { + catch all { %tmp_a = cir.load %a : !cir.ptr, !s32i - }] + } } cir.return } } + +// ----- + +module { + +cir.func dso_local @invalid_catch_multiple_catch_all() { + cir.scope { + cir.try { + cir.yield + } catch all { + cir.yield + } + // expected-error @below {{op 'cir.try' can't have more than one catch all}} + catch all { + cir.yield + } + } + cir.return +} + +} + +// ----- + +module { + +cir.func dso_local @invalid_catch_without_type_info() { + cir.scope { + cir.try { + cir.yield + } + // expected-error @below {{expected attribute value}} + // expected-error @below {{op 'cir.try' expected valid RTTI info attribute}} + catch [type] { + cir.yield + } + } + cir.return +} + +} + +// ----- + +module { + +cir.func dso_local @invalid_catch_all_with_type_info() { + cir.scope { + cir.try { + cir.yield + } + // expected-error @below {{op 'cir.try' catch all dosen't need RTTI info attribute}} + catch [all] { + cir.yield + } + } + cir.return +} + +} + diff --git a/clang/test/CIR/IR/try-catch.cir b/clang/test/CIR/IR/try-catch.cir index 7bc71ff84d4ae..7becd0b559f5e 100644 --- a/clang/test/CIR/IR/try-catch.cir +++ b/clang/test/CIR/IR/try-catch.cir @@ -11,57 +11,57 @@ cir.func dso_local @empty_try_block_with_catch_all() { cir.scope { cir.try { cir.yield - } catch [type #cir.all { + } catch all { cir.yield - }] + } } cir.return } -// CHECK: cir.func dso_local @empty_try_block_with_catch_all() { -// CHECK: cir.scope { -// CHECK: cir.try { -// CHECK: cir.yield -// CHECK: } catch [type #cir.all { -// CHECK: cir.yield -// CHECK: }] -// CHECK: } -// CHECK: cir.return -// CHECK: } +// CHECK: cir.func dso_local @empty_try_block_with_catch_all() { +// CHECK: cir.scope { +// CHECK: cir.try { +// CHECK: cir.yield +// CHECK: } catch all { +// CHECK: cir.yield +// CHECK: } +// CHECK: } +// CHECK: cir.return +// CHECK: } cir.func dso_local @empty_try_block_with_catch_unwind() { cir.scope { cir.try { cir.yield - } catch [#cir.unwind { + } unwind { cir.yield - }] + } } cir.return } // CHECK: cir.func dso_local @empty_try_block_with_catch_unwind() { -// CHECK: cir.scope { -// CHECK: cir.try { -// CHECK: cir.yield -// CHECK: } catch [#cir.unwind { -// CHECK: cir.yield -// CHECK: }] -// CHECK: } -// CHECK: cir.return +// CHECK: cir.scope { +// CHECK: cir.try { +// CHECK: cir.yield +// CHECK: } unwind { +// CHECK: cir.yield +// CHECK: } +// CHECK: } +// CHECK: cir.return // CHECK: } cir.func dso_local @empty_try_block_with_catch_ist() { cir.scope { cir.try { cir.yield - } catch [type #cir.global_view<@_ZTIi> : !cir.ptr { + } catch [type #cir.global_view<@_ZTIi> : !cir.ptr] { cir.yield - }, type #cir.global_view<@_ZTIPKc> : !cir.ptr { + } catch [type #cir.global_view<@_ZTIPKc> : !cir.ptr] { cir.yield - }, #cir.unwind { + } unwind { cir.yield - }] + } } cir.return } @@ -70,13 +70,13 @@ cir.func dso_local @empty_try_block_with_catch_ist() { // CHECK: cir.scope { // CHECK: cir.try { // CHECK: cir.yield -// CHECK: } catch [type #cir.global_view<@_ZTIi> : !cir.ptr { +// CHECK: } catch [type #cir.global_view<@_ZTIi> : !cir.ptr] { // CHECK: cir.yield -// CHECK: }, type #cir.global_view<@_ZTIPKc> : !cir.ptr { +// CHECK: } catch [type #cir.global_view<@_ZTIPKc> : !cir.ptr] { // CHECK: cir.yield -// CHECK: }, #cir.unwind { +// CHECK: } unwind { // CHECK: cir.yield -// CHECK: }] +// CHECK: } // CHECK: } // CHECK: cir.return // CHECK: }