[clang] [CIR] Support Try catch with handler for specific type (PR #171042)
Amr Hesham via cfe-commits
cfe-commits at lists.llvm.org
Sun Dec 7 09:27:17 PST 2025
https://github.com/AmrDeveloper updated https://github.com/llvm/llvm-project/pull/171042
>From f79fe746b475449855b59b62f7dcceb1b2ab563a Mon Sep 17 00:00:00 2001
From: Amr Hesham <amr96 at programmer.net>
Date: Sun, 30 Nov 2025 17:12:04 +0100
Subject: [PATCH 1/2] [CIR] Support Try catch with handler for specific type
---
clang/include/clang/CIR/Dialect/IR/CIROps.td | 5 +-
clang/lib/CIR/CodeGen/CIRGenCXXABI.h | 3 +
clang/lib/CIR/CodeGen/CIRGenCall.cpp | 2 +
clang/lib/CIR/CodeGen/CIRGenException.cpp | 149 ++++++++++++++++--
clang/lib/CIR/CodeGen/CIRGenFunction.h | 2 +
clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp | 7 +
clang/test/CIR/CodeGen/try-catch-tmp.cpp | 56 ++++++-
7 files changed, 206 insertions(+), 18 deletions(-)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index fcc7585cf81a5..710a91320a155 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -802,7 +802,7 @@ def CIR_ConditionOp : CIR_Op<"condition", [
//===----------------------------------------------------------------------===//
defvar CIR_YieldableScopes = [
- "ArrayCtor", "ArrayDtor", "AwaitOp", "CaseOp", "DoWhileOp", "ForOp",
+ "ArrayCtor", "ArrayDtor", "AwaitOp", "CallOp", "CaseOp", "DoWhileOp", "ForOp",
"GlobalOp", "IfOp", "ScopeOp", "SwitchOp", "TernaryOp", "WhileOp", "TryOp"
];
@@ -2956,6 +2956,7 @@ def CIR_CallOp : CIR_CallOpBase<"call", [NoRegionArguments]> {
let results = (outs Optional<CIR_AnyType>:$result);
let arguments = commonArgs;
+ let regions = (region AnyRegion:$cleanup);
let builders = [
OpBuilder<(ins "mlir::SymbolRefAttr":$callee, "mlir::Type":$resType,
@@ -2965,6 +2966,8 @@ def CIR_CallOp : CIR_CallOpBase<"call", [NoRegionArguments]> {
$_state.addAttribute("callee", callee);
if (resType && !isa<VoidType>(resType))
$_state.addTypes(resType);
+ // Create region placeholder for potential cleanups.
+ $_state.addRegion();
}]>
];
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
index 57b1a1f20aa17..c341a6139cea5 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
+++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
@@ -158,6 +158,9 @@ class CIRGenCXXABI {
/// Loads the incoming C++ this pointer as it was passed by the caller.
mlir::Value loadIncomingCXXThis(CIRGenFunction &cgf);
+ virtual CatchTypeInfo
+ getAddrOfCXXCatchHandlerType(mlir::Location loc, QualType ty,
+ QualType catchHandlerType) = 0;
virtual CatchTypeInfo getCatchAllTypeInfo();
/// Get the implicit (second) parameter that comes after the "this" pointer,
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index 17f0c6dbab35c..3ef443962aaa7 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -503,7 +503,9 @@ emitCallLikeOp(CIRGenFunction &cgf, mlir::Location callLoc,
callOpWithExceptions =
builder.createCallOp(callLoc, directFuncOp, cirCallArgs);
+ cgf.callWithExceptionCtx = callOpWithExceptions;
cgf.populateCatchHandlersIfRequired(tryOp);
+ cgf.callWithExceptionCtx = nullptr;
return callOpWithExceptions;
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenException.cpp b/clang/lib/CIR/CodeGen/CIRGenException.cpp
index 375828421eb1b..d5e10ccaef456 100644
--- a/clang/lib/CIR/CodeGen/CIRGenException.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenException.cpp
@@ -232,6 +232,30 @@ void CIRGenFunction::emitAnyExprToExn(const Expr *e, Address addr) {
assert(!cir::MissingFeatures::ehCleanupScope());
}
+void CIRGenFunction::populateUnwindResumeBlock(bool isCleanup,
+ cir::TryOp tryOp) {
+ const EHPersonality &personality = EHPersonality::get(*this);
+ // This can always be a call because we necessarily didn't find
+ // anything on the EH stack which needs our help.
+ const char *rethrowName = personality.catchallRethrowFn;
+ if (rethrowName != nullptr && !isCleanup) {
+ cgm.errorNYI("populateUnwindResumeBlock CatchallRethrowFn");
+ return;
+ }
+
+ unsigned regionsNum = tryOp->getNumRegions();
+ mlir::Region *unwindRegion = &tryOp->getRegion(regionsNum - 1);
+ mlir::Block *unwindResumeBlock = &unwindRegion->front();
+ if (!unwindResumeBlock->empty())
+ return;
+
+ // Emit cir.resume into the unwind region last block
+ cir::CIRBaseBuilderTy::InsertPoint ip = builder.saveInsertionPoint();
+ builder.setInsertionPointToStart(unwindResumeBlock);
+ cir::ResumeOp::create(builder, tryOp.getLoc());
+ builder.restoreInsertionPoint(ip);
+}
+
mlir::LogicalResult CIRGenFunction::emitCXXTryStmt(const CXXTryStmt &s) {
if (s.getTryBlock()->body_empty())
return mlir::LogicalResult::success();
@@ -332,21 +356,88 @@ CIRGenFunction::emitCXXTryStmtUnderScope(const CXXTryStmt &s) {
return mlir::success();
}
+/// Emit the structure of the dispatch block for the given catch scope.
+/// It is an invariant that the dispatch block already exists.
+static void emitCatchDispatchBlock(CIRGenFunction &cgf,
+ EHCatchScope &catchScope, cir::TryOp tryOp) {
+ if (EHPersonality::get(cgf).isWasmPersonality()) {
+ cgf.cgm.errorNYI("emitCatchDispatchBlock: WasmPersonality");
+ return;
+ }
+
+ if (EHPersonality::get(cgf).usesFuncletPads()) {
+ cgf.cgm.errorNYI("emitCatchDispatchBlock: usesFuncletPads");
+ return;
+ }
+
+ unsigned int numHandlers = catchScope.getNumHandlers();
+ if (numHandlers == 1 && catchScope.getHandler(0).isCatchAll()) {
+ return;
+ }
+
+ // In traditional LLVM codegen, the right handler is selected (with
+ // calls to eh_typeid_for) and the selector value is loaded. After that,
+ // blocks get connected for later codegen. In CIR, these are all
+ // implicit behaviors of cir.catch - not a lot of work to do.
+ //
+ // Test against each of the exception types we claim to catch.
+ for (unsigned i = 0;; ++i) {
+ assert(i < numHandlers && "ran off end of handlers!");
+ const EHCatchScope::Handler &handler = catchScope.getHandler(i);
+
+ [[maybe_unused]] mlir::TypedAttr typeValue = handler.type.rtti;
+ assert(handler.Type.Flags == 0 && "catch handler flags not supported");
+ assert(typeValue && "fell into catch-all case!");
+
+ // Check for address space mismatch
+ assert(!cir::MissingFeatures::addressSpace());
+
+ // If this is the last handler, we're at the end, and the next
+ // block is the block for the enclosing EH scope. Make sure to call
+ // populateEHCatchRegions for caching it.
+ if (i + 1 == numHandlers) {
+ cgf.populateEHCatchRegions(catchScope.getEnclosingEHScope(), tryOp);
+ return;
+ }
+
+ // If the next handler is a catch-all, we're at the end, and the
+ // next block is that handler.
+ if (catchScope.getHandler(i + 1).isCatchAll())
+ return;
+ }
+}
+
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);
+ mlir::Region *handler = &tryOp.getHandlerRegions()[i];
if (catchStmt->getExceptionDecl()) {
- cgm.errorNYI("enterCXXTryStmt: CatchStmt with ExceptionDecl");
- return;
- }
+ // FIXME: Dropping the reference type on the type into makes it
+ // impossible to correctly implement catch-by-reference
+ // semantics for pointers. Unfortunately, this is what all
+ // existing compilers do, and it's not clear that the standard
+ // personality routine is capable of doing this right. See C++ DR 388:
+ // http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#388
+ Qualifiers caughtTypeQuals;
+ QualType caughtType = cgm.getASTContext().getUnqualifiedArrayType(
+ catchStmt->getCaughtType().getNonReferenceType(), caughtTypeQuals);
+ if (caughtType->isObjCObjectPointerType()) {
+ cgm.errorNYI("enterCXXTryStmt: caughtType ObjCObjectPointerType");
+ return;
+ }
- // No exception decl indicates '...', a catch-all.
- mlir::Region *handler = &tryOp.getHandlerRegions()[i];
- catchScope->setHandler(i, cgm.getCXXABI().getCatchAllTypeInfo(), handler,
- s.getHandler(i));
+ CatchTypeInfo typeInfo = cgm.getCXXABI().getAddrOfCXXCatchHandlerType(
+ getLoc(catchStmt->getSourceRange()), caughtType,
+ catchStmt->getCaughtType());
+ catchScope->setHandler(i, typeInfo, handler, catchStmt);
+ } else {
+ // No exception decl indicates '...', a catch-all.
+ catchScope->setHandler(i, cgm.getCXXABI().getCatchAllTypeInfo(), handler,
+ s.getHandler(i));
+ }
// Under async exceptions, catch(...) needs to catch HW exception too
// Mark scope with SehTryBegin as a SEH __try scope
@@ -385,6 +476,9 @@ void CIRGenFunction::exitCXXTryStmt(const CXXTryStmt &s, bool isFnTryBlock) {
return;
}
+ // Emit the structure of the EH dispatch for this catch.
+ emitCatchDispatchBlock(*this, catchScope, tryOp);
+
// Copy the handler blocks off before we pop the EH stack. Emitting
// the handlers might scribble on this memory.
SmallVector<EHCatchScope::Handler> handlers(catchScope.begin(),
@@ -486,9 +580,11 @@ void CIRGenFunction::populateCatchHandlers(cir::TryOp tryOp) {
// with function local static initializers).
mlir::ArrayAttr handlerTypesAttr = tryOp.getHandlerTypesAttr();
if (!handlerTypesAttr || handlerTypesAttr.empty()) {
+ // Accumulate all the handlers in scope.
// Accumulate all the handlers in scope.
bool hasCatchAll = false;
- llvm::SmallVector<mlir::Attribute, 4> handlerAttrs;
+ llvm::SmallPtrSet<mlir::Attribute, 4> catchTypes;
+ llvm::SmallVector<mlir::Attribute> handlerAttrs;
for (EHScopeStack::iterator i = ehStack.begin(), e = ehStack.end(); i != e;
++i) {
switch (i->getKind()) {
@@ -521,8 +617,10 @@ void CIRGenFunction::populateCatchHandlers(cir::TryOp tryOp) {
break;
}
- cgm.errorNYI("emitLandingPad: non catch-all");
- return;
+ // Check whether we already have a handler for this type.
+ // If not, keep track to later add to catch op.
+ if (catchTypes.insert(handler.type.rtti).second)
+ handlerAttrs.push_back(handler.type.rtti);
}
if (hasCatchAll)
@@ -531,9 +629,12 @@ void CIRGenFunction::populateCatchHandlers(cir::TryOp tryOp) {
if (hasCatchAll) {
handlerAttrs.push_back(cir::CatchAllAttr::get(&getMLIRContext()));
- } else {
- cgm.errorNYI("emitLandingPad: non catch-all");
- return;
+ }
+
+ // If there's no catch_all, attach the unwind region. This needs to be the
+ // last region in the TryOp catch list.
+ if (!hasCatchAll) {
+ handlerAttrs.push_back(cir::UnwindAttr::get(&getMLIRContext()));
}
// Add final array of clauses into TryOp.
@@ -558,6 +659,13 @@ void CIRGenFunction::populateEHCatchRegions(EHScopeStack::stable_iterator scope,
return;
}
+ // The dispatch block for the end of the scope chain is a block that
+ // just resumes unwinding.
+ if (scope == ehStack.stable_end()) {
+ populateUnwindResumeBlock(/*isCleanup=*/true, tryOp);
+ return;
+ }
+
// Otherwise, we should look at the actual scope.
EHScope &ehScope = *ehStack.find(scope);
bool mayThrow = ehScope.mayThrow();
@@ -575,16 +683,25 @@ void CIRGenFunction::populateEHCatchRegions(EHScopeStack::stable_iterator scope,
if (!mayThrow) {
switch (ehScope.getKind()) {
case EHScope::Catch: {
+ mayThrow = true;
+
// LLVM does some optimization with branches here, CIR just keep track of
// the corresponding calls.
EHCatchScope &catchScope = cast<EHCatchScope>(ehScope);
if (catchScope.getNumHandlers() == 1 &&
catchScope.getHandler(0).isCatchAll()) {
- mayThrow = true;
break;
}
- cgm.errorNYI("getEHDispatchBlock: mayThrow non-catch all");
- return;
+
+ assert(callWithExceptionCtx && "expected call information");
+ {
+ mlir::OpBuilder::InsertionGuard guard(builder);
+ assert(callWithExceptionCtx.getCleanup().empty() &&
+ "one per call: expected empty region at this point");
+ builder.createBlock(&callWithExceptionCtx.getCleanup());
+ builder.createYield(callWithExceptionCtx.getLoc());
+ }
+ break;
}
case EHScope::Cleanup: {
cgm.errorNYI("getEHDispatchBlock: mayThrow & cleanup");
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 0df812bcfb94e..48bfcddc09009 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -969,6 +969,8 @@ class CIRGenFunction : public CIRGenTypeCache {
return false;
}
+ cir::CallOp callWithExceptionCtx = nullptr;
+ void populateUnwindResumeBlock(bool isCleanup, cir::TryOp tryOp);
void populateEHCatchRegions(EHScopeStack::stable_iterator scope,
cir::TryOp tryOp);
diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
index 7e145f2c57ce6..2e51130e271f5 100644
--- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
@@ -118,6 +118,13 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI {
mlir::Attribute getAddrOfRTTIDescriptor(mlir::Location loc,
QualType ty) override;
+ CatchTypeInfo
+ getAddrOfCXXCatchHandlerType(mlir::Location loc, QualType ty,
+ QualType catchHandlerType) override {
+ auto rtti = dyn_cast<cir::GlobalViewAttr>(getAddrOfRTTIDescriptor(loc, ty));
+ assert(rtti && "expected GlobalViewAttr");
+ return CatchTypeInfo{rtti, 0};
+ }
bool doStructorsInitializeVPtrs(const CXXRecordDecl *vtableClass) override {
return true;
diff --git a/clang/test/CIR/CodeGen/try-catch-tmp.cpp b/clang/test/CIR/CodeGen/try-catch-tmp.cpp
index 078447f844d9a..5f57af9876615 100644
--- a/clang/test/CIR/CodeGen/try-catch-tmp.cpp
+++ b/clang/test/CIR/CodeGen/try-catch-tmp.cpp
@@ -5,7 +5,7 @@
int division();
-void calling_division_inside_try_block() {
+void call_function_inside_try_catch_all() {
try {
division();
} catch (...) {
@@ -42,3 +42,57 @@ void calling_division_inside_try_block() {
// OGCG: br label %[[TRY_CONT]]
// OGCG: [[TRY_CONT]]:
// OGCG: ret void
+
+void call_function_inside_try_catch_with_exception_type() {
+ try {
+ division();
+ } catch (int e) {
+ }
+}
+
+// CIR: cir.scope {
+// CIR: cir.try {
+// CIR: %[[CALL:.*]] = cir.call @_Z8divisionv() : () -> !s32i
+// CIR: cir.yield
+// CIR: } catch [type #cir.global_view<@_ZTIi> : !cir.ptr<!u8i>] {
+// CIR: cir.yield
+// CIR: } unwind {
+// CIR: cir.resume
+// CIR: }
+// CIR: }
+
+// OGCG: %[[EXCEPTION_ADDR:.*]] = alloca ptr, align 8
+// OGCG: %[[EH_TYPE_ID_ADDR:.*]] = alloca i32, align 4
+// OGCG: %[[E_ADDR:.*]] = alloca i32, align 4
+// OGCG: %[[CALL:.*]] = invoke noundef i32 @_Z8divisionv()
+// OGCG: to label %[[INVOKE_NORMAL:.*]] unwind label %[[INVOKE_UNWIND:.*]]
+// OGCG: [[INVOKE_NORMAL]]:
+// OGCG: br label %try.cont
+// OGCG: [[INVOKE_UNWIND]]:
+// OGCG: %[[LANDING_PAD:.*]] = landingpad { ptr, i32 }
+// OGCG: catch ptr @_ZTIi
+// OGCG: %[[EXCEPTION:.*]] = extractvalue { ptr, i32 } %[[LANDING_PAD]], 0
+// OGCG: store ptr %[[EXCEPTION]], ptr %[[EXCEPTION_ADDR]], align 8
+// OGCG: %[[EH_TYPE_ID:.*]] = extractvalue { ptr, i32 } %[[LANDING_PAD]], 1
+// OGCG: store i32 %[[EH_TYPE_ID]], ptr %[[EH_TYPE_ID_ADDR]], align 4
+// OGCG: br label %[[CATCH_DISPATCH:.*]]
+// OGCG: [[CATCH_DISPATCH]]:
+// OGCG: %[[TMP_EH_TYPE_ID:.*]] = load i32, ptr %[[EH_TYPE_ID_ADDR]], align 4
+// OGCG: %[[EH_TYPE_ID:.*]] = call i32 @llvm.eh.typeid.for.p0(ptr @_ZTIi)
+// OGCG: %[[TYPE_ID_EQ:.*]] = icmp eq i32 %[[TMP_EH_TYPE_ID]], %[[EH_TYPE_ID]]
+// OGCG: br i1 %[[TYPE_ID_EQ]], label %[[CATCH_EXCEPTION:.*]], label %[[EH_RESUME:.*]]
+// OGCG: [[CATCH_EXCEPTION]]:
+// OGCG: %[[TMP_EXCEPTION:.*]] = load ptr, ptr %[[EXCEPTION_ADDR]], align 8
+// OGCG: %[[BEGIN_CATCH:.*]] = call ptr @__cxa_begin_catch(ptr %[[TMP_EXCEPTION]])
+// OGCG: %[[TMP_BEGIN_CATCH:.*]] = load i32, ptr %[[BEGIN_CATCH]], align 4
+// OGCG: store i32 %[[TMP_BEGIN_CATCH]], ptr %[[E_ADDR]], align 4
+// OGCG: call void @__cxa_end_catch()
+// OGCG: br label %[[TRY_NORMA:.*]]
+// OGCG: [[TRY_NORMA]]:
+// OGCG: ret void
+// OGCG: [[EH_RESUME]]:
+// OGCG: %[[TMP_EXCEPTION:.*]] = load ptr, ptr %[[EXCEPTION_ADDR]], align 8
+// OGCG: %[[TMP_EH_TYPE_ID:.*]] = load i32, ptr %[[EH_TYPE_ID_ADDR]], align 4
+// OGCG: %[[TMP_EXCEPTION_INFO:.*]] = insertvalue { ptr, i32 } poison, ptr %[[TMP_EXCEPTION]], 0
+// OGCG: %[[EXCEPTION_INFO:.*]] = insertvalue { ptr, i32 } %[[TMP_EXCEPTION_INFO]], i32 %[[TMP_EH_TYPE_ID]], 1
+// OGCG: resume { ptr, i32 } %[[EXCEPTION_INFO]]
>From 1629f2046003539eebe6249f42bbafc4dc70431c Mon Sep 17 00:00:00 2001
From: Amr Hesham <amr96 at programmer.net>
Date: Sun, 7 Dec 2025 18:26:42 +0100
Subject: [PATCH 2/2] Fix compiling assert
---
clang/lib/CIR/CodeGen/CIRGenException.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/lib/CIR/CodeGen/CIRGenException.cpp b/clang/lib/CIR/CodeGen/CIRGenException.cpp
index d5e10ccaef456..6716b98a9aaca 100644
--- a/clang/lib/CIR/CodeGen/CIRGenException.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenException.cpp
@@ -386,7 +386,7 @@ static void emitCatchDispatchBlock(CIRGenFunction &cgf,
const EHCatchScope::Handler &handler = catchScope.getHandler(i);
[[maybe_unused]] mlir::TypedAttr typeValue = handler.type.rtti;
- assert(handler.Type.Flags == 0 && "catch handler flags not supported");
+ assert(handler.type.flags == 0 && "catch handler flags not supported");
assert(typeValue && "fell into catch-all case!");
// Check for address space mismatch
More information about the cfe-commits
mailing list