[clang] [CIR] Upstream non-empty Try block with catch all (PR #165158)
Amr Hesham via cfe-commits
cfe-commits at lists.llvm.org
Thu Nov 6 10:20:52 PST 2025
https://github.com/AmrDeveloper updated https://github.com/llvm/llvm-project/pull/165158
>From 49898be8068694b9ddc5a7d6bf7a20c383db5cde Mon Sep 17 00:00:00 2001
From: Amr Hesham <amr96 at programmer.net>
Date: Sat, 25 Oct 2025 21:17:00 +0200
Subject: [PATCH 1/2] [CIR] Upstream non-empty Try block with catch all
---
clang/lib/CIR/CodeGen/CIRGenCall.cpp | 48 +++-
clang/lib/CIR/CodeGen/CIRGenCleanup.cpp | 10 +-
clang/lib/CIR/CodeGen/CIRGenCleanup.h | 7 +-
clang/lib/CIR/CodeGen/CIRGenException.cpp | 304 +++++++++++++++++++++-
clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 10 +
clang/lib/CIR/CodeGen/CIRGenFunction.h | 17 +-
clang/lib/CIR/CodeGen/EHScopeStack.h | 2 +
clang/test/CIR/CodeGen/try-catch-tmp.cpp | 44 ++++
8 files changed, 434 insertions(+), 8 deletions(-)
create mode 100644 clang/test/CIR/CodeGen/try-catch-tmp.cpp
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index 50d4c035d30a1..ea44c65636c6c 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -465,12 +465,48 @@ static cir::CIRCallOpInterface
emitCallLikeOp(CIRGenFunction &cgf, mlir::Location callLoc,
cir::FuncType indirectFuncTy, mlir::Value indirectFuncVal,
cir::FuncOp directFuncOp,
- const SmallVectorImpl<mlir::Value> &cirCallArgs,
+ const SmallVectorImpl<mlir::Value> &cirCallArgs, bool isInvoke,
const mlir::NamedAttrList &attrs) {
CIRGenBuilderTy &builder = cgf.getBuilder();
assert(!cir::MissingFeatures::opCallSurroundingTry());
- assert(!cir::MissingFeatures::invokeOp());
+
+ if (isInvoke) {
+ // This call can throw, few options:
+ // - If this call does not have an associated cir.try, use the
+ // one provided by InvokeDest,
+ // - User written try/catch clauses require calls to handle
+ // exceptions under cir.try.
+
+ // In OG, we build the landing pad for this scope. In CIR, we emit a
+ // synthetic cir.try because this didn't come from code generating from a
+ // try/catch in C++.
+ assert(cgf.curLexScope && "expected scope");
+ cir::TryOp tryOp = cgf.curLexScope->getClosestTryParent();
+ if (!tryOp) {
+ cgf.cgm.errorNYI(
+ "emitCallLikeOp: call does not have an associated cir.try");
+ return {};
+ }
+
+ if (tryOp.getSynthetic()) {
+ cgf.cgm.errorNYI("emitCallLikeOp: tryOp synthetic");
+ return {};
+ }
+
+ cir::CallOp callOpWithExceptions;
+ if (indirectFuncTy) {
+ cgf.cgm.errorNYI("emitCallLikeOp: indirect function type");
+ return {};
+ }
+
+ callOpWithExceptions =
+ builder.createTryCallOp(callLoc, directFuncOp, cirCallArgs);
+
+ (void)cgf.getInvokeDest(tryOp);
+
+ return callOpWithExceptions;
+ }
assert(builder.getInsertionBlock() && "expected valid basic block");
@@ -628,10 +664,16 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &funcInfo,
indirectFuncVal = calleePtr->getResult(0);
}
+ // TODO(cir): currentFunctionUsesSEHTry
+ // TODO(cir): check for MSVCXXPersonality
+ // TODO(cir): Create NoThrowAttr
+ bool cannotThrow = attrs.getNamed("nothrow").has_value();
+ bool isInvoke = !cannotThrow && isInvokeDest();
+
mlir::Location callLoc = loc;
cir::CIRCallOpInterface theCall =
emitCallLikeOp(*this, loc, indirectFuncTy, indirectFuncVal, directFuncOp,
- cirCallArgs, attrs);
+ cirCallArgs, isInvoke, attrs);
if (callOp)
*callOp = theCall;
diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
index 437db306f3369..3550a78cc1816 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
@@ -188,9 +188,17 @@ void EHScopeStack::popCleanup() {
}
}
+bool EHScopeStack::requiresLandingPad() const {
+ for (stable_iterator si = getInnermostEHScope(); si != stable_end();) {
+ // TODO(cir): Skip lifetime markers.
+ assert(!cir::MissingFeatures::emitLifetimeMarkers());
+ return true;
+ }
+ return false;
+}
+
EHCatchScope *EHScopeStack::pushCatch(unsigned numHandlers) {
char *buffer = allocate(EHCatchScope::getSizeForNumHandlers(numHandlers));
- assert(!cir::MissingFeatures::innermostEHScope());
EHCatchScope *scope =
new (buffer) EHCatchScope(numHandlers, innermostEHScope);
innermostEHScope = stable_begin();
diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.h b/clang/lib/CIR/CodeGen/CIRGenCleanup.h
index a035d792ef6d1..4e4e913574991 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCleanup.h
+++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.h
@@ -38,6 +38,8 @@ class EHScope {
};
enum { NumCommonBits = 3 };
+ bool isScopeMayThrow;
+
protected:
class CatchBitFields {
friend class EHCatchScope;
@@ -92,10 +94,11 @@ class EHScope {
// 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;
+ return isScopeMayThrow;
}
+ void setMayThrow(bool mayThrow) { isScopeMayThrow = mayThrow; }
+
EHScopeStack::stable_iterator getEnclosingEHScope() const {
return enclosingEHScope;
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenException.cpp b/clang/lib/CIR/CodeGen/CIRGenException.cpp
index 67f46ffde8fda..700e5e0c67c45 100644
--- a/clang/lib/CIR/CodeGen/CIRGenException.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenException.cpp
@@ -14,6 +14,7 @@
#include "CIRGenFunction.h"
#include "clang/AST/StmtVisitor.h"
+#include "llvm/Support/SaveAndRestore.h"
using namespace clang;
using namespace clang::CIRGen;
@@ -354,6 +355,33 @@ void CIRGenFunction::enterCXXTryStmt(const CXXTryStmt &s, cir::TryOp tryOp,
}
}
+/// 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: WASM personality");
+ return;
+ }
+
+ if (EHPersonality::get(cgf).usesFuncletPads()) {
+ cgf.cgm.errorNYI("emitCatchDispatchBlock: usesFuncletPads");
+ return;
+ }
+
+ assert(catchScope.mayThrow() &&
+ "Expected catchScope that may throw exception");
+
+ // If there's only a single catch-all, getEHDispatchBlock returned
+ // that catch-all as the dispatch block.
+ if (catchScope.getNumHandlers() == 1 &&
+ catchScope.getHandler(0).isCatchAll()) {
+ return;
+ }
+
+ cgf.cgm.errorNYI("emitCatchDispatchBlock: non-catch all handler");
+}
+
void CIRGenFunction::exitCXXTryStmt(const CXXTryStmt &s, bool isFnTryBlock) {
unsigned numHandlers = s.getNumHandlers();
EHCatchScope &catchScope = cast<EHCatchScope>(*ehStack.begin());
@@ -382,5 +410,279 @@ void CIRGenFunction::exitCXXTryStmt(const CXXTryStmt &s, bool isFnTryBlock) {
return;
}
- cgm.errorNYI("exitCXXTryStmt: Required catch");
+ // 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, 8> handlers(
+ catchScope.begin(), catchScope.begin() + numHandlers);
+
+ ehStack.popCatch();
+
+ // Determine if we need an implicit rethrow for all these catch handlers;
+ // see the comment below.
+ bool doImplicitRethrow =
+ isFnTryBlock && isa<CXXDestructorDecl, CXXConstructorDecl>(curCodeDecl);
+
+ // Wasm uses Windows-style EH instructions, but merges all catch clauses into
+ // one big catchpad. So we save the old funclet pad here before we traverse
+ // each catch handler.
+ if (EHPersonality::get(*this).isWasmPersonality()) {
+ cgm.errorNYI("exitCXXTryStmt: WASM personality");
+ return;
+ }
+
+ bool hasCatchAll = false;
+ for (unsigned i = numHandlers; i != 0; --i) {
+ hasCatchAll |= handlers[i - 1].isCatchAll();
+ mlir::Region *catchRegion = handlers[i - 1].region;
+
+ mlir::OpBuilder::InsertionGuard guard(builder);
+ builder.setInsertionPointToStart(&catchRegion->front());
+
+ const CXXCatchStmt *catchStmt = s.getHandler(i - 1);
+
+ // Enter a cleanup scope, including the catch variable and the
+ // end-catch.
+ RunCleanupsScope catchScope(*this);
+
+ // Initialize the catch variable and set up the cleanups.
+ // TODO: emitBeginCatch
+
+ // Emit the PGO counter increment.
+ assert(!cir::MissingFeatures::incrementProfileCounter());
+
+ // Perform the body of the catch.
+ mlir::LogicalResult emitResult =
+ emitStmt(catchStmt->getHandlerBlock(), /*useCurrentScope=*/true);
+ assert(emitResult.succeeded() && "failed to emit catch handler block");
+
+ // TODO(cir): This yeild should replaced by CatchParamOp once it upstreamed
+ cir::YieldOp::create(builder, tryOp->getLoc());
+
+ // [except.handle]p11:
+ // The currently handled exception is rethrown if control
+ // reaches the end of a handler of the function-try-block of a
+ // constructor or destructor.
+
+ // It is important that we only do this on fallthrough and not on
+ // return. Note that it's illegal to put a return in a
+ // constructor function-try-block's catch handler (p14), so this
+ // really only applies to destructors.
+ if (doImplicitRethrow) {
+ cgm.errorNYI("exitCXXTryStmt: doImplicitRethrow");
+ return;
+ }
+
+ // Fall out through the catch cleanups.
+ catchScope.forceCleanup();
+ }
+
+ // Because in wasm we merge all catch clauses into one big catchpad, in case
+ // none of the types in catch handlers matches after we test against each of
+ // them, we should unwind to the next EH enclosing scope. We generate a call
+ // to rethrow function here to do that.
+ if (EHPersonality::get(*this).isWasmPersonality() && !hasCatchAll) {
+ cgm.errorNYI("exitCXXTryStmt: WASM personality without catch all");
+ }
+
+ assert(!cir::MissingFeatures::incrementProfileCounter());
+}
+
+mlir::Operation *CIRGenFunction::emitLandingPad(cir::TryOp tryOp) {
+ assert(ehStack.requiresLandingPad());
+ assert(!cgm.getLangOpts().IgnoreExceptions &&
+ "LandingPad should not be emitted when -fignore-exceptions are in "
+ "effect.");
+
+ EHScope &innermostEHScope = *ehStack.find(ehStack.getInnermostEHScope());
+ switch (innermostEHScope.getKind()) {
+ case EHScope::Terminate:
+ cgm.errorNYI("emitLandingPad: terminate");
+ return {};
+
+ case EHScope::Catch:
+ case EHScope::Cleanup:
+ case EHScope::Filter:
+ // CIR does not cache landing pads.
+ break;
+ }
+
+ // If there's an existing TryOp, it means we got a `cir.try` scope
+ // that leads to this "landing pad" creation site. Otherwise, exceptions
+ // are enabled but a throwing function is called anyways (common pattern
+ // with function local static initializers).
+ mlir::ArrayAttr handlerTypesAttr = tryOp.getHandlerTypesAttr();
+ if (!handlerTypesAttr || handlerTypesAttr.empty()) {
+ // Accumulate all the handlers in scope.
+ bool hasCatchAll = false;
+ llvm::SmallVector<mlir::Attribute, 4> handlerAttrs;
+ for (EHScopeStack::iterator i = ehStack.begin(), e = ehStack.end(); i != e;
+ ++i) {
+ switch (i->getKind()) {
+ case EHScope::Cleanup: {
+ cgm.errorNYI("emitLandingPad: Cleanup");
+ return {};
+ }
+
+ case EHScope::Filter: {
+ cgm.errorNYI("emitLandingPad: Filter");
+ return {};
+ }
+
+ case EHScope::Terminate: {
+ cgm.errorNYI("emitLandingPad: Terminate");
+ return {};
+ }
+
+ case EHScope::Catch:
+ break;
+ }
+
+ EHCatchScope &catchScope = cast<EHCatchScope>(*i);
+ for (unsigned handlerIdx = 0, he = catchScope.getNumHandlers();
+ handlerIdx != he; ++handlerIdx) {
+ EHCatchScope::Handler handler = catchScope.getHandler(handlerIdx);
+ assert(handler.type.flags == 0 &&
+ "landingpads do not support catch handler flags");
+
+ // If this is a catch-all, register that and abort.
+ if (handler.isCatchAll()) {
+ assert(!hasCatchAll);
+ hasCatchAll = true;
+ goto done;
+ }
+
+ cgm.errorNYI("emitLandingPad: non catch-all");
+ return {};
+ }
+
+ goto done;
+ }
+
+ done:
+ if (hasCatchAll) {
+ handlerAttrs.push_back(cir::CatchAllAttr::get(&getMLIRContext()));
+ } else {
+ cgm.errorNYI("emitLandingPad: non catch-all");
+ return {};
+ }
+
+ // Add final array of clauses into TryOp.
+ tryOp.setHandlerTypesAttr(
+ mlir::ArrayAttr::get(&getMLIRContext(), handlerAttrs));
+ }
+
+ // In traditional LLVM codegen. this tells the backend how to generate the
+ // landing pad by generating a branch to the dispatch block. In CIR,
+ // getEHDispatchBlock is used to populate blocks for later filing during
+ // cleanup handling.
+ (void)getEHDispatchBlock(ehStack.getInnermostEHScope(), tryOp);
+
+ return tryOp;
+}
+
+// Differently from LLVM traditional codegen, there are no dispatch blocks
+// to look at given cir.try_call does not jump to blocks like invoke does.
+// However, we keep this around since other parts of CIRGen use
+// getCachedEHDispatchBlock to infer state.
+mlir::Block *
+CIRGenFunction::getEHDispatchBlock(EHScopeStack::stable_iterator scope,
+ cir::TryOp tryOp) {
+ if (EHPersonality::get(*this).usesFuncletPads()) {
+ cgm.errorNYI("getEHDispatchBlock: usesFuncletPads");
+ return {};
+ }
+
+ // Otherwise, we should look at the actual scope.
+ EHScope &ehScope = *ehStack.find(scope);
+ bool mayThrow = ehScope.mayThrow();
+
+ mlir::Block *originalBlock = nullptr;
+ if (mayThrow && tryOp) {
+ // If the dispatch is cached but comes from a different tryOp, make sure:
+ // - Populate current `tryOp` with a new dispatch block regardless.
+ // - Update the map to enqueue new dispatchBlock to also get a cleanup. See
+ // code at the end of the function.
+ cgm.errorNYI("getEHDispatchBlock: mayThrow & tryOp");
+ return {};
+ }
+
+ if (!mayThrow) {
+ switch (ehScope.getKind()) {
+ case EHScope::Catch: {
+ // 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 {};
+ }
+ case EHScope::Cleanup: {
+ cgm.errorNYI("getEHDispatchBlock: mayThrow & cleanup");
+ return {};
+ }
+ case EHScope::Filter: {
+ cgm.errorNYI("getEHDispatchBlock: mayThrow & Filter");
+ return {};
+ }
+ case EHScope::Terminate: {
+ cgm.errorNYI("getEHDispatchBlock: mayThrow & Terminate");
+ return {};
+ }
+ }
+ }
+
+ if (originalBlock) {
+ cgm.errorNYI("getEHDispatchBlock: originalBlock");
+ return {};
+ }
+
+ ehScope.setMayThrow(mayThrow);
+ return {};
+}
+
+bool CIRGenFunction::isInvokeDest() {
+ if (!ehStack.requiresLandingPad())
+ return false;
+
+ // If exceptions are disabled/ignored and SEH is not in use, then there is no
+ // invoke destination. SEH "works" even if exceptions are off. In practice,
+ // this means that C++ destructors and other EH cleanups don't run, which is
+ // consistent with MSVC's behavior, except in the presence of -EHa
+ const LangOptions &lo = cgm.getLangOpts();
+ if (!lo.Exceptions || lo.IgnoreExceptions) {
+ cgm.errorNYI("isInvokeDest: no exceptions or ignore exception");
+ return false;
+ }
+
+ // CUDA device code doesn't have exceptions.
+ if (lo.CUDA && lo.CUDAIsDevice)
+ return false;
+
+ return true;
+}
+
+mlir::Operation *CIRGenFunction::getInvokeDestImpl(cir::TryOp tryOp) {
+ assert(ehStack.requiresLandingPad());
+ assert(!ehStack.empty());
+
+ // TODO(cir): add personality function
+
+ // CIR does not cache landing pads.
+ const EHPersonality &personality = EHPersonality::get(*this);
+
+ mlir::Operation *lp = nullptr;
+ if (personality.usesFuncletPads()) {
+ cgm.errorNYI("getInvokeDestImpl: usesFuncletPads");
+ } else {
+ lp = emitLandingPad(tryOp);
+ }
+
+ return lp;
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
index 5d5209b9ffb60..7422940fe4b2b 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -412,6 +412,16 @@ void CIRGenFunction::LexicalScope::emitImplicitReturn() {
(void)emitReturn(localScope->endLoc);
}
+cir::TryOp CIRGenFunction::LexicalScope::getClosestTryParent() {
+ LexicalScope *scope = this;
+ while (scope) {
+ if (scope->isTry())
+ return scope->getTry();
+ scope = scope->parentScope;
+ }
+ return nullptr;
+}
+
void CIRGenFunction::startFunction(GlobalDecl gd, QualType returnType,
cir::FuncOp fn, cir::FuncType funcType,
FunctionArgList args, SourceLocation loc,
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 1c52a78d72e33..82b5405abb633 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -916,10 +916,23 @@ class CIRGenFunction : public CIRGenTypeCache {
return false;
}
+ mlir::Block *getEHDispatchBlock(EHScopeStack::stable_iterator scope,
+ cir::TryOp tryOp);
+
/// The cleanup depth enclosing all the cleanups associated with the
/// parameters.
EHScopeStack::stable_iterator prologueCleanupDepth;
+ mlir::Operation *getInvokeDestImpl(cir::TryOp tryOp);
+ mlir::Operation *getInvokeDest(cir::TryOp tryOp) {
+ if (!ehStack.requiresLandingPad())
+ return nullptr;
+ // Return the respective cir.try, this can be used to compute
+ // any other relevant information.
+ return getInvokeDestImpl(tryOp);
+ }
+ bool isInvokeDest();
+
/// Takes the old cleanup stack size and emits the cleanup blocks
/// that have been added.
void popCleanupBlocks(EHScopeStack::stable_iterator oldCleanupStackDepth);
@@ -1063,7 +1076,7 @@ class CIRGenFunction : public CIRGenTypeCache {
bool isSwitch() { return scopeKind == Kind::Switch; }
bool isTernary() { return scopeKind == Kind::Ternary; }
bool isTry() { return scopeKind == Kind::Try; }
-
+ cir::TryOp getClosestTryParent();
void setAsGlobalInit() { scopeKind = Kind::GlobalInit; }
void setAsSwitch() { scopeKind = Kind::Switch; }
void setAsTernary() { scopeKind = Kind::Ternary; }
@@ -1591,6 +1604,8 @@ class CIRGenFunction : public CIRGenTypeCache {
void emitLambdaDelegatingInvokeBody(const CXXMethodDecl *md);
void emitLambdaStaticInvokeBody(const CXXMethodDecl *md);
+ mlir::Operation *emitLandingPad(cir::TryOp tryOp);
+
mlir::LogicalResult emitIfStmt(const clang::IfStmt &s);
/// Emit code to compute the specified expression,
diff --git a/clang/lib/CIR/CodeGen/EHScopeStack.h b/clang/lib/CIR/CodeGen/EHScopeStack.h
index 9005b0106b2a4..699ef0b799c37 100644
--- a/clang/lib/CIR/CodeGen/EHScopeStack.h
+++ b/clang/lib/CIR/CodeGen/EHScopeStack.h
@@ -217,6 +217,8 @@ class EHScopeStack {
/// Determines whether the exception-scopes stack is empty.
bool empty() const { return startOfData == endOfBuffer; }
+ bool requiresLandingPad() const;
+
/// Determines whether there are any normal cleanups on the stack.
bool hasNormalCleanups() const {
return innermostNormalCleanup != stable_end();
diff --git a/clang/test/CIR/CodeGen/try-catch-tmp.cpp b/clang/test/CIR/CodeGen/try-catch-tmp.cpp
new file mode 100644
index 0000000000000..078447f844d9a
--- /dev/null
+++ b/clang/test/CIR/CodeGen/try-catch-tmp.cpp
@@ -0,0 +1,44 @@
+// 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 -emit-llvm %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG
+
+int division();
+
+void calling_division_inside_try_block() {
+ try {
+ division();
+ } catch (...) {
+ }
+}
+
+// CIR: cir.scope {
+// CIR: cir.try {
+// CIR: %[[CALL:.*]] = cir.call @_Z8divisionv() : () -> !s32i
+// CIR: cir.yield
+// CIR: } catch all {
+// CIR: cir.yield
+// CIR: }
+// CIR: }
+
+// OGCG: %[[EXN_OBJ_ADDR:.*]] = alloca ptr, align 8
+// OGCG: %[[EH_SELECTOR_ADDR:.*]] = alloca i32, align 4
+// OGCG: %[[CALL:.*]] = invoke noundef i32 @_Z8divisionv()
+// OGCG: to label %[[INVOKE_CONT:.*]] unwind label %[[LANDING_PAD:.*]]
+// OGCG: [[INVOKE_CONT]]:
+// OGCG: br label %[[TRY_CONT:.*]]
+// OGCG: [[LANDING_PAD]]:
+// OGCG: %[[LP:.*]] = landingpad { ptr, i32 }
+// OGCG: catch ptr null
+// OGCG: %[[EXN_OBJ:.*]] = extractvalue { ptr, i32 } %[[LP]], 0
+// OGCG: store ptr %[[EXN_OBJ]], ptr %[[EXN_OBJ_ADDR]], align 8
+// OGCG: %[[EH_SELECTOR_VAL:.*]] = extractvalue { ptr, i32 } %[[LP]], 1
+// OGCG: store i32 %[[EH_SELECTOR_VAL]], ptr %[[EH_SELECTOR_ADDR]], align 4
+// OGCG: br label %[[CATCH:.*]]
+// OGCG: [[CATCH]]:
+// OGCG: %[[EXN_OBJ:.*]] = load ptr, ptr %[[EXN_OBJ_ADDR]], align 8
+// OGCG: %[[CATCH_BEGIN:.*]] = call ptr @__cxa_begin_catch(ptr %[[EXN_OBJ]])
+// OGCG: call void @__cxa_end_catch()
+// OGCG: br label %[[TRY_CONT]]
+// OGCG: [[TRY_CONT]]:
+// OGCG: ret void
>From 43bbdcec3cbec61fdd74da3fde73ab5186108337 Mon Sep 17 00:00:00 2001
From: Amr Hesham <amr96 at programmer.net>
Date: Wed, 5 Nov 2025 21:57:33 +0100
Subject: [PATCH 2/2] Address code review comments
---
.../CIR/Dialect/Builder/CIRBaseBuilder.h | 19 ----
clang/lib/CIR/CodeGen/CIRGenCall.cpp | 17 ++--
clang/lib/CIR/CodeGen/CIRGenCleanup.cpp | 2 +-
clang/lib/CIR/CodeGen/CIRGenCleanup.h | 6 +-
clang/lib/CIR/CodeGen/CIRGenException.cpp | 95 ++++++-------------
clang/lib/CIR/CodeGen/CIRGenFunction.h | 17 +---
clang/lib/CIR/CodeGen/EHScopeStack.h | 2 +-
7 files changed, 46 insertions(+), 112 deletions(-)
diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
index 3288f5b12c77e..de98495fcd653 100644
--- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
+++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
@@ -389,25 +389,6 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
return createCallOp(loc, callee, cir::VoidType(), operands, attrs);
}
- cir::CallOp createTryCallOp(
- mlir::Location loc, mlir::SymbolRefAttr callee = mlir::SymbolRefAttr(),
- mlir::Type returnType = cir::VoidType(),
- mlir::ValueRange operands = mlir::ValueRange(),
- [[maybe_unused]] cir::SideEffect sideEffect = cir::SideEffect::All) {
- assert(!cir::MissingFeatures::opCallCallConv());
- assert(!cir::MissingFeatures::opCallSideEffect());
- return createCallOp(loc, callee, returnType, operands);
- }
-
- cir::CallOp createTryCallOp(
- mlir::Location loc, cir::FuncOp callee, mlir::ValueRange operands,
- [[maybe_unused]] cir::SideEffect sideEffect = cir::SideEffect::All) {
- assert(!cir::MissingFeatures::opCallCallConv());
- assert(!cir::MissingFeatures::opCallSideEffect());
- return createTryCallOp(loc, mlir::SymbolRefAttr::get(callee),
- callee.getFunctionType().getReturnType(), operands);
- }
-
//===--------------------------------------------------------------------===//
// Cast/Conversion Operators
//===--------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index ea44c65636c6c..d4633448010d4 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -472,11 +472,11 @@ emitCallLikeOp(CIRGenFunction &cgf, mlir::Location callLoc,
assert(!cir::MissingFeatures::opCallSurroundingTry());
if (isInvoke) {
- // This call can throw, few options:
- // - If this call does not have an associated cir.try, use the
- // one provided by InvokeDest,
- // - User written try/catch clauses require calls to handle
- // exceptions under cir.try.
+ // This call may throw and requires catch and/or cleanup handling.
+ // If this call does not appear within the `try` region of an existing
+ // TryOp, we must create a synthetic TryOp to contain the call. This
+ // happens when a call that may throw appears within a cleanup
+ // scope.
// In OG, we build the landing pad for this scope. In CIR, we emit a
// synthetic cir.try because this didn't come from code generating from a
@@ -501,10 +501,9 @@ emitCallLikeOp(CIRGenFunction &cgf, mlir::Location callLoc,
}
callOpWithExceptions =
- builder.createTryCallOp(callLoc, directFuncOp, cirCallArgs);
-
- (void)cgf.getInvokeDest(tryOp);
+ builder.createCallOp(callLoc, directFuncOp, cirCallArgs);
+ cgf.populateCatchHandlersIfRequired(tryOp);
return callOpWithExceptions;
}
@@ -668,7 +667,7 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &funcInfo,
// TODO(cir): check for MSVCXXPersonality
// TODO(cir): Create NoThrowAttr
bool cannotThrow = attrs.getNamed("nothrow").has_value();
- bool isInvoke = !cannotThrow && isInvokeDest();
+ bool isInvoke = !cannotThrow && isCatchOrCleanupRequired();
mlir::Location callLoc = loc;
cir::CIRCallOpInterface theCall =
diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
index 3550a78cc1816..9be17ce3f431f 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
@@ -188,7 +188,7 @@ void EHScopeStack::popCleanup() {
}
}
-bool EHScopeStack::requiresLandingPad() const {
+bool EHScopeStack::requiresCatchOrCleanup() const {
for (stable_iterator si = getInnermostEHScope(); si != stable_end();) {
// TODO(cir): Skip lifetime markers.
assert(!cir::MissingFeatures::emitLifetimeMarkers());
diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.h b/clang/lib/CIR/CodeGen/CIRGenCleanup.h
index 4e4e913574991..eec33aa5ad8d2 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCleanup.h
+++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.h
@@ -38,7 +38,7 @@ class EHScope {
};
enum { NumCommonBits = 3 };
- bool isScopeMayThrow;
+ bool scopeMayThrow;
protected:
class CatchBitFields {
@@ -94,10 +94,10 @@ class EHScope {
// 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`.
- return isScopeMayThrow;
+ return scopeMayThrow;
}
- void setMayThrow(bool mayThrow) { isScopeMayThrow = mayThrow; }
+ void setMayThrow(bool mayThrow) { scopeMayThrow = mayThrow; }
EHScopeStack::stable_iterator getEnclosingEHScope() const {
return enclosingEHScope;
diff --git a/clang/lib/CIR/CodeGen/CIRGenException.cpp b/clang/lib/CIR/CodeGen/CIRGenException.cpp
index 700e5e0c67c45..d5fb16ea58797 100644
--- a/clang/lib/CIR/CodeGen/CIRGenException.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenException.cpp
@@ -355,33 +355,6 @@ void CIRGenFunction::enterCXXTryStmt(const CXXTryStmt &s, cir::TryOp tryOp,
}
}
-/// 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: WASM personality");
- return;
- }
-
- if (EHPersonality::get(cgf).usesFuncletPads()) {
- cgf.cgm.errorNYI("emitCatchDispatchBlock: usesFuncletPads");
- return;
- }
-
- assert(catchScope.mayThrow() &&
- "Expected catchScope that may throw exception");
-
- // If there's only a single catch-all, getEHDispatchBlock returned
- // that catch-all as the dispatch block.
- if (catchScope.getNumHandlers() == 1 &&
- catchScope.getHandler(0).isCatchAll()) {
- return;
- }
-
- cgf.cgm.errorNYI("emitCatchDispatchBlock: non-catch all handler");
-}
-
void CIRGenFunction::exitCXXTryStmt(const CXXTryStmt &s, bool isFnTryBlock) {
unsigned numHandlers = s.getNumHandlers();
EHCatchScope &catchScope = cast<EHCatchScope>(*ehStack.begin());
@@ -410,9 +383,6 @@ 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, 8> handlers(
@@ -490,8 +460,8 @@ void CIRGenFunction::exitCXXTryStmt(const CXXTryStmt &s, bool isFnTryBlock) {
assert(!cir::MissingFeatures::incrementProfileCounter());
}
-mlir::Operation *CIRGenFunction::emitLandingPad(cir::TryOp tryOp) {
- assert(ehStack.requiresLandingPad());
+void CIRGenFunction::populateCatchHandlers(cir::TryOp tryOp) {
+ assert(ehStack.requiresCatchOrCleanup());
assert(!cgm.getLangOpts().IgnoreExceptions &&
"LandingPad should not be emitted when -fignore-exceptions are in "
"effect.");
@@ -500,7 +470,7 @@ mlir::Operation *CIRGenFunction::emitLandingPad(cir::TryOp tryOp) {
switch (innermostEHScope.getKind()) {
case EHScope::Terminate:
cgm.errorNYI("emitLandingPad: terminate");
- return {};
+ return;
case EHScope::Catch:
case EHScope::Cleanup:
@@ -523,17 +493,17 @@ mlir::Operation *CIRGenFunction::emitLandingPad(cir::TryOp tryOp) {
switch (i->getKind()) {
case EHScope::Cleanup: {
cgm.errorNYI("emitLandingPad: Cleanup");
- return {};
+ return;
}
case EHScope::Filter: {
cgm.errorNYI("emitLandingPad: Filter");
- return {};
+ return;
}
case EHScope::Terminate: {
cgm.errorNYI("emitLandingPad: Terminate");
- return {};
+ return;
}
case EHScope::Catch:
@@ -551,22 +521,22 @@ mlir::Operation *CIRGenFunction::emitLandingPad(cir::TryOp tryOp) {
if (handler.isCatchAll()) {
assert(!hasCatchAll);
hasCatchAll = true;
- goto done;
+ break;
}
cgm.errorNYI("emitLandingPad: non catch-all");
- return {};
+ return;
}
- goto done;
+ if (hasCatchAll)
+ break;
}
- done:
if (hasCatchAll) {
handlerAttrs.push_back(cir::CatchAllAttr::get(&getMLIRContext()));
} else {
cgm.errorNYI("emitLandingPad: non catch-all");
- return {};
+ return;
}
// Add final array of clauses into TryOp.
@@ -576,23 +546,19 @@ mlir::Operation *CIRGenFunction::emitLandingPad(cir::TryOp tryOp) {
// In traditional LLVM codegen. this tells the backend how to generate the
// landing pad by generating a branch to the dispatch block. In CIR,
- // getEHDispatchBlock is used to populate blocks for later filing during
+ // this is used to populate blocks for later filing during
// cleanup handling.
- (void)getEHDispatchBlock(ehStack.getInnermostEHScope(), tryOp);
-
- return tryOp;
+ populateEHCatchRegions(ehStack.getInnermostEHScope(), tryOp);
}
// Differently from LLVM traditional codegen, there are no dispatch blocks
// to look at given cir.try_call does not jump to blocks like invoke does.
-// However, we keep this around since other parts of CIRGen use
-// getCachedEHDispatchBlock to infer state.
-mlir::Block *
-CIRGenFunction::getEHDispatchBlock(EHScopeStack::stable_iterator scope,
- cir::TryOp tryOp) {
+// However.
+void CIRGenFunction::populateEHCatchRegions(EHScopeStack::stable_iterator scope,
+ cir::TryOp tryOp) {
if (EHPersonality::get(*this).usesFuncletPads()) {
cgm.errorNYI("getEHDispatchBlock: usesFuncletPads");
- return {};
+ return;
}
// Otherwise, we should look at the actual scope.
@@ -606,7 +572,7 @@ CIRGenFunction::getEHDispatchBlock(EHScopeStack::stable_iterator scope,
// - Update the map to enqueue new dispatchBlock to also get a cleanup. See
// code at the end of the function.
cgm.errorNYI("getEHDispatchBlock: mayThrow & tryOp");
- return {};
+ return;
}
if (!mayThrow) {
@@ -621,34 +587,33 @@ CIRGenFunction::getEHDispatchBlock(EHScopeStack::stable_iterator scope,
break;
}
cgm.errorNYI("getEHDispatchBlock: mayThrow non-catch all");
- return {};
+ return;
}
case EHScope::Cleanup: {
cgm.errorNYI("getEHDispatchBlock: mayThrow & cleanup");
- return {};
+ return;
}
case EHScope::Filter: {
cgm.errorNYI("getEHDispatchBlock: mayThrow & Filter");
- return {};
+ return;
}
case EHScope::Terminate: {
cgm.errorNYI("getEHDispatchBlock: mayThrow & Terminate");
- return {};
+ return;
}
}
}
if (originalBlock) {
cgm.errorNYI("getEHDispatchBlock: originalBlock");
- return {};
+ return;
}
ehScope.setMayThrow(mayThrow);
- return {};
}
-bool CIRGenFunction::isInvokeDest() {
- if (!ehStack.requiresLandingPad())
+bool CIRGenFunction::isCatchOrCleanupRequired() {
+ if (!ehStack.requiresCatchOrCleanup())
return false;
// If exceptions are disabled/ignored and SEH is not in use, then there is no
@@ -668,21 +633,17 @@ bool CIRGenFunction::isInvokeDest() {
return true;
}
-mlir::Operation *CIRGenFunction::getInvokeDestImpl(cir::TryOp tryOp) {
- assert(ehStack.requiresLandingPad());
+void CIRGenFunction::populateCatchHandlersIfRequired(cir::TryOp tryOp) {
+ assert(ehStack.requiresCatchOrCleanup());
assert(!ehStack.empty());
// TODO(cir): add personality function
// CIR does not cache landing pads.
const EHPersonality &personality = EHPersonality::get(*this);
-
- mlir::Operation *lp = nullptr;
if (personality.usesFuncletPads()) {
cgm.errorNYI("getInvokeDestImpl: usesFuncletPads");
} else {
- lp = emitLandingPad(tryOp);
+ populateCatchHandlers(tryOp);
}
-
- return lp;
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 82b5405abb633..86cdf2f0ce50b 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -916,22 +916,15 @@ class CIRGenFunction : public CIRGenTypeCache {
return false;
}
- mlir::Block *getEHDispatchBlock(EHScopeStack::stable_iterator scope,
- cir::TryOp tryOp);
+ void populateEHCatchRegions(EHScopeStack::stable_iterator scope,
+ cir::TryOp tryOp);
/// The cleanup depth enclosing all the cleanups associated with the
/// parameters.
EHScopeStack::stable_iterator prologueCleanupDepth;
- mlir::Operation *getInvokeDestImpl(cir::TryOp tryOp);
- mlir::Operation *getInvokeDest(cir::TryOp tryOp) {
- if (!ehStack.requiresLandingPad())
- return nullptr;
- // Return the respective cir.try, this can be used to compute
- // any other relevant information.
- return getInvokeDestImpl(tryOp);
- }
- bool isInvokeDest();
+ bool isCatchOrCleanupRequired();
+ void populateCatchHandlersIfRequired(cir::TryOp tryOp);
/// Takes the old cleanup stack size and emits the cleanup blocks
/// that have been added.
@@ -1604,7 +1597,7 @@ class CIRGenFunction : public CIRGenTypeCache {
void emitLambdaDelegatingInvokeBody(const CXXMethodDecl *md);
void emitLambdaStaticInvokeBody(const CXXMethodDecl *md);
- mlir::Operation *emitLandingPad(cir::TryOp tryOp);
+ void populateCatchHandlers(cir::TryOp tryOp);
mlir::LogicalResult emitIfStmt(const clang::IfStmt &s);
diff --git a/clang/lib/CIR/CodeGen/EHScopeStack.h b/clang/lib/CIR/CodeGen/EHScopeStack.h
index 699ef0b799c37..6455bd8a55c83 100644
--- a/clang/lib/CIR/CodeGen/EHScopeStack.h
+++ b/clang/lib/CIR/CodeGen/EHScopeStack.h
@@ -217,7 +217,7 @@ class EHScopeStack {
/// Determines whether the exception-scopes stack is empty.
bool empty() const { return startOfData == endOfBuffer; }
- bool requiresLandingPad() const;
+ bool requiresCatchOrCleanup() const;
/// Determines whether there are any normal cleanups on the stack.
bool hasNormalCleanups() const {
More information about the cfe-commits
mailing list