[Mlir-commits] [mlir] 97d6fec - [mlir][tensor] Verify that `tensor.pad` padding is non-negative (#231389)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Fri Oct 23 01:30:06 PDT 2026
Author: Federico Bruzzone
Date: 2026-10-23T10:30:01+02:00
New Revision: 97d6feca9de0c1fdbbb346aaa8d0ff5cdc4d6b06
URL: https://github.com/llvm/llvm-project/commit/97d6feca9de0c1fdbbb346aaa8d0ff5cdc4d6b06
DIFF: https://github.com/llvm/llvm-project/commit/97d6feca9de0c1fdbbb346aaa8d0ff5cdc4d6b06.diff
LOG: [mlir][tensor] Verify that `tensor.pad` padding is non-negative (#231389)
`tensor.pad` did not reject negative static `low` or `high` padding, so
an op like `tensor.pad %t low[-2] high[0]` was verified as long as the
result type was consistent with it.
With this PR we reject negative constant padding in `PadOp::verify`, and
document in the op description that the padding amounts must be
non-negative. A negative dynamic value is UB, since the verifier only
looks at the op itself.
Also make `FoldStaticPadding` keep negative constants as SSA operands,
so that canonicalization does not fold them into the attribute and
create an op the verifier rejects.
---------
Signed-off-by: Federico Bruzzone <federico.bruzzone.i at gmail.com>
Co-authored-by: Mehdi Amini <joker.eph at gmail.com>
Added:
Modified:
mlir/include/mlir/Dialect/Tensor/IR/TensorOps.td
mlir/lib/Dialect/Tensor/IR/TensorOps.cpp
mlir/test/Dialect/Tensor/canonicalize.mlir
mlir/test/Dialect/Tensor/invalid.mlir
mlir/test/Dialect/Tensor/ops.mlir
Removed:
################################################################################
diff --git a/mlir/include/mlir/Dialect/Tensor/IR/TensorOps.td b/mlir/include/mlir/Dialect/Tensor/IR/TensorOps.td
index beac1e91ee6e3c..6a1a19306c14c1 100644
--- a/mlir/include/mlir/Dialect/Tensor/IR/TensorOps.td
+++ b/mlir/include/mlir/Dialect/Tensor/IR/TensorOps.td
@@ -1275,6 +1275,9 @@ def Tensor_PadOp : Tensor_Op<"pad", [
The result tensor dimensions are `low[i]` + `dim[i]` + `high[i]` for each
dimension `i`. The number of elements of `low` and `high` must match the
rank of the input tensor. They can be either a constant or a dynamic value.
+ The padding amounts must be non-negative. A negative constant is rejected
+ by the verifier. A negative dynamic value is undefined behavior: the
+ verifier does not look through the defining op of an SSA value.
The region of the `tensor.pad` operation returns the value to use
for the padding. The arguments of the region represent the index
diff --git a/mlir/lib/Dialect/Tensor/IR/TensorOps.cpp b/mlir/lib/Dialect/Tensor/IR/TensorOps.cpp
index 53ee57fa72a17a..2ee6c93b313e69 100644
--- a/mlir/lib/Dialect/Tensor/IR/TensorOps.cpp
+++ b/mlir/lib/Dialect/Tensor/IR/TensorOps.cpp
@@ -3252,6 +3252,11 @@ void PadOp::getAsmResultNames(function_ref<void(Value, StringRef)> setNameFn) {
}
LogicalResult PadOp::verify() {
+ for (ArrayRef<int64_t> padding : {getStaticLow(), getStaticHigh()})
+ for (int64_t size : padding)
+ if (!ShapedType::isDynamic(size) && size < 0)
+ return emitOpError("padding must be non-negative, got ") << size;
+
auto sourceType = llvm::cast<RankedTensorType>(getSource().getType());
auto resultType = llvm::cast<RankedTensorType>(getResult().getType());
auto expectedType =
@@ -3673,12 +3678,15 @@ struct FoldStaticPadding : public OpRewritePattern<PadOp> {
auto outputDims = oldResultType.getShape();
- // Extract the static info from the high and low operands.
+ // Extract the static info from the high and low operands. A negative
+ // constant is not folded, since the attribute must be non-negative.
+ // TODO: we should consider folding to a poison value in this case.
SmallVector<int64_t> constOperandsLow;
SmallVector<Value> newLows;
for (auto operand : padTensorOp.getLow()) {
APSInt intOp;
- if (!matchPattern(operand, m_ConstantInt(&intOp))) {
+ if (!matchPattern(operand, m_ConstantInt(&intOp)) ||
+ intOp.getExtValue() < 0) {
constOperandsLow.push_back(ShapedType::kDynamic);
newLows.push_back(operand);
continue;
@@ -3689,7 +3697,8 @@ struct FoldStaticPadding : public OpRewritePattern<PadOp> {
SmallVector<Value> newHighs;
for (auto operand : padTensorOp.getHigh()) {
APSInt intOp;
- if (!matchPattern(operand, m_ConstantInt(&intOp))) {
+ if (!matchPattern(operand, m_ConstantInt(&intOp)) ||
+ intOp.getExtValue() < 0) {
constOperandsHigh.push_back(ShapedType::kDynamic);
newHighs.push_back(operand);
continue;
diff --git a/mlir/test/Dialect/Tensor/canonicalize.mlir b/mlir/test/Dialect/Tensor/canonicalize.mlir
index 217ce64cb98683..f8d333fc0a90ac 100644
--- a/mlir/test/Dialect/Tensor/canonicalize.mlir
+++ b/mlir/test/Dialect/Tensor/canonicalize.mlir
@@ -1932,6 +1932,26 @@ func.func @pad_fold_static(%arg0: tensor<?x64x?x?xf32>) -> tensor<?x?x?x?xf32> {
// -----
+// A negative constant is not folded into the attribute, since the verifier
+// requires the attribute to be non-negative.
+
+// CHECK-LABEL: func @pad_nofold_negative_constant(
+// CHECK-SAME: %[[ARG0:.*]]: tensor<4xf32>
+// CHECK: %[[C:.*]] = arith.constant -2 : index
+// CHECK: tensor.pad %[[ARG0]] low[%[[C]]] high[0]
+// CHECK: } : tensor<4xf32> to tensor<?xf32>
+func.func @pad_nofold_negative_constant(%arg0: tensor<4xf32>, %pad_value: f32)
+ -> tensor<?xf32> {
+ %c = arith.constant -2 : index
+ %0 = tensor.pad %arg0 low[%c] high[0] {
+ ^bb0(%arg1: index):
+ tensor.yield %pad_value : f32
+ } : tensor<4xf32> to tensor<?xf32>
+ return %0 : tensor<?xf32>
+}
+
+// -----
+
// CHECK-LABEL: func @pad_nofold_same_static_shape(
// CHECK-SAME: %[[ARG0:.*]]: tensor<5x6xf32>
// CHECK: %[[PAD:.*]] = tensor.pad
diff --git a/mlir/test/Dialect/Tensor/invalid.mlir b/mlir/test/Dialect/Tensor/invalid.mlir
index 19db4680b6a638..f613d309f1db45 100644
--- a/mlir/test/Dialect/Tensor/invalid.mlir
+++ b/mlir/test/Dialect/Tensor/invalid.mlir
@@ -731,3 +731,35 @@ func.func @no_fold_invalid_collapse() -> tensor<i64> {
return %0 : tensor<i64>
}
+// -----
+
+func.func @pad_negative_low(%arg0: tensor<10xf32>, %arg1: f32) -> tensor<8xf32> {
+ // expected-error @+1 {{padding must be non-negative, got -2}}
+ %0 = tensor.pad %arg0 low[-2] high[0] {
+ ^bb0(%arg2: index):
+ tensor.yield %arg1 : f32
+ } : tensor<10xf32> to tensor<8xf32>
+ return %0 : tensor<8xf32>
+}
+
+// -----
+
+func.func @pad_negative_high(%arg0: tensor<?x4xf32>, %arg1: f32) -> tensor<?x3xf32> {
+ // expected-error @+1 {{padding must be non-negative, got -1}}
+ %0 = tensor.pad %arg0 low[0, 0] high[0, -1] {
+ ^bb0(%arg2: index, %arg3: index):
+ tensor.yield %arg1 : f32
+ } : tensor<?x4xf32> to tensor<?x3xf32>
+ return %0 : tensor<?x3xf32>
+}
+
+// -----
+
+func.func @pad_negative_dynamic_dim(%arg0: tensor<?xf32>, %arg1: f32) -> tensor<?xf32> {
+ // expected-error @+1 {{padding must be non-negative, got -1}}
+ %0 = tensor.pad %arg0 low[-1] high[0] {
+ ^bb0(%arg2: index):
+ tensor.yield %arg1 : f32
+ } : tensor<?xf32> to tensor<?xf32>
+ return %0 : tensor<?xf32>
+}
diff --git a/mlir/test/Dialect/Tensor/ops.mlir b/mlir/test/Dialect/Tensor/ops.mlir
index 681a934ba0698e..91f01915fac38c 100644
--- a/mlir/test/Dialect/Tensor/ops.mlir
+++ b/mlir/test/Dialect/Tensor/ops.mlir
@@ -312,6 +312,21 @@ func.func @pad_to_static_size(%arg0: tensor<?x?xf32>, %ub0: index, %ub1: index,
// -----
+func.func @pad_positive_constant(%arg0: tensor<4xf32>, %pad_value: f32) -> tensor<?xf32> {
+ %c2 = arith.constant 2 : index
+ %0 = tensor.pad %arg0 low[%c2] high[0] {
+ ^bb0(%arg1: index):
+ tensor.yield %pad_value : f32
+ } : tensor<4xf32> to tensor<?xf32>
+ return %0 : tensor<?xf32>
+}
+// CHECK-LABEL: func @pad_positive_constant
+// CHECK: tensor.pad
+// CHECK-SAME: low[%{{.*}}]
+// CHECK: : tensor<4xf32> to tensor<?xf32>
+
+// -----
+
// CHECK-LABEL: func @test_splat_op
// CHECK-SAME: %[[S:.*]]: f32
// CHECK-SAME: %[[P:.*]]: !llvm.ptr
More information about the Mlir-commits
mailing list