[clang] [CIR] Upstream Exception CXXTryStmt (PR #162528)
Amr Hesham via cfe-commits
cfe-commits at lists.llvm.org
Wed Oct 8 14:28:40 PDT 2025
https://github.com/AmrDeveloper updated https://github.com/llvm/llvm-project/pull/162528
>From 22d4680619e8b41b2985090d78c3ddcd687a2eef Mon Sep 17 00:00:00 2001
From: Amr Hesham <amr96 at programmer.net>
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<RegionBranchOpInterface>,
+ 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<ArrayAttr>:$catch_types);
+ let regions = (region AnyRegion:$try_region,
+ VariadicRegion<AnyRegion>:$catch_regions);
+
+ let assemblyFormat = [{
+ (`synthetic` $synthetic^)?
+ (`cleanup` $cleanup^)?
+ $try_region
+ custom<CatchRegions>($catch_regions, $catch_types)
+ attr-dict
+ }];
+
+ // Everything already covered elsewhere.
+ let builders = [
+ OpBuilder<(ins
+ "llvm::function_ref<void(mlir::OpBuilder &, mlir::Location)>":$tryBuilder,
+ "llvm::function_ref<void(mlir::OpBuilder &, mlir::Location, mlir::OperationState &)>"
+ :$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<CXXMethodDecl>(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<Kind>(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<Handler *>(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<EHCatchScope>(*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<EHCatchScope>(*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<mlir::Block *> 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<WhileStmt>(*s));
case Stmt::DoStmtClass:
return emitDoStmt(cast<DoStmt>(*s));
+ case Stmt::CXXTryStmtClass:
+ return emitCXXTryStmt(cast<CXXTryStmt>(*s));
case Stmt::CXXForRangeStmtClass:
return emitCXXForRangeStmt(cast<CXXForRangeStmt>(*s), attr);
case Stmt::OpenACCComputeConstructClass:
@@ -199,7 +201,6 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s,
case Stmt::CoroutineBodyStmtClass:
return emitCoroutineBody(cast<CoroutineBodyStmt>(*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<void(OpBuilder &, Location)> tryBodyBuilder,
+ function_ref<void(OpBuilder &, Location, OperationState &)> 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<RegionSuccessor> ®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<cir::CatchUnwindAttr>(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<std::unique_ptr<::mlir::Region>> ®ions,
+ ::mlir::ArrayAttr &catchersAttr) {
+ llvm::SmallVector<mlir::Attribute, 4> 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<cir::TernaryOp> {
}
};
+class CIRTryOpFlattening : public mlir::OpRewritePattern<cir::TryOp> {
+public:
+ using OpRewritePattern<cir::TryOp>::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<YieldOp>(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<CIRIfFlattening, CIRLoopOpInterfaceFlattening, CIRScopeOpFlattening,
- CIRSwitchOpFlattening, CIRTernaryOpFlattening>(
+ 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<IfOp, ScopeOp, SwitchOp, LoopOpInterface, TernaryOp>(op))
+ if (isa<IfOp, ScopeOp, SwitchOp, LoopOpInterface, TernaryOp, TryOp>(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 <amr96 at programmer.net>
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 <amr96 at programmer.net>
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<CIR_ReturnableScopes>, 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<CIR_YieldableScopes>
@@ -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<RegionBranchOpInterface>,
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<!some_record>
+ %1 = cir.get_global @d2 : !cir.ptr<!some_record>
+ cir.try synthetic cleanup {
+ cir.call exception @_ZN7test2_DC1ERKS_(%0, %1)
+ : (!cir.ptr<!some_record>, !cir.ptr<!some_record>) -> () cleanup {
+ %2 = cir.cast bitcast %0 : !cir.ptr<!some_record> -> !cir.ptr<!void>
+ 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<void(mlir::OpBuilder &, mlir::Location)>":$tryBuilder,
- "llvm::function_ref<void(mlir::OpBuilder &, mlir::Location, mlir::OperationState &)>"
- :$catchBuilder)>,
- ];
+ let builders = [OpBuilder<(ins
+ "llvm::function_ref<void(mlir::OpBuilder &, "
+ "mlir::Location)>":$tryBuilder,
+ "llvm::function_ref<void(mlir::OpBuilder &, mlir::Location, "
+ "mlir::OperationState &)>":$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<void(OpBuilder &, Location)> tryBodyBuilder,
+ function_ref<void(OpBuilder &, Location)> tryBuilder,
function_ref<void(OpBuilder &, Location, OperationState &)> 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
More information about the cfe-commits
mailing list