Author: adams381 Date: 2025-12-18T14:40:41-08:00 New Revision: d524ecbf0f735fa2b5ea73ea70da8c68985afe33
URL: https://github.com/llvm/llvm-project/commit/d524ecbf0f735fa2b5ea73ea70da8c68985afe33 DIFF: https://github.com/llvm/llvm-project/commit/d524ecbf0f735fa2b5ea73ea70da8c68985afe33.diff LOG: [CIR] Add emitDeclInvariant for global with constant storage (#171915) 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 ## Implementation Details 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. The intrinsic is only emitted when optimizations are enabled, matching classic codegen behavior. ## Testing All tests pass (411/412, 1 unsupported). The test file includes CIR, LLVM, and OGCG checks for both optimized and non-optimized builds. Added: clang/test/CIR/CodeGen/global-constant-storage.cpp Modified: clang/lib/CIR/CodeGen/CIRGenCXX.cpp clang/lib/CIR/CodeGen/CIRGenFunction.h clang/lib/CIR/CodeGen/CIRGenModule.cpp Removed: ################################################################################ diff --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp index 71568ec87a31b..f8a058b521c54 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp @@ -21,6 +21,35 @@ 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)); + + // Create the intrinsic call. The llvm.invariant.start intrinsic returns a + // 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("invariant.start"), addr.getType(), + mlir::ValueRange{sizeValue, addr}); +} + static void emitDeclInit(CIRGenFunction &cgf, const VarDecl *varDecl, cir::GlobalOp globalOp) { assert((varDecl->hasGlobalStorage() || @@ -234,13 +263,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 254a8c5883d48..faba6878a9707 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -1630,6 +1630,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 15bc870da91e7..42df6304628dc 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -662,10 +662,21 @@ CIRGenModule::getOrCreateCIRGlobal(StringRef mangledName, mlir::Type ty, mlir::Location loc = getLoc(d->getSourceRange()); + // 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 = + 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 @@ -685,10 +696,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); @@ -880,10 +887,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 classic codegen EmitGlobalVarDefinition. gv.setConstant((vd->hasAttr<CUDAConstantAttr>() && langOpts.CUDAIsDevice) || (!needsGlobalCtor && !needsGlobalDtor && - vd->getType().isConstantStorage( - astContext, /*ExcludeCtor=*/true, /*ExcludeDtor=*/true))); + 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 new file mode 100644 index 0000000000000..6f62823b8235f --- /dev/null +++ b/clang/test/CIR/CodeGen/global-constant-storage.cpp @@ -0,0 +1,266 @@ +// 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 +// 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(); + +// Simple case - just const C c; (no initializer) - Andy's suggestion +class C { +public: + C(); + int a; + int b; +}; + +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 +// 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> +// CIR: cir.call @_ZN1AC1Ev(%[[OBJ]]) : (!cir.ptr<!rec_A>) -> () +// CIR: cir.return +// CIR: } + +// 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> +// CIR: cir.call @_ZN2A2C1Ev(%[[OBJ]]) : (!cir.ptr<!rec_A2>) -> () +// CIR: cir.return +// CIR: } + +// 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> +// CIR: cir.call @_ZN1BC1Ev(%[[OBJ]]) : (!cir.ptr<!rec_B>) -> () +// CIR: cir.return +// CIR: } + +// 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> +// CIR: cir.call @_ZN1CC1Ev(%[[OBJ]]) : (!cir.ptr<!rec_C>) -> () +// CIR: cir.return +// CIR: } + +// 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 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 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 + +// 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: } + +// 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: } + +// 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
