https://github.com/AmrDeveloper updated https://github.com/llvm/llvm-project/pull/165158
>From c4ead2dc0edd6698a6a661133dc633bd3b6a771e Mon Sep 17 00:00:00 2001 From: Amr Hesham <[email protected]> Date: Sat, 25 Oct 2025 21:17:00 +0200 Subject: [PATCH] [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 e5cecaa573a6e..4d01f4adfa053 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; } @@ -1587,6 +1600,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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
