[Mlir-commits] [mlir] [mlir][LLVM] Fold scalar LoadOp from constant GlobalOp (PR #234337)

Alexander Shaposhnikov llvmlistbot at llvm.org
Wed Oct 21 13:01:38 PDT 2026


https://github.com/alexander-shaposhnikov updated https://github.com/llvm/llvm-project/pull/234337

>From 4007cd7b9cdfbef039788754fcf267fdff7501aa 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  | 177 ++++++++++++++++++++
 3 files changed, 249 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..36a79ed008019b 100644
--- a/mlir/test/Dialect/LLVMIR/canonicalize.mlir
+++ b/mlir/test/Dialect/LLVMIR/canonicalize.mlir
@@ -549,3 +549,180 @@ 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_interposable_globals
+llvm.func @do_not_fold_load_from_interposable_globals() -> i32 {
+  // CHECK-NEXT: %[[WEAK:.*]] = llvm.mlir.addressof @weak_global : !llvm.ptr
+  // CHECK-NEXT: %[[WEAK_VAL:.*]] = llvm.load %[[WEAK]] : !llvm.ptr -> i32
+  // CHECK-NEXT: %[[LINKONCE:.*]] = llvm.mlir.addressof @linkonce_global : !llvm.ptr
+  // CHECK-NEXT: %[[LINKONCE_VAL:.*]] = llvm.load %[[LINKONCE]] : !llvm.ptr -> i32
+  // CHECK-NEXT: %[[EXTERN_WEAK:.*]] = llvm.mlir.addressof @extern_weak_global : !llvm.ptr
+  // CHECK-NEXT: %[[EXTERN_WEAK_VAL:.*]] = llvm.load %[[EXTERN_WEAK]] : !llvm.ptr -> i32
+  // CHECK-NEXT: %[[COMMON:.*]] = llvm.mlir.addressof @common_global : !llvm.ptr
+  // CHECK-NEXT: %[[COMMON_VAL:.*]] = llvm.load %[[COMMON]] : !llvm.ptr -> i32
+  // CHECK-NEXT: %[[SUM0:.*]] = llvm.add %[[WEAK_VAL]], %[[LINKONCE_VAL]] : i32
+  // CHECK-NEXT: %[[SUM1:.*]] = llvm.add %[[SUM0]], %[[EXTERN_WEAK_VAL]] : i32
+  // CHECK-NEXT: %[[SUM2:.*]] = llvm.add %[[SUM1]], %[[COMMON_VAL]] : i32
+  // CHECK-NEXT: llvm.return %[[SUM2]] : i32
+  %0 = llvm.mlir.addressof @weak_global : !llvm.ptr
+  %1 = llvm.load %0 : !llvm.ptr -> i32
+  %2 = llvm.mlir.addressof @linkonce_global : !llvm.ptr
+  %3 = llvm.load %2 : !llvm.ptr -> i32
+  %4 = llvm.mlir.addressof @extern_weak_global : !llvm.ptr
+  %5 = llvm.load %4 : !llvm.ptr -> i32
+  %6 = llvm.mlir.addressof @common_global : !llvm.ptr
+  %7 = llvm.load %6 : !llvm.ptr -> i32
+  %8 = llvm.add %1, %3 : i32
+  %9 = llvm.add %8, %5 : i32
+  %10 = llvm.add %9, %7 : i32
+  llvm.return %10 : 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