[Mlir-commits] [mlir] [mlir][memref]: Added OOB value bound check to ViewOp (PR #183985)
Aviad Cohen
llvmlistbot at llvm.org
Sun Mar 1 11:52:21 PST 2026
https://github.com/AviadCo updated https://github.com/llvm/llvm-project/pull/183985
>From 939e4827d30a4d21bea989a818357bdcd2bc4a8d Mon Sep 17 00:00:00 2001
From: Aviad Cohen <aviad.cohen2 at mobileye.com>
Date: Sun, 1 Mar 2026 08:45:06 +0200
Subject: [PATCH] [mlir][memref] Add ValueBoundsOpInterface for ViewOp with OOB
constraint
Implement ValueBoundsOpInterface for memref.view so that value bounds and reification can use both the declared sizes and an out-of-bounds (OOB) constraint.
---
.../MemRef/IR/ValueBoundsOpInterfaceImpl.cpp | 39 ++++
.../value-bounds-op-interface-impl.mlir | 170 ++++++++++++++++++
2 files changed, 209 insertions(+)
diff --git a/mlir/lib/Dialect/MemRef/IR/ValueBoundsOpInterfaceImpl.cpp b/mlir/lib/Dialect/MemRef/IR/ValueBoundsOpInterfaceImpl.cpp
index 69afbcadb0b07..ea2dcbf4227de 100644
--- a/mlir/lib/Dialect/MemRef/IR/ValueBoundsOpInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/MemRef/IR/ValueBoundsOpInterfaceImpl.cpp
@@ -9,6 +9,7 @@
#include "mlir/Dialect/MemRef/IR/ValueBoundsOpInterfaceImpl.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
+#include "mlir/IR/Builders.h"
#include "mlir/Interfaces/ValueBoundsOpInterface.h"
using namespace mlir;
@@ -142,6 +143,43 @@ 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;
+ MemRefType memrefType = viewOp.getType();
+ if (!memrefType.getElementType().isIntOrFloat())
+ return;
+ int64_t elementSizeBytes = memrefType.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 +200,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..34821c5c05303 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,173 @@ 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_dynamic_sizes(
+// CHECK-SAME: %[[raw:.*]]: memref<?xi8>, %[[shift:.*]]: index, %[[sz0:.*]]: index, %[[sz1:.*]]: index) -> (index, index) {
+// CHECK: %[[view:.*]] = memref.view %[[raw]][%[[shift]]][%[[sz0]], %[[sz1]]]
+// CHECK: return %[[sz0]], %[[sz1]] : 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: 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: dynamic shift and dynamic size. Reify returns the size operand.
+// Source 256 bytes (alloc), view ?xf32. test.compare checks dim0 >= 0 (OOB + size imply non-negative).
+// CHECK-LABEL: func @memref_view_oob_shift_plus_size_eq_src_with_shift(
+// CHECK-SAME: %[[shift:.*]]: index, %[[n:.*]]: index
+// CHECK: %[[raw:.*]] = memref.alloc()
+// CHECK: %[[view:.*]] = memref.view %[[raw]][%[[shift]]][%[[n]]]
+// CHECK: return %[[n]]
+func.func @memref_view_oob_shift_plus_size_eq_src_with_shift(%shift: index, %n: index) -> index {
+ %c0 = arith.constant 0 : index
+ %raw = memref.alloc() : memref<256xi8>
+ %0 = memref.view %raw[%shift][%n] : memref<256xi8> 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 shift and sizes): 2D view. Source 1024 bytes, dynamic shift and sizes.
+// test.compare checks OOB on both dims: dim0 >= 0, dim1 >= 0. Reify returns size operands.
+// CHECK-LABEL: func @memref_view_oob_dynamic_2d_in_bounds(
+// CHECK-SAME: %[[raw:.*]]: memref<1024xi8>, %[[shift:.*]]: index, %[[sz0:.*]]: index, %[[sz1:.*]]: index
+// CHECK: %[[view:.*]] = memref.view %[[raw]][%[[shift]]][%[[sz0]], %[[sz1]]]
+// CHECK: return %[[sz0]], %[[sz1]] : index, index
+func.func @memref_view_oob_dynamic_2d_in_bounds(%raw: memref<1024xi8>, %shift: index, %sz0: index, %sz1: index) -> (index, index) {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %0 = memref.view %raw[%shift][%sz0, %sz1] : memref<1024xi8> 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): 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 OOB (2D, one dynamic dim, constant source): alloc 256 bytes, view with dynamic dim0 %n and static dim1=4.
+// OOB constraint forces dim0 == (256-0)/(4*4) == 16; test.compare proves dim0 == 16, dim1 == 4.
+// CHECK-LABEL: func @memref_view_oob_dynamic_2d_constant_source(
+// CHECK: arith.constant 16 : index
+// CHECK: arith.constant 4 : index
+// CHECK: return {{.*}}, {{.*}} : index, index
+func.func @memref_view_oob_dynamic_2d_constant_source(%n: index) -> (index, index) {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %c16 = arith.constant 16 : index
+ %c4 = arith.constant 4 : index
+ %raw = memref.alloc() : memref<256xi8>
+ %0 = memref.view %raw[%c0][%n] : memref<256xi8> to memref<?x4xf32>
+ %dim0 = memref.dim %0, %c0 : memref<?x4xf32>
+ %dim1 = memref.dim %0, %c1 : memref<?x4xf32>
+ // expected-remark @below{{true}}
+ "test.compare"(%dim0, %c16) {cmp = "EQ"} : (index, index) -> ()
+ // expected-remark @below{{true}}
+ "test.compare"(%dim1, %c4) {cmp = "EQ"} : (index, index) -> ()
+ %1 = "test.reify_bound"(%0) {dim = 0} : (memref<?x4xf32>) -> (index)
+ %2 = "test.reify_bound"(%0) {dim = 1} : (memref<?x4xf32>) -> (index)
+ return %1, %2 : index, 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
+}
More information about the Mlir-commits
mailing list