[Mlir-commits] [mlir] [mlir][LLVM] Fold LoadOp from constant GlobalOp (PR #234337)
Alexander Shaposhnikov
llvmlistbot at llvm.org
Wed Oct 21 13:54:05 PDT 2026
https://github.com/alexander-shaposhnikov updated https://github.com/llvm/llvm-project/pull/234337
>From 147487576b65f182f48de65d5e5c23e179061839 Mon Sep 17 00:00:00 2001
From: Alexander Shaposhnikov <ashaposhnikov at google.com>
Date: Wed, 21 Oct 2026 04:41:25 -0700
Subject: [PATCH] [mlir][LLVM] Fold scalar LoadOp from constant GlobalOp
Add a folder to LLVM::LoadOp that folds non-volatile, non-atomic scalar
loads directly from non-interposable constant LLVM::GlobalOp symbols
into their constant initializer attributes.
---
mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td | 1 +
mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp | 71 ++++++++
mlir/test/Dialect/LLVMIR/canonicalize.mlir | 189 ++++++++++++++++++++
3 files changed, 261 insertions(+)
diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
index 9ff3b2d75e3f67..902fcfd944ce10 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
@@ -485,6 +485,7 @@ def LLVM_LoadOp : LLVM_MemAccessOpBase<"load",
CArg<"AtomicOrdering", "AtomicOrdering::not_atomic">:$ordering,
CArg<"StringRef", "StringRef()">:$syncscope)>
];
+ let hasFolder = 1;
let hasVerifier = 1;
}
diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
index 1c8d2e229b7d5c..99bf771ab06627 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
@@ -940,6 +940,77 @@ void LoadOp::build(OpBuilder &builder, OperationState &state, Type type,
/*tbaa=*/nullptr);
}
+/// Returns true if the global's initializer may be replaced at link time, in
+/// which case loads from it must not be folded. Mirrors
+/// `llvm::GlobalValue::isInterposableLinkage`.
+static bool isInterposableLinkage(Linkage linkage) {
+ switch (linkage) {
+ case Linkage::Weak:
+ case Linkage::Linkonce:
+ case Linkage::ExternWeak:
+ case Linkage::Common:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/// Returns the attribute the given constant global is initialized with, or
+/// null if the initializer is absent or not a constant.
+static Attribute getConstantGlobalInitializer(GlobalOp globalOp) {
+ if (Attribute value = globalOp.getValueOrNull())
+ return value;
+ Block *initBlock = globalOp.getInitializerBlock();
+ if (!initBlock)
+ return {};
+ auto retOp = dyn_cast<LLVM::ReturnOp>(initBlock->getTerminator());
+ if (!retOp || !retOp.getArg())
+ return {};
+ Operation *defOp = retOp.getArg().getDefiningOp();
+ if (auto cstOp = dyn_cast_if_present<LLVM::ConstantOp>(defOp))
+ return cstOp.getValue();
+ if (isa_and_present<LLVM::ZeroOp>(defOp))
+ return LLVM::ZeroAttr::get(globalOp.getContext());
+ return {};
+}
+
+OpFoldResult LLVM::LoadOp::fold(FoldAdaptor adaptor) {
+ if (getVolatile_() || getOrdering() != AtomicOrdering::not_atomic)
+ return {};
+
+ // `llvm.mlir.addressof` is constant-like and folds to its symbol reference,
+ // so the address operand is already materialized as an attribute here.
+ auto symRef = dyn_cast_if_present<FlatSymbolRefAttr>(adaptor.getAddr());
+ if (!symRef)
+ return {};
+
+ // Mirror `llvm::GlobalVariable::hasDefinitiveInitializer`: the global must
+ // be constant, non-interposable, and not externally initialized.
+ auto globalOp = SymbolTable::lookupNearestSymbolFrom<GlobalOp>(*this, symRef);
+ if (!globalOp || !globalOp.getConstant() ||
+ globalOp.getExternallyInitialized() ||
+ isInterposableLinkage(globalOp.getLinkage()))
+ return {};
+
+ if (globalOp.getType() != getType())
+ return {};
+
+ Attribute value = getConstantGlobalInitializer(globalOp);
+ if (!value)
+ return {};
+
+ if (isa<LLVM::ZeroAttr>(value)) {
+ if (Attribute zero = Builder(getContext()).getZeroAttr(getType()))
+ return zero;
+ return value;
+ }
+
+ if (ConstantOp::isBuildableWith(value, getType()))
+ return value;
+
+ return {};
+}
+
//===----------------------------------------------------------------------===//
// StoreOp
//===----------------------------------------------------------------------===//
diff --git a/mlir/test/Dialect/LLVMIR/canonicalize.mlir b/mlir/test/Dialect/LLVMIR/canonicalize.mlir
index 2dd0bbfe4922bd..097c9113a8d608 100644
--- a/mlir/test/Dialect/LLVMIR/canonicalize.mlir
+++ b/mlir/test/Dialect/LLVMIR/canonicalize.mlir
@@ -549,3 +549,192 @@ llvm.func @metadata_as_value_dedup() -> i32 {
%4 = llvm.add %2, %3 : i32
llvm.return %4 : i32
}
+
+// -----
+
+llvm.mlir.global internal constant @const_global_i32(42 : i32) : i32
+llvm.mlir.global internal constant @const_global_f32(3.5 : f32) : f32
+llvm.mlir.global internal constant @const_global_vec(dense<[1, 2, 3, 4]> : vector<4xi32>) : vector<4xi32>
+llvm.mlir.global internal constant @const_global_region() : i32 {
+ %0 = llvm.mlir.constant(7 : i32) : i32
+ llvm.return %0 : i32
+}
+llvm.mlir.global internal constant @const_global_zero_region() : i32 {
+ %0 = llvm.mlir.zero : i32
+ llvm.return %0 : i32
+}
+llvm.mlir.global internal constant @const_global_zero_ptr_region() : !llvm.ptr {
+ %0 = llvm.mlir.zero : !llvm.ptr
+ llvm.return %0 : !llvm.ptr
+}
+llvm.mlir.global linkonce_odr constant @const_global_linkonce_odr(5 : i32) : i32
+llvm.mlir.global internal @mutable_global_i32(42 : i32) : i32
+llvm.mlir.global internal constant @externally_initialized_global(42 : i32) {externally_initialized} : i32
+llvm.mlir.global weak constant @weak_global(42 : i32) : i32
+llvm.mlir.global linkonce constant @linkonce_global(42 : i32) : i32
+llvm.mlir.global extern_weak constant @extern_weak_global() : i32
+llvm.mlir.global common constant @common_global(0 : i32) : i32
+llvm.mlir.global external constant @external_decl() : i32
+
+// CHECK-LABEL: llvm.func @fold_load_from_constant_global_int
+llvm.func @fold_load_from_constant_global_int() -> i32 {
+ // CHECK-NEXT: %[[C42:.*]] = llvm.mlir.constant(42 : i32) : i32
+ // CHECK-NEXT: llvm.return %[[C42]] : i32
+ %0 = llvm.mlir.addressof @const_global_i32 : !llvm.ptr
+ %1 = llvm.load %0 : !llvm.ptr -> i32
+ llvm.return %1 : i32
+}
+
+// CHECK-LABEL: llvm.func @fold_load_from_constant_global_float
+llvm.func @fold_load_from_constant_global_float() -> f32 {
+ // CHECK-NEXT: %[[C35:.*]] = llvm.mlir.constant(3.500000e+00 : f32) : f32
+ // CHECK-NEXT: llvm.return %[[C35]] : f32
+ %0 = llvm.mlir.addressof @const_global_f32 : !llvm.ptr
+ %1 = llvm.load %0 : !llvm.ptr -> f32
+ llvm.return %1 : f32
+}
+
+// CHECK-LABEL: llvm.func @fold_load_from_constant_global_vector
+llvm.func @fold_load_from_constant_global_vector() -> vector<4xi32> {
+ // CHECK-NEXT: %[[VEC:.*]] = llvm.mlir.constant(dense<[1, 2, 3, 4]> : vector<4xi32>) : vector<4xi32>
+ // CHECK-NEXT: llvm.return %[[VEC]] : vector<4xi32>
+ %0 = llvm.mlir.addressof @const_global_vec : !llvm.ptr
+ %1 = llvm.load %0 : !llvm.ptr -> vector<4xi32>
+ llvm.return %1 : vector<4xi32>
+}
+
+// CHECK-LABEL: llvm.func @fold_load_from_constant_global_region
+llvm.func @fold_load_from_constant_global_region() -> i32 {
+ // CHECK-NEXT: %[[C7:.*]] = llvm.mlir.constant(7 : i32) : i32
+ // CHECK-NEXT: llvm.return %[[C7]] : i32
+ %0 = llvm.mlir.addressof @const_global_region : !llvm.ptr
+ %1 = llvm.load %0 : !llvm.ptr -> i32
+ llvm.return %1 : i32
+}
+
+// CHECK-LABEL: llvm.func @fold_load_from_constant_global_zero_region
+llvm.func @fold_load_from_constant_global_zero_region() -> i32 {
+ // CHECK-NEXT: %[[C0:.*]] = llvm.mlir.constant(0 : i32) : i32
+ // CHECK-NEXT: llvm.return %[[C0]] : i32
+ %0 = llvm.mlir.addressof @const_global_zero_region : !llvm.ptr
+ %1 = llvm.load %0 : !llvm.ptr -> i32
+ llvm.return %1 : i32
+}
+
+// CHECK-LABEL: llvm.func @fold_load_from_constant_global_zero_ptr_region
+llvm.func @fold_load_from_constant_global_zero_ptr_region() -> !llvm.ptr {
+ // CHECK-NEXT: %[[ZERO:.*]] = llvm.mlir.zero : !llvm.ptr
+ // CHECK-NEXT: llvm.return %[[ZERO]] : !llvm.ptr
+ %0 = llvm.mlir.addressof @const_global_zero_ptr_region : !llvm.ptr
+ %1 = llvm.load %0 : !llvm.ptr -> !llvm.ptr
+ llvm.return %1 : !llvm.ptr
+}
+
+// CHECK-LABEL: llvm.func @fold_load_from_constant_global_linkonce_odr
+llvm.func @fold_load_from_constant_global_linkonce_odr() -> i32 {
+ // CHECK-NEXT: %[[C5:.*]] = llvm.mlir.constant(5 : i32) : i32
+ // CHECK-NEXT: llvm.return %[[C5]] : i32
+ %0 = llvm.mlir.addressof @const_global_linkonce_odr : !llvm.ptr
+ %1 = llvm.load %0 : !llvm.ptr -> i32
+ llvm.return %1 : i32
+}
+
+// CHECK-LABEL: llvm.func @do_not_fold_load_from_mutable_global
+llvm.func @do_not_fold_load_from_mutable_global() -> i32 {
+ // CHECK-NEXT: %[[ADDR:.*]] = llvm.mlir.addressof @mutable_global_i32 : !llvm.ptr
+ // CHECK-NEXT: %[[VAL:.*]] = llvm.load %[[ADDR]] : !llvm.ptr -> i32
+ // CHECK-NEXT: llvm.return %[[VAL]] : i32
+ %0 = llvm.mlir.addressof @mutable_global_i32 : !llvm.ptr
+ %1 = llvm.load %0 : !llvm.ptr -> i32
+ llvm.return %1 : i32
+}
+
+// CHECK-LABEL: llvm.func @do_not_fold_load_from_externally_initialized_global
+llvm.func @do_not_fold_load_from_externally_initialized_global() -> i32 {
+ // CHECK-NEXT: %[[ADDR:.*]] = llvm.mlir.addressof @externally_initialized_global : !llvm.ptr
+ // CHECK-NEXT: %[[VAL:.*]] = llvm.load %[[ADDR]] : !llvm.ptr -> i32
+ // CHECK-NEXT: llvm.return %[[VAL]] : i32
+ %0 = llvm.mlir.addressof @externally_initialized_global : !llvm.ptr
+ %1 = llvm.load %0 : !llvm.ptr -> i32
+ llvm.return %1 : i32
+}
+
+// CHECK-LABEL: llvm.func @do_not_fold_load_from_weak_global
+llvm.func @do_not_fold_load_from_weak_global() -> i32 {
+ // CHECK-NEXT: %[[ADDR:.*]] = llvm.mlir.addressof @weak_global : !llvm.ptr
+ // CHECK-NEXT: %[[VAL:.*]] = llvm.load %[[ADDR]] : !llvm.ptr -> i32
+ // CHECK-NEXT: llvm.return %[[VAL]] : i32
+ %0 = llvm.mlir.addressof @weak_global : !llvm.ptr
+ %1 = llvm.load %0 : !llvm.ptr -> i32
+ llvm.return %1 : i32
+}
+
+// CHECK-LABEL: llvm.func @do_not_fold_load_from_linkonce_global
+llvm.func @do_not_fold_load_from_linkonce_global() -> i32 {
+ // CHECK-NEXT: %[[ADDR:.*]] = llvm.mlir.addressof @linkonce_global : !llvm.ptr
+ // CHECK-NEXT: %[[VAL:.*]] = llvm.load %[[ADDR]] : !llvm.ptr -> i32
+ // CHECK-NEXT: llvm.return %[[VAL]] : i32
+ %0 = llvm.mlir.addressof @linkonce_global : !llvm.ptr
+ %1 = llvm.load %0 : !llvm.ptr -> i32
+ llvm.return %1 : i32
+}
+
+// CHECK-LABEL: llvm.func @do_not_fold_load_from_extern_weak_global
+llvm.func @do_not_fold_load_from_extern_weak_global() -> i32 {
+ // CHECK-NEXT: %[[ADDR:.*]] = llvm.mlir.addressof @extern_weak_global : !llvm.ptr
+ // CHECK-NEXT: %[[VAL:.*]] = llvm.load %[[ADDR]] : !llvm.ptr -> i32
+ // CHECK-NEXT: llvm.return %[[VAL]] : i32
+ %0 = llvm.mlir.addressof @extern_weak_global : !llvm.ptr
+ %1 = llvm.load %0 : !llvm.ptr -> i32
+ llvm.return %1 : i32
+}
+
+// CHECK-LABEL: llvm.func @do_not_fold_load_from_common_global
+llvm.func @do_not_fold_load_from_common_global() -> i32 {
+ // CHECK-NEXT: %[[ADDR:.*]] = llvm.mlir.addressof @common_global : !llvm.ptr
+ // CHECK-NEXT: %[[VAL:.*]] = llvm.load %[[ADDR]] : !llvm.ptr -> i32
+ // CHECK-NEXT: llvm.return %[[VAL]] : i32
+ %0 = llvm.mlir.addressof @common_global : !llvm.ptr
+ %1 = llvm.load %0 : !llvm.ptr -> i32
+ llvm.return %1 : i32
+}
+
+// CHECK-LABEL: llvm.func @do_not_fold_load_from_external_declaration
+llvm.func @do_not_fold_load_from_external_declaration() -> i32 {
+ // CHECK-NEXT: %[[ADDR:.*]] = llvm.mlir.addressof @external_decl : !llvm.ptr
+ // CHECK-NEXT: %[[VAL:.*]] = llvm.load %[[ADDR]] : !llvm.ptr -> i32
+ // CHECK-NEXT: llvm.return %[[VAL]] : i32
+ %0 = llvm.mlir.addressof @external_decl : !llvm.ptr
+ %1 = llvm.load %0 : !llvm.ptr -> i32
+ llvm.return %1 : i32
+}
+
+// CHECK-LABEL: llvm.func @do_not_fold_volatile_load_from_constant_global
+llvm.func @do_not_fold_volatile_load_from_constant_global() -> i32 {
+ // CHECK-NEXT: %[[ADDR:.*]] = llvm.mlir.addressof @const_global_i32 : !llvm.ptr
+ // CHECK-NEXT: %[[VAL:.*]] = llvm.load volatile %[[ADDR]] : !llvm.ptr -> i32
+ // CHECK-NEXT: llvm.return %[[VAL]] : i32
+ %0 = llvm.mlir.addressof @const_global_i32 : !llvm.ptr
+ %1 = llvm.load volatile %0 : !llvm.ptr -> i32
+ llvm.return %1 : i32
+}
+
+// CHECK-LABEL: llvm.func @do_not_fold_atomic_load_from_constant_global
+llvm.func @do_not_fold_atomic_load_from_constant_global() -> i32 {
+ // CHECK-NEXT: %[[ADDR:.*]] = llvm.mlir.addressof @const_global_i32 : !llvm.ptr
+ // CHECK-NEXT: %[[VAL:.*]] = llvm.load %[[ADDR]] atomic monotonic <alignment = 4> : !llvm.ptr -> i32
+ // CHECK-NEXT: llvm.return %[[VAL]] : i32
+ %0 = llvm.mlir.addressof @const_global_i32 : !llvm.ptr
+ %1 = llvm.load %0 atomic monotonic <alignment = 4> : !llvm.ptr -> i32
+ llvm.return %1 : i32
+}
+
+// CHECK-LABEL: llvm.func @do_not_fold_load_with_mismatched_type
+llvm.func @do_not_fold_load_with_mismatched_type() -> i64 {
+ // CHECK-NEXT: %[[ADDR:.*]] = llvm.mlir.addressof @const_global_i32 : !llvm.ptr
+ // CHECK-NEXT: %[[VAL:.*]] = llvm.load %[[ADDR]] : !llvm.ptr -> i64
+ // CHECK-NEXT: llvm.return %[[VAL]] : i64
+ %0 = llvm.mlir.addressof @const_global_i32 : !llvm.ptr
+ %1 = llvm.load %0 : !llvm.ptr -> i64
+ llvm.return %1 : i64
+}
More information about the Mlir-commits
mailing list