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..db832330aaae2 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,81 @@ 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), + [{ + 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; +} + //===----------------------------------------------------------------------===// // Atomic operations //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index 5f88590c48d30..9f06d799a04ce 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -2878,6 +2878,120 @@ LogicalResult cir::TypeInfoAttr::verify( return success(); } +//===----------------------------------------------------------------------===// +// TryOp +//===----------------------------------------------------------------------===// + +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/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 +} + +} 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: } + +}