[Mlir-commits] [mlir] [MLIR][Tensor] canonicalize splats with static shapes (PR #237560)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Sat Oct 31 06:02:48 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-mlir-tensor

Author: Lukas Scheller (Schottkyc137)

<details>
<summary>Changes</summary>

Adds the `ReplaceSplatStaticShapeDims` canonicalizer pattern to `tensor.splat`.
This is similar to `tensor.empty` [`ReplaceEmptyTensorStaticShapeDims`](https://github.com/llvm/llvm-project/blob/33e1c2c1b8a6fea5aaf6067fde636dee648df794/mlir/lib/Dialect/Tensor/IR/TensorOps.cpp#L1158) pattern and turns
```mlir
%dim = arith.constant 4 : index
%splat = tensor.splat %arg0[%dim] : tensor<?xf64>
```
into
```mlir
%splat = tensor.splat %arg0 : tensor<4xf64>
%casted = tensor.cast %splat : tensor<4xf64> to tensor<?xf64>
```

I think this is ready for review, but I'd like to have some pointers concerning design decisions:

1) This pattern can potentially regress the [`SelectSplatCondition`](https://github.com/llvm/llvm-project/blob/33e1c2c1b8a6fea5aaf6067fde636dee648df794/mlir/lib/Dialect/Tensor/IR/TensorOps.cpp#L4205) pattern (and potentially others too):
```mlir
%c = arith.constant 4 : index
%m = tensor.splat %p[%c] : tensor<?xi1>
%x = arith.select %m, %a, %b : tensor<?xi1>, tensor<?xi32>
```
Using top-down canonicalisation, this pattern now inserts a cast before the `select`. Previously, this would have canonicalised to using `p` directly.
I had considered having the `SelectSplatCondition` look through cast ops, but this must be repeated for every other op as well. Maybe a canonicalisation pattern to push casts through an `arith::SelectOp` is correct?

2) The new pattern is very similar to the [`ReplaceEmptyTensorStaticShapeDims`](https://github.com/llvm/llvm-project/blob/33e1c2c1b8a6fea5aaf6067fde636dee648df794/mlir/lib/Dialect/Tensor/IR/TensorOps.cpp#L1158) pattern.
I'm happy to refactor the two if this is desired (either here or in a follow-up PR)

---
Full diff: https://github.com/llvm/llvm-project/pull/237560.diff


3 Files Affected:

- (modified) mlir/include/mlir/Dialect/Tensor/IR/TensorOps.td (+1) 
- (modified) mlir/lib/Dialect/Tensor/IR/TensorOps.cpp (+38) 
- (modified) mlir/test/Dialect/Tensor/canonicalize.mlir (+31) 


``````````diff
diff --git a/mlir/include/mlir/Dialect/Tensor/IR/TensorOps.td b/mlir/include/mlir/Dialect/Tensor/IR/TensorOps.td
index 6a1a19306c14c1..00057218448d13 100644
--- a/mlir/include/mlir/Dialect/Tensor/IR/TensorOps.td
+++ b/mlir/include/mlir/Dialect/Tensor/IR/TensorOps.td
@@ -1820,6 +1820,7 @@ def Tensor_SplatOp : Tensor_Op<"splat", [
 
   let hasFolder = 1;
   let hasVerifier = 1;
+  let hasCanonicalizer = 1;
 }
 
 //===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Dialect/Tensor/IR/TensorOps.cpp b/mlir/lib/Dialect/Tensor/IR/TensorOps.cpp
index 5a9b9358337031..f3a59df715f3f8 100644
--- a/mlir/lib/Dialect/Tensor/IR/TensorOps.cpp
+++ b/mlir/lib/Dialect/Tensor/IR/TensorOps.cpp
@@ -4132,6 +4132,44 @@ OpFoldResult SplatOp::fold(FoldAdaptor adaptor) {
   return SplatElementsAttr::get(getType(), {constOperand});
 }
 
+namespace {
+/// Change the type of the result of a `tensor.splat` by making the result
+/// type statically sized along dimensions that in the original operation were
+/// defined as dynamic, but the size was defined using a `constant` op. For
+/// example
+///
+///  %c4 = arith.constant 4 : index
+///  %0 = tensor.splat %arg0[%c4] : tensor<?xf32>
+///
+///  to
+///
+///  %0 = tensor.splat %arg0 : tensor<4xf32>
+struct ReplaceSplatStaticShapeDims : OpRewritePattern<SplatOp> {
+  using OpRewritePattern<SplatOp>::OpRewritePattern;
+
+  LogicalResult matchAndRewrite(SplatOp op,
+                                PatternRewriter &rewriter) const override {
+    SmallVector<Value> foldedDynamicSizes;
+    RankedTensorType foldedTensorType = foldDynamicToStaticDimSizes(
+        op.getType(), op.getDynamicSizes(), foldedDynamicSizes);
+
+    // Stop here if no dynamic size was promoted to static.
+    if (foldedTensorType == op.getType())
+      return failure();
+
+    auto newOp = SplatOp::create(rewriter, op.getLoc(), op.getInput(),
+                                 foldedTensorType, foldedDynamicSizes);
+    rewriter.replaceOpWithNewOp<tensor::CastOp>(op, op.getType(), newOp);
+    return success();
+  }
+};
+} // namespace
+
+void SplatOp::getCanonicalizationPatterns(RewritePatternSet &results,
+                                          MLIRContext *context) {
+  results.add<ReplaceSplatStaticShapeDims>(context);
+}
+
 //===----------------------------------------------------------------------===//
 // Common Canonicalizers and Folders.
 //===----------------------------------------------------------------------===//
diff --git a/mlir/test/Dialect/Tensor/canonicalize.mlir b/mlir/test/Dialect/Tensor/canonicalize.mlir
index 690b08620987a2..ef44904219c7ab 100644
--- a/mlir/test/Dialect/Tensor/canonicalize.mlir
+++ b/mlir/test/Dialect/Tensor/canonicalize.mlir
@@ -3267,3 +3267,34 @@ func.func @select_uniform_unselected_poison_tensor(%p: i1, %a: tensor<4xi32>) ->
   %x = arith.select %m, %a, %bad : tensor<4xi1>, tensor<4xi32>
   return %x : tensor<4xi32>
 }
+
+// -----
+
+// Non-constant size with constant index -> splat with static type + cast
+// CHECK-LABEL:   func.func @splat_dynamic_size_constant(
+// CHECK-SAME:      %[[ARG0:.*]]: f32) -> tensor<?xf32> {
+// CHECK:           %[[SPLAT:.*]] = tensor.splat %[[ARG0]] : tensor<4xf32>
+// CHECK:           %[[CAST:.*]] = tensor.cast %[[SPLAT]] : tensor<4xf32> to tensor<?xf32>
+// CHECK:           return %[[CAST]] : tensor<?xf32>
+// CHECK:         }
+func.func @splat_dynamic_size_constant(%v: f32) -> tensor<?xf32> {
+  %c4 = arith.constant 4 : index
+  %0 = tensor.splat %v[%c4] : tensor<?xf32>
+  return %0 : tensor<?xf32>
+}
+
+// -----
+
+// Mixed sizes -> splat with semi-static type + cast
+// CHECK-LABEL:   func.func @splat_dynamic_size_mixed(
+// CHECK-SAME:      %[[ARG0:.*]]: f32,
+// CHECK-SAME:      %[[ARG1:.*]]: index) -> tensor<?x?xf32> {
+// CHECK:           %[[SPLAT:.*]] = tensor.splat %[[ARG0]]{{\[}}%[[ARG1]]] : tensor<?x4xf32>
+// CHECK:           %[[CAST:.*]] = tensor.cast %[[SPLAT]] : tensor<?x4xf32> to tensor<?x?xf32>
+// CHECK:           return %[[CAST]] : tensor<?x?xf32>
+// CHECK:         }
+func.func @splat_dynamic_size_mixed(%v: f32, %m: index) -> tensor<?x?xf32> {
+  %c4 = arith.constant 4 : index
+  %0 = tensor.splat %v[%m, %c4] : tensor<?x?xf32>
+  return %0 : tensor<?x?xf32>
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/237560


More information about the Mlir-commits mailing list