[Mlir-commits] [mlir] d08cbc1 - [mlir][linalg] Fix Linalg runtime verification pass to handle tensors with dimensions of size 0 (#163791)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Wed Oct 22 06:19:44 PDT 2025
Author: Hanumanth
Date: 2025-10-22T15:19:40+02:00
New Revision: d08cbc1cdd7b73e9a582f5602e8ca4829decab8c
URL: https://github.com/llvm/llvm-project/commit/d08cbc1cdd7b73e9a582f5602e8ca4829decab8c
DIFF: https://github.com/llvm/llvm-project/commit/d08cbc1cdd7b73e9a582f5602e8ca4829decab8c.diff
LOG: [mlir][linalg] Fix Linalg runtime verification pass to handle tensors with dimensions of size 0 (#163791)
Runtime verification on Linalg structured ops unconditionally computed
`end - 1` to determine the last iteration index before composing
indexing maps. This caused spurious "negative index" assertion failures
while operating on empty tensors (tensors with a dimension of size 0).
The issue occurs because:
1. Empty tensors create loop ranges [0, 0) with zero trip count
2. Computing end - 1 = 0 - 1 = -1 creates a fictitious negative index
3. The negative index check triggers even though no loop iterations
occur
The fix is to guard all runtime verification with a check that ensures
all loop ranges are non-empty (start < end) before performing any index
arithmetic.
Example MLIR that previously failed:
```mlir
func.func @fill_empty() -> tensor<0xi32> {
%c0 = arith.constant 0 : i32
%empty = tensor.empty() : tensor<0xi32>
%filled = linalg.fill ins(%c0 : i32) outs(%empty : tensor<0xi32>) -> tensor<0xi32>
return %filled : tensor<0xi32>
}
```
---------
Co-authored-by: Hanumanth Hanumantharayappa <hhanuman at ah-hhanuman-l.dhcp.mathworks.com>
Added:
Modified:
mlir/lib/Dialect/Linalg/Transforms/RuntimeOpVerification.cpp
mlir/test/Integration/Dialect/Linalg/CPU/runtime-verification.mlir
Removed:
################################################################################
diff --git a/mlir/lib/Dialect/Linalg/Transforms/RuntimeOpVerification.cpp b/mlir/lib/Dialect/Linalg/Transforms/RuntimeOpVerification.cpp
index 15eb51a6dcab2..181b4846835c0 100644
--- a/mlir/lib/Dialect/Linalg/Transforms/RuntimeOpVerification.cpp
+++ b/mlir/lib/Dialect/Linalg/Transforms/RuntimeOpVerification.cpp
@@ -17,6 +17,7 @@
#include "mlir/Dialect/Index/IR/IndexOps.h"
#include "mlir/Dialect/Linalg/IR/Linalg.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
+#include "mlir/Dialect/SCF/IR/SCF.h"
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/Interfaces/RuntimeVerifiableOpInterface.h"
@@ -43,6 +44,32 @@ struct StructuredOpInterface
auto zero = arith::ConstantIndexOp::create(builder, loc, 0);
auto one = arith::ConstantIndexOp::create(builder, loc, 1);
+ Value iterationDomainIsNonDegenerate;
+ for (auto [start, end] : llvm::zip(starts, ends)) {
+ auto startValue = getValueOrCreateConstantIndexOp(builder, loc, start);
+ auto endValue = getValueOrCreateConstantIndexOp(builder, loc, end);
+
+ // Loop Trip count > 0 iff start < end
+ Value dimensionHasNonZeroTripCount = builder.create<index::CmpOp>(
+ loc, index::IndexCmpPredicate::SLT, startValue, endValue);
+
+ if (!iterationDomainIsNonDegenerate) {
+ iterationDomainIsNonDegenerate = dimensionHasNonZeroTripCount;
+ } else {
+ // Iteration domain is non-degenerate iff all dimensions have loop trip
+ // count > 0
+ iterationDomainIsNonDegenerate = builder.create<arith::AndIOp>(
+ loc, iterationDomainIsNonDegenerate, dimensionHasNonZeroTripCount);
+ }
+ }
+
+ if (!iterationDomainIsNonDegenerate)
+ return;
+
+ auto ifOp = builder.create<scf::IfOp>(loc, iterationDomainIsNonDegenerate,
+ /*withElseRegion=*/false);
+ builder.setInsertionPointToStart(&ifOp.getThenRegion().front());
+
// Subtract one from the loop ends before composing with the indexing map
transform(ends, ends.begin(), [&](OpFoldResult end) {
auto endValue = getValueOrCreateConstantIndexOp(builder, loc, end);
@@ -110,6 +137,7 @@ struct StructuredOpInterface
builder.createOrFold<cf::AssertOp>(loc, cmpOp, msg);
}
}
+ builder.setInsertionPointAfter(ifOp);
}
};
diff --git a/mlir/test/Integration/Dialect/Linalg/CPU/runtime-verification.mlir b/mlir/test/Integration/Dialect/Linalg/CPU/runtime-verification.mlir
index 9f4393efc87bf..127ab70cb4539 100644
--- a/mlir/test/Integration/Dialect/Linalg/CPU/runtime-verification.mlir
+++ b/mlir/test/Integration/Dialect/Linalg/CPU/runtime-verification.mlir
@@ -103,6 +103,17 @@ func.func @main() {
// CHECK: unexpected negative result on dimension #0 of input/output operand #0
func.call @reverse_from_3(%d5x) : (tensor<?xf32>) -> (tensor<?xf32>)
+ %c0x = arith.constant dense<1.0> : tensor<0xf32>
+ %d0x = tensor.cast %c0x : tensor<0xf32> to tensor<?xf32>
+ // CHECK-NOT: ERROR: Runtime op verification failed
+ func.call @fill_empty_1d(%d0x) : (tensor<?xf32>) -> (tensor<?xf32>)
+
+ %c0x5 = arith.constant dense<0.0> : tensor<0x5xf32>
+ %d0x5 = tensor.cast %c0x5 : tensor<0x5xf32> to tensor<?x?xf32>
+
+ // CHECK-NOT: ERROR: Runtime op verification failed
+ func.call @fill_empty_2d(%d0x5) : (tensor<?x?xf32>) -> (tensor<?x?xf32>)
+
return
}
@@ -297,3 +308,15 @@ func.func @reverse_from_3(%arg0: tensor<?xf32>) -> (tensor<?xf32>) {
} -> tensor<?xf32>
return %result : tensor<?xf32>
}
+
+func.func @fill_empty_1d(%arg0: tensor<?xf32>) -> (tensor<?xf32>) {
+ %c0 = arith.constant 0.0 : f32
+ %0 = linalg.fill ins(%c0 : f32) outs(%arg0 : tensor<?xf32>) -> tensor<?xf32>
+ return %0 : tensor<?xf32>
+}
+
+func.func @fill_empty_2d(%arg0: tensor<?x?xf32>) -> (tensor<?x?xf32>) {
+ %c0 = arith.constant 0.0 : f32
+ %0 = linalg.fill ins(%c0 : f32) outs(%arg0 : tensor<?x?xf32>) -> tensor<?x?xf32>
+ return %0 : tensor<?x?xf32>
+}
More information about the Mlir-commits
mailing list