[Mlir-commits] [mlir] [mlir][vector] Verify non-unit strides on `masked/expand/compress` ops (PR #216485)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Sat Aug 15 05:31:30 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-mlir-vector

Author: Federico Bruzzone (FedericoBruzzone)

<details>
<summary>Changes</summary>

Closes the stride-verification gap left open by #<!-- -->204611 and #<!-- -->205869 (**Stacked on #<!-- -->211004**).

`vector.maskedload`/`maskedstore`/`expandload`/`compressstore` lower to LLVM masked intrinsics that read/write N *consecutive* elements from a single pointer (see [LangRef](https://llvm.org/docs/LangRef.htm)), but none of them verified the memref's minor-dim stride, so `strided<[2]>` verified successfully and silently miscompiled. This PR rejects statically-known non-unit and dynamic strides.

---

The original PR (#<!-- -->210952, **identical to this one**) has been reverted (#<!-- -->215224) due to `Linalg/CPU/ArmSME/matmul.mlir` and `Linalg/CPU/ArmSME/multi-tile-matmul-mixed-types.mlir` 2e test failures (see the [buildbot](https://lab.llvm.org/buildbot/#/builders/121/builds/2588). With #<!-- -->215296 merged I'm able to run the SME test and with #<!-- -->214959 the failures should be resolved. Note that `multi-tile-matmul-mixed-types.mlir` is disabled on macOS (my machine), while `matmul.mlir` I can confirm that it passes without any problems. However, since the failure was the same, I think we can say with fairly certainty that `multi-tile-matmul-mixed-types.mlir` is solved as well.

---
Full diff: https://github.com/llvm/llvm-project/pull/216485.diff


2 Files Affected:

- (modified) mlir/lib/Dialect/Vector/IR/VectorOps.cpp (+24) 
- (modified) mlir/test/Dialect/Vector/invalid.mlir (+107) 


``````````diff
diff --git a/mlir/lib/Dialect/Vector/IR/VectorOps.cpp b/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
index 7dbe1847f077d..95d16c93b186a 100644
--- a/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
+++ b/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
@@ -6303,6 +6303,9 @@ 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.
@@ -6369,6 +6372,9 @@ 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.
@@ -6652,6 +6658,15 @@ LogicalResult ExpandLoadOp::verify() {
   VectorType resVType = getVectorType();
   MemRefType memType = getMemRefType();
 
+  if (failed(verifyLoadStoreMemRefLayout(*this, resVType, memType)))
+    return failure();
+
+  // Negative strides are not supported on vector.expandload. 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(memType))
+    return emitOpError("memref strides must be non-negative");
+
   if (failed(
           verifyElementTypesMatch(*this, memType, resVType, "base", "result")))
     return failure();
@@ -6709,6 +6724,15 @@ LogicalResult CompressStoreOp::verify() {
   VectorType valueVType = getVectorType();
   MemRefType memType = getMemRefType();
 
+  if (failed(verifyLoadStoreMemRefLayout(*this, valueVType, memType)))
+    return failure();
+
+  // Negative strides are not supported on vector.compressstore. 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(memType))
+    return emitOpError("memref strides must be non-negative");
+
   if (failed(verifyElementTypesMatch(*this, memType, valueVType, "base",
                                      "valueToStore")))
     return failure();
diff --git a/mlir/test/Dialect/Vector/invalid.mlir b/mlir/test/Dialect/Vector/invalid.mlir
index 0097db39f6ed9..4e5788dc28899 100644
--- a/mlir/test/Dialect/Vector/invalid.mlir
+++ b/mlir/test/Dialect/Vector/invalid.mlir
@@ -1422,6 +1422,24 @@ func.func @maskedload_negative_stride(%src: memref<100x100xf32, strided<[-100, 1
 
 // -----
 
+func.func @maskedload_non_unit_stride(%src: memref<?xi8, strided<[2], offset: ?>>, %mask: vector<8xi1>, %pass: vector<8xi8>) -> vector<8xi8> {
+  %c0 = arith.constant 0 : index
+  // expected-error @+1 {{'vector.maskedload' op most minor memref dim must have unit stride}}
+  %0 = vector.maskedload %src[%c0], %mask, %pass : memref<?xi8, strided<[2], offset: ?>>, vector<8xi1>, vector<8xi8> into vector<8xi8>
+  return %0 : vector<8xi8>
+}
+
+// -----
+
+func.func @maskedload_dynamic_stride(%src: memref<?xi8, strided<[?], offset: ?>>, %mask: vector<8xi1>, %pass: vector<8xi8>) -> vector<8xi8> {
+  %c0 = arith.constant 0 : index
+  // expected-error @+1 {{'vector.maskedload' op most minor memref dim must have unit stride}}
+  %0 = vector.maskedload %src[%c0], %mask, %pass : memref<?xi8, strided<[?], offset: ?>>, vector<8xi1>, vector<8xi8> into vector<8xi8>
+  return %0 : vector<8xi8>
+}
+
+// -----
+
 //===----------------------------------------------------------------------===//
 // vector.maskedstore
 //===----------------------------------------------------------------------===//
@@ -1475,6 +1493,24 @@ func.func @maskedstore_negative_stride(%src: memref<100x100xf32, strided<[-100,
 
 // -----
 
+func.func @maskedstore_non_unit_stride(%src: memref<?xi8, strided<[2], offset: ?>>, %mask: vector<8xi1>, %value: vector<8xi8>) {
+  %c0 = arith.constant 0 : index
+  // expected-error @+1 {{'vector.maskedstore' op most minor memref dim must have unit stride}}
+  vector.maskedstore %src[%c0], %mask, %value : memref<?xi8, strided<[2], offset: ?>>, vector<8xi1>, vector<8xi8>
+  return
+}
+
+// -----
+
+func.func @maskedstore_dynamic_stride(%src: memref<?xi8, strided<[?], offset: ?>>, %mask: vector<8xi1>, %value: vector<8xi8>) {
+  %c0 = arith.constant 0 : index
+  // expected-error @+1 {{'vector.maskedstore' op most minor memref dim must have unit stride}}
+  vector.maskedstore %src[%c0], %mask, %value : memref<?xi8, strided<[?], offset: ?>>, vector<8xi1>, vector<8xi8>
+  return
+}
+
+// -----
+
 func.func @gather_from_vector(%base: vector<16xf32>, %indices: vector<16xi32>,
                                 %mask: vector<16xi1>, %pass_thru: vector<16xf32>) {
   %c0 = arith.constant 0 : index
@@ -1741,6 +1777,33 @@ func.func @expand_scalable_dims_mismatch(%base: memref<?xf32>, %mask: vector<16x
 
 // -----
 
+func.func @expandload_non_unit_stride(%src: memref<?xi8, strided<[2], offset: ?>>, %mask: vector<8xi1>, %pass_thru: vector<8xi8>) -> vector<8xi8> {
+  %c0 = arith.constant 0 : index
+  // expected-error @+1 {{'vector.expandload' op most minor memref dim must have unit stride}}
+  %0 = vector.expandload %src[%c0], %mask, %pass_thru : memref<?xi8, strided<[2], offset: ?>>, vector<8xi1>, vector<8xi8> into vector<8xi8>
+  return %0 : vector<8xi8>
+}
+
+// -----
+
+func.func @expandload_dynamic_stride(%src: memref<?xi8, strided<[?], offset: ?>>, %mask: vector<8xi1>, %pass_thru: vector<8xi8>) -> vector<8xi8> {
+  %c0 = arith.constant 0 : index
+  // expected-error @+1 {{'vector.expandload' op most minor memref dim must have unit stride}}
+  %0 = vector.expandload %src[%c0], %mask, %pass_thru : memref<?xi8, strided<[?], offset: ?>>, vector<8xi1>, vector<8xi8> into vector<8xi8>
+  return %0 : vector<8xi8>
+}
+
+// -----
+
+func.func @expandload_negative_stride(%src: memref<100x100xf32, strided<[-100, 1]>>, %mask: vector<8xi1>, %pass_thru: vector<8xf32>) -> vector<8xf32> {
+  %c0 = arith.constant 0 : index
+  // expected-error @+1 {{'vector.expandload' op memref strides must be non-negative}}
+  %0 = vector.expandload %src[%c0, %c0], %mask, %pass_thru : memref<100x100xf32, strided<[-100, 1]>>, vector<8xi1>, vector<8xf32> into vector<8xf32>
+  return %0 : vector<8xf32>
+}
+
+// -----
+
 func.func @compress_base_type_mismatch(%base: memref<?xf64>, %mask: vector<16xi1>, %value: vector<16xf32>) {
   %c0 = arith.constant 0 : index
   // expected-error at +1 {{'vector.compressstore' op base element type ('f64') does not match valueToStore element type ('f32')}}
@@ -1795,6 +1858,33 @@ func.func @compress_scalable_dims_mismatch(%base: memref<?xf32>, %mask: vector<1
 
 // -----
 
+func.func @compressstore_non_unit_stride(%src: memref<?xi8, strided<[2], offset: ?>>, %mask: vector<8xi1>, %value: vector<8xi8>) {
+  %c0 = arith.constant 0 : index
+  // expected-error @+1 {{'vector.compressstore' op most minor memref dim must have unit stride}}
+  vector.compressstore %src[%c0], %mask, %value : memref<?xi8, strided<[2], offset: ?>>, vector<8xi1>, vector<8xi8>
+  return
+}
+
+// -----
+
+func.func @compressstore_dynamic_stride(%src: memref<?xi8, strided<[?], offset: ?>>, %mask: vector<8xi1>, %value: vector<8xi8>) {
+  %c0 = arith.constant 0 : index
+  // expected-error @+1 {{'vector.compressstore' op most minor memref dim must have unit stride}}
+  vector.compressstore %src[%c0], %mask, %value : memref<?xi8, strided<[?], offset: ?>>, vector<8xi1>, vector<8xi8>
+  return
+}
+
+// -----
+
+func.func @compressstore_negative_stride(%src: memref<100x100xf32, strided<[-100, 1]>>, %mask: vector<8xi1>, %value: vector<8xf32>) {
+  %c0 = arith.constant 0 : index
+  // expected-error @+1 {{'vector.compressstore' op memref strides must be non-negative}}
+  vector.compressstore %src[%c0, %c0], %mask, %value : memref<100x100xf32, strided<[-100, 1]>>, vector<8xi1>, vector<8xf32>
+  return
+}
+
+// -----
+
 func.func @scan_reduction_dim_constraint(%arg0: vector<2x3xi32>, %arg1: vector<3xi32>) -> vector<3xi32> {
   // expected-error at +1 {{'vector.scan' op reduction dimension 5 has to be less than 2}}
   %0:2 = vector.scan <add>, %arg0, %arg1 {inclusive = true, reduction_dim = 5} :
@@ -2178,6 +2268,15 @@ func.func @load_non_unit_stride(%src : memref<?xi8, strided<[2], offset: ?>>) {
 
 // -----
 
+func.func @load_dynamic_stride(%src : memref<?xi8, strided<[?], offset: ?>>) {
+  %c0 = arith.constant 0 : index
+  // expected-error @+1 {{'vector.load' op most minor memref dim must have unit stride}}
+  %0 = vector.load %src[%c0] : memref<?xi8, strided<[?], offset: ?>>, vector<16xi8>
+  return
+}
+
+// -----
+
 //===----------------------------------------------------------------------===//
 // vector.store
 //===----------------------------------------------------------------------===//
@@ -2214,6 +2313,14 @@ func.func @store_non_unit_stride(%src : memref<?xi8, strided<[2], offset:?>>,%va
 
 // -----
 
+func.func @store_dynamic_stride(%src : memref<?xi8, strided<[?], offset: ?>>, %val : vector<16xi8>, %c0: index) {
+  // expected-error @below {{'vector.store' op most minor memref dim must have unit stride}}
+  vector.store %val, %src[%c0] : memref<?xi8, strided<[?], offset: ?>>, vector<16xi8>
+  return
+}
+
+// -----
+
 func.func @store_negative_stride(%src: memref<100x100xf32, strided<[-100, 1]>>, %val: vector<4xf32>) {
   // expected-error @+2 {{'vector.store' op memref strides must be non-negative}}
   %c0 = arith.constant 0 : index

``````````

</details>


https://github.com/llvm/llvm-project/pull/216485


More information about the Mlir-commits mailing list