[Mlir-commits] [mlir] [mlir][VectorToLLVM] emit `inbounds|nuw` GEP flags when lowering `vector.load/store` (PR #202118)
Federico Bruzzone
llvmlistbot at llvm.org
Tue Jun 9 11:52:38 PDT 2026
https://github.com/FedericoBruzzone updated https://github.com/llvm/llvm-project/pull/202118
>From f174b30074d92c8c11ce527dfa3fd68ea20a9f7b Mon Sep 17 00:00:00 2001
From: Federico Bruzzone <federico.bruzzone.i at gmail.com>
Date: Sun, 7 Jun 2026 12:52:36 +0200
Subject: [PATCH] [mlir][VectorToLLVM] emit inbounds|nuw GEP flags when
lowering vector.load/store
Signed-off-by: Federico Bruzzone <federico.bruzzone.i at gmail.com>
---
.../VectorToLLVM/ConvertVectorToLLVM.cpp | 13 +++-
.../Conversion/GPUCommon/transfer_write.mlir | 2 +-
.../VectorToLLVM/vector-scalable-memcpy.mlir | 4 +-
.../vector-to-llvm-interface.mlir | 60 +++++++++----------
.../VectorToLLVM/vector-xfer-to-llvm.mlir | 4 +-
.../SuperVectorize/vectorize_2d_inbounds.mlir | 5 +-
.../vectorize_inbounds_llvmopt.mlir | 49 +++++++++++++++
7 files changed, 99 insertions(+), 38 deletions(-)
create mode 100644 mlir/test/Dialect/Affine/SuperVectorize/vectorize_inbounds_llvmopt.mlir
diff --git a/mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVM.cpp b/mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVM.cpp
index 43e0824fef6cd..0ba48b1f3862e 100644
--- a/mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVM.cpp
+++ b/mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVM.cpp
@@ -256,10 +256,19 @@ class VectorLoadStoreConversion : public ConvertOpToLLVMPattern<LoadOrStoreOp> {
"could not resolve alignment");
// Resolve address.
+ // Per vector.load/store spec, indices must be in-bounds (0 <= idx <
+ // dim_size). Emit inbounds|nuw so LLVM can apply no-wrap optimizations on
+ // the generated index arithmetic and GEP. Masked variants are designed for
+ // near-boundary access, so they conservatively omit these flags.
+ LLVM::GEPNoWrapFlags noWrapFlags = LLVM::GEPNoWrapFlags::none;
+ if constexpr (std::is_same_v<LoadOrStoreOp, vector::LoadOp> ||
+ std::is_same_v<LoadOrStoreOp, vector::StoreOp>)
+ noWrapFlags = LLVM::GEPNoWrapFlags::inbounds | LLVM::GEPNoWrapFlags::nuw;
auto vtype = cast<VectorType>(
this->typeConverter->convertType(loadOrStoreOp.getVectorType()));
- Value dataPtr = this->getStridedElementPtr(
- rewriter, loc, memRefTy, adaptor.getBase(), adaptor.getIndices());
+ Value dataPtr =
+ this->getStridedElementPtr(rewriter, loc, memRefTy, adaptor.getBase(),
+ adaptor.getIndices(), noWrapFlags);
replaceLoadOrStoreOp(loadOrStoreOp, adaptor, vtype, dataPtr, align,
rewriter);
return success();
diff --git a/mlir/test/Conversion/GPUCommon/transfer_write.mlir b/mlir/test/Conversion/GPUCommon/transfer_write.mlir
index 4d2ae8c39240c..34dd28077eb47 100644
--- a/mlir/test/Conversion/GPUCommon/transfer_write.mlir
+++ b/mlir/test/Conversion/GPUCommon/transfer_write.mlir
@@ -3,7 +3,7 @@
// CHECK-LABEL: @warp_extract
// CHECK-SAME: %[[VEC:[a-zA-Z0-9_]+]]: vector<1xf32>
// CHECK:%[[BASE:[0-9]+]] = llvm.extractvalue
-// CHECK:%[[PTR:[0-9]+]] = llvm.getelementptr %[[BASE]]
+// CHECK:%[[PTR:[0-9]+]] = llvm.getelementptr inbounds|nuw %[[BASE]]
// CHECK:llvm.store %[[VEC]], %[[PTR]] {alignment = 4 : i64} : vector<1xf32>, !llvm.ptr
func.func @warp_extract(%arg0: index, %arg1: memref<1024x1024xf32>, %arg2: vector<1xf32>) {
diff --git a/mlir/test/Conversion/VectorToLLVM/vector-scalable-memcpy.mlir b/mlir/test/Conversion/VectorToLLVM/vector-scalable-memcpy.mlir
index 80e6caa05db5e..58fb69e03c85b 100644
--- a/mlir/test/Conversion/VectorToLLVM/vector-scalable-memcpy.mlir
+++ b/mlir/test/Conversion/VectorToLLVM/vector-scalable-memcpy.mlir
@@ -12,11 +12,11 @@ func.func @vector_scalable_memcopy(%src : memref<?xf32>, %dst : memref<?xf32>, %
scf.for %i0 = %c0 to %size step %step {
// CHECK: [[DATAIDX:%[0-9]+]] = builtin.unrealized_conversion_cast [[LOOPIDX]] : index to i64
// CHECK: [[SRCMEM:%[0-9]+]] = llvm.extractvalue [[SRCMRS]][1] : !llvm.struct<(ptr
- // CHECK-NEXT: [[SRCPTR:%[0-9]+]] = llvm.getelementptr [[SRCMEM]]{{.}}[[DATAIDX]]{{.}} : (!llvm.ptr, i64) -> !llvm.ptr, f32
+ // CHECK-NEXT: [[SRCPTR:%[0-9]+]] = llvm.getelementptr inbounds|nuw [[SRCMEM]]{{.}}[[DATAIDX]]{{.}} : (!llvm.ptr, i64) -> !llvm.ptr, f32
// CHECK-NEXT: [[LDVAL:%[0-9]+]] = llvm.load [[SRCPTR]]{{.*}}: !llvm.ptr -> vector<[4]xf32>
%0 = vector.load %src[%i0] : memref<?xf32>, vector<[4]xf32>
// CHECK: [[DSTMEM:%[0-9]+]] = llvm.extractvalue [[DSTMRS]][1] : !llvm.struct<(ptr
- // CHECK-NEXT: [[DSTPTR:%[0-9]+]] = llvm.getelementptr [[DSTMEM]]{{.}}[[DATAIDX]]{{.}} : (!llvm.ptr, i64) -> !llvm.ptr, f32
+ // CHECK-NEXT: [[DSTPTR:%[0-9]+]] = llvm.getelementptr inbounds|nuw [[DSTMEM]]{{.}}[[DATAIDX]]{{.}} : (!llvm.ptr, i64) -> !llvm.ptr, f32
// CHECK-NEXT: llvm.store [[LDVAL]], [[DSTPTR]]{{.*}}: vector<[4]xf32>, !llvm.ptr
vector.store %0, %dst[%i0] : memref<?xf32>, vector<[4]xf32>
}
diff --git a/mlir/test/Conversion/VectorToLLVM/vector-to-llvm-interface.mlir b/mlir/test/Conversion/VectorToLLVM/vector-to-llvm-interface.mlir
index d570d46e11b4a..0dda2333f962f 100644
--- a/mlir/test/Conversion/VectorToLLVM/vector-to-llvm-interface.mlir
+++ b/mlir/test/Conversion/VectorToLLVM/vector-to-llvm-interface.mlir
@@ -1588,9 +1588,9 @@ func.func @load(%memref : memref<200x100xf32>, %i : index, %j : index) -> vector
// CHECK-LABEL: func @load
// CHECK: %[[C100:.*]] = llvm.mlir.constant(100 : index) : i64
-// CHECK: %[[MUL:.*]] = llvm.mul %{{.*}}, %[[C100]] : i64
-// CHECK: %[[ADD:.*]] = llvm.add %[[MUL]], %{{.*}} : i64
-// CHECK: %[[GEP:.*]] = llvm.getelementptr %{{.*}}[%[[ADD]]] : (!llvm.ptr, i64) -> !llvm.ptr, f32
+// CHECK: %[[MUL:.*]] = llvm.mul %{{.*}}, %[[C100]] overflow<nsw, nuw> : i64
+// CHECK: %[[ADD:.*]] = llvm.add %[[MUL]], %{{.*}} overflow<nsw, nuw> : i64
+// CHECK: %[[GEP:.*]] = llvm.getelementptr inbounds|nuw %{{.*}}[%[[ADD]]] : (!llvm.ptr, i64) -> !llvm.ptr, f32
// CHECK: llvm.load %[[GEP]] {alignment = 4 : i64} : !llvm.ptr -> vector<8xf32>
// -----
@@ -1602,9 +1602,9 @@ func.func @load_scalable(%memref : memref<200x100xf32>, %i : index, %j : index)
// CHECK-LABEL: func @load_scalable
// CHECK: %[[C100:.*]] = llvm.mlir.constant(100 : index) : i64
-// CHECK: %[[MUL:.*]] = llvm.mul %{{.*}}, %[[C100]] : i64
-// CHECK: %[[ADD:.*]] = llvm.add %[[MUL]], %{{.*}} : i64
-// CHECK: %[[GEP:.*]] = llvm.getelementptr %{{.*}}[%[[ADD]]] : (!llvm.ptr, i64) -> !llvm.ptr, f32
+// CHECK: %[[MUL:.*]] = llvm.mul %{{.*}}, %[[C100]] overflow<nsw, nuw> : i64
+// CHECK: %[[ADD:.*]] = llvm.add %[[MUL]], %{{.*}} overflow<nsw, nuw> : i64
+// CHECK: %[[GEP:.*]] = llvm.getelementptr inbounds|nuw %{{.*}}[%[[ADD]]] : (!llvm.ptr, i64) -> !llvm.ptr, f32
// CHECK: llvm.load %[[GEP]] {alignment = 4 : i64} : !llvm.ptr -> vector<[8]xf32>
// -----
@@ -1616,9 +1616,9 @@ func.func @load_nontemporal(%memref : memref<200x100xf32>, %i : index, %j : inde
// CHECK-LABEL: func @load_nontemporal
// CHECK: %[[C100:.*]] = llvm.mlir.constant(100 : index) : i64
-// CHECK: %[[MUL:.*]] = llvm.mul %{{.*}}, %[[C100]] : i64
-// CHECK: %[[ADD:.*]] = llvm.add %[[MUL]], %{{.*}} : i64
-// CHECK: %[[GEP:.*]] = llvm.getelementptr %{{.*}}[%[[ADD]]] : (!llvm.ptr, i64) -> !llvm.ptr, f32
+// CHECK: %[[MUL:.*]] = llvm.mul %{{.*}}, %[[C100]] overflow<nsw, nuw> : i64
+// CHECK: %[[ADD:.*]] = llvm.add %[[MUL]], %{{.*}} overflow<nsw, nuw> : i64
+// CHECK: %[[GEP:.*]] = llvm.getelementptr inbounds|nuw %{{.*}}[%[[ADD]]] : (!llvm.ptr, i64) -> !llvm.ptr, f32
// CHECK: llvm.load %[[GEP]] {alignment = 4 : i64, nontemporal} : !llvm.ptr -> vector<8xf32>
// -----
@@ -1630,9 +1630,9 @@ func.func @load_nontemporal_scalable(%memref : memref<200x100xf32>, %i : index,
// CHECK-LABEL: func @load_nontemporal_scalable
// CHECK: %[[C100:.*]] = llvm.mlir.constant(100 : index) : i64
-// CHECK: %[[MUL:.*]] = llvm.mul %{{.*}}, %[[C100]] : i64
-// CHECK: %[[ADD:.*]] = llvm.add %[[MUL]], %{{.*}} : i64
-// CHECK: %[[GEP:.*]] = llvm.getelementptr %{{.*}}[%[[ADD]]] : (!llvm.ptr, i64) -> !llvm.ptr, f32
+// CHECK: %[[MUL:.*]] = llvm.mul %{{.*}}, %[[C100]] overflow<nsw, nuw> : i64
+// CHECK: %[[ADD:.*]] = llvm.add %[[MUL]], %{{.*}} overflow<nsw, nuw> : i64
+// CHECK: %[[GEP:.*]] = llvm.getelementptr inbounds|nuw %{{.*}}[%[[ADD]]] : (!llvm.ptr, i64) -> !llvm.ptr, f32
// CHECK: llvm.load %[[GEP]] {alignment = 4 : i64, nontemporal} : !llvm.ptr -> vector<[8]xf32>
// -----
@@ -1670,9 +1670,9 @@ func.func @load_0d(%memref : memref<200x100xf32>, %i : index, %j : index) -> vec
// CHECK: %[[CAST_MEMREF:.*]] = builtin.unrealized_conversion_cast %{{.*}} : memref<200x100xf32> to !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)>
// CHECK: %[[REF:.*]] = llvm.extractvalue %[[CAST_MEMREF]][1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)>
// CHECK: %[[C100:.*]] = llvm.mlir.constant(100 : index) : i64
-// CHECK: %[[MUL:.*]] = llvm.mul %[[I]], %[[C100]] : i64
-// CHECK: %[[ADD:.*]] = llvm.add %[[MUL]], %[[J]] : i64
-// CHECK: %[[ADDR:.*]] = llvm.getelementptr %[[REF]][%[[ADD]]] : (!llvm.ptr, i64) -> !llvm.ptr, f32
+// CHECK: %[[MUL:.*]] = llvm.mul %[[I]], %[[C100]] overflow<nsw, nuw> : i64
+// CHECK: %[[ADD:.*]] = llvm.add %[[MUL]], %[[J]] overflow<nsw, nuw> : i64
+// CHECK: %[[ADDR:.*]] = llvm.getelementptr inbounds|nuw %[[REF]][%[[ADD]]] : (!llvm.ptr, i64) -> !llvm.ptr, f32
// CHECK: %[[LOAD:.*]] = llvm.load %[[ADDR]] {alignment = 4 : i64} : !llvm.ptr -> vector<1xf32>
// CHECK: %[[RES:.*]] = builtin.unrealized_conversion_cast %[[LOAD]] : vector<1xf32> to vector<f32>
// CHECK: return %[[RES]] : vector<f32>
@@ -1701,9 +1701,9 @@ func.func @store(%memref : memref<200x100xf32>, %i : index, %j : index) {
// CHECK-LABEL: func @store
// CHECK: %[[C100:.*]] = llvm.mlir.constant(100 : index) : i64
-// CHECK: %[[MUL:.*]] = llvm.mul %{{.*}}, %[[C100]] : i64
-// CHECK: %[[ADD:.*]] = llvm.add %[[MUL]], %{{.*}} : i64
-// CHECK: %[[GEP:.*]] = llvm.getelementptr %{{.*}}[%[[ADD]]] : (!llvm.ptr, i64) -> !llvm.ptr, f32
+// CHECK: %[[MUL:.*]] = llvm.mul %{{.*}}, %[[C100]] overflow<nsw, nuw> : i64
+// CHECK: %[[ADD:.*]] = llvm.add %[[MUL]], %{{.*}} overflow<nsw, nuw> : i64
+// CHECK: %[[GEP:.*]] = llvm.getelementptr inbounds|nuw %{{.*}}[%[[ADD]]] : (!llvm.ptr, i64) -> !llvm.ptr, f32
// CHECK: llvm.store %{{.*}}, %[[GEP]] {alignment = 4 : i64} : vector<4xf32>, !llvm.ptr
// -----
@@ -1716,9 +1716,9 @@ func.func @store_scalable(%memref : memref<200x100xf32>, %i : index, %j : index)
// CHECK-LABEL: func @store_scalable
// CHECK: %[[C100:.*]] = llvm.mlir.constant(100 : index) : i64
-// CHECK: %[[MUL:.*]] = llvm.mul %{{.*}}, %[[C100]] : i64
-// CHECK: %[[ADD:.*]] = llvm.add %[[MUL]], %{{.*}} : i64
-// CHECK: %[[GEP:.*]] = llvm.getelementptr %{{.*}}[%[[ADD]]] : (!llvm.ptr, i64) -> !llvm.ptr, f32
+// CHECK: %[[MUL:.*]] = llvm.mul %{{.*}}, %[[C100]] overflow<nsw, nuw> : i64
+// CHECK: %[[ADD:.*]] = llvm.add %[[MUL]], %{{.*}} overflow<nsw, nuw> : i64
+// CHECK: %[[GEP:.*]] = llvm.getelementptr inbounds|nuw %{{.*}}[%[[ADD]]] : (!llvm.ptr, i64) -> !llvm.ptr, f32
// CHECK: llvm.store %{{.*}}, %[[GEP]] {alignment = 4 : i64} : vector<[4]xf32>, !llvm.ptr
// -----
@@ -1731,9 +1731,9 @@ func.func @store_nontemporal(%memref : memref<200x100xf32>, %i : index, %j : ind
// CHECK-LABEL: func @store_nontemporal
// CHECK: %[[C100:.*]] = llvm.mlir.constant(100 : index) : i64
-// CHECK: %[[MUL:.*]] = llvm.mul %{{.*}}, %[[C100]] : i64
-// CHECK: %[[ADD:.*]] = llvm.add %[[MUL]], %{{.*}} : i64
-// CHECK: %[[GEP:.*]] = llvm.getelementptr %{{.*}}[%[[ADD]]] : (!llvm.ptr, i64) -> !llvm.ptr, f32
+// CHECK: %[[MUL:.*]] = llvm.mul %{{.*}}, %[[C100]] overflow<nsw, nuw> : i64
+// CHECK: %[[ADD:.*]] = llvm.add %[[MUL]], %{{.*}} overflow<nsw, nuw> : i64
+// CHECK: %[[GEP:.*]] = llvm.getelementptr inbounds|nuw %{{.*}}[%[[ADD]]] : (!llvm.ptr, i64) -> !llvm.ptr, f32
// CHECK: llvm.store %{{.*}}, %[[GEP]] {alignment = 4 : i64, nontemporal} : vector<4xf32>, !llvm.ptr
// -----
@@ -1746,9 +1746,9 @@ func.func @store_nontemporal_scalable(%memref : memref<200x100xf32>, %i : index,
// CHECK-LABEL: func @store_nontemporal_scalable
// CHECK: %[[C100:.*]] = llvm.mlir.constant(100 : index) : i64
-// CHECK: %[[MUL:.*]] = llvm.mul %{{.*}}, %[[C100]] : i64
-// CHECK: %[[ADD:.*]] = llvm.add %[[MUL]], %{{.*}} : i64
-// CHECK: %[[GEP:.*]] = llvm.getelementptr %{{.*}}[%[[ADD]]] : (!llvm.ptr, i64) -> !llvm.ptr, f32
+// CHECK: %[[MUL:.*]] = llvm.mul %{{.*}}, %[[C100]] overflow<nsw, nuw> : i64
+// CHECK: %[[ADD:.*]] = llvm.add %[[MUL]], %{{.*}} overflow<nsw, nuw> : i64
+// CHECK: %[[GEP:.*]] = llvm.getelementptr inbounds|nuw %{{.*}}[%[[ADD]]] : (!llvm.ptr, i64) -> !llvm.ptr, f32
// CHECK: llvm.store %{{.*}}, %[[GEP]] {alignment = 4 : i64, nontemporal} : vector<[4]xf32>, !llvm.ptr
// -----
@@ -1787,9 +1787,9 @@ func.func @store_0d(%memref : memref<200x100xf32>, %i : index, %j : index) {
// CHECK: %[[VAL:.*]] = builtin.unrealized_conversion_cast %[[CST]] : vector<f32> to vector<1xf32>
// CHECK: %[[REF:.*]] = llvm.extractvalue %[[CAST_MEMREF]][1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)>
// CHECK: %[[C100:.*]] = llvm.mlir.constant(100 : index) : i64
-// CHECK: %[[MUL:.*]] = llvm.mul %[[I]], %[[C100]] : i64
-// CHECK: %[[ADD:.*]] = llvm.add %[[MUL]], %[[J]] : i64
-// CHECK: %[[ADDR:.*]] = llvm.getelementptr %[[REF]][%[[ADD]]] : (!llvm.ptr, i64) -> !llvm.ptr, f32
+// CHECK: %[[MUL:.*]] = llvm.mul %[[I]], %[[C100]] overflow<nsw, nuw> : i64
+// CHECK: %[[ADD:.*]] = llvm.add %[[MUL]], %[[J]] overflow<nsw, nuw> : i64
+// CHECK: %[[ADDR:.*]] = llvm.getelementptr inbounds|nuw %[[REF]][%[[ADD]]] : (!llvm.ptr, i64) -> !llvm.ptr, f32
// CHECK: llvm.store %[[VAL]], %[[ADDR]] {alignment = 4 : i64} : vector<1xf32>, !llvm.ptr
// CHECK: return
diff --git a/mlir/test/Conversion/VectorToLLVM/vector-xfer-to-llvm.mlir b/mlir/test/Conversion/VectorToLLVM/vector-xfer-to-llvm.mlir
index 18deadd0d7a79..1d998e09212b4 100644
--- a/mlir/test/Conversion/VectorToLLVM/vector-xfer-to-llvm.mlir
+++ b/mlir/test/Conversion/VectorToLLVM/vector-xfer-to-llvm.mlir
@@ -298,7 +298,7 @@ func.func @transfer_read_1d_inbounds(%A : memref<?xf32>, %base: index) -> vector
// CHECK-SAME: %[[BASE:[a-zA-Z0-9]*]]: index) -> vector<17xf32>
//
// 1. Bitcast to vector form.
-// CHECK: %[[gep:.*]] = llvm.getelementptr {{.*}} :
+// CHECK: %[[gep:.*]] = llvm.getelementptr inbounds|nuw {{.*}} :
// CHECK-SAME: (!llvm.ptr, i64) -> !llvm.ptr, f32
//
// 2. Rewrite as a load.
@@ -314,7 +314,7 @@ func.func @transfer_read_1d_inbounds_scalable(%A : memref<?xf32>, %base: index)
// CHECK-SAME: %[[BASE:[a-zA-Z0-9]*]]: index) -> vector<[17]xf32>
//
// 1. Bitcast to vector form.
-// CHECK: %[[gep:.*]] = llvm.getelementptr {{.*}} :
+// CHECK: %[[gep:.*]] = llvm.getelementptr inbounds|nuw {{.*}} :
// CHECK-SAME: (!llvm.ptr, i64) -> !llvm.ptr, f32
//
// 2. Rewrite as a load.
diff --git a/mlir/test/Dialect/Affine/SuperVectorize/vectorize_2d_inbounds.mlir b/mlir/test/Dialect/Affine/SuperVectorize/vectorize_2d_inbounds.mlir
index d95c5bdee79d7..10a96654c2b36 100644
--- a/mlir/test/Dialect/Affine/SuperVectorize/vectorize_2d_inbounds.mlir
+++ b/mlir/test/Dialect/Affine/SuperVectorize/vectorize_2d_inbounds.mlir
@@ -22,8 +22,11 @@
// CHECK-NOT: vector.transfer_write
// LLVM-LABEL: llvm.func @copy
-// Verify that in_bounds lowers to plain llvm.load/store, not masked intrinsics.
+// Verify that in_bounds lowers to plain llvm.load/store with inbounds|nuw GEP,
+// not masked intrinsics, enabling LLVM to apply no-wrap optimizations.
+// LLVM: llvm.getelementptr inbounds|nuw {{.*}} : (!llvm.ptr, i64) -> !llvm.ptr, f32
// LLVM: llvm.load {{.*}} : !llvm.ptr -> vector<4xf32>
+// LLVM: llvm.getelementptr inbounds|nuw {{.*}} : (!llvm.ptr, i64) -> !llvm.ptr, f32
// LLVM: llvm.store {{.*}} : vector<4xf32>, !llvm.ptr
// LLVM-NOT: llvm.intr.masked.load
// LLVM-NOT: llvm.intr.masked.store
diff --git a/mlir/test/Dialect/Affine/SuperVectorize/vectorize_inbounds_llvmopt.mlir b/mlir/test/Dialect/Affine/SuperVectorize/vectorize_inbounds_llvmopt.mlir
new file mode 100644
index 0000000000000..aa79ae373be79
--- /dev/null
+++ b/mlir/test/Dialect/Affine/SuperVectorize/vectorize_inbounds_llvmopt.mlir
@@ -0,0 +1,49 @@
+// Verify that the inbounds|nuw flags emitted on vector.load/store GEPs (when
+// in_bounds=true) enable LLVM to perform alias analysis and produce
+// well-optimized LLVM IR. The pipeline lowers MLIR all the way to native LLVM
+// IR via mlir-translate, then passes the result through LLVM's -O2 pipeline.
+//
+// RUN: mlir-opt %s \
+// RUN: --affine-super-vectorize="virtual-vector-size=4" \
+// RUN: --lower-affine \
+// RUN: --convert-scf-to-cf \
+// RUN: --expand-strided-metadata \
+// RUN: --convert-arith-to-llvm \
+// RUN: --convert-cf-to-llvm \
+// RUN: --convert-vector-to-llvm \
+// RUN: --finalize-memref-to-llvm \
+// RUN: --convert-func-to-llvm \
+// RUN: --reconcile-unrealized-casts \
+// RUN: | mlir-translate --mlir-to-llvmir \
+// RUN: | opt -S -passes="default<O2>" \
+// RUN: | FileCheck %s --check-prefix=OPT
+
+// OPT-LABEL: define void @copy(
+// After -O2, LLVM alias-analysis annotates the source arg as read-only and
+// the destination arg as write-only. This requires the GEP to carry inbounds
+// and nuw flags (produced by our vector.load/store lowering fix) so LLVM can
+// prove the two memory regions do not overlap.
+// OPT-SAME: readonly
+// OPT-SAME: writeonly
+
+// The GEP for the load carries inbounds nuw — our fix propagated through opt.
+// OPT: getelementptr inbounds nuw
+// The MLIR-level vectorization (vector<4xf32> from affine-super-vectorize)
+// must be preserved through LLVM optimisation — no scalar regression.
+// OPT-NEXT: load <4 x float>
+// OPT-NEXT: getelementptr inbounds nuw
+// OPT-NEXT: store <4 x float>
+
+// No masked-load/store intrinsics: in_bounds=true correctly skipped masking.
+// OPT-NOT: @llvm.masked.load
+// OPT-NOT: @llvm.masked.store
+
+func.func @copy(%A: memref<512x512xf32>, %B: memref<512x512xf32>) {
+ affine.for %i = 0 to 512 {
+ affine.for %j = 0 to 512 {
+ %v = affine.load %A[%i, %j] : memref<512x512xf32>
+ affine.store %v, %B[%i, %j] : memref<512x512xf32>
+ }
+ }
+ return
+}
More information about the Mlir-commits
mailing list