[Mlir-commits] [mlir] [mlir][memref]: Added OOB value bound check to ViewOp (PR #183985)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Sat Feb 28 23:41:00 PST 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir-memref
Author: Aviad Cohen (AviadCo)
<details>
<summary>Changes</summary>
---
Full diff: https://github.com/llvm/llvm-project/pull/183985.diff
2 Files Affected:
- (modified) mlir/lib/Dialect/MemRef/IR/ValueBoundsOpInterfaceImpl.cpp (+34)
- (modified) mlir/test/Dialect/MemRef/value-bounds-op-interface-impl.mlir (+211)
``````````diff
diff --git a/mlir/lib/Dialect/MemRef/IR/ValueBoundsOpInterfaceImpl.cpp b/mlir/lib/Dialect/MemRef/IR/ValueBoundsOpInterfaceImpl.cpp
index 69afbcadb0b07..6d4c2e474013a 100644
--- a/mlir/lib/Dialect/MemRef/IR/ValueBoundsOpInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/MemRef/IR/ValueBoundsOpInterfaceImpl.cpp
@@ -142,6 +142,39 @@ struct SubViewOpInterface
}
};
+struct ViewOpInterface
+ : public ValueBoundsOpInterface::ExternalModel<ViewOpInterface, ViewOp> {
+ void populateBoundsForShapedValueDim(Operation *op, Value value, int64_t dim,
+ ValueBoundsConstraintSet &cstr) const {
+ auto viewOp = cast<ViewOp>(op);
+ assert(value == viewOp.getResult() && "invalid value");
+
+ // Each result dimension equals the corresponding mixed size (static or
+ // dynamic operand).
+ cstr.bound(value)[dim] == viewOp.getMixedSizes()[dim];
+
+ // Out-of-bounds constraint in bytes.
+ // The total number of bytes in the result should be the product of all result dimensions according to the element type size.
+ // Therefore, the out-of-bounds constraint in bytes is:
+ // (src_bytes - byte_shift) / dim_size == total_result_bytes_without_dim
+ // meaning that:
+ // dim_size == (src_bytes - byte_shift) / total_result_bytes_without_dim
+ AffineExpr sourceBytes = cstr.getExpr(viewOp.getSource(), 0);
+ AffineExpr byteShift = cstr.getExpr(viewOp.getByteShift());
+ AffineExpr availableBytes = sourceBytes - byteShift;
+ // Element size in bytes (getElementTypeBitWidth() returns bits).
+ int64_t elementSizeBytes = viewOp.getType().getElementTypeBitWidth() / 8;
+ if (elementSizeBytes == 0)
+ return;
+ AffineExpr totalResultBytesWithoutDim = cstr.getExpr(elementSizeBytes);
+ for (unsigned i = 0; i < viewOp.getType().getRank(); ++i)
+ if (i != dim)
+ totalResultBytesWithoutDim = totalResultBytesWithoutDim * cstr.getExpr(value, i);
+
+ cstr.bound(value)[dim] == availableBytes.floorDiv(totalResultBytesWithoutDim);
+ }
+};
+
} // namespace
} // namespace memref
} // namespace mlir
@@ -162,5 +195,6 @@ void mlir::memref::registerValueBoundsOpInterfaceExternalModels(
memref::GetGlobalOp::attachInterface<memref::GetGlobalOpInterface>(*ctx);
memref::RankOp::attachInterface<memref::RankOpInterface>(*ctx);
memref::SubViewOp::attachInterface<memref::SubViewOpInterface>(*ctx);
+ memref::ViewOp::attachInterface<memref::ViewOpInterface>(*ctx);
});
}
diff --git a/mlir/test/Dialect/MemRef/value-bounds-op-interface-impl.mlir b/mlir/test/Dialect/MemRef/value-bounds-op-interface-impl.mlir
index d0aec68d54988..a29a356d01f85 100644
--- a/mlir/test/Dialect/MemRef/value-bounds-op-interface-impl.mlir
+++ b/mlir/test/Dialect/MemRef/value-bounds-op-interface-impl.mlir
@@ -127,3 +127,214 @@ func.func @memref_subview(%m: memref<?xf32>, %sz: index) -> index {
%1 = "test.reify_bound"(%0) {dim = 0} : (memref<?xf32, strided<[1], offset: 2>>) -> (index)
return %1 : index
}
+
+// -----
+
+// CHECK-LABEL: func @memref_view_static_sizes(
+// CHECK: %[[c64:.*]] = arith.constant 64 : index
+// CHECK: %[[c4:.*]] = arith.constant 4 : index
+// CHECK: return %[[c64]], %[[c4]]
+func.func @memref_view_static_sizes(%raw: memref<2048xi8>, %shift: index) -> (index, index) {
+ %0 = memref.view %raw[%shift][] : memref<2048xi8> to memref<64x4xf32>
+ %1 = "test.reify_bound"(%0) {dim = 0} : (memref<64x4xf32>) -> (index)
+ %2 = "test.reify_bound"(%0) {dim = 1} : (memref<64x4xf32>) -> (index)
+ return %1, %2 : index, index
+}
+
+// -----
+
+// CHECK-LABEL: func @memref_view_dynamic_sizes(
+// CHECK: %[[view:.*]] = memref.view
+// CHECK: return {{.*}}, {{.*}} : index, index
+func.func @memref_view_dynamic_sizes(%raw: memref<?xi8>, %shift: index, %sz0: index, %sz1: index) -> (index, index) {
+ %0 = memref.view %raw[%shift][%sz0, %sz1] : memref<?xi8> to memref<?x?xf32>
+ %1 = "test.reify_bound"(%0) {dim = 0} : (memref<?x?xf32>) -> (index)
+ %2 = "test.reify_bound"(%0) {dim = 1} : (memref<?x?xf32>) -> (index)
+ return %1, %2 : index, index
+}
+
+// -----
+
+// ViewOp OOB (static): 1D view in-bounds. Source 256 bytes, view 64xf32 (64*4=256).
+// test.compare checks OOB on dim 0 (dim == 64).
+// CHECK-LABEL: func @memref_view_oob_static_1d_in_bounds(
+// CHECK: %[[c64:.*]] = arith.constant 64 : index
+// CHECK: return %[[c64]]
+func.func @memref_view_oob_static_1d_in_bounds(%raw: memref<256xi8>, %shift: index) -> index {
+ %c0 = arith.constant 0 : index
+ %c64 = arith.constant 64 : index
+ %0 = memref.view %raw[%shift][] : memref<256xi8> to memref<64xf32>
+ %dim0 = memref.dim %0, %c0 : memref<64xf32>
+ // expected-remark @below{{true}}
+ "test.compare"(%dim0, %c64) {cmp = "EQ"} : (index, index) -> ()
+ %1 = "test.reify_bound"(%0) {dim = 0} : (memref<64xf32>) -> (index)
+ return %1 : index
+}
+
+// -----
+
+// ViewOp OOB: verify shift_bytes + view_size_bytes == src_size (1D f32: view_size = dim0*4).
+// Source 256 bytes, shift 0, view 64xf32 -> 0 + 64*4 == 256.
+func.func @memref_view_oob_shift_plus_size_eq_src_1d() {
+ %c0 = arith.constant 0 : index
+ %raw = memref.alloc() : memref<256xi8>
+ %0 = memref.view %raw[%c0][] : memref<256xi8> to memref<64xf32>
+ %src_size = memref.dim %raw, %c0 : memref<256xi8>
+ %dim0 = memref.dim %0, %c0 : memref<64xf32>
+ // expected-remark @below{{true}}
+ "test.compare"(%c0, %dim0, %src_size)
+ {cmp = "EQ", lhs_map = affine_map<(d0, d1) -> (d0 + d1 * 4)>,
+ rhs_map = affine_map<(d0) -> (d0)>}
+ : (index, index, index) -> ()
+ return
+}
+
+// -----
+
+// ViewOp OOB: shift_bytes + view_size_bytes == src_size with non-zero shift.
+// Source 256 bytes, shift 128, view 32xf32 -> 128 + 32*4 == 256.
+func.func @memref_view_oob_shift_plus_size_eq_src_with_shift() {
+ %c0 = arith.constant 0 : index
+ %c128 = arith.constant 128 : index
+ %raw = memref.alloc() : memref<256xi8>
+ %0 = memref.view %raw[%c128][] : memref<256xi8> to memref<32xf32>
+ %src_size = memref.dim %raw, %c0 : memref<256xi8>
+ %dim0 = memref.dim %0, %c0 : memref<32xf32>
+ // expected-remark @below{{true}}
+ "test.compare"(%c128, %dim0, %src_size)
+ {cmp = "EQ", lhs_map = affine_map<(d0, d1) -> (d0 + d1 * 4)>,
+ rhs_map = affine_map<(d0) -> (d0)>}
+ : (index, index, index) -> ()
+ return
+}
+
+// -----
+
+// ViewOp OOB (static): 2D view in-bounds. Source 1024 bytes, shift 0, view 64x4xf32 (1024 bytes).
+// test.compare checks OOB on both dims: dim0 == 64, dim1 == 4.
+// CHECK-LABEL: func @memref_view_oob_static_2d_in_bounds(
+// CHECK: arith.constant 64 : index
+// CHECK: arith.constant 4 : index
+// CHECK: return {{.*}}, {{.*}} : index, index
+func.func @memref_view_oob_static_2d_in_bounds(%raw: memref<1024xi8>, %shift: index) -> (index, index) {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %c64 = arith.constant 64 : index
+ %c4 = arith.constant 4 : index
+ %0 = memref.view %raw[%shift][] : memref<1024xi8> to memref<64x4xf32>
+ %dim0 = memref.dim %0, %c0 : memref<64x4xf32>
+ %dim1 = memref.dim %0, %c1 : memref<64x4xf32>
+ // expected-remark @below{{true}}
+ "test.compare"(%dim0, %c64) {cmp = "EQ"} : (index, index) -> ()
+ // expected-remark @below{{true}}
+ "test.compare"(%dim1, %c4) {cmp = "EQ"} : (index, index) -> ()
+ %1 = "test.reify_bound"(%0) {dim = 0} : (memref<64x4xf32>) -> (index)
+ %2 = "test.reify_bound"(%0) {dim = 1} : (memref<64x4xf32>) -> (index)
+ return %1, %2 : index, index
+}
+
+// -----
+
+// ViewOp OOB (static) with byte_shift: source 256 bytes, shift 128, view 32xf32 (128 bytes).
+// test.compare checks OOB on dim 0 (dim == 32).
+// CHECK-LABEL: func @memref_view_oob_static_with_shift(
+// CHECK: %[[c32:.*]] = arith.constant 32 : index
+// CHECK: return %[[c32]]
+func.func @memref_view_oob_static_with_shift(%raw: memref<256xi8>, %shift: index) -> index {
+ %c0 = arith.constant 0 : index
+ %c32 = arith.constant 32 : index
+ %0 = memref.view %raw[%shift][] : memref<256xi8> to memref<32xf32>
+ %dim0 = memref.dim %0, %c0 : memref<32xf32>
+ // expected-remark @below{{true}}
+ "test.compare"(%dim0, %c32) {cmp = "EQ"} : (index, index) -> ()
+ %1 = "test.reify_bound"(%0) {dim = 0} : (memref<32xf32>) -> (index)
+ return %1 : index
+}
+
+// -----
+
+// ViewOp OOB (static) different element type: f16 = 2 bytes. Source 128 bytes, view 64xf16.
+// test.compare checks OOB on dim 0 (dim == 64).
+// CHECK-LABEL: func @memref_view_oob_static_f16(
+// CHECK: %[[c64:.*]] = arith.constant 64 : index
+// CHECK: return %[[c64]]
+func.func @memref_view_oob_static_f16(%raw: memref<128xi8>, %shift: index) -> index {
+ %c0 = arith.constant 0 : index
+ %c64 = arith.constant 64 : index
+ %0 = memref.view %raw[%shift][] : memref<128xi8> to memref<64xf16>
+ %dim0 = memref.dim %0, %c0 : memref<64xf16>
+ // expected-remark @below{{true}}
+ "test.compare"(%dim0, %c64) {cmp = "EQ"} : (index, index) -> ()
+ %1 = "test.reify_bound"(%0) {dim = 0} : (memref<64xf16>) -> (index)
+ return %1 : index
+}
+
+// -----
+
+// ViewOp OOB (dynamic): 1D view with dynamic size. Reify returns the size operand.
+// test.compare checks that dim 0 is non-negative (OOB + mixed size both imply >= 0).
+// CHECK-LABEL: func @memref_view_oob_dynamic_1d(
+// CHECK-SAME: %[[raw:.*]]: memref<?xi8>, %[[shift:.*]]: index, %[[n:.*]]: index
+// CHECK: %[[view:.*]] = memref.view %[[raw]][%[[shift]]][%[[n]]]
+// CHECK: return %[[n]]
+func.func @memref_view_oob_dynamic_1d(%raw: memref<?xi8>, %shift: index, %n: index) -> index {
+ %c0 = arith.constant 0 : index
+ %0 = memref.view %raw[%shift][%n] : memref<?xi8> to memref<?xf32>
+ %dim0 = memref.dim %0, %c0 : memref<?xf32>
+ // expected-remark @below{{true}}
+ "test.compare"(%dim0, %c0) {cmp = "GE"} : (index, index) -> ()
+ %1 = "test.reify_bound"(%0) {dim = 0} : (memref<?xf32>) -> (index)
+ return %1 : index
+}
+
+// -----
+
+// ViewOp OOB (dynamic): 2D view with dynamic sizes. Reify returns size operands.
+// test.compare checks OOB on both dims: dim0 >= 0, dim1 >= 0.
+// CHECK-LABEL: func @memref_view_oob_dynamic_2d(
+// CHECK: %[[view:.*]] = memref.view
+// CHECK: return {{.*}}, {{.*}} : index, index
+func.func @memref_view_oob_dynamic_2d(%raw: memref<?xi8>, %shift: index, %a: index, %b: index) -> (index, index) {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %0 = memref.view %raw[%shift][%a, %b] : memref<?xi8> to memref<?x?xf32>
+ %dim0 = memref.dim %0, %c0 : memref<?x?xf32>
+ %dim1 = memref.dim %0, %c1 : memref<?x?xf32>
+ // expected-remark @below{{true}}
+ "test.compare"(%dim0, %c0) {cmp = "GE"} : (index, index) -> ()
+ // expected-remark @below{{true}}
+ "test.compare"(%dim1, %c0) {cmp = "GE"} : (index, index) -> ()
+ %1 = "test.reify_bound"(%0) {dim = 0} : (memref<?x?xf32>) -> (index)
+ %2 = "test.reify_bound"(%0) {dim = 1} : (memref<?x?xf32>) -> (index)
+ return %1, %2 : index, index
+}
+
+// -----
+
+// ViewOp OOB (dynamic sizes, constant source): alloc 256 bytes, view with dynamic size %n.
+// OOB constraint forces dim0 == (256-0)/4 == 64; test.compare proves dim0 == 64.
+// CHECK-LABEL: func @memref_view_oob_dynamic_1d_constant_source(
+// CHECK: %[[c64:.*]] = arith.constant 64 : index
+// CHECK: return %[[c64]]
+func.func @memref_view_oob_dynamic_1d_constant_source(%n: index) -> index {
+ %c0 = arith.constant 0 : index
+ %c64 = arith.constant 64 : index
+ %raw = memref.alloc() : memref<256xi8>
+ %0 = memref.view %raw[%c0][%n] : memref<256xi8> to memref<?xf32>
+ %dim0 = memref.dim %0, %c0 : memref<?xf32>
+ // expected-remark @below{{true}}
+ "test.compare"(%dim0, %c64) {cmp = "EQ"} : (index, index) -> ()
+ %1 = "test.reify_bound"(%0) {dim = 0} : (memref<?xf32>) -> (index)
+ return %1 : index
+}
+
+// -----
+
+// ViewOp value bound failure: constant bound requested but view dim is dynamic (from block arg).
+// Only constraint is bound == %size; no constant available.
+func.func @memref_view_reify_constant_fails(%raw: memref<?xi8>, %shift: index, %size: index) -> index {
+ %0 = memref.view %raw[%shift][%size] : memref<?xi8> to memref<?xf32>
+ // expected-error @below {{could not reify bound}}
+ %1 = "test.reify_bound"(%0) {dim = 0, constant} : (memref<?xf32>) -> (index)
+ return %1 : index
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/183985
More information about the Mlir-commits
mailing list