[Mlir-commits] [mlir] [mlir][linalg] Fix UBSan division-by-zero in PackOp folding (PR #186271)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Thu Mar 12 15:58:11 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir
@llvm/pr-subscribers-mlir-linalg
Author: Mehdi Amini (joker-eph)
<details>
<summary>Changes</summary>
When tensor-cast folding propagates a zero constant into a dynamic tile size, `FoldTensorCastPackOp` would proceed to fold the pack into an `expand_shape` using that zero tile — causing undefined behaviour (integer division/modulo by zero, caught by UBSan as SIGFPE).
Two fixes:
1. Guard `FoldTensorCastPackOp`: bail out early if any of the resolved tile sizes is zero, preventing the invalid fold entirely.
2. Restrict the `hasZeros` check in `commonVerifierPackAndUnPackOp` to only inspect `Attribute` operands (statically-known zeros), not dynamic `Value` operands. The verifier can only meaningfully reject zero tiles that are statically visible; dynamic zeros are an inherently runtime condition.
Add a regression test that ensures a pack with a zero tile size is not folded into `tensor.expand_shape`.
Fixes #<!-- -->185352
Assisted-by: Claude Code
---
Full diff: https://github.com/llvm/llvm-project/pull/186271.diff
2 Files Affected:
- (modified) mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp (+5-1)
- (modified) mlir/test/Dialect/Linalg/simplify-pack-unpack.mlir (+18)
``````````diff
diff --git a/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp b/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
index ad2909f656eea..f908827b255f2 100644
--- a/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
+++ b/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
@@ -5112,7 +5112,9 @@ static LogicalResult commonVerifierPackAndUnPackOp(OpTy packOrUnPack) {
// Return true if we have a zero-value tile.
auto hasZeros = [&](ArrayRef<OpFoldResult> tiles) {
- return llvm::any_of(tiles, isZeroInteger);
+ return llvm::any_of(tiles, [](OpFoldResult tile) {
+ return isa<Attribute>(tile) && isZeroInteger(tile);
+ });
};
// Verify that the source and destination are ranked types.
@@ -5997,6 +5999,8 @@ struct FoldTensorCastPackOp : public OpRewritePattern<PackOp> {
// Get the updated mixed-tile-sizes attribute.
SmallVector<OpFoldResult> newMixedTileSizes =
getNewMixedTileSizes(rewriter, newResultTypes[0], op.getMixedTiles());
+ if (llvm::any_of(newMixedTileSizes, isZeroInteger))
+ return failure();
// Clone op.
// TODO: Strictly speaking, discardable attributes should be _discarded_ at
diff --git a/mlir/test/Dialect/Linalg/simplify-pack-unpack.mlir b/mlir/test/Dialect/Linalg/simplify-pack-unpack.mlir
index 6979770154bab..afd3a04a8996f 100644
--- a/mlir/test/Dialect/Linalg/simplify-pack-unpack.mlir
+++ b/mlir/test/Dialect/Linalg/simplify-pack-unpack.mlir
@@ -134,6 +134,24 @@ func.func @pack_32x1_to_16x1x1x2(%arg0 : tensor<32x1xf32>) -> tensor<16x1x1x2xf3
// -----
+// Zero tile size: pack must not be folded into expand_shape.
+// CHECK-LABEL: func.func @pack_zero_tile_not_folded
+// CHECK-NOT: tensor.expand_shape
+// CHECK: linalg.pack
+func.func @pack_zero_tile_not_folded(%A: tensor<7x16xi32>) -> tensor<1x16x?x1xi32> {
+ %pad_val = arith.constant 123 : i32
+ %tile_size = arith.constant 0 : index
+ %empty = tensor.empty(%tile_size) : tensor<1x16x?x1xi32>
+ %pack = linalg.pack %A
+ padding_value(%pad_val : i32)
+ inner_dims_pos = [0, 1]
+ inner_tiles = [%tile_size, 1]
+ into %empty : tensor<7x16xi32> -> tensor<1x16x?x1xi32>
+ return %pack : tensor<1x16x?x1xi32>
+}
+
+// -----
+
// CHECK-LABEL: func.func @unpack_1d_to_collapse
// CHECK-SAME: %[[ARG0:.+]]: tensor<8x32xf32>)
// CHECK: %[[COLLAPSED:.+]] = tensor.collapse_shape %[[ARG0]] {{\[}}[0, 1]] : tensor<8x32xf32> into tensor<256xf32>
``````````
</details>
https://github.com/llvm/llvm-project/pull/186271
More information about the Mlir-commits
mailing list