diff --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp index 71568ec87a31b..9b3cc40fb5a4f 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(width)); + + // Determine address space for intrinsic name + unsigned addrSpace = 0; + if (auto ptrTy = mlir::dyn_cast(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..fcd897ddb5c09 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 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() && 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 +// CIR: cir.call @_ZN1AC1Ev(%[[OBJ]]) : (!cir.ptr) -> () +// 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 +// CIR: cir.call @_ZN2A2C1Ev(%[[OBJ]]) : (!cir.ptr) -> () +// 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 +// CIR: cir.call @_ZN1BC1Ev(%[[OBJ]]) : (!cir.ptr) -> () +// 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 +// CIR: cir.call @_ZN9CWithDtorC1Ev(%[[OBJ]]) : (!cir.ptr) -> () +// 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 +// CIR: cir.call @_ZN1CC1Ev(%[[OBJ]]) : (!cir.ptr) -> () +// 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: } +