[Mlir-commits] [mlir] [mlir][vector] Verify non-unit strides on `masked/expand/compress` ops (PR #210952)
Federico Bruzzone
llvmlistbot at llvm.org
Tue Jul 21 09:51:00 PDT 2026
https://github.com/FedericoBruzzone updated https://github.com/llvm/llvm-project/pull/210952
>From ee815c22e10991ea39025101b8bc71d00558f0a4 Mon Sep 17 00:00:00 2001
From: Federico Bruzzone <federico.bruzzone.i at gmail.com>
Date: Tue, 21 Jul 2026 15:41:28 +0200
Subject: [PATCH 1/3] [mlir][sparse] Avoid vectorizing non-contiguous COO
coordinate loads
SparseVectorization emits vector.maskedload/maskedstore for direct
loop accesses, assuming consecutive loop indices map to consecutive
memory. This is false for sparse_tensor.coordinates of a level inside
a trailing AoS COO region, whose buffer is interleaved with other
levels -- a real, silent miscompile.
Fall back to a scalar load/store when the stride is statically known
to be non-unit, including cases only derivable from the sparse
tensor's encoding before sparse-tensor-codegen materializes the
concrete stride.
Signed-off-by: Federico Bruzzone <federico.bruzzone.i at gmail.com>
---
.../Transforms/SparseVectorization.cpp | 50 +++++++++++++++++++
1 file changed, 50 insertions(+)
diff --git a/mlir/lib/Dialect/SparseTensor/Transforms/SparseVectorization.cpp b/mlir/lib/Dialect/SparseTensor/Transforms/SparseVectorization.cpp
index 23436a68535fc..c60ca523d81f3 100644
--- a/mlir/lib/Dialect/SparseTensor/Transforms/SparseVectorization.cpp
+++ b/mlir/lib/Dialect/SparseTensor/Transforms/SparseVectorization.cpp
@@ -54,6 +54,50 @@ static bool isInvariantArg(BlockArgument arg, Block *block) {
return arg.getOwner() != block;
}
+/// Returns true when `mem`'s most minor dimension has a statically known
+/// non-unit stride.
+///
+/// `genVectorLoad/genVectorStore` assume a contiguous
+/// `vector.maskedload/vector.maskedstore` is safe for consecutive loop
+/// indices, which breaks for a strided view extracting one component out
+/// of an interleaved (AoS) COO coordinate buffer.
+///
+/// Example:
+/// A `compressed(nonunique) + singleton` region stores coordinates as
+/// `[row0, col0, row1, col1, ...]`, so a 2-lane masked load of `col[0:2]`
+/// (offset=1) would read physical offsets {1, 2} = `[col0, row1]` instead
+/// of the intended {1, 3} = `[col0, col1]`: a silent miscompile.
+///
+/// NOTE: A stride that can't be proven non-unit by either means is assumed
+/// safe.
+static bool hasKnownNonUnitStride(Value mem) {
+ // sparse_tensor.coordinates isn't lowered to a concrete strided memref
+ // until sparse-tensor-codegen runs, so at this point its type
+ // still has a dynamic stride even when the true stride is already known
+ // from the tensor's encoding -- hence the special case below instead of
+ // trusting the memref type.
+ if (auto toCoords = mem.getDefiningOp<ToCoordinatesOp>()) {
+ SparseTensorType stt = getSparseTensorType(toCoords.getTensor());
+ Level cooStart = stt.getAoSCOOStart();
+ // A single trailing level (lvlRank - cooStart == 1) is not actually
+ // interleaved with anything else, so it degenerates to a contiguous
+ // buffer.
+ if (toCoords.getLevel() >= cooStart)
+ return stt.getLvlRank() - cooStart != 1;
+ return false;
+ }
+
+ auto memTp = dyn_cast<MemRefType>(mem.getType());
+ if (!memTp)
+ return false;
+ SmallVector<int64_t> strides;
+ int64_t offset;
+ if (failed(memTp.getStridesAndOffset(strides, offset)))
+ return false;
+ return !strides.empty() && !ShapedType::isDynamic(strides.back()) &&
+ strides.back() != 1;
+}
+
/// Constructs vector type for element type.
static VectorType vectorType(VL vl, Type etp) {
return VectorType::get(vl.vectorLength, etp, vl.enableVLAVectorization);
@@ -292,6 +336,8 @@ static bool vectorizeSubscripts(PatternRewriter &rewriter, scf::ForOp forOp,
if (auto load = cast.getDefiningOp<memref::LoadOp>()) {
if (!innermost)
return false;
+ if (hasKnownNonUnitStride(load.getMemRef()))
+ return false;
if (codegen) {
SmallVector<Value> idxs2(load.getIndices()); // no need to analyze
Location loc = forOp.getLoc();
@@ -408,6 +454,8 @@ static bool vectorizeExpr(PatternRewriter &rewriter, scf::ForOp forOp, VL vl,
// a[lo:hi] = ind[lo:hi], where 'lo' denotes the current index
// and 'hi = lo + vl - 1'.
if (auto load = dyn_cast<memref::LoadOp>(def)) {
+ if (hasKnownNonUnitStride(load.getMemRef()))
+ return false;
auto subs = load.getIndices();
SmallVector<Value> idxs;
if (vectorizeSubscripts(rewriter, forOp, vl, subs, codegen, vmask, idxs)) {
@@ -582,6 +630,8 @@ static bool vectorizeStmt(PatternRewriter &rewriter, scf::ForOp forOp, VL vl,
}
} else if (auto store = dyn_cast<memref::StoreOp>(last)) {
// Analyze/vectorize store operation.
+ if (hasKnownNonUnitStride(store.getMemRef()))
+ return false;
auto subs = store.getIndices();
SmallVector<Value> idxs;
Value rhs = store.getValue();
>From 01b65145ac56c914001612fc6a6ed72f138fa976 Mon Sep 17 00:00:00 2001
From: Federico Bruzzone <federico.bruzzone.i at gmail.com>
Date: Tue, 21 Jul 2026 18:31:37 +0200
Subject: [PATCH 2/3] [mlir][sparse] Add fail-before/pass-after test for COO
vectorization fix
Adds a dedicated pass-level test: vectorizing X(i,j) = A(i,j) * j over
a compressed(nonunique)+singleton (COO) tensor must not vectorize the
level-1 coordinate load, since that level's buffer is interleaved
(AoS) and not contiguous.
Verified this test fails on main (pre-fix): the loop gets vectorized
with vl=2 and vector.maskedload reads the interleaved buffer as if
contiguous. It passes on this branch: the loop stays scalar (step 1).
Signed-off-by: Federico Bruzzone <federico.bruzzone.i at gmail.com>
---
.../SparseTensor/sparse_vector_coo.mlir | 105 ++++++++++++++++++
1 file changed, 105 insertions(+)
create mode 100644 mlir/test/Dialect/SparseTensor/sparse_vector_coo.mlir
diff --git a/mlir/test/Dialect/SparseTensor/sparse_vector_coo.mlir b/mlir/test/Dialect/SparseTensor/sparse_vector_coo.mlir
new file mode 100644
index 0000000000000..9b13b15a60e87
--- /dev/null
+++ b/mlir/test/Dialect/SparseTensor/sparse_vector_coo.mlir
@@ -0,0 +1,105 @@
+// RUN: mlir-opt %s --sparse-reinterpret-map -sparsification -cse -sparse-vectorization="vl=2" -cse | FileCheck %s
+
+// NOTE: Assertions have been autogenerated by utils/generate-test-checks.py
+
+#SortedCOO = #sparse_tensor.encoding<{
+ map = (d0, d1) -> (d0 : compressed(nonunique), d1 : singleton)
+}>
+
+#trait_index = {
+ indexing_maps = [
+ affine_map<(i,j) -> (i,j)>, // A
+ affine_map<(i,j) -> (i,j)> // X (out)
+ ],
+ iterator_types = ["parallel", "parallel"],
+ doc = "X(i,j) = A(i,j) * j"
+}
+
+// CHECK: #[[$ATTR_0:.+]] = #sparse_tensor.encoding<{ map = (d0, d1) -> (d0 : compressed(nonunique), d1 : singleton) }>
+// CHECK-LABEL: func.func @sparse_index_2d_coo(
+// CHECK-SAME: %[[ARG0:.*]]: tensor<8x8xi64, #[[$ATTR_0]]>) -> tensor<8x8xi64> {
+// CHECK: %[[CONSTANT_0:.*]] = arith.constant true
+// CHECK: %[[CONSTANT_1:.*]] = arith.constant false
+// CHECK: %[[CONSTANT_2:.*]] = arith.constant 1 : index
+// CHECK: %[[CONSTANT_3:.*]] = arith.constant 0 : index
+// CHECK: %[[CONSTANT_4:.*]] = arith.constant 0 : i64
+// CHECK: %[[EMPTY_0:.*]] = tensor.empty() : tensor<8x8xi64>
+// CHECK: %[[VALUES_0:.*]] = sparse_tensor.values %[[ARG0]] : tensor<8x8xi64, #[[$ATTR_0]]> to memref<?xi64>
+// CHECK: %[[TO_BUFFER_0:.*]] = bufferization.to_buffer %[[EMPTY_0]] : tensor<8x8xi64> to memref<8x8xi64>
+// CHECK: linalg.fill ins(%[[CONSTANT_4]] : i64) outs(%[[TO_BUFFER_0]] : memref<8x8xi64>)
+// CHECK: %[[POSITIONS_0:.*]] = sparse_tensor.positions %[[ARG0]] {level = 0 : index} : tensor<8x8xi64, #[[$ATTR_0]]> to memref<?xindex>
+// CHECK: %[[COORDINATES_0:.*]] = sparse_tensor.coordinates %[[ARG0]] {level = 0 : index} : tensor<8x8xi64, #[[$ATTR_0]]> to memref<?xindex, strided<[?], offset: ?>>
+// CHECK: %[[COORDINATES_1:.*]] = sparse_tensor.coordinates %[[ARG0]] {level = 1 : index} : tensor<8x8xi64, #[[$ATTR_0]]> to memref<?xindex, strided<[?], offset: ?>>
+// CHECK: %[[LOAD_0:.*]] = memref.load %[[POSITIONS_0]]{{\[}}%[[CONSTANT_3]]] : memref<?xindex>
+// CHECK: %[[LOAD_1:.*]] = memref.load %[[POSITIONS_0]]{{\[}}%[[CONSTANT_2]]] : memref<?xindex>
+// CHECK: %[[WHILE_0:.*]] = scf.while (%[[VAL_0:.*]] = %[[LOAD_0]]) : (index) -> index {
+// CHECK: %[[CMPI_0:.*]] = arith.cmpi ult, %[[VAL_0]], %[[LOAD_1]] : index
+// CHECK: %[[IF_0:.*]] = scf.if %[[CMPI_0]] -> (i1) {
+// CHECK: %[[LOAD_2:.*]] = memref.load %[[COORDINATES_0]]{{\[}}%[[LOAD_0]]] : memref<?xindex, strided<[?], offset: ?>>
+// CHECK: %[[LOAD_3:.*]] = memref.load %[[COORDINATES_0]]{{\[}}%[[VAL_0]]] : memref<?xindex, strided<[?], offset: ?>>
+// CHECK: %[[CMPI_1:.*]] = arith.cmpi eq, %[[LOAD_2]], %[[LOAD_3]] : index
+// CHECK: scf.yield %[[CMPI_1]] : i1
+// CHECK: } else {
+// CHECK: scf.yield %[[CONSTANT_1]] : i1
+// CHECK: }
+// CHECK: scf.condition(%[[IF_0]]) %[[VAL_0]] : index
+// CHECK: } do {
+// CHECK: ^bb0(%[[VAL_1:.*]]: index):
+// CHECK: %[[ADDI_0:.*]] = arith.addi %[[VAL_1]], %[[CONSTANT_2]] : index
+// CHECK: scf.yield %[[ADDI_0]] : index
+// CHECK: }
+// CHECK: %[[WHILE_1:.*]]:2 = scf.while (%[[VAL_2:.*]] = %[[LOAD_0]], %[[VAL_3:.*]] = %[[WHILE_0]]) : (index, index) -> (index, index) {
+// CHECK: %[[CMPI_2:.*]] = arith.cmpi ult, %[[VAL_2]], %[[LOAD_1]] : index
+// CHECK: scf.condition(%[[CMPI_2]]) %[[VAL_2]], %[[VAL_3]] : index, index
+// CHECK: } do {
+// CHECK: ^bb0(%[[VAL_4:.*]]: index, %[[VAL_5:.*]]: index):
+// CHECK: %[[LOAD_4:.*]] = memref.load %[[COORDINATES_0]]{{\[}}%[[VAL_4]]] : memref<?xindex, strided<[?], offset: ?>>
+// CHECK: scf.if %[[CONSTANT_0]] {
+// CHECK: scf.for %[[VAL_6:.*]] = %[[VAL_4]] to %[[VAL_5]] step %[[CONSTANT_2]] {
+// CHECK: %[[LOAD_5:.*]] = memref.load %[[COORDINATES_1]]{{\[}}%[[VAL_6]]] : memref<?xindex, strided<[?], offset: ?>>
+// CHECK: %[[LOAD_6:.*]] = memref.load %[[VALUES_0]]{{\[}}%[[VAL_6]]] : memref<?xi64>
+// CHECK: %[[INDEX_CAST_0:.*]] = arith.index_cast %[[LOAD_5]] : index to i64
+// CHECK: %[[MULI_0:.*]] = arith.muli %[[LOAD_6]], %[[INDEX_CAST_0]] : i64
+// CHECK: memref.store %[[MULI_0]], %[[TO_BUFFER_0]]{{\[}}%[[LOAD_4]], %[[LOAD_5]]] : memref<8x8xi64>
+// CHECK: } {"Emitted from" = "linalg.generic"}
+// CHECK: } else {
+// CHECK: }
+// CHECK: %[[IF_1:.*]]:2 = scf.if %[[CONSTANT_0]] -> (index, index) {
+// CHECK: %[[WHILE_2:.*]] = scf.while (%[[VAL_7:.*]] = %[[VAL_5]]) : (index) -> index {
+// CHECK: %[[CMPI_3:.*]] = arith.cmpi ult, %[[VAL_7]], %[[LOAD_1]] : index
+// CHECK: %[[IF_2:.*]] = scf.if %[[CMPI_3]] -> (i1) {
+// CHECK: %[[LOAD_7:.*]] = memref.load %[[COORDINATES_0]]{{\[}}%[[VAL_5]]] : memref<?xindex, strided<[?], offset: ?>>
+// CHECK: %[[LOAD_8:.*]] = memref.load %[[COORDINATES_0]]{{\[}}%[[VAL_7]]] : memref<?xindex, strided<[?], offset: ?>>
+// CHECK: %[[CMPI_4:.*]] = arith.cmpi eq, %[[LOAD_7]], %[[LOAD_8]] : index
+// CHECK: scf.yield %[[CMPI_4]] : i1
+// CHECK: } else {
+// CHECK: scf.yield %[[CONSTANT_1]] : i1
+// CHECK: }
+// CHECK: scf.condition(%[[IF_2]]) %[[VAL_7]] : index
+// CHECK: } do {
+// CHECK: ^bb0(%[[VAL_8:.*]]: index):
+// CHECK: %[[ADDI_1:.*]] = arith.addi %[[VAL_8]], %[[CONSTANT_2]] : index
+// CHECK: scf.yield %[[ADDI_1]] : index
+// CHECK: }
+// CHECK: scf.yield %[[VAL_5]], %[[WHILE_2]] : index, index
+// CHECK: } else {
+// CHECK: scf.yield %[[VAL_4]], %[[VAL_5]] : index, index
+// CHECK: }
+// CHECK: scf.yield %[[VAL_9:.*]]#0, %[[VAL_9]]#1 : index, index
+// CHECK: } attributes {"Emitted from" = "linalg.generic"}
+// CHECK: %[[TO_TENSOR_0:.*]] = bufferization.to_tensor %[[TO_BUFFER_0]] : memref<8x8xi64> to tensor<8x8xi64>
+// CHECK: return %[[TO_TENSOR_0]] : tensor<8x8xi64>
+// CHECK: }
+func.func @sparse_index_2d_coo(%arga: tensor<8x8xi64, #SortedCOO>) -> tensor<8x8xi64> {
+ %init = tensor.empty() : tensor<8x8xi64>
+ %r = linalg.generic #trait_index
+ ins(%arga: tensor<8x8xi64, #SortedCOO>)
+ outs(%init: tensor<8x8xi64>) {
+ ^bb(%a: i64, %x: i64):
+ %j = linalg.index 1 : index
+ %jj = arith.index_cast %j : index to i64
+ %m1 = arith.muli %a, %jj : i64
+ linalg.yield %m1 : i64
+ } -> tensor<8x8xi64>
+ return %r : tensor<8x8xi64>
+}
>From 2f328a1688b0e8ecf5ec7f88543da1003e4d8bfe Mon Sep 17 00:00:00 2001
From: Federico Bruzzone <federico.bruzzone.i at gmail.com>
Date: Tue, 21 Jul 2026 15:41:54 +0200
Subject: [PATCH 3/3] [mlir][vector] Reject non-unit strides on
masked/expand/compress ops
vector.maskedload/maskedstore/expandload/compressstore lower to LLVM
masked intrinsics that read/write N *consecutive* elements from a
single pointer, but none of them verified the memref's minor-dim
stride, so e.g. `strided<[2]>` verified successfully and silently
miscompiled.
Reject statically-known non-unit strides; a dynamic stride is still
accepted (see linked issue for discussion on why this differs from
vector.load/store). expandload/compressstore also gain the
negative-stride check vector.load/store already have.
Depends on the SparseVectorization fix in the preceding commit: the
sparsifier's vectorizer previously relied on maskedload having no
stride check to (unsafely) vectorize non-contiguous COO coordinate
buffers.
Signed-off-by: Federico Bruzzone <federico.bruzzone.i at gmail.com>
---
mlir/lib/Dialect/Vector/IR/VectorOps.cpp | 51 ++++++++++++++++
mlir/test/Dialect/Vector/invalid.mlir | 77 ++++++++++++++++++++++++
2 files changed, 128 insertions(+)
diff --git a/mlir/lib/Dialect/Vector/IR/VectorOps.cpp b/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
index f37083803a2a1..8b594c378912e 100644
--- a/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
+++ b/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
@@ -6190,6 +6190,33 @@ static LogicalResult verifyLoadStoreMemRefLayout(Operation *op,
return success();
}
+/// Verifies that `memRefTy`'s most minor dimension does not have a
+/// statically known non-unit stride; a dynamic (not provably unit) stride
+/// is accepted.
+///
+/// This is more permissive than vector.load/store's stride check: ops such
+/// as vector.maskedload/maskedstore and vector.expandload/compressstore are
+/// also used on memrefs whose most minor dimension is contiguous at runtime
+/// but not provable as such at the type level (e.g., buffers produced by
+/// the sparsifier).
+static LogicalResult verifyNonStaticNonUnitStrideRejected(Operation *op,
+ VectorType vecTy,
+ MemRefType memRefTy) {
+ if (!vecTy.isScalable() &&
+ (vecTy.getRank() == 0 || vecTy.getNumElements() == 1))
+ return success();
+
+ SmallVector<int64_t> strides;
+ int64_t offset;
+ if (failed(memRefTy.getStridesAndOffset(strides, offset)))
+ return success();
+
+ if (!strides.empty() && !ShapedType::isDynamic(strides.back()) &&
+ strides.back() != 1)
+ return op->emitOpError("most minor memref dim must have unit stride");
+ return success();
+}
+
LogicalResult vector::LoadOp::verify() {
VectorType resVecTy = getVectorType();
MemRefType memRefTy = getMemRefType();
@@ -6299,6 +6326,9 @@ LogicalResult MaskedLoadOp::verify() {
VectorType resVType = getVectorType();
MemRefType memType = getMemRefType();
+ if (failed(verifyNonStaticNonUnitStrideRejected(*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.
@@ -6365,6 +6395,9 @@ LogicalResult MaskedStoreOp::verify() {
VectorType valueVType = getVectorType();
MemRefType memType = getMemRefType();
+ if (failed(verifyNonStaticNonUnitStrideRejected(*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.
@@ -6648,6 +6681,15 @@ LogicalResult ExpandLoadOp::verify() {
VectorType resVType = getVectorType();
MemRefType memType = getMemRefType();
+ if (failed(verifyNonStaticNonUnitStrideRejected(*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();
@@ -6702,6 +6744,15 @@ LogicalResult CompressStoreOp::verify() {
VectorType valueVType = getVectorType();
MemRefType memType = getMemRefType();
+ if (failed(verifyNonStaticNonUnitStrideRejected(*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 aaa55cface958..9ec17024bc1d3 100644
--- a/mlir/test/Dialect/Vector/invalid.mlir
+++ b/mlir/test/Dialect/Vector/invalid.mlir
@@ -1422,6 +1422,15 @@ 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>
+}
+
+// -----
+
//===----------------------------------------------------------------------===//
// vector.maskedstore
//===----------------------------------------------------------------------===//
@@ -1475,6 +1484,15 @@ 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 @gather_from_vector(%base: vector<16xf32>, %indices: vector<16xi32>,
%mask: vector<16xi1>, %pass_thru: vector<16xf32>) {
%c0 = arith.constant 0 : index
@@ -1742,6 +1760,24 @@ func.func @expand_non_power_of_2_alignment(%base: memref<?xf32>, %mask: vector<1
// -----
+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_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')}}
@@ -1796,6 +1832,24 @@ func.func @compress_non_power_of_2_alignment(%base: memref<?xf32>, %mask: vector
// -----
+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_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} :
@@ -2179,6 +2233,19 @@ func.func @load_non_unit_stride(%src : memref<?xi8, strided<[2], offset: ?>>) {
// -----
+// Unlike vector.maskedload/maskedstore/expandload/compressstore, a dynamic
+// (unprovable) stride is rejected here too: vector.load/store require a
+// statically known unit stride, with no exception for strides that merely
+// aren't provably non-unit.
+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
//===----------------------------------------------------------------------===//
@@ -2215,6 +2282,16 @@ func.func @store_non_unit_stride(%src : memref<?xi8, strided<[2], offset:?>>,%va
// -----
+// See load_dynamic_stride above: vector.store also rejects a dynamic stride,
+// unlike vector.maskedstore.
+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
More information about the Mlir-commits
mailing list