[Mlir-commits] [mlir] [mlir][vector] add consistent stride verification to masked load/store and gather/scatter ops (PR #205869)

Federico Bruzzone llvmlistbot at llvm.org
Thu Jun 25 10:51:32 PDT 2026


https://github.com/FedericoBruzzone created https://github.com/llvm/llvm-project/pull/205869

Extend negative stride checks to MaskedLoadOp, MaskedStoreOp, GatherOp, and ScatterOp to match LoadOp and StoreOp behavior.

Depends on: https://github.com/llvm/llvm-project/pull/204611.

AI Disclaimer: I used AI for the tests.


@banach-space @dcaballe If you look at the latest commit, you can see why the integration tests were failing (those two calls were added in this PR).

>From d6b9d19669ca33229c1a4b3251660c5d15d48aa6 Mon Sep 17 00:00:00 2001
From: Federico Bruzzone <federico.bruzzone.i at gmail.com>
Date: Fri, 19 Jun 2026 16:17:54 +0200
Subject: [PATCH 1/6] Add consistent stride verification to masked load/store
 and gather/scatter ops

---
 mlir/lib/Dialect/Vector/IR/VectorOps.cpp | 38 ++++++++++++++++++++++
 mlir/test/Dialect/Vector/invalid.mlir    | 40 ++++++++++++++++++++++++
 2 files changed, 78 insertions(+)

diff --git a/mlir/lib/Dialect/Vector/IR/VectorOps.cpp b/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
index 81ffabca6ecf0..e6e154bd7db95 100644
--- a/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
+++ b/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
@@ -6293,6 +6293,16 @@ LogicalResult MaskedLoadOp::verify() {
   VectorType resVType = getVectorType();
   MemRefType memType = getMemRefType();
 
+  if (failed(verifyLoadStoreMemRefLayout(*this, resVType, memType)))
+    return failure();
+
+  // Negative strides are not supported on vector.maskedload.
+  auto [strides, offset] = memType.getStridesAndOffset();
+  for (int64_t stride : strides) {
+    if (ShapedType::isStatic(stride) && stride < 0)
+      return emitOpError("memref strides must be non-negative");
+  }
+
   if (failed(
           verifyElementTypesMatch(*this, memType, resVType, "base", "result")))
     return failure();
@@ -6353,6 +6363,16 @@ LogicalResult MaskedStoreOp::verify() {
   VectorType valueVType = getVectorType();
   MemRefType memType = getMemRefType();
 
+  if (failed(verifyLoadStoreMemRefLayout(*this, valueVType, memType)))
+    return failure();
+
+  // Negative strides are not supported on vector.maskedstore.
+  auto [strides, offset] = memType.getStridesAndOffset();
+  for (int64_t stride : strides) {
+    if (ShapedType::isStatic(stride) && stride < 0)
+      return emitOpError("memref strides must be non-negative");
+  }
+
   if (failed(verifyElementTypesMatch(*this, memType, valueVType, "base",
                                      "valueToStore")))
     return failure();
@@ -6414,6 +6434,15 @@ LogicalResult GatherOp::verify() {
   if (!llvm::isa<MemRefType, RankedTensorType>(baseType))
     return emitOpError("requires base to be a memref or ranked tensor type");
 
+  // Negative strides are not supported on vector.gather.
+  if (auto memRefType = dyn_cast<MemRefType>(baseType)) {
+    auto [strides, offset] = memRefType.getStridesAndOffset();
+    for (int64_t stride : strides) {
+      if (ShapedType::isStatic(stride) && stride < 0)
+        return emitOpError("memref strides must be non-negative");
+    }
+  }
+
   if (failed(
           verifyElementTypesMatch(*this, baseType, resVType, "base", "result")))
     return failure();
@@ -6528,6 +6557,15 @@ LogicalResult ScatterOp::verify() {
   if (!llvm::isa<MemRefType, RankedTensorType>(baseType))
     return emitOpError("requires base to be a memref or ranked tensor type");
 
+  // Negative strides are not supported on vector.scatter.
+  if (auto memRefType = dyn_cast<MemRefType>(baseType)) {
+    auto [strides, offset] = memRefType.getStridesAndOffset();
+    for (int64_t stride : strides) {
+      if (ShapedType::isStatic(stride) && stride < 0)
+        return emitOpError("memref strides must be non-negative");
+    }
+  }
+
   if (failed(verifyElementTypesMatch(*this, baseType, valueVType, "base",
                                      "valueToStore")))
     return failure();
diff --git a/mlir/test/Dialect/Vector/invalid.mlir b/mlir/test/Dialect/Vector/invalid.mlir
index 403581e338a6f..85b67673d11d5 100644
--- a/mlir/test/Dialect/Vector/invalid.mlir
+++ b/mlir/test/Dialect/Vector/invalid.mlir
@@ -1413,6 +1413,15 @@ func.func @maskedload_memref_mismatch(%base: memref<?xf32>, %mask: vector<16xi1>
 
 // -----
 
+func.func @maskedload_negative_stride(%src: memref<100x100xf32, strided<[-100, 1]>>, %mask: vector<8xi1>, %pass: vector<8xf32>) -> vector<8xf32> {
+  %c0 = arith.constant 0 : index
+  // expected-error @+1 {{'vector.maskedload' op memref strides must be non-negative}}
+  %0 = vector.maskedload %src[%c0, %c0], %mask, %pass : memref<100x100xf32, strided<[-100, 1]>>, vector<8xi1>, vector<8xf32> into vector<8xf32>
+  return %0 : vector<8xf32>
+}
+
+// -----
+
 //===----------------------------------------------------------------------===//
 // vector.maskedstore
 //===----------------------------------------------------------------------===//
@@ -1457,6 +1466,15 @@ func.func @maskedstore_memref_mismatch(%base: memref<?xf32>, %mask: vector<16xi1
 
 // -----
 
+func.func @maskedstore_negative_stride(%src: memref<100x100xf32, strided<[-100, 1]>>, %mask: vector<8xi1>, %value: vector<8xf32>) {
+  %c0 = arith.constant 0 : index
+  // expected-error @+1 {{'vector.maskedstore' op memref strides must be non-negative}}
+  vector.maskedstore %src[%c0, %c0], %mask, %value : memref<100x100xf32, strided<[-100, 1]>>, vector<8xi1>, vector<8xf32>
+  return
+}
+
+// -----
+
 func.func @gather_from_vector(%base: vector<16xf32>, %indices: vector<16xi32>,
                                 %mask: vector<16xi1>, %pass_thru: vector<16xf32>) {
   %c0 = arith.constant 0 : index
@@ -1554,6 +1572,17 @@ func.func @gather_tensor_alignment(%base: tensor<16xf32>, %indices: vector<16xi3
 
 // -----
 
+func.func @gather_negative_stride(%src: memref<100x100xf32, strided<[-100, 1]>>, %indices: vector<16xi32>,
+                                  %mask: vector<16xi1>, %pass_thru: vector<16xf32>) -> vector<16xf32> {
+  %c0 = arith.constant 0 : index
+  // expected-error @+1 {{'vector.gather' op memref strides must be non-negative}}
+  %0 = vector.gather %src[%c0, %c0][%indices], %mask, %pass_thru
+    : memref<100x100xf32, strided<[-100, 1]>>, vector<16xi32>, vector<16xi1>, vector<16xf32> into vector<16xf32>
+  return %0 : vector<16xf32>
+}
+
+// -----
+
 func.func @scatter_to_vector(%base: vector<16xf32>, %indices: vector<16xi32>,
                              %mask: vector<16xi1>, %pass_thru: vector<16xf32>) {
   %c0 = arith.constant 0 : index
@@ -1642,6 +1671,17 @@ func.func @scatter_tensor_alignment(%base: tensor<?xf32>, %indices: vector<16xi3
 
 // -----
 
+func.func @scatter_negative_stride(%src: memref<100x100xf32, strided<[-100, 1]>>, %indices: vector<16xi32>,
+                                   %mask: vector<16xi1>, %value: vector<16xf32>) {
+  %c0 = arith.constant 0 : index
+  // expected-error @+1 {{'vector.scatter' op memref strides must be non-negative}}
+  vector.scatter %src[%c0, %c0][%indices], %mask, %value
+    : memref<100x100xf32, strided<[-100, 1]>>, vector<16xi32>, vector<16xi1>, vector<16xf32>
+  return
+}
+
+// -----
+
 func.func @expand_base_type_mismatch(%base: memref<?xf64>, %mask: vector<16xi1>, %pass_thru: vector<16xf32>) {
   %c0 = arith.constant 0 : index
   // expected-error at +1 {{'vector.expandload' op base element type ('f64') does not match result element type ('f32')}}

>From 3c372edf8c25e212a2068beecf84278e2aee2350 Mon Sep 17 00:00:00 2001
From: Federico Bruzzone <federico.bruzzone.i at gmail.com>
Date: Sat, 20 Jun 2026 09:57:10 +0200
Subject: [PATCH 2/6] Address nits

Signed-off-by: Federico Bruzzone <federico.bruzzone.i at gmail.com>
---
 mlir/test/Dialect/Vector/invalid.mlir | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/mlir/test/Dialect/Vector/invalid.mlir b/mlir/test/Dialect/Vector/invalid.mlir
index 85b67673d11d5..800ef75fde864 100644
--- a/mlir/test/Dialect/Vector/invalid.mlir
+++ b/mlir/test/Dialect/Vector/invalid.mlir
@@ -1573,10 +1573,9 @@ func.func @gather_tensor_alignment(%base: tensor<16xf32>, %indices: vector<16xi3
 // -----
 
 func.func @gather_negative_stride(%src: memref<100x100xf32, strided<[-100, 1]>>, %indices: vector<16xi32>,
-                                  %mask: vector<16xi1>, %pass_thru: vector<16xf32>) -> vector<16xf32> {
-  %c0 = arith.constant 0 : index
+                                  %mask: vector<16xi1>, %pass_thru: vector<16xf32>, %idx: index) -> vector<16xf32> {
   // expected-error @+1 {{'vector.gather' op memref strides must be non-negative}}
-  %0 = vector.gather %src[%c0, %c0][%indices], %mask, %pass_thru
+  %0 = vector.gather %src[%idx, %idx][%indices], %mask, %pass_thru
     : memref<100x100xf32, strided<[-100, 1]>>, vector<16xi32>, vector<16xi1>, vector<16xf32> into vector<16xf32>
   return %0 : vector<16xf32>
 }
@@ -1672,10 +1671,9 @@ func.func @scatter_tensor_alignment(%base: tensor<?xf32>, %indices: vector<16xi3
 // -----
 
 func.func @scatter_negative_stride(%src: memref<100x100xf32, strided<[-100, 1]>>, %indices: vector<16xi32>,
-                                   %mask: vector<16xi1>, %value: vector<16xf32>) {
-  %c0 = arith.constant 0 : index
+                                   %mask: vector<16xi1>, %value: vector<16xf32>, %idx: index) {
   // expected-error @+1 {{'vector.scatter' op memref strides must be non-negative}}
-  vector.scatter %src[%c0, %c0][%indices], %mask, %value
+  vector.scatter %src[%idx, %idx][%indices], %mask, %value
     : memref<100x100xf32, strided<[-100, 1]>>, vector<16xi32>, vector<16xi1>, vector<16xf32>
   return
 }

>From 86e4da30a234804e654f14367a0a2df2a1c7c471 Mon Sep 17 00:00:00 2001
From: Federico Bruzzone <federico.bruzzone.i at gmail.com>
Date: Sat, 20 Jun 2026 10:05:10 +0200
Subject: [PATCH 3/6] Add rationale comments

---
 mlir/lib/Dialect/Vector/IR/VectorOps.cpp | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/mlir/lib/Dialect/Vector/IR/VectorOps.cpp b/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
index e6e154bd7db95..d951b316ef32c 100644
--- a/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
+++ b/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
@@ -6296,7 +6296,9 @@ LogicalResult MaskedLoadOp::verify() {
   if (failed(verifyLoadStoreMemRefLayout(*this, resVType, memType)))
     return failure();
 
-  // Negative strides are not supported on vector.maskedload.
+  // Negative strides are not supported on vector.maskedload. The lowering to
+  // LLVM emits arithmetic operations (e.g., GEP, mul) with nuw flags that
+  // assume non-negative strides to avoid undefined behavior.
   auto [strides, offset] = memType.getStridesAndOffset();
   for (int64_t stride : strides) {
     if (ShapedType::isStatic(stride) && stride < 0)
@@ -6366,7 +6368,9 @@ LogicalResult MaskedStoreOp::verify() {
   if (failed(verifyLoadStoreMemRefLayout(*this, valueVType, memType)))
     return failure();
 
-  // Negative strides are not supported on vector.maskedstore.
+  // Negative strides are not supported on vector.maskedstore. The lowering to
+  // LLVM emits arithmetic operations (e.g., GEP, mul) with nuw flags that
+  // assume non-negative strides to avoid undefined behavior.
   auto [strides, offset] = memType.getStridesAndOffset();
   for (int64_t stride : strides) {
     if (ShapedType::isStatic(stride) && stride < 0)
@@ -6435,6 +6439,8 @@ LogicalResult GatherOp::verify() {
     return emitOpError("requires base to be a memref or ranked tensor type");
 
   // Negative strides are not supported on vector.gather.
+  // The lowering to LLVM emits arithmetic operations (e.g., GEP, mul) with nuw
+  // flags that assume non-negative strides to avoid undefined behavior.
   if (auto memRefType = dyn_cast<MemRefType>(baseType)) {
     auto [strides, offset] = memRefType.getStridesAndOffset();
     for (int64_t stride : strides) {
@@ -6558,6 +6564,8 @@ LogicalResult ScatterOp::verify() {
     return emitOpError("requires base to be a memref or ranked tensor type");
 
   // Negative strides are not supported on vector.scatter.
+  // The lowering to LLVM emits arithmetic operations (e.g., GEP, mul) with nuw
+  // flags that assume non-negative strides to avoid undefined behavior.
   if (auto memRefType = dyn_cast<MemRefType>(baseType)) {
     auto [strides, offset] = memRefType.getStridesAndOffset();
     for (int64_t stride : strides) {

>From c92b1fab2ebef0dd18e01ae9d1498cd27d17b42d Mon Sep 17 00:00:00 2001
From: Federico Bruzzone <federico.bruzzone.i at gmail.com>
Date: Mon, 22 Jun 2026 07:54:25 +0200
Subject: [PATCH 4/6] Add docs

Signed-off-by: Federico Bruzzone <federico.bruzzone.i at gmail.com>
---
 mlir/include/mlir/Dialect/Vector/IR/VectorOps.td | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/mlir/include/mlir/Dialect/Vector/IR/VectorOps.td b/mlir/include/mlir/Dialect/Vector/IR/VectorOps.td
index 24442a6336090..a5e5095514dc2 100644
--- a/mlir/include/mlir/Dialect/Vector/IR/VectorOps.td
+++ b/mlir/include/mlir/Dialect/Vector/IR/VectorOps.td
@@ -1946,6 +1946,9 @@ def Vector_MaskedLoadOp :
        : memref<?x?xf32>, vector<16xi1>, vector<16xf32> into vector<16xf32>
     ```
 
+    The memref must have non-negative strides. Negative strides are not supported
+    and will trigger a verification error.
+
     An optional `alignment` attribute allows to specify the byte alignment of the
     load operation. It must be a positive power of 2. The operation must access
     memory at an address aligned to this boundary. Violating this requirement
@@ -2041,6 +2044,9 @@ def Vector_MaskedStoreOp :
       : memref<?x?xf32>, vector<16xi1>, vector<16xf32>
     ```
 
+    The memref must have non-negative strides. Negative strides are not supported
+    and will trigger a verification error.
+
     An optional `alignment` attribute allows to specify the byte alignment of the
     store operation. It must be a positive power of 2. The operation must access
     memory at an address aligned to this boundary. Violating this requirement
@@ -2135,6 +2141,9 @@ def Vector_GatherOp :
     during progressively lowering to bring other memory operations closer to
     hardware ISA support for a gather.
 
+    The memref must have non-negative strides. Negative strides are not supported
+    and will trigger a verification error.
+
     An optional `alignment` attribute allows to specify the byte alignment of the
     gather operation. It must be a positive power of 2. The operation must access
     memory at an address aligned to this boundary. Violating this requirement
@@ -2228,6 +2237,9 @@ def Vector_ScatterOp
     correspond to those of the `llvm.masked.scatter`
     [intrinsic](https://llvm.org/docs/LangRef.html#llvm-masked-scatter-intrinsics).
 
+    The memref must have non-negative strides. Negative strides are not supported
+    and will trigger a verification error.
+
     An optional `alignment` attribute allows to specify the byte alignment of the
     scatter operation. It must be a positive power of 2. The operation must access
     memory at an address aligned to this boundary. Violating this requirement

>From bebd0f40039445ff15604ac40f683d0974c87378 Mon Sep 17 00:00:00 2001
From: Federico Bruzzone <federico.bruzzone.i at gmail.com>
Date: Wed, 24 Jun 2026 23:02:41 +0200
Subject: [PATCH 5/6] Align the PR to 204611

Signed-off-by: Federico Bruzzone <federico.bruzzone.i at gmail.com>
---
 mlir/lib/Dialect/Vector/IR/VectorOps.cpp | 42 +++++++++---------------
 1 file changed, 16 insertions(+), 26 deletions(-)

diff --git a/mlir/lib/Dialect/Vector/IR/VectorOps.cpp b/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
index d951b316ef32c..9ce05fd70cd6b 100644
--- a/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
+++ b/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
@@ -6195,7 +6195,9 @@ LogicalResult vector::LoadOp::verify() {
   if (failed(verifyLoadStoreMemRefLayout(*this, resVecTy, memRefTy)))
     return failure();
 
-  // Negative strides are not supported on vector.load.
+  // Negative strides are not supported on vector.load. The lowering to LLVM
+  // emits arithmetic operations (e.g., GEP, mul) with nuw flags that assume
+  // non-negative strides to avoid undefined behavior.
   if (memref::hasNegativeStaticStride(memRefTy))
     return emitOpError("memref strides must be non-negative");
 
@@ -6245,7 +6247,9 @@ LogicalResult vector::StoreOp::verify() {
   if (failed(verifyLoadStoreMemRefLayout(*this, valueVecTy, memRefTy)))
     return failure();
 
-  // Negative strides are not supported on vector.store.
+  // Negative strides are not supported on vector.store. The lowering to LLVM
+  // emits arithmetic operations (e.g., GEP, mul) with nuw flags that assume
+  // non-negative strides to avoid undefined behavior.
   if (memref::hasNegativeStaticStride(memRefTy))
     return emitOpError("memref strides must be non-negative");
 
@@ -6299,11 +6303,8 @@ LogicalResult MaskedLoadOp::verify() {
   // Negative strides are not supported on vector.maskedload. The lowering to
   // LLVM emits arithmetic operations (e.g., GEP, mul) with nuw flags that
   // assume non-negative strides to avoid undefined behavior.
-  auto [strides, offset] = memType.getStridesAndOffset();
-  for (int64_t stride : strides) {
-    if (ShapedType::isStatic(stride) && stride < 0)
-      return emitOpError("memref strides must be non-negative");
-  }
+  if (memref::hasNegativeStaticStride(memType))
+    return emitOpError("memref strides must be non-negative");
 
   if (failed(
           verifyElementTypesMatch(*this, memType, resVType, "base", "result")))
@@ -6371,11 +6372,8 @@ LogicalResult MaskedStoreOp::verify() {
   // Negative strides are not supported on vector.maskedstore. The lowering to
   // LLVM emits arithmetic operations (e.g., GEP, mul) with nuw flags that
   // assume non-negative strides to avoid undefined behavior.
-  auto [strides, offset] = memType.getStridesAndOffset();
-  for (int64_t stride : strides) {
-    if (ShapedType::isStatic(stride) && stride < 0)
-      return emitOpError("memref strides must be non-negative");
-  }
+  if (memref::hasNegativeStaticStride(memType))
+    return emitOpError("memref strides must be non-negative");
 
   if (failed(verifyElementTypesMatch(*this, memType, valueVType, "base",
                                      "valueToStore")))
@@ -6441,13 +6439,9 @@ LogicalResult GatherOp::verify() {
   // Negative strides are not supported on vector.gather.
   // The lowering to LLVM emits arithmetic operations (e.g., GEP, mul) with nuw
   // flags that assume non-negative strides to avoid undefined behavior.
-  if (auto memRefType = dyn_cast<MemRefType>(baseType)) {
-    auto [strides, offset] = memRefType.getStridesAndOffset();
-    for (int64_t stride : strides) {
-      if (ShapedType::isStatic(stride) && stride < 0)
-        return emitOpError("memref strides must be non-negative");
-    }
-  }
+  if (auto memRefType = dyn_cast<MemRefType>(baseType))
+    if (memref::hasNegativeStaticStride(memRefType))
+      return emitOpError("memref strides must be non-negative");
 
   if (failed(
           verifyElementTypesMatch(*this, baseType, resVType, "base", "result")))
@@ -6566,13 +6560,9 @@ LogicalResult ScatterOp::verify() {
   // Negative strides are not supported on vector.scatter.
   // The lowering to LLVM emits arithmetic operations (e.g., GEP, mul) with nuw
   // flags that assume non-negative strides to avoid undefined behavior.
-  if (auto memRefType = dyn_cast<MemRefType>(baseType)) {
-    auto [strides, offset] = memRefType.getStridesAndOffset();
-    for (int64_t stride : strides) {
-      if (ShapedType::isStatic(stride) && stride < 0)
-        return emitOpError("memref strides must be non-negative");
-    }
-  }
+  if (auto memRefType = dyn_cast<MemRefType>(baseType))
+    if (memref::hasNegativeStaticStride(memRefType))
+      return emitOpError("memref strides must be non-negative");
 
   if (failed(verifyElementTypesMatch(*this, baseType, valueVType, "base",
                                      "valueToStore")))

>From 92f945e8fe6736381ac70b0c323ca28a7d286c1d Mon Sep 17 00:00:00 2001
From: Federico Bruzzone <federico.bruzzone.i at gmail.com>
Date: Thu, 25 Jun 2026 19:39:53 +0200
Subject: [PATCH 6/6] Fix integration test

Signed-off-by: Federico Bruzzone <federico.bruzzone.i at gmail.com>
---
 mlir/lib/Dialect/Vector/IR/VectorOps.cpp | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/mlir/lib/Dialect/Vector/IR/VectorOps.cpp b/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
index 9ce05fd70cd6b..7d4c4b55f1f12 100644
--- a/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
+++ b/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
@@ -6297,9 +6297,6 @@ LogicalResult MaskedLoadOp::verify() {
   VectorType resVType = getVectorType();
   MemRefType memType = getMemRefType();
 
-  if (failed(verifyLoadStoreMemRefLayout(*this, resVType, memType)))
-    return failure();
-
   // Negative strides are not supported on vector.maskedload. The lowering to
   // LLVM emits arithmetic operations (e.g., GEP, mul) with nuw flags that
   // assume non-negative strides to avoid undefined behavior.
@@ -6366,9 +6363,6 @@ LogicalResult MaskedStoreOp::verify() {
   VectorType valueVType = getVectorType();
   MemRefType memType = getMemRefType();
 
-  if (failed(verifyLoadStoreMemRefLayout(*this, valueVType, memType)))
-    return failure();
-
   // Negative strides are not supported on vector.maskedstore. The lowering to
   // LLVM emits arithmetic operations (e.g., GEP, mul) with nuw flags that
   // assume non-negative strides to avoid undefined behavior.



More information about the Mlir-commits mailing list