From 22d4680619e8b41b2985090d78c3ddcd687a2eef Mon Sep 17 00:00:00 2001 From: Amr Hesham Date: Tue, 7 Oct 2025 20:26:05 +0200 Subject: [PATCH 1/3] [CIR] Upstream Exception CXXTryStmt --- clang/include/clang/CIR/Dialect/IR/CIROps.td | 64 +++++++- clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp | 4 + clang/lib/CIR/CodeGen/CIRGenCXXABI.h | 3 + clang/lib/CIR/CodeGen/CIRGenCleanup.cpp | 7 + clang/lib/CIR/CodeGen/CIRGenCleanup.h | 94 ++++++++++- clang/lib/CIR/CodeGen/CIRGenException.cpp | 155 ++++++++++++++++++ clang/lib/CIR/CodeGen/CIRGenFunction.h | 21 +++ clang/lib/CIR/CodeGen/CIRGenStmt.cpp | 3 +- clang/lib/CIR/CodeGen/EHScopeStack.h | 8 + clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 133 +++++++++++++++ .../lib/CIR/Dialect/Transforms/FlattenCFG.cpp | 32 +++- clang/test/CIR/CodeGen/try-catch.cpp | 29 ++++ 12 files changed, 546 insertions(+), 7 deletions(-) create mode 100644 clang/test/CIR/CodeGen/try-catch.cpp diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 66c4c04f23108..d472445362e6f 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -537,7 +537,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", [ @@ -684,7 +684,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", [ @@ -4202,6 +4202,66 @@ def CIR_AllocExceptionOp : CIR_Op<"alloc.exception"> { }]; } +//===----------------------------------------------------------------------===// +// TryOp +//===----------------------------------------------------------------------===// + +// Represents the unwind region where unwind continues or +// the program std::terminate's. +def CIR_CatchUnwind : CIR_UnitAttr<"CatchUnwind", "unwind"> { + let storageType = [{ CatchUnwind }]; +} + +// Represents the catch_all region. +def CIR_CatchAll : CIR_UnitAttr<"CatchAll", "all"> { + let storageType = [{ CatchAllAttr }]; +} + +def CIR_TryOp : CIR_Op<"try",[ + DeclareOpInterfaceMethods, + RecursivelySpeculatable, AutomaticAllocationScope, NoRegionArguments +]> { + let summary = "C++ try block"; + let description = [{ + ```mlir + + 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: TBD + ``` + }]; + + 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/CodeGen/CIRGenCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp index 5f1faabde22a5..9c5ea54c3adb6 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp @@ -36,6 +36,10 @@ CIRGenCXXABI::AddedStructorArgCounts CIRGenCXXABI::addImplicitConstructorArgs( addedArgs.suffix.size()); } +CatchTypeInfo CIRGenCXXABI::getCatchAllTypeInfo() { + return CatchTypeInfo{nullptr, 0}; +} + void CIRGenCXXABI::buildThisParam(CIRGenFunction &cgf, FunctionArgList ¶ms) { const auto *md = cast(cgf.curGD.getDecl()); diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h index be66240c280ec..fc16e15fd6950 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h +++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h @@ -15,6 +15,7 @@ #define LLVM_CLANG_LIB_CIR_CIRGENCXXABI_H #include "CIRGenCall.h" +#include "CIRGenCleanup.h" #include "CIRGenFunction.h" #include "CIRGenModule.h" @@ -147,6 +148,8 @@ class CIRGenCXXABI { /// Loads the incoming C++ this pointer as it was passed by the caller. mlir::Value loadIncomingCXXThis(CIRGenFunction &cgf); + virtual CatchTypeInfo getCatchAllTypeInfo(); + /// Emit constructor variants required by this ABI. virtual void emitCXXConstructors(const clang::CXXConstructorDecl *d) = 0; diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp index 870069715df22..aabe4bbdf18c8 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp @@ -108,6 +108,13 @@ void EHScopeStack::popCleanup() { assert(!cir::MissingFeatures::ehCleanupBranchFixups()); } +EHCatchScope *EHScopeStack::pushCatch(unsigned numHandlers) { + char *buffer = allocate(EHCatchScope::getSizeForNumHandlers(numHandlers)); + assert(!cir::MissingFeatures::innermostEHScope()); + EHCatchScope *scope = new (buffer) EHCatchScope(numHandlers); + return scope; +} + static void emitCleanup(CIRGenFunction &cgf, EHScopeStack::Cleanup *cleanup) { // Ask the cleanup to emit itself. assert(cgf.haveInsertPoint() && "expected insertion point"); diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.h b/clang/lib/CIR/CodeGen/CIRGenCleanup.h index 30f5607d655da..5cce1cad57f44 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCleanup.h +++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.h @@ -14,14 +14,22 @@ #ifndef CLANG_LIB_CIR_CODEGEN_CIRGENCLEANUP_H #define CLANG_LIB_CIR_CODEGEN_CIRGENCLEANUP_H -#include "Address.h" #include "EHScopeStack.h" -#include "mlir/IR/Value.h" +#include "mlir/IR/BuiltinAttributeInterfaces.h" +#include "clang/CIR/MissingFeatures.h" namespace clang::CIRGen { +/// The MS C++ ABI needs a pointer to RTTI data plus some flags to describe the +/// type of a catch handler, so we use this wrapper. +struct CatchTypeInfo { + mlir::TypedAttr rtti; + unsigned flags; +}; + /// A protected scope for zero-cost EH handling. class EHScope { + class CommonBitFields { friend class EHScope; unsigned kind : 3; @@ -29,6 +37,13 @@ class EHScope { enum { NumCommonBits = 3 }; protected: + class CatchBitFields { + friend class EHCatchScope; + unsigned : NumCommonBits; + + unsigned numHandlers : 32 - NumCommonBits; + }; + class CleanupBitFields { friend class EHCleanupScope; unsigned : NumCommonBits; @@ -58,6 +73,7 @@ class EHScope { union { CommonBitFields commonBits; + CatchBitFields catchBits; CleanupBitFields cleanupBits; }; @@ -67,6 +83,72 @@ class EHScope { EHScope(Kind kind) { commonBits.kind = kind; } Kind getKind() const { return static_cast(commonBits.kind); } + + bool hasEHBranches() const { + // Traditional LLVM codegen also checks for `!block->use_empty()`, but + // in CIRGen the block content is not important, just used as a way to + // signal `hasEHBranches`. + assert(!cir::MissingFeatures::ehstackBranches()); + return false; + } +}; + +/// A scope which attempts to handle some, possibly all, types of +/// exceptions. +/// +/// Objective C \@finally blocks are represented using a cleanup scope +/// after the catch scope. + +class EHCatchScope : public EHScope { + // In effect, we have a flexible array member + // Handler Handlers[0]; + // But that's only standard in C99, not C++, so we have to do + // annoying pointer arithmetic instead. + +public: + struct Handler { + /// A type info value, or null (C++ null, not an LLVM null pointer) + /// for a catch-all. + CatchTypeInfo type; + + /// The catch handler for this type. + mlir::Block *block; + }; + +private: + friend class EHScopeStack; + + Handler *getHandlers() { return reinterpret_cast(this + 1); } + +public: + static size_t getSizeForNumHandlers(unsigned n) { + return sizeof(EHCatchScope) + n * sizeof(Handler); + } + + EHCatchScope(unsigned numHandlers) : EHScope(Catch) { + catchBits.numHandlers = numHandlers; + assert(catchBits.numHandlers == numHandlers && "NumHandlers overflow?"); + } + + unsigned getNumHandlers() const { return catchBits.numHandlers; } + + void setHandler(unsigned i, CatchTypeInfo type, mlir::Block *block) { + assert(i < getNumHandlers()); + getHandlers()[i].type = type; + getHandlers()[i].block = block; + } + + // Clear all handler blocks. + // FIXME: it's better to always call clearHandlerBlocks in DTOR and have a + // 'takeHandler' or some such function which removes ownership from the + // EHCatchScope object if the handlers should live longer than EHCatchScope. + void clearHandlerBlocks() { + // The blocks are owned by TryOp, nothing to delete. + } + + static bool classof(const EHScope *scope) { + return scope->getKind() == Catch; + } }; /// A cleanup scope which generates the cleanup blocks lazily. @@ -147,5 +229,13 @@ EHScopeStack::find(stable_iterator savePoint) const { return iterator(endOfBuffer - savePoint.size); } +inline void EHScopeStack::popCatch() { + assert(!empty() && "popping exception stack when not empty"); + + EHCatchScope &scope = llvm::cast(*begin()); + assert(!cir::MissingFeatures::innermostEHScope()); + deallocate(EHCatchScope::getSizeForNumHandlers(scope.getNumHandlers())); +} + } // namespace clang::CIRGen #endif // CLANG_LIB_CIR_CODEGEN_CIRGENCLEANUP_H diff --git a/clang/lib/CIR/CodeGen/CIRGenException.cpp b/clang/lib/CIR/CodeGen/CIRGenException.cpp index 645384383711b..975e031ed5cd2 100644 --- a/clang/lib/CIR/CodeGen/CIRGenException.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenException.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "CIRGenCXXABI.h" +#include "CIRGenCleanup.h" #include "CIRGenFunction.h" #include "clang/AST/StmtVisitor.h" @@ -64,3 +65,157 @@ void CIRGenFunction::emitAnyExprToExn(const Expr *e, Address addr) { // Deactivate the cleanup block. assert(!cir::MissingFeatures::ehCleanupScope()); } + +mlir::LogicalResult CIRGenFunction::emitCXXTryStmt(const CXXTryStmt &s) { + auto loc = getLoc(s.getSourceRange()); + + // Create a scope to hold try local storage for catch params. + mlir::OpBuilder::InsertPoint scopeIP; + cir::ScopeOp::create(builder, loc, /*scopeBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + scopeIP = builder.saveInsertionPoint(); + }); + + mlir::LogicalResult result = mlir::success(); + { + mlir::OpBuilder::InsertionGuard guard(builder); + builder.restoreInsertionPoint(scopeIP); + result = emitCXXTryStmtUnderScope(s); + cir::YieldOp::create(builder, loc); + } + + return result; +} + +mlir::LogicalResult +CIRGenFunction::emitCXXTryStmtUnderScope(const CXXTryStmt &s) { + const llvm::Triple &t = getTarget().getTriple(); + // If we encounter a try statement on in an OpenMP target region offloaded to + // a GPU, we treat it as a basic block. + const bool isTargetDevice = + (cgm.getLangOpts().OpenMPIsTargetDevice && (t.isNVPTX() || t.isAMDGCN())); + if (isTargetDevice) { + cgm.errorNYI( + "emitCXXTryStmtUnderScope: OpenMP target region offloaded to GPU"); + return mlir::success(); + } + + auto hasCatchAll = [&]() { + if (!s.getNumHandlers()) + return false; + unsigned lastHandler = s.getNumHandlers() - 1; + return s.getHandler(lastHandler)->getExceptionDecl() == nullptr; + }; + + unsigned numHandlers = s.getNumHandlers(); + mlir::Location tryLoc = getLoc(s.getBeginLoc()); + + mlir::OpBuilder::InsertPoint beginInsertTryBody; + + // Create the scope to represent only the C/C++ `try {}` part. However, + // don't populate right away. Reserve some space to store the exception + // info but don't emit the bulk right away, for now only make sure the + // scope returns the exception information. + auto tryOp = cir::TryOp::create( + builder, tryLoc, + /*tryBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + beginInsertTryBody = builder.saveInsertionPoint(); + }, + /*catchBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc, + mlir::OperationState &result) { + mlir::OpBuilder::InsertionGuard guard(b); + unsigned numRegionsToCreate = + hasCatchAll() ? numHandlers : numHandlers + 1; + for (unsigned i = 0; i != numRegionsToCreate; ++i) { + builder.createBlock(result.addRegion()); + } + }); + + // Finally emit the body for try/catch. + auto emitTryCatchBody = [&]() -> mlir::LogicalResult { + mlir::Location loc = tryOp.getLoc(); + mlir::OpBuilder::InsertionGuard guard(builder); + builder.restoreInsertionPoint(beginInsertTryBody); + CIRGenFunction::LexicalScope tryScope{*this, loc, + builder.getInsertionBlock()}; + + tryScope.setAsTry(tryOp); + + // Attach the basic blocks for the catch regions. + enterCXXTryStmt(s, tryOp); + + // Emit the body for the `try {}` part. + { + mlir::OpBuilder::InsertionGuard guard(builder); + CIRGenFunction::LexicalScope tryBodyScope{*this, loc, + builder.getInsertionBlock()}; + if (emitStmt(s.getTryBlock(), /*useCurrentScope=*/true).failed()) + return mlir::failure(); + } + + // Emit catch clauses. + exitCXXTryStmt(s); + return mlir::success(); + }; + + return emitTryCatchBody(); +} + +void CIRGenFunction::enterCXXTryStmt(const CXXTryStmt &s, cir::TryOp tryOp, + bool isFnTryBlock) { + + unsigned numHandlers = s.getNumHandlers(); + EHCatchScope *catchScope = ehStack.pushCatch(numHandlers); + for (unsigned i = 0; i != numHandlers; ++i) { + const CXXCatchStmt *catchStmt = s.getHandler(i); + if (catchStmt->getExceptionDecl()) { + cgm.errorNYI("enterCXXTryStmt: CatchStmt with ExceptionDecl"); + return; + } + + mlir::Block *handler = &tryOp.getCatchRegions()[i].getBlocks().front(); + + // No exception decl indicates '...', a catch-all. + catchScope->setHandler(i, cgm.getCXXABI().getCatchAllTypeInfo(), handler); + + // Under async exceptions, catch(...) need to catch HW exception too + // Mark scope with SehTryBegin as a SEH __try scope + if (getLangOpts().EHAsynch) { + cgm.errorNYI("enterCXXTryStmt: EHAsynch"); + return; + } + } +} + +void CIRGenFunction::exitCXXTryStmt(const CXXTryStmt &s, bool isFnTryBlock) { + unsigned numHandlers = s.getNumHandlers(); + EHCatchScope &catchScope = cast(*ehStack.begin()); + assert(catchScope.getNumHandlers() == numHandlers); + cir::TryOp tryOp = curLexScope->getTry(); + + // If the catch was not required, bail out now. + if (!catchScope.hasEHBranches()) { + catchScope.clearHandlerBlocks(); + ehStack.popCatch(); + + // Drop all basic block from all catch regions. + SmallVector eraseBlocks; + for (mlir::Region ®ion : tryOp.getCatchRegions()) { + if (region.empty()) + continue; + + for (mlir::Block &b : region.getBlocks()) + eraseBlocks.push_back(&b); + } + + for (mlir::Block *b : eraseBlocks) + b->erase(); + + tryOp.setCatchTypesAttr({}); + return; + } + + cgm.errorNYI("exitCXXTryStmt: Required catch"); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index d10d058ef289a..c0adab3abb4fd 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -883,6 +883,9 @@ class CIRGenFunction : public CIRGenTypeCache { LexicalScope *parentScope = nullptr; + // Holds actual value for ScopeKind::Try + cir::TryOp tryOp = nullptr; + // Only Regular is used at the moment. Support for other kinds will be // added as the relevant statements/expressions are upstreamed. enum Kind { @@ -942,6 +945,10 @@ class CIRGenFunction : public CIRGenTypeCache { void setAsGlobalInit() { scopeKind = Kind::GlobalInit; } void setAsSwitch() { scopeKind = Kind::Switch; } void setAsTernary() { scopeKind = Kind::Ternary; } + void setAsTry(cir::TryOp op) { + scopeKind = Kind::Try; + tryOp = op; + } // Lazy create cleanup block or return what's available. mlir::Block *getOrCreateCleanupBlock(mlir::OpBuilder &builder) { @@ -964,6 +971,11 @@ class CIRGenFunction : public CIRGenTypeCache { return cleanupBlock; } + cir::TryOp getTry() { + assert(isTry()); + return tryOp; + } + // --- // Return handling. // --- @@ -1265,6 +1277,15 @@ class CIRGenFunction : public CIRGenTypeCache { void emitCXXThrowExpr(const CXXThrowExpr *e); + mlir::LogicalResult emitCXXTryStmt(const clang::CXXTryStmt &s); + + mlir::LogicalResult emitCXXTryStmtUnderScope(const clang::CXXTryStmt &s); + + void enterCXXTryStmt(const CXXTryStmt &s, cir::TryOp tryOp, + bool isFnTryBlock = false); + + void exitCXXTryStmt(const CXXTryStmt &s, bool isFnTryBlock = false); + void emitCtorPrologue(const clang::CXXConstructorDecl *ctor, clang::CXXCtorType ctorType, FunctionArgList &args); diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp index 0b8f8bfdfb046..cfd48a227ed20 100644 --- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp @@ -154,6 +154,8 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s, return emitWhileStmt(cast(*s)); case Stmt::DoStmtClass: return emitDoStmt(cast(*s)); + case Stmt::CXXTryStmtClass: + return emitCXXTryStmt(cast(*s)); case Stmt::CXXForRangeStmtClass: return emitCXXForRangeStmt(cast(*s), attr); case Stmt::OpenACCComputeConstructClass: @@ -199,7 +201,6 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s, case Stmt::CoroutineBodyStmtClass: return emitCoroutineBody(cast(*s)); case Stmt::CoreturnStmtClass: - case Stmt::CXXTryStmtClass: case Stmt::IndirectGotoStmtClass: case Stmt::OMPParallelDirectiveClass: case Stmt::OMPTaskwaitDirectiveClass: diff --git a/clang/lib/CIR/CodeGen/EHScopeStack.h b/clang/lib/CIR/CodeGen/EHScopeStack.h index 66c1f76094c58..1dce5ac50b09d 100644 --- a/clang/lib/CIR/CodeGen/EHScopeStack.h +++ b/clang/lib/CIR/CodeGen/EHScopeStack.h @@ -158,6 +158,14 @@ class EHScopeStack { /// Pops a cleanup scope off the stack. This is private to CIRGenCleanup.cpp. void popCleanup(); + /// Push a set of catch handlers on the stack. The catch is + /// uninitialized and will need to have the given number of handlers + /// set on it. + class EHCatchScope *pushCatch(unsigned numHandlers); + + /// Pops a catch scope off the stack. This is private to CIRGenException.cpp. + void popCatch(); + /// Determines whether the exception-scopes stack is empty. bool empty() const { return startOfData == endOfBuffer; } diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index cdd4e3c96ca98..c6c142255e16b 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -1083,6 +1083,139 @@ LogicalResult cir::ScopeOp::verify() { return success(); } +//===----------------------------------------------------------------------===// +// TryOp +//===----------------------------------------------------------------------===// + +void cir::TryOp::build( + OpBuilder &builder, OperationState &result, + function_ref tryBodyBuilder, + function_ref catchBuilder) { + assert(tryBodyBuilder && "expected builder callback for 'cir.try' body"); + + OpBuilder::InsertionGuard guard(builder); + + // Try body region + Region *tryBodyRegion = result.addRegion(); + + // Create try body region and set insertion point + builder.createBlock(tryBodyRegion); + tryBodyBuilder(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 (auto &r : this->getCatchRegions()) + regions.push_back(RegionSuccessor(&r)); +} + +void printCatchRegions(OpAsmPrinter &p, cir::TryOp op, + mlir::MutableArrayRef<::mlir::Region> regions, + mlir::ArrayAttr catchList) { + + int currCatchIdx = 0; + if (!catchList) + return; + p << "catch ["; + llvm::interleaveComma(catchList, p, [&](const Attribute &a) { + auto exRtti = a; + + if (mlir::isa(a)) { + p.printAttribute(a); + p << " "; + } else if (!exRtti) { + p << "all"; + } else { + p << "type "; + p.printAttribute(exRtti); + p << " "; + } + p.printRegion(regions[currCatchIdx], /*printEntryBLockArgs=*/false, + /*printBlockTerminators=*/true); + currCatchIdx++; + }); + p << "]"; +} + +ParseResult parseCatchRegions( + OpAsmParser &parser, + llvm::SmallVectorImpl> ®ions, + ::mlir::ArrayAttr &catchersAttr) { + llvm::SmallVector catchList; + + 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(); + }; + + 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.parseKeyword("catch").failed()) + return parser.emitError(parser.getCurrentLocation(), + "expected 'catch' keyword here"); + + if (parser + .parseCommaSeparatedList(OpAsmParser::Delimiter::Square, + parseCatchEntry, " in catch list") + .failed()) + return failure(); + + catchersAttr = parser.getBuilder().getArrayAttr(catchList); + return ::mlir::success(); +} + //===----------------------------------------------------------------------===// // BrOp //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp index 26e5c0572f12e..6b7f92299ffe7 100644 --- a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp +++ b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp @@ -532,10 +532,38 @@ class CIRTernaryOpFlattening : public mlir::OpRewritePattern { } }; +class CIRTryOpFlattening : public mlir::OpRewritePattern { +public: + using OpRewritePattern::OpRewritePattern; + + mlir::LogicalResult + matchAndRewrite(cir::TryOp tryOp, + mlir::PatternRewriter &rewriter) const override { + mlir::Region *tryRegion = &tryOp.getTryRegion(); + + // Empty scope can be removed. + if (tryRegion->empty()) { + rewriter.eraseOp(tryOp); + return mlir::success(); + } + + // Scope with only yield can be removed. + mlir::Block *tryRegionFirstBlock = &tryRegion->front(); + if (tryRegionFirstBlock->getOperations().size() == 1 && + mlir::isa(tryRegionFirstBlock->front())) { + rewriter.eraseOp(tryOp); + return mlir::success(); + } + + llvm_unreachable("TryOp: non-empty try block is NYI"); + return mlir::success(); + } +}; + void populateFlattenCFGPatterns(RewritePatternSet &patterns) { patterns .add( + CIRSwitchOpFlattening, CIRTernaryOpFlattening, CIRTryOpFlattening>( patterns.getContext()); } @@ -549,7 +577,7 @@ void CIRFlattenCFGPass::runOnOperation() { assert(!cir::MissingFeatures::ifOp()); assert(!cir::MissingFeatures::switchOp()); assert(!cir::MissingFeatures::tryOp()); - if (isa(op)) + if (isa(op)) ops.push_back(op); }); diff --git a/clang/test/CIR/CodeGen/try-catch.cpp b/clang/test/CIR/CodeGen/try-catch.cpp new file mode 100644 index 0000000000000..3dea2ac67093f --- /dev/null +++ b/clang/test/CIR/CodeGen/try-catch.cpp @@ -0,0 +1,29 @@ +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fcxx-exceptions -fexceptions -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fcxx-exceptions -fexceptions -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fcxx-exceptions -fexceptions -emit-llvm %s -o %t.ll +// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG + +void empty_try_block_with_catch_all() { + try {} catch (...) {} +} + +// CIR: cir.func{{.*}} @_Z30empty_try_block_with_catch_allv() +// CIR: cir.scope { +// CIR: cir.try { +// CIR: cir.yield +// CIR: } +// CIR: } + +// TODO(CIR): Those blocks will be removed once RemoveEmptyScope RewritePattern is upstreamed + +// LLVM: define{{.*}} void @_Z30empty_try_block_with_catch_allv() +// LLVM: br label %1 +// LLVM: 1: +// LLVM: br label %2 +// LLVM: 2: +// LLVM: ret void + +// OGCG: define{{.*}} void @_Z30empty_try_block_with_catch_allv() +// OGCG: ret void From 7504b031d50ffde4c66e65d11cd224a5fdc1dfa1 Mon Sep 17 00:00:00 2001 From: Amr Hesham Date: Wed, 8 Oct 2025 22:00:37 +0200 Subject: [PATCH 2/3] Remove invalid TODO --- clang/test/CIR/CodeGen/try-catch.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/clang/test/CIR/CodeGen/try-catch.cpp b/clang/test/CIR/CodeGen/try-catch.cpp index 3dea2ac67093f..76749c752e7d4 100644 --- a/clang/test/CIR/CodeGen/try-catch.cpp +++ b/clang/test/CIR/CodeGen/try-catch.cpp @@ -16,8 +16,6 @@ void empty_try_block_with_catch_all() { // CIR: } // CIR: } -// TODO(CIR): Those blocks will be removed once RemoveEmptyScope RewritePattern is upstreamed - // LLVM: define{{.*}} void @_Z30empty_try_block_with_catch_allv() // LLVM: br label %1 // LLVM: 1: From 28e6cc84c071d128cb812b86d3363ce829b1e097 Mon Sep 17 00:00:00 2001 From: Amr Hesham Date: Wed, 8 Oct 2025 23:03:52 +0200 Subject: [PATCH 3/3] Address code review comments --- .../include/clang/CIR/Dialect/IR/CIRAttrs.td | 15 ++++++ clang/include/clang/CIR/Dialect/IR/CIROps.td | 53 +++++++++---------- clang/lib/CIR/CodeGen/CIRGenException.cpp | 45 ++++++++-------- clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 9 ++-- clang/test/CIR/IR/try-catch.cir | 27 ++++++++++ 5 files changed, 93 insertions(+), 56 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 7714750a53d44..573e1707b8255 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td @@ -858,4 +858,19 @@ def CIR_TypeInfoAttr : CIR_Attr<"TypeInfo", "typeinfo", [TypedAttrInterface]> { }]; } +//===----------------------------------------------------------------------===// +// CatAllAttr & 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 d472445362e6f..af22d72bc5c51 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -535,10 +535,9 @@ def CIR_StoreOp : CIR_Op<"store", [ // ReturnOp //===----------------------------------------------------------------------===// -defvar CIR_ReturnableScopes = [ - "FuncOp", "ScopeOp", "IfOp", "SwitchOp", "CaseOp", - "DoWhileOp", "WhileOp", "ForOp", "TryOp" -]; +defvar CIR_ReturnableScopes = ["FuncOp", "ScopeOp", "IfOp", "SwitchOp", + "CaseOp", "DoWhileOp", "WhileOp", "ForOp", + "TryOp"]; def CIR_ReturnOp : CIR_Op<"return", [ ParentOneOf, Terminator @@ -682,10 +681,9 @@ def CIR_ConditionOp : CIR_Op<"condition", [ // YieldOp //===----------------------------------------------------------------------===// -defvar CIR_YieldableScopes = [ - "ArrayCtor", "ArrayDtor", "CaseOp", "DoWhileOp", "ForOp", "GlobalOp", "IfOp", - "ScopeOp", "SwitchOp", "TernaryOp", "WhileOp", "TryOp" -]; +defvar CIR_YieldableScopes = ["ArrayCtor", "ArrayDtor", "CaseOp", "DoWhileOp", + "ForOp", "GlobalOp", "IfOp", "ScopeOp", + "SwitchOp", "TernaryOp", "WhileOp", "TryOp"]; def CIR_YieldOp : CIR_Op<"yield", [ ReturnLike, Terminator, ParentOneOf @@ -4206,25 +4204,12 @@ def CIR_AllocExceptionOp : CIR_Op<"alloc.exception"> { // TryOp //===----------------------------------------------------------------------===// -// Represents the unwind region where unwind continues or -// the program std::terminate's. -def CIR_CatchUnwind : CIR_UnitAttr<"CatchUnwind", "unwind"> { - let storageType = [{ CatchUnwind }]; -} - -// Represents the catch_all region. -def CIR_CatchAll : CIR_UnitAttr<"CatchAll", "all"> { - let storageType = [{ CatchAllAttr }]; -} - def CIR_TryOp : CIR_Op<"try",[ DeclareOpInterfaceMethods, RecursivelySpeculatable, AutomaticAllocationScope, NoRegionArguments ]> { let summary = "C++ try block"; let description = [{ - ```mlir - Holds the lexical scope of `try {}`. Note that resources used on catch clauses are usually allocated in the same parent as `cir.try`. @@ -4234,7 +4219,20 @@ def CIR_TryOp : CIR_Op<"try",[ `cleanup`: signal to targets (LLVM for now) that this try/catch, needs to specially tag their landing pads as needing "cleanup". - Example: TBD + 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 + } + ... + } ``` }]; @@ -4252,12 +4250,11 @@ def CIR_TryOp : CIR_Op<"try",[ }]; // Everything already covered elsewhere. - let builders = [ - OpBuilder<(ins - "llvm::function_ref":$tryBuilder, - "llvm::function_ref" - :$catchBuilder)>, - ]; + let builders = [OpBuilder<(ins + "llvm::function_ref":$tryBuilder, + "llvm::function_ref":$catchBuilder)>]; let hasLLVMLowering = false; } diff --git a/clang/lib/CIR/CodeGen/CIRGenException.cpp b/clang/lib/CIR/CodeGen/CIRGenException.cpp index 975e031ed5cd2..19ecd1b635d5d 100644 --- a/clang/lib/CIR/CodeGen/CIRGenException.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenException.cpp @@ -67,7 +67,7 @@ void CIRGenFunction::emitAnyExprToExn(const Expr *e, Address addr) { } mlir::LogicalResult CIRGenFunction::emitCXXTryStmt(const CXXTryStmt &s) { - auto loc = getLoc(s.getSourceRange()); + mlir::Location loc = getLoc(s.getSourceRange()); // Create a scope to hold try local storage for catch params. mlir::OpBuilder::InsertPoint scopeIP; @@ -76,14 +76,10 @@ mlir::LogicalResult CIRGenFunction::emitCXXTryStmt(const CXXTryStmt &s) { scopeIP = builder.saveInsertionPoint(); }); - mlir::LogicalResult result = mlir::success(); - { - mlir::OpBuilder::InsertionGuard guard(builder); - builder.restoreInsertionPoint(scopeIP); - result = emitCXXTryStmtUnderScope(s); - cir::YieldOp::create(builder, loc); - } - + mlir::OpBuilder::InsertionGuard guard(builder); + builder.restoreInsertionPoint(scopeIP); + mlir::LogicalResult result = emitCXXTryStmtUnderScope(s); + cir::YieldOp::create(builder, loc); return result; } @@ -100,16 +96,8 @@ CIRGenFunction::emitCXXTryStmtUnderScope(const CXXTryStmt &s) { return mlir::success(); } - auto hasCatchAll = [&]() { - if (!s.getNumHandlers()) - return false; - unsigned lastHandler = s.getNumHandlers() - 1; - return s.getHandler(lastHandler)->getExceptionDecl() == nullptr; - }; - unsigned numHandlers = s.getNumHandlers(); mlir::Location tryLoc = getLoc(s.getBeginLoc()); - mlir::OpBuilder::InsertPoint beginInsertTryBody; // Create the scope to represent only the C/C++ `try {}` part. However, @@ -126,15 +114,25 @@ CIRGenFunction::emitCXXTryStmtUnderScope(const CXXTryStmt &s) { [&](mlir::OpBuilder &b, mlir::Location loc, mlir::OperationState &result) { mlir::OpBuilder::InsertionGuard guard(b); + + bool hasCatchAll = false; + for (unsigned i = 0; i != numHandlers; ++i) { + hasCatchAll |= s.getHandler(i)->getExceptionDecl() == nullptr; + if (hasCatchAll) + break; + } + + // We create an extra region for an unwind catch handler in case the + // catch-all handler doesn't exists unsigned numRegionsToCreate = - hasCatchAll() ? numHandlers : numHandlers + 1; - for (unsigned i = 0; i != numRegionsToCreate; ++i) { + hasCatchAll ? numHandlers : numHandlers + 1; + + for (unsigned i = 0; i != numRegionsToCreate; ++i) builder.createBlock(result.addRegion()); - } }); // Finally emit the body for try/catch. - auto emitTryCatchBody = [&]() -> mlir::LogicalResult { + { mlir::Location loc = tryOp.getLoc(); mlir::OpBuilder::InsertionGuard guard(builder); builder.restoreInsertionPoint(beginInsertTryBody); @@ -157,10 +155,9 @@ CIRGenFunction::emitCXXTryStmtUnderScope(const CXXTryStmt &s) { // Emit catch clauses. exitCXXTryStmt(s); - return mlir::success(); - }; + } - return emitTryCatchBody(); + return mlir::success(); } void CIRGenFunction::enterCXXTryStmt(const CXXTryStmt &s, cir::TryOp tryOp, diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index c6c142255e16b..3865ca8924728 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -1089,9 +1089,10 @@ LogicalResult cir::ScopeOp::verify() { void cir::TryOp::build( OpBuilder &builder, OperationState &result, - function_ref tryBodyBuilder, + function_ref tryBuilder, function_ref catchBuilder) { - assert(tryBodyBuilder && "expected builder callback for 'cir.try' body"); + assert(tryBuilder && "expected builder callback for 'cir.try' body"); + assert(catchBuilder && "expected builder callback for 'catch' body"); OpBuilder::InsertionGuard guard(builder); @@ -1100,7 +1101,7 @@ void cir::TryOp::build( // Create try body region and set insertion point builder.createBlock(tryBodyRegion); - tryBodyBuilder(builder, result.location); + tryBuilder(builder, result.location); catchBuilder(builder, result.location, result); } @@ -1119,7 +1120,7 @@ void cir::TryOp::getSuccessorRegions( // FIXME: optimize, ideas include: // - If we know a target function never throws a specific type, we can // remove the catch handler. - for (auto &r : this->getCatchRegions()) + for (mlir::Region &r : this->getCatchRegions()) regions.push_back(RegionSuccessor(&r)); } diff --git a/clang/test/CIR/IR/try-catch.cir b/clang/test/CIR/IR/try-catch.cir new file mode 100644 index 0000000000000..66a2ddcc4b861 --- /dev/null +++ b/clang/test/CIR/IR/try-catch.cir @@ -0,0 +1,27 @@ +// RUN: cir-opt %s --verify-roundtrip | FileCheck %s + +module { + +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: } + +} \ No newline at end of file