[Mlir-commits] [mlir] [mlir][vector] Fold transpose(broadcast(shape_cast)) to broadcast (PR #215940)

Federico Bruzzone llvmlistbot at llvm.org
Fri Oct 23 04:46:14 PDT 2026


================
@@ -7777,13 +7788,118 @@ class FoldTransposeBroadcast : public OpRewritePattern<vector::TransposeOp> {
   }
 };
 
+/// Folds transpose(broadcast(shape_cast(x))) to broadcast(x) when:
+///  - x is broadcastable to the transpose's result type.
+///  - The shape_cast only inserts, removes, or moves fixed size-1 dimensions.
+///    All other dimensions retain their sizes, scalability, and relative order.
+///  - The transpose preserves the relative order of all dimensions inherited
+///    from the shape_cast result, including size-1 dimensions. Dimensions
+///    prepended by the broadcast may be freely permuted around them.
+///  - Each non-unit dimension of x ends up at the position it would occupy in
+///    a direct broadcast of x. Scalable dimensions count as non-unit.
+///
+/// These conditions ensure that the direct broadcast replicates the same
+/// source elements along the same result dimensions as the original chain.
+///
+/// For example, the broadcast prepends a dimension that the transpose moves
+/// to the back, where x already has a size-1 dimension:
+/// ```
+///  %0 = vector.shape_cast %x : vector<1x32x1xf32> to vector<1x32xf32>
+///  %1 = vector.broadcast %0 : vector<1x32xf32> to vector<64x1x32xf32>
+///  %2 = vector.transpose %1, [1, 2, 0] : vector<64x1x32xf32>
+///                                                    to vector<1x32x64xf32>
+/// ```
+/// This rewrites to:
+/// ```
+///  %2 = vector.broadcast %x : vector<1x32x1xf32> to vector<1x32x64xf32>
+/// ```
+///
+/// The order-preservation check is conservative: a size-1 dimension expanded
+/// by the broadcast is still an inherited dimension, not a prepended one.
+/// The following chain therefore does not fold, even though broadcasting x
+/// directly to vector<3x4xf32> would produce the same result:
+/// ```
+///  %0 = vector.shape_cast %x : vector<1x4xf32> to vector<4x1xf32>
+///  %1 = vector.broadcast %0 : vector<4x1xf32> to vector<4x3xf32>
+///  %2 = vector.transpose %1, [1, 0] : vector<4x3xf32> to vector<3x4xf32>
+/// ```
+class FoldTransposeShapeCastBroadcast
+    : public OpRewritePattern<vector::TransposeOp> {
+public:
+  using Base::Base;
+
+  LogicalResult matchAndRewrite(vector::TransposeOp transpose,
+                                PatternRewriter &rewriter) const override {
+    auto broadcast = transpose.getVector().getDefiningOp<vector::BroadcastOp>();
+    if (!broadcast)
+      return rewriter.notifyMatchFailure(transpose, "not a broadcast source");
+    auto shapeCast = broadcast.getSource().getDefiningOp<vector::ShapeCastOp>();
+    if (!shapeCast)
+      return rewriter.notifyMatchFailure(transpose, "not a shape_cast source");
+
+    VectorType srcType = shapeCast.getSourceVectorType(); // x
+    VectorType midType =
+        shapeCast.getResultVectorType(); // y, the broadcast input
+    VectorType outType = transpose.getResultVectorType();
+
+    if (vector::isBroadcastableTo(srcType, outType) !=
+        vector::BroadcastableToResult::Success)
+      return rewriter.notifyMatchFailure(transpose, "not broadcastable");
+
+    // Size-1-dim-only shape_cast: x and y must have identical non-broadcast
+    // dims in the same order. E.g. rejects 5x4 -> 4x5x1, which reorders 5
+    // and 4.
+    SmallVector<int64_t> srcAxes = nonUnitAxes(srcType);
+    SmallVector<int64_t> midAxes = nonUnitAxes(midType);
+    if (srcAxes.size() != midAxes.size())
+      return rewriter.notifyMatchFailure(transpose,
+                                         "reshapes a non-broadcast dim");
+    for (auto [srcAxis, midAxis] : llvm::zip_equal(srcAxes, midAxes))
+      if (srcType.getDimSize(srcAxis) != midType.getDimSize(midAxis) ||
+          srcType.getScalableDims()[srcAxis] !=
+              midType.getScalableDims()[midAxis])
+        return rewriter.notifyMatchFailure(transpose,
+                                           "reshapes a non-broadcast dim");
+
+    // The broadcast prepends `numPrepended` dims to y; its remaining dims are
+    // one-to-one with y's dims, i.e. y's dim `i` is at index `i + numPrepended`
+    // in the broadcast result. `invPerm[i]` is where the transpose moves the
+    // broadcast's dim `i` to.
+    int64_t bcastRank = broadcast.getResultVectorType().getRank();
+    int64_t numPrepended = bcastRank - midType.getRank();
+    SmallVector<int64_t> invPerm =
+        invertPermutationVector(transpose.getPermutation());
+
+    // Only the prepended dims may be permuted: the dims coming from y must keep
+    // their relative order. This rejects reordering non-broadcast dims, and
+    // also stretching a size-1 dim of y and then transposing it.
+    for (int64_t i = numPrepended + 1; i < bcastRank; ++i)
+      if (invPerm[i - 1] > invPerm[i])
+        return rewriter.notifyMatchFailure(
+            transpose, "permutes the broadcast's non-prepended dims");
+
+    // Each non-unit source dimension must land at the same result index as
+    // in a direct broadcast of x. For the dimension of size 32 above:
+    //   broadcast + transpose: 64x1x32 -> 1x32x64 (index 2 -> index 1)
+    //   direct broadcast of x: 1x32x1  -> 1x32x64 (index 1 -> index 1)
+    for (auto [srcAxis, midAxis] : llvm::zip_equal(srcAxes, midAxes))
+      if (invPerm[midAxis + numPrepended] !=
+          srcAxis + outType.getRank() - srcType.getRank())
+        return rewriter.notifyMatchFailure(
+            transpose, "not a plain broadcast of the source");
+
+    rewriter.replaceOpWithNewOp<vector::BroadcastOp>(transpose, outType,
+                                                     shapeCast.getSource());
+    return success();
+  }
+};
 } // namespace
----------------
FedericoBruzzone wrote:

```suggestion

} // namespace
```

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


More information about the Mlir-commits mailing list