[clang] [CIR] Upstream global variable replacement (PR #184686)
Andy Kaylor via cfe-commits
cfe-commits at lists.llvm.org
Wed Mar 4 12:46:18 PST 2026
https://github.com/andykaylor created https://github.com/llvm/llvm-project/pull/184686
This change upstreams the CIR implementation of global variable replacement. When we get a call to get or create a global variable using a type that does not match the previous type for a variable of the same name, we need to replace the old definition with the new one. In classic codegen that was as simple as replaceAllUses+eraseFromParent, but in CIR because we have typed pointers, we need to visit the uses and update them with bitcasts to reflect the new type.
>From 05d11486371f98150db78540774e7f9e3dabb038 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Tue, 3 Mar 2026 17:05:14 -0800
Subject: [PATCH] [CIR] Upstream global variable replacement
This change upstreams the CIR implementation of global variable replacement.
When we get a call to get or create a global variable using a type that
does not match the previous type for a variable of the same name, we need
to replace the old definition with the new one. In classic codegen that
was as simple as replaceAllUses+eraseFromParent, but in CIR because we
have typed pointers, we need to visit the uses and update them with bitcasts
to reflect the new type.
---
clang/lib/CIR/CodeGen/CIRGenModule.cpp | 48 +++++++++++++++++++++++
clang/lib/CIR/CodeGen/CIRGenModule.h | 4 ++
clang/test/CIR/CodeGen/replace-global.cpp | 37 +++++++++++++++++
3 files changed, 89 insertions(+)
create mode 100644 clang/test/CIR/CodeGen/replace-global.cpp
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 3ef487465ab80..0653fee57428c 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -674,6 +674,49 @@ static void setLinkageForGV(cir::GlobalOp &gv, const NamedDecl *nd) {
gv.setLinkage(cir::GlobalLinkageKind::ExternalWeakLinkage);
}
+// We want to replace a global value, but because of CIR's typed pointers,
+// we need to update the existing uses to reflect the new type, not just replace
+// them directly.
+void CIRGenModule::replaceGlobal(cir::GlobalOp oldGV, cir::GlobalOp newGV) {
+ assert(oldGV.getSymName() == newGV.getSymName() && "symbol names must match");
+
+ mlir::Type oldTy = oldGV.getSymType();
+ mlir::Type newTy = newGV.getSymType();
+
+ assert(!cir::MissingFeatures::addressSpace());
+
+ // If the type didn't change, why are we here?
+ assert(oldTy != newTy && "expected type change in replaceGlobal");
+
+ // Otherwise, visit all uses and add handling to fix up the types.
+ std::optional<mlir::SymbolTable::UseRange> oldSymUses =
+ oldGV.getSymbolUses(theModule);
+ if (oldSymUses) {
+ for (mlir::SymbolTable::SymbolUse use : *oldSymUses) {
+ mlir::Operation *userOp = use.getUser();
+ assert((mlir::isa<cir::GetGlobalOp, cir::GlobalOp, cir::ConstantOp>(
+ userOp)) &&
+ "Unexpected user for global op");
+
+ if (auto getGlobalOp = dyn_cast<cir::GetGlobalOp>(use.getUser())) {
+ mlir::Value useOpResultValue = getGlobalOp.getAddr();
+ useOpResultValue.setType(cir::PointerType::get(newTy));
+
+ mlir::OpBuilder::InsertionGuard guard(builder);
+ builder.setInsertionPointAfter(getGlobalOp);
+ mlir::Type ptrTy = builder.getPointerTo(oldTy);
+ mlir::Value cast = builder.createBitcast(getGlobalOp->getLoc(),
+ useOpResultValue, ptrTy);
+ useOpResultValue.replaceAllUsesExcept(cast, cast.getDefiningOp());
+ } else {
+ errorNYI(userOp->getLoc(), "Replace global op use in global view attr");
+ }
+ }
+ }
+
+ oldGV.erase();
+}
+
/// If the specified mangled name is not in the module,
/// create and return an mlir GlobalOp with the specified type (TODO(cir):
/// address space).
@@ -748,6 +791,11 @@ CIRGenModule::getOrCreateCIRGlobal(StringRef mangledName, mlir::Type ty,
CIRGenModule::createGlobalOp(*this, loc, mangledName, ty, isConstant,
/*insertPoint=*/entry.getOperation());
+ // If we already created a global with the same mangled name (but different
+ // type) before, remove it from its parent.
+ if (entry)
+ replaceGlobal(entry, gv);
+
// This is the first use or definition of a mangled name. If there is a
// deferred decl with this name, remember that we need to emit it at the end
// of the file.
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h
index 0f456f1f39ceb..48bb1f2ae20d9 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -613,6 +613,10 @@ class CIRGenModule : public CIRGenTypeCache {
// related attributes.
bool shouldEmitCUDAGlobalVar(const VarDecl *global) const;
+ /// Replace all uses of the old global with the new global, updating types
+ /// and references as needed. Erases the old global when done.
+ void replaceGlobal(cir::GlobalOp oldGV, cir::GlobalOp newGV);
+
void replaceUsesOfNonProtoTypeWithRealFunction(mlir::Operation *old,
cir::FuncOp newFn);
diff --git a/clang/test/CIR/CodeGen/replace-global.cpp b/clang/test/CIR/CodeGen/replace-global.cpp
new file mode 100644
index 0000000000000..b6c22d5562801
--- /dev/null
+++ b/clang/test/CIR/CodeGen/replace-global.cpp
@@ -0,0 +1,37 @@
+// RUN: %clang_cc1 -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 -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 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
+
+struct S {
+ char arr[28];
+};
+
+void use(void*);
+
+static S gS = {{0x50, 0x4B, 0x03, 0x04}};
+
+struct R {
+ R() { use(&gS); }
+};
+static R gR;
+
+// CIR: cir.func {{.*}} @_ZN1RC2Ev
+// CIR: %[[GS_PTR:.*]] = cir.get_global @_ZL2gS : !cir.ptr<!rec_anon_struct1>
+// CIR: %[[GS_AS_S:.*]] = cir.cast bitcast %[[GS_PTR]] : !cir.ptr<!rec_anon_struct1> -> !cir.ptr<!rec_S>
+// CIR: %[[GS_AS_VOID:.*]] = cir.cast bitcast %[[GS_AS_S]] : !cir.ptr<!rec_S> -> !cir.ptr<!void>
+// CIR: cir.call @_Z3usePv(%[[GS_AS_VOID]]) : (!cir.ptr<!void> {{.*}}) -> ()
+
+// CIR: cir.global {{.*}} @_ZL2gS = #cir.const_record<{#cir.const_record<{#cir.int<80> : !s8i, #cir.int<75> : !s8i, #cir.int<3> : !s8i, #cir.int<4> : !s8i, #cir.zero : !cir.array<!s8i x 24>}> : !rec_anon_struct}> : !rec_anon_struct1
+
+// LLVM: @_ZL2gS = internal global { <{ i8, i8, i8, i8, [24 x i8] }> } { <{ i8, i8, i8, i8, [24 x i8] }> <{ i8 80, i8 75, i8 3, i8 4, [24 x i8] zeroinitializer }> }, align 1
+
+// LLVM: define {{.*}} void @_ZN1RC2Ev
+// LLVM: call void @_Z3usePv(ptr noundef @_ZL2gS)
+
+// OGCG: @_ZL2gS = internal global { <{ i8, i8, i8, i8, [24 x i8] }> } { <{ i8, i8, i8, i8, [24 x i8] }> <{ i8 80, i8 75, i8 3, i8 4, [24 x i8] zeroinitializer }> }, align 1
+
+// OGCG: define {{.*}} void @_ZN1RC2Ev
+// OGCG: call void @_Z3usePv(ptr noundef @_ZL2gS)
More information about the cfe-commits
mailing list