https://github.com/erichkeane created https://github.com/llvm/llvm-project/pull/190613
C++ supports the ability to have the 'body' of a constructor be a 'try' block. The effect of this is that base construction and initializers are also within the same try/catch chain. This patch implements this lowering. Classic codegen does this by just emitting the 'begin' and 'end' try as their own functions, then can emit the body separately between calls. However, CIR doesn't really afford us that ability, so this patch extracts a function that takes a callback object that is used to emit the function body. This allows us to do a simple 'emitStmt' in the normal cases, plus a `emitCtorPrologue` followed by `emitStmt` in the try-body case. >From d0750b0e4c5863ccc001e100340845599972d567 Mon Sep 17 00:00:00 2001 From: erichkeane <[email protected]> Date: Fri, 3 Apr 2026 13:46:05 -0700 Subject: [PATCH] [CIR] Implement constructor 'try-body' lowering C++ supports the ability to have the 'body' of a constructor be a 'try' block. The effect of this is that base construction and initializers are also within the same try/catch chain. This patch implements this lowering. Classic codegen does this by just emitting the 'begin' and 'end' try as their own functions, then can emit the body separately between calls. However, CIR doesn't really afford us that ability, so this patch extracts a function that takes a callback object that is used to emit the function body. This allows us to do a simple 'emitStmt' in the normal cases, plus a `emitCtorPrologue` followed by `emitStmt` in the try-body case. --- clang/lib/CIR/CodeGen/CIRGenException.cpp | 30 +++++++-- clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 51 ++++++++++----- clang/lib/CIR/CodeGen/CIRGenFunction.h | 7 +++ clang/test/CIR/CodeGen/ctor-try-body.cpp | 77 +++++++++++++++++++++++ 4 files changed, 142 insertions(+), 23 deletions(-) create mode 100644 clang/test/CIR/CodeGen/ctor-try-body.cpp diff --git a/clang/lib/CIR/CodeGen/CIRGenException.cpp b/clang/lib/CIR/CodeGen/CIRGenException.cpp index d5e176e18a11e..c80f292553a1c 100644 --- a/clang/lib/CIR/CodeGen/CIRGenException.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenException.cpp @@ -273,13 +273,12 @@ void CIRGenFunction::addCatchHandlerAttr( } } -mlir::LogicalResult CIRGenFunction::emitCXXTryStmt(const CXXTryStmt &s) { - if (s.getTryBlock()->body_empty()) - return mlir::LogicalResult::success(); - +mlir::LogicalResult +CIRGenFunction::emitCXXTryStmt(const CXXTryStmt &s, + cxxTryBodyEmitter &bodyCallback) { mlir::Location loc = getLoc(s.getSourceRange()); - // Create a scope to hold try local storage for catch params. + // Create a scope to hold try local storage for catch params. mlir::OpBuilder::InsertPoint scopeIP; cir::ScopeOp::create( builder, loc, @@ -326,7 +325,7 @@ mlir::LogicalResult CIRGenFunction::emitCXXTryStmt(const CXXTryStmt &s) { // are created for statements within the try body before exiting the // try body. RunCleanupsScope tryBodyCleanups(*this); - if (emitStmt(s.getTryBlock(), /*useCurrentScope=*/true).failed()) + if (bodyCallback(*this).failed()) tryRes = mlir::failure(); tryBodyCleanups.forceCleanup(); cir::YieldOp::create(builder, loc); @@ -417,6 +416,25 @@ mlir::LogicalResult CIRGenFunction::emitCXXTryStmt(const CXXTryStmt &s) { return mlir::success(); } +mlir::LogicalResult CIRGenFunction::emitCXXTryStmt(const CXXTryStmt &s) { + if (s.getTryBlock()->body_empty()) + return mlir::LogicalResult::success(); + + struct simpleTryBodyEmitter final : cxxTryBodyEmitter { + const clang::CXXTryStmt &s; + simpleTryBodyEmitter(const clang::CXXTryStmt &s) : s(s) {} + + mlir::LogicalResult operator()(CIRGenFunction &cgf) override { + return cgf.emitStmt(s.getTryBlock(), /*useCurrentScope=*/true); + } + ~simpleTryBodyEmitter() override = default; + }; + + simpleTryBodyEmitter emitter{s}; + + return emitCXXTryStmt(s, emitter); +} + // in classic codegen this function is mapping to `isInvokeDest` previously and // currently it's mapping to the conditions that performs early returns in // `getInvokeDestImpl`, in CIR we need the condition to know if the EH scope may diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index 58fe699eeff1f..78044cdb97c5d 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -849,27 +849,44 @@ void CIRGenFunction::emitConstructorBody(FunctionArgList &args) { Stmt *body = ctor->getBody(definition); assert(definition == ctor && "emitting wrong constructor body"); - if (isa_and_nonnull<CXXTryStmt>(body)) { - cgm.errorNYI(ctor->getSourceRange(), "emitConstructorBody: try body"); - return; - } - - assert(!cir::MissingFeatures::incrementProfileCounter()); - assert(!cir::MissingFeatures::runCleanupsScope()); - - // TODO: in restricted cases, we can emit the vbase initializers of a - // complete ctor and then delegate to the base ctor. + bool isTryBody = isa_and_nonnull<CXXTryStmt>(body); + + // A type that handles the emission of the constructor body, that can be + // called directly for cases where we don't have a try-body, or passed to + // emitCXXTryStmt. + struct ctorTryBodyEmitter final : cxxTryBodyEmitter { + const CXXConstructorDecl *ctor = nullptr; + CXXCtorType ctorType; + FunctionArgList &args; + Stmt *emitterBody = nullptr; + ctorTryBodyEmitter(const CXXConstructorDecl *ctor, CXXCtorType ctorType, + FunctionArgList &args, bool isTryBody, Stmt *b) + : ctor(ctor), ctorType(ctorType), args(args), + emitterBody(isTryBody ? cast<CXXTryStmt>(b)->getTryBlock() : b) {} + ~ctorTryBodyEmitter() override = default; + + mlir::LogicalResult operator()(CIRGenFunction &cgf) override { + assert(!cir::MissingFeatures::incrementProfileCounter()); + assert(!cir::MissingFeatures::runCleanupsScope()); + + //// TODO: in restricted cases, we can emit the vbase initializers of a + //// complete ctor and then delegate to the base ctor. + + cgf.emitCtorPrologue(ctor, ctorType, args); + return cgf.emitStmt(emitterBody, /*useCurrentScope=*/true); + } + }; - // Emit the constructor prologue, i.e. the base and member initializers. - emitCtorPrologue(ctor, ctorType, args); + ctorTryBodyEmitter emitter{ctor, ctorType, args, isTryBody, body}; + mlir::LogicalResult bodyRes = + isTryBody ? emitCXXTryStmt(*cast<CXXTryStmt>(body), emitter) + : emitter(*this); - // TODO(cir): propagate this result via mlir::logical result. Just unreachable - // now just to have it handled. - if (mlir::failed(emitStmt(body, true))) { + // TODO(cir): propagate this result via mlir::logical result. Just + // unreachable now just to have it handled. + if (bodyRes.failed()) cgm.errorNYI(ctor->getSourceRange(), "emitConstructorBody: emit body statement failed."); - return; - } } /// Emits the body of the current destructor. diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index 7914a03a7f1a8..fd7964ec8e9a1 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -1655,6 +1655,13 @@ class CIRGenFunction : public CIRGenTypeCache { void emitCXXThrowExpr(const CXXThrowExpr *e); + struct cxxTryBodyEmitter { + virtual mlir::LogicalResult operator()(CIRGenFunction &cgf) = 0; + virtual ~cxxTryBodyEmitter() = default; + }; + + mlir::LogicalResult emitCXXTryStmt(const clang::CXXTryStmt &s, + cxxTryBodyEmitter &bodyCallback); mlir::LogicalResult emitCXXTryStmt(const clang::CXXTryStmt &s); void emitCtorPrologue(const clang::CXXConstructorDecl *ctor, diff --git a/clang/test/CIR/CodeGen/ctor-try-body.cpp b/clang/test/CIR/CodeGen/ctor-try-body.cpp new file mode 100644 index 0000000000000..14049e33054a4 --- /dev/null +++ b/clang/test/CIR/CodeGen/ctor-try-body.cpp @@ -0,0 +1,77 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefix=LLVM,LLVMCIR +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -emit-llvm %s -o %t.ll +// RUN: FileCheck --input-file=%t.ll %s --check-prefix=LLVM,OGCG + +struct Ctor { + Ctor(); +}; + +struct FromCtor { + FromCtor(const Ctor&); +}; + +void side_effect(); +void side_effect2(); + +struct Base { + Base(); +}; + +struct HasThings : Base { + FromCtor ct; + + HasThings(const Ctor &c) try : ct(c) { side_effect(); } catch (...) { + side_effect2(); + } + +// CIR: cir.func {{.*}}@_ZN9HasThingsC2ERK4Ctor(%[[THIS_ARG:.*]]: !cir.ptr<!rec_HasThings> {{.*}}, %[[C_ARG:.*]]: !cir.ptr<!rec_Ctor> {{.*}}) {{.*}}special_member<#cir.cxx_ctor<!rec_HasThings, custom>> { +// CIR-NEXT: %[[THIS_ALLOC:.*]] = cir.alloca !cir.ptr<!rec_HasThings>, !cir.ptr<!cir.ptr<!rec_HasThings>>, ["this", init] +// CIR-NEXT: %[[C_ALLOC:.*]] = cir.alloca !cir.ptr<!rec_Ctor>, !cir.ptr<!cir.ptr<!rec_Ctor>>, ["c", init, const] +// CIR-NEXT: cir.store %[[THIS_ARG]], %[[THIS_ALLOC]] : !cir.ptr<!rec_HasThings>, !cir.ptr<!cir.ptr<!rec_HasThings>> +// CIR-NEXT: cir.store %[[C_ARG]], %[[C_ALLOC]] : !cir.ptr<!rec_Ctor>, !cir.ptr<!cir.ptr<!rec_Ctor>> +// CIR-NEXT: %[[THIS_LOAD:.*]] = cir.load %[[THIS_ALLOC]] : !cir.ptr<!cir.ptr<!rec_HasThings>>, !cir.ptr<!rec_HasThings> +// CIR-NEXT: cir.scope { +// CIR-NEXT: cir.try { +// CIR-NEXT: %[[BASE_ADDR:.*]] = cir.base_class_addr %[[THIS_LOAD]] : !cir.ptr<!rec_HasThings> nonnull [0] -> !cir.ptr<!rec_Base> +// CIR-NEXT: cir.call @_ZN4BaseC2Ev(%[[BASE_ADDR]]) : (!cir.ptr<!rec_Base>{{.*}}) -> () +// CIR-NEXT: %[[FROMCTOR_ADDR:.*]] = cir.cast bitcast %[[THIS_LOAD]] : !cir.ptr<!rec_HasThings> -> !cir.ptr<!rec_FromCtor> +// CIR-NEXT: %[[C_LOAD:.*]] = cir.load %[[C_ALLOC]] : !cir.ptr<!cir.ptr<!rec_Ctor>>, !cir.ptr<!rec_Ctor> +// CIR-NEXT: cir.call @_ZN8FromCtorC1ERK4Ctor(%[[FROMCTOR_ADDR]], %[[C_LOAD]]) : (!cir.ptr<!rec_FromCtor> {{.*}}, !cir.ptr<!rec_Ctor> {{.*}}) -> () +// CIR-NEXT: cir.call @_Z11side_effectv() : () -> () +// CIR-NEXT: cir.yield +// CIR-NEXT: } catch all (%[[CATCH_ARG:.*]]: !cir.eh_token {{.*}}) { +// CIR-NEXT: %[[CATCH_TOK:.*]], %[[EX_PTR:.*]] = cir.begin_catch %[[CATCH_ARG]] : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!void>) +// CIR-NEXT: cir.cleanup.scope { +// CIR-NEXT: cir.call @_Z12side_effect2v() : () -> () +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup all { +// CIR-NEXT: cir.end_catch %[[CATCH_TOK]] : !cir.catch_token +// CIR-NEXT: cir.yield +// CIR-NEXT: } +// CIR-NEXT: cir.yield +// CIR-NEXT: } +// CIR-NEXT: } +// CIR-NEXT: cir.return +// CIR-NEXT:} + +// Note: This skips a LOT of lines, but otherwise dives into an absolutely +// 'normal' try/catch/etc block, which both differs between LLVM and OGCG, but +// isn't particularly relevant to the fact that we generate the base, +// initializers, and body all in a try block. +// LLVM: define linkonce_odr void @_ZN9HasThingsC2ERK4Ctor(ptr {{.*}} %[[THIS_ARG:.*]], ptr {{.*}} %[[C_ARG:.*]]) +// LLVM: invoke void @_ZN4BaseC2Ev(ptr {{.*}}%{{.*}}) +// LLVM: invoke void @_ZN8FromCtorC1ERK4Ctor(ptr {{.*}}%{{.*}}, ptr {{.*}}%{{.*}}) +// LLVM: invoke void @_Z11side_effectv() +// LLVM: call ptr @__cxa_begin_catch(ptr %{{.*}}) +// LLVM: invoke void @_Z12side_effect2v() +// LLVMCIR: call void @__cxa_end_catch() +// OGCG: invoke void @__cxa_end_catch() +}; + +void foo() { + Ctor ct; + HasThings ht(ct); +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
