https://github.com/adams381 updated https://github.com/llvm/llvm-project/pull/171915
>From 517a1a251c8a5ec4422a2c63e6f779642dd5d160 Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Thu, 11 Dec 2025 13:48:23 -0800 Subject: [PATCH 1/9] [CIR] Add emitDeclInvariant for global with constant storage Implement emitDeclInvariant to emit llvm.invariant.start intrinsic for global variables with constant storage. This enables optimizations by marking when a global becomes read-only after initialization. Changes: - Add emitDeclInvariant and emitInvariantStart functions in CIRGenCXX.cpp - Add emitInvariantStart declaration in CIRGenFunction.h - Update emitCXXGlobalVarDeclInit to call emitDeclInvariant for constant storage globals after initialization - Update getOrCreateCIRGlobal to set constant flag on globals with constant storage - Add comprehensive test covering positive and negative cases The implementation handles address spaces correctly, dynamically constructing the intrinsic name (e.g., invariant.start.p0, invariant.start.p10) based on the pointer's address space. --- clang/lib/CIR/CodeGen/CIRGenCXX.cpp | 72 +++++- clang/lib/CIR/CodeGen/CIRGenFunction.h | 2 + clang/lib/CIR/CodeGen/CIRGenModule.cpp | 25 +- .../CIR/CodeGen/global-constant-storage.cpp | 244 ++++++++++++++++++ 4 files changed, 330 insertions(+), 13 deletions(-) create mode 100644 clang/test/CIR/CodeGen/global-constant-storage.cpp diff --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp index 71568ec87a31b..c0890f6b42663 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp @@ -16,11 +16,54 @@ #include "clang/AST/GlobalDecl.h" #include "clang/CIR/MissingFeatures.h" +#include "llvm/ADT/SmallString.h" #include "llvm/Support/SaveAndRestore.h" +#include "llvm/Support/raw_ostream.h" using namespace clang; using namespace clang::CIRGen; +/// Emit code to cause the variable at the given address to be considered as +/// constant from this point onwards. +static void emitDeclInvariant(CIRGenFunction &CGF, const VarDecl *D) { + mlir::Value addr = CGF.cgm.getAddrOfGlobalVar(D); + CGF.emitInvariantStart(CGF.getContext().getTypeSizeInChars(D->getType()), + addr, CGF.getLoc(D->getSourceRange())); +} + +void CIRGenFunction::emitInvariantStart(CharUnits Size, mlir::Value Addr, + mlir::Location loc) { + // Do not emit the intrinsic if we're not optimizing. + if (!cgm.getCodeGenOpts().OptimizationLevel) + return; + + CIRGenBuilderTy &builder = getBuilder(); + + // Create the size constant as i64 + uint64_t width = Size.getQuantity(); + mlir::Value sizeValue = builder.getConstInt(loc, builder.getSInt64Ty(), + static_cast<int64_t>(width)); + + // Determine address space for intrinsic name + unsigned addrSpace = 0; + if (auto ptrTy = mlir::dyn_cast<cir::PointerType>(Addr.getType())) + addrSpace = + ptrTy.getAddrSpace() ? ptrTy.getAddrSpace().getValue().getUInt() : 0; + + // Format intrinsic name with address space suffix (e.g., + // "invariant.start.p0", "invariant.start.p10") + llvm::SmallString<32> intrinsicName; + llvm::raw_svector_ostream os(intrinsicName); + os << "invariant.start.p" << addrSpace; + + // Create the intrinsic call. The llvm.invariant.start intrinsic returns a + // token, but we don't need to capture it. The return type is set to match + // the address type for consistency with the operation signature. + cir::LLVMIntrinsicCallOp::create( + builder, loc, builder.getStringAttr(intrinsicName), Addr.getType(), + mlir::ValueRange{sizeValue, Addr}); +} + static void emitDeclInit(CIRGenFunction &cgf, const VarDecl *varDecl, cir::GlobalOp globalOp) { assert((varDecl->hasGlobalStorage() || @@ -234,13 +277,32 @@ void CIRGenModule::emitCXXGlobalVarDeclInit(const VarDecl *varDecl, bool needsDtor = varDecl->needsDestruction(getASTContext()) == QualType::DK_cxx_destructor; + bool isConstantStorage = + varDecl->getType().isConstantStorage(getASTContext(), true, !needsDtor); // PerformInit, constant store invariant / destroy handled below. - if (performInit) + if (performInit) { emitDeclInit(cgf, varDecl, addr); - - if (varDecl->getType().isConstantStorage(getASTContext(), true, !needsDtor)) - errorNYI(varDecl->getSourceRange(), "global with constant storage"); - else + // For constant storage, emit invariant.start in the ctor region after + // initialization but before the yield. + if (isConstantStorage) { + CIRGenBuilderTy &builder = cgf.getBuilder(); + mlir::OpBuilder::InsertionGuard guard(builder); + // Set insertion point to end of ctor region (before yield) + if (!addr.getCtorRegion().empty()) { + mlir::Block *block = &addr.getCtorRegion().back(); + // Find the yield op and insert before it + mlir::Operation *yieldOp = block->getTerminator(); + if (yieldOp) { + builder.setInsertionPoint(yieldOp); + emitDeclInvariant(cgf, varDecl); + } + } + } + } else if (isConstantStorage) { + emitDeclInvariant(cgf, varDecl); + } + + if (!isConstantStorage) emitDeclDestroy(cgf, varDecl, addr); return; } diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index efe0fe5fcc979..627698c5b19af 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -1611,6 +1611,8 @@ class CIRGenFunction : public CIRGenTypeCache { mlir::Value emitRuntimeCall(mlir::Location loc, cir::FuncOp callee, llvm::ArrayRef<mlir::Value> args = {}); + void emitInvariantStart(CharUnits Size, mlir::Value Addr, mlir::Location loc); + /// Emit the computation of the specified expression of scalar type. mlir::Value emitScalarExpr(const clang::Expr *e, bool ignoreResultAssign = false); diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index 1ad1c2fa41aa1..715e877fcca3d 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -652,10 +652,19 @@ CIRGenModule::getOrCreateCIRGlobal(StringRef mangledName, mlir::Type ty, mlir::Location loc = getLoc(d->getSourceRange()); + // Calculate constant storage flag before creating the global. + bool isConstant = false; + if (d) { + bool needsDtor = + d->needsDestruction(astContext) == QualType::DK_cxx_destructor; + isConstant = d->getType().isConstantStorage( + astContext, /*ExcludeCtor=*/true, /*ExcludeDtor=*/!needsDtor); + } + // mlir::SymbolTable::Visibility::Public is the default, no need to explicitly // mark it as such. cir::GlobalOp gv = - CIRGenModule::createGlobalOp(*this, loc, mangledName, ty, false, + CIRGenModule::createGlobalOp(*this, loc, mangledName, ty, isConstant, /*insertPoint=*/entry.getOperation()); // This is the first use or definition of a mangled name. If there is a @@ -675,10 +684,6 @@ CIRGenModule::getOrCreateCIRGlobal(StringRef mangledName, mlir::Type ty, errorNYI(d->getSourceRange(), "OpenMP target global variable"); gv.setAlignmentAttr(getSize(astContext.getDeclAlign(d))); - // FIXME: This code is overly simple and should be merged with other global - // handling. - gv.setConstant(d->getType().isConstantStorage( - astContext, /*ExcludeCtor=*/false, /*ExcludeDtor=*/false)); setLinkageForGV(gv, d); @@ -870,10 +875,14 @@ void CIRGenModule::emitGlobalVarDefinition(const clang::VarDecl *vd, emitter->finalize(gv); // If it is safe to mark the global 'constant', do so now. + // Use the same logic as emitCXXGlobalVarDeclInit to determine constant + // storage. + bool needsDtor = + vd->needsDestruction(astContext) == QualType::DK_cxx_destructor; gv.setConstant((vd->hasAttr<CUDAConstantAttr>() && langOpts.CUDAIsDevice) || - (!needsGlobalCtor && !needsGlobalDtor && - vd->getType().isConstantStorage( - astContext, /*ExcludeCtor=*/true, /*ExcludeDtor=*/true))); + vd->getType().isConstantStorage(astContext, + /*ExcludeCtor=*/true, + /*ExcludeDtor=*/!needsDtor)); assert(!cir::MissingFeatures::opGlobalSection()); // Set CIR's linkage type as appropriate. diff --git a/clang/test/CIR/CodeGen/global-constant-storage.cpp b/clang/test/CIR/CodeGen/global-constant-storage.cpp new file mode 100644 index 0000000000000..8aa240cf8fc5b --- /dev/null +++ b/clang/test/CIR/CodeGen/global-constant-storage.cpp @@ -0,0 +1,244 @@ +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll +// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm -O1 -disable-llvm-passes %s -o %t-opt.ll +// RUN: FileCheck --check-prefix=LLVM-OPT --input-file=%t-opt.ll %s +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -emit-llvm -O1 -disable-llvm-passes %s -o %t-opt-ogcg.ll +// RUN: FileCheck --check-prefix=OGCG-OPT --input-file=%t-opt-ogcg.ll %s + +// Test for global with constant storage - const object with constructor but no destructor +// Check that we add an llvm.invariant.start to mark when a global becomes read-only. + +struct A { + A(); + int n; +}; + +// Should emit invariant.start - has constructor, no destructor, no mutable +extern const A a = A(); + +struct A2 { + A2(); + constexpr ~A2() {} + int n; +}; + +// Should emit invariant.start - constexpr destructor doesn't prevent constant storage +extern const A2 a2 = A2(); + +struct B { + B(); + mutable int n; +}; + +// Should NOT emit invariant.start - has mutable member +extern const B b = B(); + +struct CWithDtor { + CWithDtor(); + ~CWithDtor(); + int n; +}; + +// Should NOT emit invariant.start - has non-constexpr destructor +extern const CWithDtor c_with_dtor = CWithDtor(); + +// Simple case - just const C c; (no initializer) - Andy's suggestion +class C { +public: + C(); + int a; + int b; +}; + +const C c; + +// CIR checks for 'a' - should have constant storage +// CIR: cir.global constant external @a = #cir.zero : !rec_A +// CIR: cir.func internal private @__cxx_global_var_init() { +// CIR: %[[OBJ:.*]] = cir.get_global @a : !cir.ptr<!rec_A> +// CIR: cir.call @_ZN1AC1Ev(%[[OBJ]]) : (!cir.ptr<!rec_A>) -> () +// CIR: cir.return +// CIR: } + +// CIR checks for 'a2' - should have constant storage (constexpr dtor) +// CIR: cir.global constant external @a2 = #cir.zero : !rec_A2 +// CIR: cir.func internal private @__cxx_global_var_init.1() { +// CIR: %[[OBJ:.*]] = cir.get_global @a2 : !cir.ptr<!rec_A2> +// CIR: cir.call @_ZN2A2C1Ev(%[[OBJ]]) : (!cir.ptr<!rec_A2>) -> () +// CIR: cir.return +// CIR: } + +// CIR checks for 'b' - should NOT have constant storage (mutable member) +// CIR: cir.global external @b = #cir.zero : !rec_B +// CIR: cir.func internal private @__cxx_global_var_init.2() { +// CIR: %[[OBJ:.*]] = cir.get_global @b : !cir.ptr<!rec_B> +// CIR: cir.call @_ZN1BC1Ev(%[[OBJ]]) : (!cir.ptr<!rec_B>) -> () +// CIR: cir.return +// CIR: } + +// CIR checks for 'c_with_dtor' - should NOT have constant storage (non-constexpr dtor) +// CIR: cir.global external @c_with_dtor = #cir.zero : !rec_CWithDtor +// CIR: cir.func internal private @__cxx_global_var_init.3() { +// CIR: %[[OBJ:.*]] = cir.get_global @c_with_dtor : !cir.ptr<!rec_CWithDtor> +// CIR: cir.call @_ZN9CWithDtorC1Ev(%[[OBJ]]) : (!cir.ptr<!rec_CWithDtor>) -> () +// CIR: cir.return +// CIR: } + +// CIR checks for 'c' - Andy's simple case, should have constant storage (internal linkage) +// CIR: cir.global {{.*}} constant internal {{.*}} @_ZL1c = #cir.zero : !rec_C +// CIR: cir.func internal private @__cxx_global_var_init.4() { +// CIR: %[[OBJ:.*]] = cir.get_global @_ZL1c : !cir.ptr<!rec_C> +// CIR: cir.call @_ZN1CC1Ev(%[[OBJ]]) : (!cir.ptr<!rec_C>) -> () +// CIR: cir.return +// CIR: } + +// LLVM checks (no optimization) +// Check all globals first (they appear at the top) +// LLVM: @a ={{.*}} constant {{.*}} zeroinitializer +// LLVM: @a2 ={{.*}} constant {{.*}} zeroinitializer +// LLVM: @b ={{.*}} global {{.*}} zeroinitializer +// LLVM: @c_with_dtor ={{.*}} global {{.*}} zeroinitializer +// LLVM: @_ZL1c ={{.*}} constant {{.*}} zeroinitializer + +// Then check the init functions +// LLVM: define internal void @__cxx_global_var_init() { +// LLVM: call void @_ZN1AC1Ev(ptr @a) +// LLVM: ret void +// LLVM: } + +// LLVM: define internal void @__cxx_global_var_init.1() { +// LLVM: call void @_ZN2A2C1Ev(ptr @a2) +// LLVM: ret void +// LLVM: } + +// LLVM: define internal void @__cxx_global_var_init.2() { +// LLVM: call void @_ZN1BC1Ev(ptr @b) +// LLVM: ret void +// LLVM: } + +// LLVM: define internal void @__cxx_global_var_init.3() { +// LLVM: call void @_ZN9CWithDtorC1Ev(ptr @c_with_dtor) +// LLVM: ret void +// LLVM: } + +// LLVM: define internal void @__cxx_global_var_init.4() { +// LLVM: call void @_ZN1CC1Ev(ptr @_ZL1c) +// LLVM: ret void +// LLVM: } + +// OGCG checks (no optimization) +// Check all globals first (they appear at the top) +// OGCG: @a ={{.*}} global {{.*}} zeroinitializer +// OGCG: @a2 ={{.*}} global {{.*}} zeroinitializer +// OGCG: @b ={{.*}} global {{.*}} zeroinitializer +// OGCG: @c_with_dtor ={{.*}} global {{.*}} zeroinitializer +// OGCG: @_ZL1c ={{.*}} global {{.*}} zeroinitializer + +// Then check the init functions +// OGCG: define internal void @__cxx_global_var_init() {{.*}} section ".text.startup" { +// OGCG: call void @_ZN1AC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @a) +// OGCG: ret void +// OGCG: } + +// OGCG: define internal void @__cxx_global_var_init.1() {{.*}} section ".text.startup" { +// OGCG: call void @_ZN2A2C1Ev(ptr noundef nonnull align 4 dereferenceable(4) @a2) +// OGCG: ret void +// OGCG: } + +// OGCG: define internal void @__cxx_global_var_init.2() {{.*}} section ".text.startup" { +// OGCG: call void @_ZN1BC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @b) +// OGCG: ret void +// OGCG: } + +// OGCG: define internal void @__cxx_global_var_init.3() {{.*}} section ".text.startup" { +// OGCG: call void @_ZN9CWithDtorC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @c_with_dtor) +// OGCG: ret void +// OGCG: } + +// OGCG: define internal void @__cxx_global_var_init.4() {{.*}} section ".text.startup" { +// OGCG: call void @_ZN1CC1Ev(ptr noundef nonnull align 4 dereferenceable(8) @_ZL1c) +// OGCG: ret void +// OGCG: } + +// With optimization enabled, should emit invariant.start intrinsic for constant storage cases +// Check all globals first (they appear at the top) +// LLVM-OPT: @a ={{.*}} constant {{.*}} zeroinitializer +// LLVM-OPT: @a2 ={{.*}} constant {{.*}} zeroinitializer +// LLVM-OPT: @b ={{.*}} global {{.*}} zeroinitializer +// LLVM-OPT: @c_with_dtor ={{.*}} global {{.*}} zeroinitializer +// LLVM-OPT: @_ZL1c ={{.*}} constant {{.*}} zeroinitializer + +// Then check the init functions with invariant.start +// LLVM-OPT: define internal void @__cxx_global_var_init() { +// LLVM-OPT: call void @_ZN1AC1Ev(ptr @a) +// LLVM-OPT: call {{.*}}@llvm.invariant.start.p0(i64 4, ptr @a) +// LLVM-OPT: ret void +// LLVM-OPT: } + +// LLVM-OPT: define internal void @__cxx_global_var_init.1() { +// LLVM-OPT: call void @_ZN2A2C1Ev(ptr @a2) +// LLVM-OPT: call {{.*}}@llvm.invariant.start.p0(i64 4, ptr @a2) +// LLVM-OPT: ret void +// LLVM-OPT: } + +// LLVM-OPT: define internal void @__cxx_global_var_init.2() { +// LLVM-OPT: call void @_ZN1BC1Ev(ptr @b) +// LLVM-OPT-NOT: call {{.*}}@llvm.invariant.start.p0(i64 {{.*}}, ptr @b) +// LLVM-OPT: ret void +// LLVM-OPT: } + +// LLVM-OPT: define internal void @__cxx_global_var_init.3() { +// LLVM-OPT: call void @_ZN9CWithDtorC1Ev(ptr @c_with_dtor) +// LLVM-OPT-NOT: call {{.*}}@llvm.invariant.start.p0(i64 {{.*}}, ptr @c_with_dtor) +// LLVM-OPT: ret void +// LLVM-OPT: } + +// LLVM-OPT: define internal void @__cxx_global_var_init.4() { +// LLVM-OPT: call void @_ZN1CC1Ev(ptr @_ZL1c) +// LLVM-OPT: call {{.*}}@llvm.invariant.start.p0(i64 8, ptr @_ZL1c) +// LLVM-OPT: ret void +// LLVM-OPT: } + +// OGCG-OPT checks (with optimization) +// Check all globals first (they appear at the top) +// OGCG-OPT: @a ={{.*}} global {{.*}} zeroinitializer +// OGCG-OPT: @a2 ={{.*}} global {{.*}} zeroinitializer +// OGCG-OPT: @b ={{.*}} global {{.*}} zeroinitializer +// OGCG-OPT: @c_with_dtor ={{.*}} global {{.*}} zeroinitializer +// OGCG-OPT: @_ZL1c ={{.*}} global {{.*}} zeroinitializer + +// Then check the init functions with invariant.start +// OGCG-OPT: define internal void @__cxx_global_var_init() {{.*}} section ".text.startup" { +// OGCG-OPT: call void @_ZN1AC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @a) +// OGCG-OPT: call {{.*}}@llvm.invariant.start.p0(i64 4, ptr @a) +// OGCG-OPT: ret void +// OGCG-OPT: } + +// OGCG-OPT: define internal void @__cxx_global_var_init.1() {{.*}} section ".text.startup" { +// OGCG-OPT: call void @_ZN2A2C1Ev(ptr noundef nonnull align 4 dereferenceable(4) @a2) +// OGCG-OPT: call {{.*}}@llvm.invariant.start.p0(i64 4, ptr @a2) +// OGCG-OPT: ret void +// OGCG-OPT: } + +// OGCG-OPT: define internal void @__cxx_global_var_init.2() {{.*}} section ".text.startup" { +// OGCG-OPT: call void @_ZN1BC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @b) +// OGCG-OPT-NOT: call {{.*}}@llvm.invariant.start.p0(i64 {{.*}}, ptr @b) +// OGCG-OPT: ret void +// OGCG-OPT: } + +// OGCG-OPT: define internal void @__cxx_global_var_init.3() {{.*}} section ".text.startup" { +// OGCG-OPT: call void @_ZN9CWithDtorC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @c_with_dtor) +// OGCG-OPT-NOT: call {{.*}}@llvm.invariant.start.p0(i64 {{.*}}, ptr @c_with_dtor) +// OGCG-OPT: ret void +// OGCG-OPT: } + +// OGCG-OPT: define internal void @__cxx_global_var_init.4() {{.*}} section ".text.startup" { +// OGCG-OPT: call void @_ZN1CC1Ev(ptr noundef nonnull align 4 dereferenceable(8) @_ZL1c) +// OGCG-OPT: call {{.*}}@llvm.invariant.start.p0(i64 8, ptr @_ZL1c) +// OGCG-OPT: ret void +// OGCG-OPT: } + >From 72b9a9c22c7990e1f1cfa9966a15fb9df6814152 Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Fri, 12 Dec 2025 10:46:08 -0800 Subject: [PATCH 2/9] [CIR] Fix parameter naming in emitDeclInvariant Change parameter names from CGF/D to cgf/d to match LLVM coding standards. Addresses review comments. --- clang/lib/CIR/CodeGen/CIRGenCXX.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp index c0890f6b42663..b61e520de0e4e 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp @@ -25,10 +25,10 @@ using namespace clang::CIRGen; /// Emit code to cause the variable at the given address to be considered as /// constant from this point onwards. -static void emitDeclInvariant(CIRGenFunction &CGF, const VarDecl *D) { - mlir::Value addr = CGF.cgm.getAddrOfGlobalVar(D); - CGF.emitInvariantStart(CGF.getContext().getTypeSizeInChars(D->getType()), - addr, CGF.getLoc(D->getSourceRange())); +static void emitDeclInvariant(CIRGenFunction &cgf, const VarDecl *d) { + mlir::Value addr = cgf.cgm.getAddrOfGlobalVar(d); + cgf.emitInvariantStart(cgf.getContext().getTypeSizeInChars(d->getType()), + addr, cgf.getLoc(d->getSourceRange())); } void CIRGenFunction::emitInvariantStart(CharUnits Size, mlir::Value Addr, >From 5ecf23df1eeff7c1a4238965a2e2d6d81439f0b8 Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Tue, 16 Dec 2025 10:03:16 -0800 Subject: [PATCH 3/9] [CIR] Fix parameter naming in emitInvariantStart Change parameter names from PascalCase (Size, Addr) to lowercase (size, addr) to match LLVM coding standards for function parameters. --- clang/lib/CIR/CodeGen/CIRGenCXX.cpp | 10 +++++----- clang/lib/CIR/CodeGen/CIRGenFunction.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp index b61e520de0e4e..9b3cc40fb5a4f 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp @@ -31,7 +31,7 @@ static void emitDeclInvariant(CIRGenFunction &cgf, const VarDecl *d) { addr, cgf.getLoc(d->getSourceRange())); } -void CIRGenFunction::emitInvariantStart(CharUnits Size, mlir::Value Addr, +void CIRGenFunction::emitInvariantStart(CharUnits size, mlir::Value addr, mlir::Location loc) { // Do not emit the intrinsic if we're not optimizing. if (!cgm.getCodeGenOpts().OptimizationLevel) @@ -40,13 +40,13 @@ void CIRGenFunction::emitInvariantStart(CharUnits Size, mlir::Value Addr, CIRGenBuilderTy &builder = getBuilder(); // Create the size constant as i64 - uint64_t width = Size.getQuantity(); + uint64_t width = size.getQuantity(); mlir::Value sizeValue = builder.getConstInt(loc, builder.getSInt64Ty(), static_cast<int64_t>(width)); // Determine address space for intrinsic name unsigned addrSpace = 0; - if (auto ptrTy = mlir::dyn_cast<cir::PointerType>(Addr.getType())) + if (auto ptrTy = mlir::dyn_cast<cir::PointerType>(addr.getType())) addrSpace = ptrTy.getAddrSpace() ? ptrTy.getAddrSpace().getValue().getUInt() : 0; @@ -60,8 +60,8 @@ void CIRGenFunction::emitInvariantStart(CharUnits Size, mlir::Value Addr, // token, but we don't need to capture it. The return type is set to match // the address type for consistency with the operation signature. cir::LLVMIntrinsicCallOp::create( - builder, loc, builder.getStringAttr(intrinsicName), Addr.getType(), - mlir::ValueRange{sizeValue, Addr}); + builder, loc, builder.getStringAttr(intrinsicName), addr.getType(), + mlir::ValueRange{sizeValue, addr}); } static void emitDeclInit(CIRGenFunction &cgf, const VarDecl *varDecl, diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index 627698c5b19af..fcd897ddb5c09 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -1611,7 +1611,7 @@ class CIRGenFunction : public CIRGenTypeCache { mlir::Value emitRuntimeCall(mlir::Location loc, cir::FuncOp callee, llvm::ArrayRef<mlir::Value> args = {}); - void emitInvariantStart(CharUnits Size, mlir::Value Addr, mlir::Location loc); + void emitInvariantStart(CharUnits size, mlir::Value addr, mlir::Location loc); /// Emit the computation of the specified expression of scalar type. mlir::Value emitScalarExpr(const clang::Expr *e, >From 2adae263d0d3477b2700a24e31f7517bdcac20b7 Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Tue, 16 Dec 2025 14:21:17 -0800 Subject: [PATCH 4/9] [CIR] Remove address space suffix from invariant.start intrinsic Use base intrinsic name 'invariant.start' instead of constructing address space-specific names (e.g., 'invariant.start.p0'). The address space will be automatically handled when the intrinsic is lowered to LLVM IR, matching classic codegen behavior. Also add comment explaining why constant storage calculation was moved to before global creation in CIRGenModule.cpp. --- clang/lib/CIR/CodeGen/CIRGenCXX.cpp | 20 +++----------------- clang/lib/CIR/CodeGen/CIRGenModule.cpp | 4 +++- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp index 9b3cc40fb5a4f..f8a058b521c54 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp @@ -16,9 +16,7 @@ #include "clang/AST/GlobalDecl.h" #include "clang/CIR/MissingFeatures.h" -#include "llvm/ADT/SmallString.h" #include "llvm/Support/SaveAndRestore.h" -#include "llvm/Support/raw_ostream.h" using namespace clang; using namespace clang::CIRGen; @@ -44,23 +42,11 @@ void CIRGenFunction::emitInvariantStart(CharUnits size, mlir::Value addr, mlir::Value sizeValue = builder.getConstInt(loc, builder.getSInt64Ty(), static_cast<int64_t>(width)); - // Determine address space for intrinsic name - unsigned addrSpace = 0; - if (auto ptrTy = mlir::dyn_cast<cir::PointerType>(addr.getType())) - addrSpace = - ptrTy.getAddrSpace() ? ptrTy.getAddrSpace().getValue().getUInt() : 0; - - // Format intrinsic name with address space suffix (e.g., - // "invariant.start.p0", "invariant.start.p10") - llvm::SmallString<32> intrinsicName; - llvm::raw_svector_ostream os(intrinsicName); - os << "invariant.start.p" << addrSpace; - // Create the intrinsic call. The llvm.invariant.start intrinsic returns a - // token, but we don't need to capture it. The return type is set to match - // the address type for consistency with the operation signature. + // token, but we don't need to capture it. The address space will be + // automatically handled when the intrinsic is lowered to LLVM IR. cir::LLVMIntrinsicCallOp::create( - builder, loc, builder.getStringAttr(intrinsicName), addr.getType(), + builder, loc, builder.getStringAttr("invariant.start"), addr.getType(), mlir::ValueRange{sizeValue, addr}); } diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index 715e877fcca3d..29adb7d35c1f1 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -652,7 +652,9 @@ CIRGenModule::getOrCreateCIRGlobal(StringRef mangledName, mlir::Type ty, mlir::Location loc = getLoc(d->getSourceRange()); - // Calculate constant storage flag before creating the global. + // Calculate constant storage flag before creating the global. This was moved + // from after the global creation to ensure the constant flag is set correctly + // at creation time, matching the logic used in emitCXXGlobalVarDeclInit. bool isConstant = false; if (d) { bool needsDtor = >From 3971eee75e5c96e3acedfb4af41ab04fd74083da Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Wed, 17 Dec 2025 09:51:25 -0800 Subject: [PATCH 5/9] [CIR] Align emitGlobalVarDefinition constant logic with classic codegen Match classic codegen EmitGlobalVarDefinition logic by checking !needsGlobalCtor && !needsGlobalDtor before calling isConstantStorage. This ensures CIR behaves consistently with classic codegen for global variable constant flag determination. The isConstantStorage call now uses ExcludeCtor=true and ExcludeDtor=true to match classic codegen, instead of the previous logic that used !needsDtor for ExcludeDtor. Update test expectations to match correct behavior: globals that need constructors are not marked as constant, even if they have constant storage semantics (which still triggers invariant.start emission). --- clang/lib/CIR/CodeGen/CIRGenModule.cpp | 12 +++++------- .../CIR/CodeGen/global-constant-storage.cpp | 18 +++++++++--------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index 29adb7d35c1f1..e1cd4bd3271ed 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -877,14 +877,12 @@ void CIRGenModule::emitGlobalVarDefinition(const clang::VarDecl *vd, emitter->finalize(gv); // If it is safe to mark the global 'constant', do so now. - // Use the same logic as emitCXXGlobalVarDeclInit to determine constant - // storage. - bool needsDtor = - vd->needsDestruction(astContext) == QualType::DK_cxx_destructor; + // Use the same logic as classic codegen EmitGlobalVarDefinition. gv.setConstant((vd->hasAttr<CUDAConstantAttr>() && langOpts.CUDAIsDevice) || - vd->getType().isConstantStorage(astContext, - /*ExcludeCtor=*/true, - /*ExcludeDtor=*/!needsDtor)); + (!needsGlobalCtor && !needsGlobalDtor && + vd->getType().isConstantStorage(astContext, + /*ExcludeCtor=*/true, + /*ExcludeDtor=*/true))); assert(!cir::MissingFeatures::opGlobalSection()); // Set CIR's linkage type as appropriate. diff --git a/clang/test/CIR/CodeGen/global-constant-storage.cpp b/clang/test/CIR/CodeGen/global-constant-storage.cpp index 8aa240cf8fc5b..b3c5737522eb7 100644 --- a/clang/test/CIR/CodeGen/global-constant-storage.cpp +++ b/clang/test/CIR/CodeGen/global-constant-storage.cpp @@ -57,7 +57,7 @@ class C { const C c; // CIR checks for 'a' - should have constant storage -// CIR: cir.global constant external @a = #cir.zero : !rec_A +// CIR: cir.global external @a = #cir.zero : !rec_A // CIR: cir.func internal private @__cxx_global_var_init() { // CIR: %[[OBJ:.*]] = cir.get_global @a : !cir.ptr<!rec_A> // CIR: cir.call @_ZN1AC1Ev(%[[OBJ]]) : (!cir.ptr<!rec_A>) -> () @@ -65,7 +65,7 @@ const C c; // CIR: } // CIR checks for 'a2' - should have constant storage (constexpr dtor) -// CIR: cir.global constant external @a2 = #cir.zero : !rec_A2 +// CIR: cir.global external @a2 = #cir.zero : !rec_A2 // CIR: cir.func internal private @__cxx_global_var_init.1() { // CIR: %[[OBJ:.*]] = cir.get_global @a2 : !cir.ptr<!rec_A2> // CIR: cir.call @_ZN2A2C1Ev(%[[OBJ]]) : (!cir.ptr<!rec_A2>) -> () @@ -89,7 +89,7 @@ const C c; // CIR: } // CIR checks for 'c' - Andy's simple case, should have constant storage (internal linkage) -// CIR: cir.global {{.*}} constant internal {{.*}} @_ZL1c = #cir.zero : !rec_C +// CIR: cir.global {{.*}} internal {{.*}} @_ZL1c = #cir.zero : !rec_C // CIR: cir.func internal private @__cxx_global_var_init.4() { // CIR: %[[OBJ:.*]] = cir.get_global @_ZL1c : !cir.ptr<!rec_C> // CIR: cir.call @_ZN1CC1Ev(%[[OBJ]]) : (!cir.ptr<!rec_C>) -> () @@ -98,11 +98,11 @@ const C c; // LLVM checks (no optimization) // Check all globals first (they appear at the top) -// LLVM: @a ={{.*}} constant {{.*}} zeroinitializer -// LLVM: @a2 ={{.*}} constant {{.*}} zeroinitializer +// LLVM: @a ={{.*}} global {{.*}} zeroinitializer +// LLVM: @a2 ={{.*}} global {{.*}} zeroinitializer // LLVM: @b ={{.*}} global {{.*}} zeroinitializer // LLVM: @c_with_dtor ={{.*}} global {{.*}} zeroinitializer -// LLVM: @_ZL1c ={{.*}} constant {{.*}} zeroinitializer +// LLVM: @_ZL1c ={{.*}} global {{.*}} zeroinitializer // Then check the init functions // LLVM: define internal void @__cxx_global_var_init() { @@ -166,11 +166,11 @@ const C c; // With optimization enabled, should emit invariant.start intrinsic for constant storage cases // Check all globals first (they appear at the top) -// LLVM-OPT: @a ={{.*}} constant {{.*}} zeroinitializer -// LLVM-OPT: @a2 ={{.*}} constant {{.*}} zeroinitializer +// LLVM-OPT: @a ={{.*}} global {{.*}} zeroinitializer +// LLVM-OPT: @a2 ={{.*}} global {{.*}} zeroinitializer // LLVM-OPT: @b ={{.*}} global {{.*}} zeroinitializer // LLVM-OPT: @c_with_dtor ={{.*}} global {{.*}} zeroinitializer -// LLVM-OPT: @_ZL1c ={{.*}} constant {{.*}} zeroinitializer +// LLVM-OPT: @_ZL1c ={{.*}} global {{.*}} zeroinitializer // Then check the init functions with invariant.start // LLVM-OPT: define internal void @__cxx_global_var_init() { >From 5b706556b11c881f08496ea96b783704b0f0bb0a Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Wed, 17 Dec 2025 09:56:38 -0800 Subject: [PATCH 6/9] [CIR] Remove duplicate CWithDtor case from test Remove CWithDtor test case as it duplicates the A case. The C case already exists and tests the simple case with internal linkage, matching classic codegen test patterns. Update function numbering: C case changes from __cxx_global_var_init.4 to __cxx_global_var_init.3 after removing the duplicate case. --- .../CIR/CodeGen/global-constant-storage.cpp | 45 +------------------ 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/clang/test/CIR/CodeGen/global-constant-storage.cpp b/clang/test/CIR/CodeGen/global-constant-storage.cpp index b3c5737522eb7..03e1d4ab51919 100644 --- a/clang/test/CIR/CodeGen/global-constant-storage.cpp +++ b/clang/test/CIR/CodeGen/global-constant-storage.cpp @@ -37,15 +37,6 @@ struct B { // Should NOT emit invariant.start - has mutable member extern const B b = B(); -struct CWithDtor { - CWithDtor(); - ~CWithDtor(); - int n; -}; - -// Should NOT emit invariant.start - has non-constexpr destructor -extern const CWithDtor c_with_dtor = CWithDtor(); - // Simple case - just const C c; (no initializer) - Andy's suggestion class C { public: @@ -80,17 +71,9 @@ const C c; // CIR: cir.return // CIR: } -// CIR checks for 'c_with_dtor' - should NOT have constant storage (non-constexpr dtor) -// CIR: cir.global external @c_with_dtor = #cir.zero : !rec_CWithDtor -// CIR: cir.func internal private @__cxx_global_var_init.3() { -// CIR: %[[OBJ:.*]] = cir.get_global @c_with_dtor : !cir.ptr<!rec_CWithDtor> -// CIR: cir.call @_ZN9CWithDtorC1Ev(%[[OBJ]]) : (!cir.ptr<!rec_CWithDtor>) -> () -// CIR: cir.return -// CIR: } - // CIR checks for 'c' - Andy's simple case, should have constant storage (internal linkage) // CIR: cir.global {{.*}} internal {{.*}} @_ZL1c = #cir.zero : !rec_C -// CIR: cir.func internal private @__cxx_global_var_init.4() { +// CIR: cir.func internal private @__cxx_global_var_init.3() { // CIR: %[[OBJ:.*]] = cir.get_global @_ZL1c : !cir.ptr<!rec_C> // CIR: cir.call @_ZN1CC1Ev(%[[OBJ]]) : (!cir.ptr<!rec_C>) -> () // CIR: cir.return @@ -101,7 +84,6 @@ const C c; // LLVM: @a ={{.*}} global {{.*}} zeroinitializer // LLVM: @a2 ={{.*}} global {{.*}} zeroinitializer // LLVM: @b ={{.*}} global {{.*}} zeroinitializer -// LLVM: @c_with_dtor ={{.*}} global {{.*}} zeroinitializer // LLVM: @_ZL1c ={{.*}} global {{.*}} zeroinitializer // Then check the init functions @@ -121,11 +103,6 @@ const C c; // LLVM: } // LLVM: define internal void @__cxx_global_var_init.3() { -// LLVM: call void @_ZN9CWithDtorC1Ev(ptr @c_with_dtor) -// LLVM: ret void -// LLVM: } - -// LLVM: define internal void @__cxx_global_var_init.4() { // LLVM: call void @_ZN1CC1Ev(ptr @_ZL1c) // LLVM: ret void // LLVM: } @@ -135,7 +112,6 @@ const C c; // OGCG: @a ={{.*}} global {{.*}} zeroinitializer // OGCG: @a2 ={{.*}} global {{.*}} zeroinitializer // OGCG: @b ={{.*}} global {{.*}} zeroinitializer -// OGCG: @c_with_dtor ={{.*}} global {{.*}} zeroinitializer // OGCG: @_ZL1c ={{.*}} global {{.*}} zeroinitializer // Then check the init functions @@ -155,11 +131,6 @@ const C c; // OGCG: } // OGCG: define internal void @__cxx_global_var_init.3() {{.*}} section ".text.startup" { -// OGCG: call void @_ZN9CWithDtorC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @c_with_dtor) -// OGCG: ret void -// OGCG: } - -// OGCG: define internal void @__cxx_global_var_init.4() {{.*}} section ".text.startup" { // OGCG: call void @_ZN1CC1Ev(ptr noundef nonnull align 4 dereferenceable(8) @_ZL1c) // OGCG: ret void // OGCG: } @@ -169,7 +140,6 @@ const C c; // LLVM-OPT: @a ={{.*}} global {{.*}} zeroinitializer // LLVM-OPT: @a2 ={{.*}} global {{.*}} zeroinitializer // LLVM-OPT: @b ={{.*}} global {{.*}} zeroinitializer -// LLVM-OPT: @c_with_dtor ={{.*}} global {{.*}} zeroinitializer // LLVM-OPT: @_ZL1c ={{.*}} global {{.*}} zeroinitializer // Then check the init functions with invariant.start @@ -192,12 +162,6 @@ const C c; // LLVM-OPT: } // LLVM-OPT: define internal void @__cxx_global_var_init.3() { -// LLVM-OPT: call void @_ZN9CWithDtorC1Ev(ptr @c_with_dtor) -// LLVM-OPT-NOT: call {{.*}}@llvm.invariant.start.p0(i64 {{.*}}, ptr @c_with_dtor) -// LLVM-OPT: ret void -// LLVM-OPT: } - -// LLVM-OPT: define internal void @__cxx_global_var_init.4() { // LLVM-OPT: call void @_ZN1CC1Ev(ptr @_ZL1c) // LLVM-OPT: call {{.*}}@llvm.invariant.start.p0(i64 8, ptr @_ZL1c) // LLVM-OPT: ret void @@ -208,7 +172,6 @@ const C c; // OGCG-OPT: @a ={{.*}} global {{.*}} zeroinitializer // OGCG-OPT: @a2 ={{.*}} global {{.*}} zeroinitializer // OGCG-OPT: @b ={{.*}} global {{.*}} zeroinitializer -// OGCG-OPT: @c_with_dtor ={{.*}} global {{.*}} zeroinitializer // OGCG-OPT: @_ZL1c ={{.*}} global {{.*}} zeroinitializer // Then check the init functions with invariant.start @@ -231,12 +194,6 @@ const C c; // OGCG-OPT: } // OGCG-OPT: define internal void @__cxx_global_var_init.3() {{.*}} section ".text.startup" { -// OGCG-OPT: call void @_ZN9CWithDtorC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @c_with_dtor) -// OGCG-OPT-NOT: call {{.*}}@llvm.invariant.start.p0(i64 {{.*}}, ptr @c_with_dtor) -// OGCG-OPT: ret void -// OGCG-OPT: } - -// OGCG-OPT: define internal void @__cxx_global_var_init.4() {{.*}} section ".text.startup" { // OGCG-OPT: call void @_ZN1CC1Ev(ptr noundef nonnull align 4 dereferenceable(8) @_ZL1c) // OGCG-OPT: call {{.*}}@llvm.invariant.start.p0(i64 8, ptr @_ZL1c) // OGCG-OPT: ret void >From 70ab5f27364a3933e812b6fcc30b4c60548f80ca Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Wed, 17 Dec 2025 10:18:17 -0800 Subject: [PATCH 7/9] [CIR] Reorganize test checks to interleave by test case Reorganize global-constant-storage.cpp test file to group CIR, LLVM, and OGCG checks together for each test case, making it easier to compare the three implementations side-by-side. This addresses reviewer feedback requesting that checks be interleaved so that the various implementations of each case are adjacent. --- .../CIR/CodeGen/global-constant-storage.cpp | 165 +++++++++--------- 1 file changed, 86 insertions(+), 79 deletions(-) diff --git a/clang/test/CIR/CodeGen/global-constant-storage.cpp b/clang/test/CIR/CodeGen/global-constant-storage.cpp index 03e1d4ab51919..193e09aef259a 100644 --- a/clang/test/CIR/CodeGen/global-constant-storage.cpp +++ b/clang/test/CIR/CodeGen/global-constant-storage.cpp @@ -47,7 +47,19 @@ class C { const C c; -// CIR checks for 'a' - should have constant storage +// Check all globals first (they appear at the top of LLVM/OGCG output) +// LLVM: @a ={{.*}} global {{.*}} zeroinitializer +// LLVM: @a2 ={{.*}} global {{.*}} zeroinitializer +// LLVM: @b ={{.*}} global {{.*}} zeroinitializer +// LLVM: @_ZL1c ={{.*}} global {{.*}} zeroinitializer + +// OGCG: @a ={{.*}} global {{.*}} zeroinitializer +// OGCG: @a2 ={{.*}} global {{.*}} zeroinitializer +// OGCG: @b ={{.*}} global {{.*}} zeroinitializer +// OGCG: @_ZL1c ={{.*}} global {{.*}} zeroinitializer + +// Test case 'a' - should have constant storage +// CIR checks for 'a' // CIR: cir.global external @a = #cir.zero : !rec_A // CIR: cir.func internal private @__cxx_global_var_init() { // CIR: %[[OBJ:.*]] = cir.get_global @a : !cir.ptr<!rec_A> @@ -55,7 +67,20 @@ const C c; // CIR: cir.return // CIR: } -// CIR checks for 'a2' - should have constant storage (constexpr dtor) +// LLVM checks for 'a' (no optimization) +// LLVM: define internal void @__cxx_global_var_init() { +// LLVM: call void @_ZN1AC1Ev(ptr @a) +// LLVM: ret void +// LLVM: } + +// OGCG checks for 'a' (no optimization) +// OGCG: define internal void @__cxx_global_var_init() {{.*}} section ".text.startup" { +// OGCG: call void @_ZN1AC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @a) +// OGCG: ret void +// OGCG: } + +// Test case 'a2' - should have constant storage (constexpr dtor) +// CIR checks for 'a2' // CIR: cir.global external @a2 = #cir.zero : !rec_A2 // CIR: cir.func internal private @__cxx_global_var_init.1() { // CIR: %[[OBJ:.*]] = cir.get_global @a2 : !cir.ptr<!rec_A2> @@ -63,7 +88,20 @@ const C c; // CIR: cir.return // CIR: } -// CIR checks for 'b' - should NOT have constant storage (mutable member) +// LLVM checks for 'a2' (no optimization) +// LLVM: define internal void @__cxx_global_var_init.1() { +// LLVM: call void @_ZN2A2C1Ev(ptr @a2) +// LLVM: ret void +// LLVM: } + +// OGCG checks for 'a2' (no optimization) +// OGCG: define internal void @__cxx_global_var_init.1() {{.*}} section ".text.startup" { +// OGCG: call void @_ZN2A2C1Ev(ptr noundef nonnull align 4 dereferenceable(4) @a2) +// OGCG: ret void +// OGCG: } + +// Test case 'b' - should NOT have constant storage (mutable member) +// CIR checks for 'b' // CIR: cir.global external @b = #cir.zero : !rec_B // CIR: cir.func internal private @__cxx_global_var_init.2() { // CIR: %[[OBJ:.*]] = cir.get_global @b : !cir.ptr<!rec_B> @@ -71,7 +109,20 @@ const C c; // CIR: cir.return // CIR: } -// CIR checks for 'c' - Andy's simple case, should have constant storage (internal linkage) +// LLVM checks for 'b' (no optimization) +// LLVM: define internal void @__cxx_global_var_init.2() { +// LLVM: call void @_ZN1BC1Ev(ptr @b) +// LLVM: ret void +// LLVM: } + +// OGCG checks for 'b' (no optimization) +// OGCG: define internal void @__cxx_global_var_init.2() {{.*}} section ".text.startup" { +// OGCG: call void @_ZN1BC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @b) +// OGCG: ret void +// OGCG: } + +// Test case 'c' - Andy's simple case, should have constant storage (internal linkage) +// CIR checks for 'c' // CIR: cir.global {{.*}} internal {{.*}} @_ZL1c = #cir.zero : !rec_C // CIR: cir.func internal private @__cxx_global_var_init.3() { // CIR: %[[OBJ:.*]] = cir.get_global @_ZL1c : !cir.ptr<!rec_C> @@ -79,123 +130,79 @@ const C c; // CIR: cir.return // CIR: } -// LLVM checks (no optimization) -// Check all globals first (they appear at the top) -// LLVM: @a ={{.*}} global {{.*}} zeroinitializer -// LLVM: @a2 ={{.*}} global {{.*}} zeroinitializer -// LLVM: @b ={{.*}} global {{.*}} zeroinitializer -// LLVM: @_ZL1c ={{.*}} global {{.*}} zeroinitializer - -// Then check the init functions -// LLVM: define internal void @__cxx_global_var_init() { -// LLVM: call void @_ZN1AC1Ev(ptr @a) -// LLVM: ret void -// LLVM: } - -// LLVM: define internal void @__cxx_global_var_init.1() { -// LLVM: call void @_ZN2A2C1Ev(ptr @a2) -// LLVM: ret void -// LLVM: } - -// LLVM: define internal void @__cxx_global_var_init.2() { -// LLVM: call void @_ZN1BC1Ev(ptr @b) -// LLVM: ret void -// LLVM: } - +// LLVM checks for 'c' (no optimization) // LLVM: define internal void @__cxx_global_var_init.3() { // LLVM: call void @_ZN1CC1Ev(ptr @_ZL1c) // LLVM: ret void // LLVM: } -// OGCG checks (no optimization) -// Check all globals first (they appear at the top) -// OGCG: @a ={{.*}} global {{.*}} zeroinitializer -// OGCG: @a2 ={{.*}} global {{.*}} zeroinitializer -// OGCG: @b ={{.*}} global {{.*}} zeroinitializer -// OGCG: @_ZL1c ={{.*}} global {{.*}} zeroinitializer - -// Then check the init functions -// OGCG: define internal void @__cxx_global_var_init() {{.*}} section ".text.startup" { -// OGCG: call void @_ZN1AC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @a) -// OGCG: ret void -// OGCG: } - -// OGCG: define internal void @__cxx_global_var_init.1() {{.*}} section ".text.startup" { -// OGCG: call void @_ZN2A2C1Ev(ptr noundef nonnull align 4 dereferenceable(4) @a2) -// OGCG: ret void -// OGCG: } - -// OGCG: define internal void @__cxx_global_var_init.2() {{.*}} section ".text.startup" { -// OGCG: call void @_ZN1BC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @b) -// OGCG: ret void -// OGCG: } - +// OGCG checks for 'c' (no optimization) // OGCG: define internal void @__cxx_global_var_init.3() {{.*}} section ".text.startup" { // OGCG: call void @_ZN1CC1Ev(ptr noundef nonnull align 4 dereferenceable(8) @_ZL1c) // OGCG: ret void // OGCG: } // With optimization enabled, should emit invariant.start intrinsic for constant storage cases -// Check all globals first (they appear at the top) + +// Check all globals first (they appear at the top of optimized LLVM/OGCG output) // LLVM-OPT: @a ={{.*}} global {{.*}} zeroinitializer // LLVM-OPT: @a2 ={{.*}} global {{.*}} zeroinitializer // LLVM-OPT: @b ={{.*}} global {{.*}} zeroinitializer // LLVM-OPT: @_ZL1c ={{.*}} global {{.*}} zeroinitializer -// Then check the init functions with invariant.start +// OGCG-OPT: @a ={{.*}} global {{.*}} zeroinitializer +// OGCG-OPT: @a2 ={{.*}} global {{.*}} zeroinitializer +// OGCG-OPT: @b ={{.*}} global {{.*}} zeroinitializer +// OGCG-OPT: @_ZL1c ={{.*}} global {{.*}} zeroinitializer + +// Test case 'a' - optimized checks // LLVM-OPT: define internal void @__cxx_global_var_init() { // LLVM-OPT: call void @_ZN1AC1Ev(ptr @a) // LLVM-OPT: call {{.*}}@llvm.invariant.start.p0(i64 4, ptr @a) // LLVM-OPT: ret void // LLVM-OPT: } -// LLVM-OPT: define internal void @__cxx_global_var_init.1() { -// LLVM-OPT: call void @_ZN2A2C1Ev(ptr @a2) -// LLVM-OPT: call {{.*}}@llvm.invariant.start.p0(i64 4, ptr @a2) -// LLVM-OPT: ret void -// LLVM-OPT: } - -// LLVM-OPT: define internal void @__cxx_global_var_init.2() { -// LLVM-OPT: call void @_ZN1BC1Ev(ptr @b) -// LLVM-OPT-NOT: call {{.*}}@llvm.invariant.start.p0(i64 {{.*}}, ptr @b) -// LLVM-OPT: ret void -// LLVM-OPT: } - -// LLVM-OPT: define internal void @__cxx_global_var_init.3() { -// LLVM-OPT: call void @_ZN1CC1Ev(ptr @_ZL1c) -// LLVM-OPT: call {{.*}}@llvm.invariant.start.p0(i64 8, ptr @_ZL1c) -// LLVM-OPT: ret void -// LLVM-OPT: } - -// OGCG-OPT checks (with optimization) -// Check all globals first (they appear at the top) -// OGCG-OPT: @a ={{.*}} global {{.*}} zeroinitializer -// OGCG-OPT: @a2 ={{.*}} global {{.*}} zeroinitializer -// OGCG-OPT: @b ={{.*}} global {{.*}} zeroinitializer -// OGCG-OPT: @_ZL1c ={{.*}} global {{.*}} zeroinitializer - -// Then check the init functions with invariant.start // OGCG-OPT: define internal void @__cxx_global_var_init() {{.*}} section ".text.startup" { // OGCG-OPT: call void @_ZN1AC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @a) // OGCG-OPT: call {{.*}}@llvm.invariant.start.p0(i64 4, ptr @a) // OGCG-OPT: ret void // OGCG-OPT: } +// Test case 'a2' - optimized checks +// LLVM-OPT: define internal void @__cxx_global_var_init.1() { +// LLVM-OPT: call void @_ZN2A2C1Ev(ptr @a2) +// LLVM-OPT: call {{.*}}@llvm.invariant.start.p0(i64 4, ptr @a2) +// LLVM-OPT: ret void +// LLVM-OPT: } + // OGCG-OPT: define internal void @__cxx_global_var_init.1() {{.*}} section ".text.startup" { // OGCG-OPT: call void @_ZN2A2C1Ev(ptr noundef nonnull align 4 dereferenceable(4) @a2) // OGCG-OPT: call {{.*}}@llvm.invariant.start.p0(i64 4, ptr @a2) // OGCG-OPT: ret void // OGCG-OPT: } +// Test case 'b' - optimized checks (should NOT emit invariant.start) +// LLVM-OPT: define internal void @__cxx_global_var_init.2() { +// LLVM-OPT: call void @_ZN1BC1Ev(ptr @b) +// LLVM-OPT-NOT: call {{.*}}@llvm.invariant.start.p0(i64 {{.*}}, ptr @b) +// LLVM-OPT: ret void +// LLVM-OPT: } + // OGCG-OPT: define internal void @__cxx_global_var_init.2() {{.*}} section ".text.startup" { // OGCG-OPT: call void @_ZN1BC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @b) // OGCG-OPT-NOT: call {{.*}}@llvm.invariant.start.p0(i64 {{.*}}, ptr @b) // OGCG-OPT: ret void // OGCG-OPT: } +// Test case 'c' - optimized checks +// LLVM-OPT: define internal void @__cxx_global_var_init.3() { +// LLVM-OPT: call void @_ZN1CC1Ev(ptr @_ZL1c) +// LLVM-OPT: call {{.*}}@llvm.invariant.start.p0(i64 8, ptr @_ZL1c) +// LLVM-OPT: ret void +// LLVM-OPT: } + // OGCG-OPT: define internal void @__cxx_global_var_init.3() {{.*}} section ".text.startup" { // OGCG-OPT: call void @_ZN1CC1Ev(ptr noundef nonnull align 4 dereferenceable(8) @_ZL1c) // OGCG-OPT: call {{.*}}@llvm.invariant.start.p0(i64 8, ptr @_ZL1c) // OGCG-OPT: ret void // OGCG-OPT: } - >From be3c0dbcb330798f07ad945703c563a5282b1940 Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Wed, 17 Dec 2025 10:27:04 -0800 Subject: [PATCH 8/9] [CIR] Add checks for before/after LoweringPrepare transformation Add CIR-BEFORE-LPP checks to verify the initial CIR format with ctor regions before LoweringPrepare transformation. This shows globals in their initial state (cir.global external @a = ctor : !rec_A { ... }) before they are transformed into separate init functions. This addresses reviewer feedback requesting checks for both the initial CIR state and the state after LoweringPrepare transformation. --- .../CIR/CodeGen/global-constant-storage.cpp | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/clang/test/CIR/CodeGen/global-constant-storage.cpp b/clang/test/CIR/CodeGen/global-constant-storage.cpp index 193e09aef259a..86b21fc3df087 100644 --- a/clang/test/CIR/CodeGen/global-constant-storage.cpp +++ b/clang/test/CIR/CodeGen/global-constant-storage.cpp @@ -1,4 +1,5 @@ -// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir -mmlir --mlir-print-ir-before=cir-lowering-prepare %s -o %t.cir 2> %t-before.cir +// RUN: FileCheck --check-prefix=CIR-BEFORE-LPP --input-file=%t-before.cir %s // RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s // RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll // RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s @@ -47,6 +48,34 @@ class C { const C c; +// CIR checks before LoweringPrepare transformation - globals have ctor regions +// Test case 'a' - before LoweringPrepare +// CIR-BEFORE-LPP: cir.global external @a = ctor : !rec_A { +// CIR-BEFORE-LPP: %[[OBJ:.*]] = cir.get_global @a : !cir.ptr<!rec_A> +// CIR-BEFORE-LPP: cir.call @_ZN1AC1Ev(%[[OBJ]]) : (!cir.ptr<!rec_A>) -> () +// CIR-BEFORE-LPP: %[[OBJ2:.*]] = cir.get_global @a : !cir.ptr<!rec_A> +// CIR-BEFORE-LPP: } + +// Test case 'a2' - before LoweringPrepare +// CIR-BEFORE-LPP: cir.global external @a2 = ctor : !rec_A2 { +// CIR-BEFORE-LPP: %[[OBJ:.*]] = cir.get_global @a2 : !cir.ptr<!rec_A2> +// CIR-BEFORE-LPP: cir.call @_ZN2A2C1Ev(%[[OBJ]]) : (!cir.ptr<!rec_A2>) -> () +// CIR-BEFORE-LPP: %[[OBJ2:.*]] = cir.get_global @a2 : !cir.ptr<!rec_A2> +// CIR-BEFORE-LPP: } + +// Test case 'b' - before LoweringPrepare +// CIR-BEFORE-LPP: cir.global external @b = ctor : !rec_B { +// CIR-BEFORE-LPP: %[[OBJ:.*]] = cir.get_global @b : !cir.ptr<!rec_B> +// CIR-BEFORE-LPP: cir.call @_ZN1BC1Ev(%[[OBJ]]) : (!cir.ptr<!rec_B>) -> () +// CIR-BEFORE-LPP: } + +// Test case 'c' - before LoweringPrepare (internal linkage) +// CIR-BEFORE-LPP: cir.global {{.*}} internal {{.*}} @_ZL1c = ctor : !rec_C { +// CIR-BEFORE-LPP: %[[OBJ:.*]] = cir.get_global @_ZL1c : !cir.ptr<!rec_C> +// CIR-BEFORE-LPP: cir.call @_ZN1CC1Ev(%[[OBJ]]) : (!cir.ptr<!rec_C>) -> () +// CIR-BEFORE-LPP: %[[OBJ2:.*]] = cir.get_global @_ZL1c : !cir.ptr<!rec_C> +// CIR-BEFORE-LPP: } + // Check all globals first (they appear at the top of LLVM/OGCG output) // LLVM: @a ={{.*}} global {{.*}} zeroinitializer // LLVM: @a2 ={{.*}} global {{.*}} zeroinitializer >From a5d549232d94d06ade5250798195ab4283820619 Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Wed, 17 Dec 2025 10:31:17 -0800 Subject: [PATCH 9/9] [CIR] Add -O1 RUN line to test CIR output at optimization level Add a RUN line to emit CIR at -O1 optimization level and add corresponding FileCheck assertions to verify that invariant.start intrinsic calls are present in the CIR representation for constant storage cases. This addresses reviewer feedback requesting a RUN line to test CIR output at -O1, ensuring that optimization level correctly triggers the emission of invariant.start intrinsics in CIR. --- .../CIR/CodeGen/global-constant-storage.cpp | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/clang/test/CIR/CodeGen/global-constant-storage.cpp b/clang/test/CIR/CodeGen/global-constant-storage.cpp index 86b21fc3df087..6f62823b8235f 100644 --- a/clang/test/CIR/CodeGen/global-constant-storage.cpp +++ b/clang/test/CIR/CodeGen/global-constant-storage.cpp @@ -1,6 +1,8 @@ // RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir -mmlir --mlir-print-ir-before=cir-lowering-prepare %s -o %t.cir 2> %t-before.cir // RUN: FileCheck --check-prefix=CIR-BEFORE-LPP --input-file=%t-before.cir %s // RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir -O1 %s -o %t-o1.cir +// RUN: FileCheck --check-prefix=CIR-O1 --input-file=%t-o1.cir %s // RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll // RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s // RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll @@ -235,3 +237,30 @@ const C c; // OGCG-OPT: call {{.*}}@llvm.invariant.start.p0(i64 8, ptr @_ZL1c) // OGCG-OPT: ret void // OGCG-OPT: } + +// CIR checks at -O1 - should include invariant.start intrinsic calls for constant storage cases +// CIR-O1: module {{.*}} attributes {{.*}} cir.opt_info = #cir.opt_info<level = 1 + +// Test case 'a' - CIR at -O1 +// CIR-O1: cir.func internal private @__cxx_global_var_init() { +// CIR-O1: cir.call @_ZN1AC1Ev(%{{.*}}) : (!cir.ptr<!rec_A>) -> () +// CIR-O1: cir.call_llvm_intrinsic "invariant.start" {{.*}} : (!s64i, !cir.ptr<!rec_A>) -> !cir.ptr<!rec_A> +// CIR-O1: } + +// Test case 'a2' - CIR at -O1 +// CIR-O1: cir.func internal private @__cxx_global_var_init.1() { +// CIR-O1: cir.call @_ZN2A2C1Ev(%{{.*}}) : (!cir.ptr<!rec_A2>) -> () +// CIR-O1: cir.call_llvm_intrinsic "invariant.start" {{.*}} : (!s64i, !cir.ptr<!rec_A2>) -> !cir.ptr<!rec_A2> +// CIR-O1: } + +// Test case 'b' - CIR at -O1 (should NOT emit invariant.start) +// CIR-O1: cir.func internal private @__cxx_global_var_init.2() { +// CIR-O1: cir.call @_ZN1BC1Ev(%{{.*}}) : (!cir.ptr<!rec_B>) -> () +// CIR-O1-NOT: cir.call_llvm_intrinsic "invariant.start" +// CIR-O1: } + +// Test case 'c' - CIR at -O1 +// CIR-O1: cir.func internal private @__cxx_global_var_init.3() { +// CIR-O1: cir.call @_ZN1CC1Ev(%{{.*}}) : (!cir.ptr<!rec_C>) -> () +// CIR-O1: cir.call_llvm_intrinsic "invariant.start" {{.*}} : (!s64i, !cir.ptr<!rec_C>) -> !cir.ptr<!rec_C> +// CIR-O1: } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
