[Mlir-commits] [mlir] [MLIR][Vector] Refine transfer in_bounds inference for indexed accesses (PR #193920)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Tue May 5 05:08:37 PDT 2026


https://github.com/zackc6 updated https://github.com/llvm/llvm-project/pull/193920

>From 7b863e908a306d01a39defd69286cabb84924c97 Mon Sep 17 00:00:00 2001
From: zack <zackchen666 at gmail.com>
Date: Fri, 24 Apr 2026 14:53:38 +0800
Subject: [PATCH] [MLIR][Vector] Refine transfer in_bounds inference for
 indexed accesses

Compute transfer `in_bounds` from mapped indices and static shapes, with explicit read/write dimension alignment. Add bounded `scf.for` IV handling and a conservative fallback for unknown indices to preserve existing loop lowering behavior. Extend vectorization tests for insert-slice and transform-vector pipelines, including IV-indexed cases.
---
 mlir/lib/Dialect/Vector/Utils/VectorUtils.cpp | 159 ++++++++++++++++--
 .../insert-slice-with-patterns.mlir           |  97 +++++++++++
 .../test/Dialect/Vector/transform-vector.mlir |  67 ++++++++
 3 files changed, 306 insertions(+), 17 deletions(-)

diff --git a/mlir/lib/Dialect/Vector/Utils/VectorUtils.cpp b/mlir/lib/Dialect/Vector/Utils/VectorUtils.cpp
index 576023dbc9de1..13f73af057651 100644
--- a/mlir/lib/Dialect/Vector/Utils/VectorUtils.cpp
+++ b/mlir/lib/Dialect/Vector/Utils/VectorUtils.cpp
@@ -18,6 +18,7 @@
 #include "mlir/Dialect/Arith/Utils/Utils.h"
 #include "mlir/Dialect/Func/IR/FuncOps.h"
 #include "mlir/Dialect/MemRef/IR/MemRef.h"
+#include "mlir/Dialect/SCF/IR/SCF.h"
 #include "mlir/Dialect/Tensor/IR/Tensor.h"
 #include "mlir/Dialect/Utils/IndexingUtils.h"
 #include "mlir/Dialect/Vector/IR/VectorOps.h"
@@ -406,6 +407,137 @@ static bool isMaskTriviallyFoldable(SmallVector<OpFoldResult> &maskSizes,
   return true;
 }
 
+/// Returns true if `index` is an scf.for IV that is statically proven in-bounds
+/// for accesses of length `vecSize` into a static dimension `baseDimSize`.
+///
+/// This handles the common vector loop form:
+///   scf.for %iv = %c0 to %ub step %c<vecSize>
+/// and proves `%iv + vecSize <= baseDimSize` for all iterations.
+static bool isKnownInBoundsForLoopIV(Value index, int64_t vecSize,
+                                     int64_t baseDimSize) {
+  auto iv = dyn_cast<BlockArgument>(index);
+  if (!iv)
+    return false;
+
+  auto forOp = dyn_cast<scf::ForOp>(iv.getOwner()->getParentOp());
+  if (!forOp || forOp.getInductionVar() != index)
+    return false;
+
+  APSInt lbInt, ubInt, stepInt;
+  if (!matchPattern(forOp.getLowerBound(), m_ConstantInt(&lbInt)) ||
+      !matchPattern(forOp.getUpperBound(), m_ConstantInt(&ubInt)) ||
+      !matchPattern(forOp.getStep(), m_ConstantInt(&stepInt)))
+    return false;
+
+  int64_t lb = lbInt.getSExtValue();
+  int64_t ub = ubInt.getSExtValue();
+  int64_t step = stepInt.getSExtValue();
+  if (lb != 0 || step <= 0 || vecSize <= 0 || step != vecSize)
+    return false;
+
+  // Empty loop executes no transfer accesses.
+  if (lb >= ub)
+    return true;
+
+  if (baseDimSize < vecSize)
+    return false;
+
+  // With lb = 0 and step = vecSize, proving ub <= baseDimSize is sufficient to
+  // guarantee iv + vecSize <= baseDimSize on all iterations.
+  return ub <= baseDimSize;
+}
+
+/// Returns `in_bounds` values derived from static shapes and constant indices.
+/// Conservatively returns false for any dimension that cannot be proven safe.
+///
+/// `baseDims` and `alignedIndices` must be aligned with `vectorShape`:
+///   * `baseDims[vecDim]` is the base tensor dim checked for `vecDim`.
+///   * `alignedIndices[vecDim]` is the index used for that mapped base dim.
+static SmallVector<bool> computeInBoundsFromStaticShapeAndIndices(
+    ArrayRef<int64_t> baseShape, ArrayRef<int64_t> vectorShape,
+    ArrayRef<int64_t> baseDims, ArrayRef<Value> alignedIndices,
+    bool unknownIndexRequiresExactSize) {
+  SmallVector<bool> inBounds(vectorShape.size(), false);
+  if (baseDims.size() != vectorShape.size() ||
+      alignedIndices.size() != vectorShape.size())
+    return inBounds;
+
+  for (auto [vecDim, vecSize] : llvm::enumerate(vectorShape)) {
+    int64_t baseDim = baseDims[vecDim];
+    Value index = alignedIndices[vecDim];
+    if (!ShapedType::isStatic(baseShape[baseDim]) || vecSize < 0)
+      continue;
+
+    int64_t baseDimSize = baseShape[baseDim];
+    APSInt indexValue;
+    if (matchPattern(index, m_ConstantInt(&indexValue))) {
+      int64_t idx = indexValue.getSExtValue();
+      // Indexes must be non-negative and stay in range for the whole vector
+      // dim. Compare as `idx <= baseDimSize - vecSize` to avoid signed
+      // overflow in `idx + vecSize`.
+      inBounds[vecDim] =
+          idx >= 0 && idx <= baseDimSize && vecSize <= baseDimSize - idx;
+      continue;
+    }
+
+    if (isKnownInBoundsForLoopIV(index, vecSize, baseDimSize)) {
+      inBounds[vecDim] = true;
+      continue;
+    }
+
+    // Relax unknown-index handling to preserve previous behavior for
+    // loop-structured code paths while still preferring index-aware proofs.
+    inBounds[vecDim] = unknownIndexRequiresExactSize ? (baseDimSize == vecSize)
+                                                     : (baseDimSize >= vecSize);
+  }
+  return inBounds;
+}
+
+/// Compute in_bounds for transfer_read where source and vector ranks match.
+static SmallVector<bool>
+computeReadInBoundsFromStaticShape(ArrayRef<int64_t> sourceShape,
+                                   ArrayRef<int64_t> vectorShape,
+                                   ArrayRef<Value> readIndices) {
+  if (sourceShape.size() != vectorShape.size() ||
+      readIndices.size() != vectorShape.size())
+    return SmallVector<bool>(vectorShape.size(), false);
+
+  SmallVector<int64_t> sourceDims;
+  sourceDims.reserve(vectorShape.size());
+  for (auto [dim, _] : llvm::enumerate(vectorShape))
+    sourceDims.push_back(dim);
+
+  return computeInBoundsFromStaticShapeAndIndices(
+      sourceShape, vectorShape, sourceDims, readIndices,
+      /*unknownIndexRequiresExactSize=*/true);
+}
+
+/// Compute in_bounds for transfer_write where write indices are expressed in
+/// destination-rank coordinates.
+static SmallVector<bool>
+computeWriteInBoundsFromStaticShape(ArrayRef<int64_t> destShape,
+                                    ArrayRef<int64_t> vectorShape,
+                                    ArrayRef<Value> writeIndices) {
+  if (writeIndices.size() != destShape.size() ||
+      destShape.size() < vectorShape.size())
+    return SmallVector<bool>(vectorShape.size(), false);
+
+  int64_t rankDiff = destShape.size() - vectorShape.size();
+  SmallVector<int64_t> destDims;
+  SmallVector<Value> alignedIndices;
+  destDims.reserve(vectorShape.size());
+  alignedIndices.reserve(vectorShape.size());
+  for (auto [vecDim, _] : llvm::enumerate(vectorShape)) {
+    int64_t destDim = rankDiff + vecDim;
+    destDims.push_back(destDim);
+    alignedIndices.push_back(writeIndices[destDim]);
+  }
+
+  return computeInBoundsFromStaticShapeAndIndices(
+      destShape, vectorShape, destDims, alignedIndices,
+      /*unknownIndexRequiresExactSize=*/false);
+}
+
 Value vector::createReadOrMaskedRead(OpBuilder &builder, Location loc,
                                      Value source,
                                      ArrayRef<int64_t> inputVectorSizes,
@@ -441,16 +573,13 @@ Value vector::createReadOrMaskedRead(OpBuilder &builder, Location loc,
          "expected same pad element type to match source element type");
 
   auto zero = arith::ConstantIndexOp::create(builder, loc, 0);
+  SmallVector<Value> indices(vecToReadRank, zero);
   SmallVector<bool> inBoundsVal(vecToReadRank, true);
 
   if (useInBoundsInsteadOfMasking) {
-    // Update the inBounds attribute.
-    // FIXME: This computation is too weak - it ignores the read indices.
-    for (unsigned i = 0; i < vecToReadRank; i++)
-      inBoundsVal[i] = (sourceShape[i] == vecToReadShape[i]) &&
-                       ShapedType::isStatic(sourceShape[i]);
+    inBoundsVal = computeReadInBoundsFromStaticShape(sourceShape,
+                                                     vecToReadShape, indices);
   }
-  SmallVector<Value> indices(vecToReadRank, zero);
   auto transferReadOp =
       vector::TransferReadOp::create(builder, loc,
                                      /*vectorType=*/vecToReadTy,
@@ -491,17 +620,6 @@ Operation *vector::createWriteOrMaskedWrite(OpBuilder &builder, Location loc,
   int64_t vecToStoreRank = vecToStoreType.getRank();
   auto vecToStoreShape = vecToStoreType.getShape();
 
-  // Compute the in_bounds attribute
-  SmallVector<bool> inBoundsVal(vecToStoreRank, true);
-  if (useInBoundsInsteadOfMasking) {
-    // Update the inBounds attribute.
-    // FIXME: This computation is too weak - it ignores the write indices.
-    for (unsigned i = 0; i < vecToStoreRank; i++)
-      inBoundsVal[i] =
-          (destShape[destRank - vecToStoreRank + i] >= vecToStoreShape[i]) &&
-          ShapedType::isStatic(destShape[destRank - vecToStoreRank + i]);
-  }
-
   // If missing, initialize the write indices to 0.
   bool useDefaultWriteIdxs = writeIndices.empty();
   assert((useDefaultWriteIdxs ||
@@ -512,6 +630,13 @@ Operation *vector::createWriteOrMaskedWrite(OpBuilder &builder, Location loc,
     writeIndices.assign(destRank, zero);
   }
 
+  // Compute the in_bounds attribute.
+  SmallVector<bool> inBoundsVal(vecToStoreRank, true);
+  if (useInBoundsInsteadOfMasking) {
+    inBoundsVal = computeWriteInBoundsFromStaticShape(
+        destShape, vecToStoreShape, writeIndices);
+  }
+
   // Generate the xfer_write Op
   Operation *write = vector::TransferWriteOp::create(builder, loc,
                                                      /*vector=*/vecToStore,
diff --git a/mlir/test/Dialect/Linalg/vectorization/insert-slice-with-patterns.mlir b/mlir/test/Dialect/Linalg/vectorization/insert-slice-with-patterns.mlir
index f7764be9be73f..c83856a7c99de 100644
--- a/mlir/test/Dialect/Linalg/vectorization/insert-slice-with-patterns.mlir
+++ b/mlir/test/Dialect/Linalg/vectorization/insert-slice-with-patterns.mlir
@@ -59,6 +59,103 @@ module attributes {transform.with_named_sequence} {
 
 // -----
 
+// All source dimensions are dynamic, so read in_bounds cannot be proven for
+// any vector dimension. The transfer_read should therefore print without an
+// in_bounds attribute (all-false canonical form).
+//
+// CHECK-LABEL:   func.func @insert_dynamic_slice_all_false_read_in_bounds(
+// CHECK-SAME:      %[[ARG_0:.*]]: tensor<?x?x?xf32>,
+// CHECK-SAME:      %[[PAD:.*]]: f32, %[[SZ0:.*]]: index, %[[SZ1:.*]]: index, %[[SZ2:.*]]: index) -> tensor<9x8x7x1x2x3xf32> {
+// CHECK:           %[[EMPTY:.*]] = tensor.empty() : tensor<9x8x7x1x2x3xf32>
+// CHECK:           %[[BC:.*]] = vector.broadcast %[[PAD]] : f32 to vector<9x8x7x1x2x3xf32>
+// CHECK:           %[[WRITE:.*]] = vector.transfer_write %[[BC]], %[[EMPTY]]{{.*}} {in_bounds = [true, true, true, true, true, true]} : vector<9x8x7x1x2x3xf32>, tensor<9x8x7x1x2x3xf32>
+// CHECK:           %[[READ:.*]] = vector.transfer_read %[[ARG_0]]{{.*}}, %[[PAD]] : tensor<?x?x?xf32>, vector<1x2x3xf32>
+// CHECK:           %[[RES:.*]] = vector.transfer_write %[[READ]], %[[WRITE]]{{.*}} {in_bounds = [true, true, true]} : vector<1x2x3xf32>, tensor<9x8x7x1x2x3xf32>
+// CHECK:           return %[[RES]] : tensor<9x8x7x1x2x3xf32>
+func.func @insert_dynamic_slice_all_false_read_in_bounds(
+    %arg0: tensor<?x?x?xf32>, %pad : f32,
+    %sz0: index, %sz1: index, %sz2: index) -> tensor<9x8x7x1x2x3xf32> {
+  %init = tensor.empty() : tensor<9x8x7x1x2x3xf32>
+  %fill = linalg.fill ins(%pad : f32) outs(%init : tensor<9x8x7x1x2x3xf32>) -> tensor<9x8x7x1x2x3xf32>
+  %res = tensor.insert_slice %arg0 into %fill[0, 0, 0, 0, 0, 0] [1, 1, 1, %sz0, %sz1, %sz2][1, 1, 1, 1, 1, 1] : tensor<?x?x?xf32> into tensor<9x8x7x1x2x3xf32>
+  return %res : tensor<9x8x7x1x2x3xf32>
+}
+
+module attributes {transform.with_named_sequence} {
+  transform.named_sequence @__transform_main(%arg1: !transform.any_op {transform.readonly}) {
+    %0 = transform.structured.match ops{["tensor.insert_slice"]} in %arg1 : (!transform.any_op) -> !transform.any_op
+    %1 = transform.get_parent_op %0 {isolated_from_above} : (!transform.any_op) -> !transform.any_op
+    %2 = transform.structured.vectorize_children_and_apply_patterns %1 : (!transform.any_op) -> !transform.any_op
+    transform.yield
+  }
+}
+
+// -----
+
+// All write offsets in the vectorized dimensions are dynamic, so write
+// in_bounds cannot be proven for any vector dimension. The transfer_write
+// should print without an in_bounds attribute (all-false canonical form).
+//
+// CHECK-LABEL:   func.func @insert_static_slice_all_false_write_in_bounds(
+// CHECK-SAME:      %[[ARG_0:.*]]: tensor<1x2x3xf32>,
+// CHECK-SAME:      %[[PAD:.*]]: f32, %[[OFF0:.*]]: index, %[[OFF1:.*]]: index, %[[OFF2:.*]]: index) -> tensor<9x8x7x1x2x3xf32> {
+// CHECK:           %[[EMPTY:.*]] = tensor.empty() : tensor<9x8x7x1x2x3xf32>
+// CHECK:           %[[BC:.*]] = vector.broadcast %[[PAD]] : f32 to vector<9x8x7x1x2x3xf32>
+// CHECK:           %[[WRITE:.*]] = vector.transfer_write %[[BC]], %[[EMPTY]]{{.*}} {in_bounds = [true, true, true, true, true, true]} : vector<9x8x7x1x2x3xf32>, tensor<9x8x7x1x2x3xf32>
+// CHECK:           %[[READ:.*]] = vector.transfer_read %[[ARG_0]]{{.*}}, %[[PAD]] {in_bounds = [true, true, true]} : tensor<1x2x3xf32>, vector<1x2x3xf32>
+// CHECK:           %[[RES:.*]] = vector.transfer_write %[[READ]], %[[WRITE]]{{.*}} : vector<1x2x3xf32>, tensor<9x8x7x1x2x3xf32>
+// CHECK:           return %[[RES]] : tensor<9x8x7x1x2x3xf32>
+func.func @insert_static_slice_all_false_write_in_bounds(
+    %arg0: tensor<1x2x3xf32>, %pad : f32,
+    %off0: index, %off1: index, %off2: index) -> tensor<9x8x7x1x2x3xf32> {
+  %init = tensor.empty() : tensor<9x8x7x1x2x3xf32>
+  %fill = linalg.fill ins(%pad : f32) outs(%init : tensor<9x8x7x1x2x3xf32>) -> tensor<9x8x7x1x2x3xf32>
+  %res = tensor.insert_slice %arg0 into %fill[0, 0, 0, %off0, %off1, %off2] [1, 1, 1, 1, 2, 3][1, 1, 1, 1, 1, 1] : tensor<1x2x3xf32> into tensor<9x8x7x1x2x3xf32>
+  return %res : tensor<9x8x7x1x2x3xf32>
+}
+
+module attributes {transform.with_named_sequence} {
+  transform.named_sequence @__transform_main(%arg1: !transform.any_op {transform.readonly}) {
+    %0 = transform.structured.match ops{["tensor.insert_slice"]} in %arg1 : (!transform.any_op) -> !transform.any_op
+    %1 = transform.get_parent_op %0 {isolated_from_above} : (!transform.any_op) -> !transform.any_op
+    %2 = transform.structured.vectorize_children_and_apply_patterns %1 : (!transform.any_op) -> !transform.any_op
+    transform.yield
+  }
+}
+
+// -----
+
+// Non-zero insert offsets should affect transfer_write in_bounds. Since source
+// shape is dynamic, transfer_read also keeps a conservative in_bounds.
+//
+// CHECK-LABEL:   func.func @insert_dynamic_slice_non_zero_offset(
+// CHECK-SAME:      %[[ARG_0:.*]]: tensor<1x?x3xf32>,
+// CHECK-SAME:      %[[PAD:.*]]: f32,
+// CHECK-SAME:      %[[SIZE:.*]]: index) -> tensor<9x8x7x1x2x3xf32> {
+// CHECK:           %[[EMPTY:.*]] = tensor.empty() : tensor<9x8x7x1x2x3xf32>
+// CHECK:           %[[BC:.*]] = vector.broadcast %[[PAD]] : f32 to vector<9x8x7x1x2x3xf32>
+// CHECK:           %[[WRITE:.*]] = vector.transfer_write %[[BC]], %[[EMPTY]]{{.*}} {in_bounds = [true, true, true, true, true, true]} : vector<9x8x7x1x2x3xf32>, tensor<9x8x7x1x2x3xf32>
+// CHECK:           %[[READ:.*]] = vector.transfer_read %[[ARG_0]]{{.*}}, %[[PAD]] {in_bounds = [true, false, true]} : tensor<1x?x3xf32>, vector<1x2x3xf32>
+// CHECK:           %[[RES:.*]] = vector.transfer_write %[[READ]], %[[WRITE]]{{.*}} {in_bounds = [true, false, true]} : vector<1x2x3xf32>, tensor<9x8x7x1x2x3xf32>
+// CHECK:           return %[[RES]] : tensor<9x8x7x1x2x3xf32>
+func.func @insert_dynamic_slice_non_zero_offset(%arg0: tensor<1x?x3xf32>, %pad : f32, %size: index) -> tensor<9x8x7x1x2x3xf32> {
+  %init = tensor.empty() : tensor<9x8x7x1x2x3xf32>
+  %fill = linalg.fill ins(%pad : f32) outs(%init : tensor<9x8x7x1x2x3xf32>) -> tensor<9x8x7x1x2x3xf32>
+  %res = tensor.insert_slice %arg0 into %fill[0, 0, 0, 0, 1, 0] [1, 1, 1, 1, %size, 3][1, 1, 1, 1, 1, 1] : tensor<1x?x3xf32> into tensor<9x8x7x1x2x3xf32>
+  return %res : tensor<9x8x7x1x2x3xf32>
+}
+
+module attributes {transform.with_named_sequence} {
+  transform.named_sequence @__transform_main(%arg1: !transform.any_op {transform.readonly}) {
+    %0 = transform.structured.match ops{["tensor.insert_slice"]} in %arg1 : (!transform.any_op) -> !transform.any_op
+    %1 = transform.get_parent_op %0 {isolated_from_above} : (!transform.any_op) -> !transform.any_op
+    %2 = transform.structured.vectorize_children_and_apply_patterns %1 : (!transform.any_op) -> !transform.any_op
+    transform.yield
+  }
+}
+
+// -----
+
 // Same as above, but the source type has is dynamically shaped. This means
 // that the pad value is now required and the vector dim corresponding to the
 // dynamic shape has to be inferred from the shape of the destination tensor.
diff --git a/mlir/test/Dialect/Vector/transform-vector.mlir b/mlir/test/Dialect/Vector/transform-vector.mlir
index 4dc11c26e83f1..fc788867fbbd0 100644
--- a/mlir/test/Dialect/Vector/transform-vector.mlir
+++ b/mlir/test/Dialect/Vector/transform-vector.mlir
@@ -69,6 +69,73 @@ module attributes {transform.with_named_sequence} {
 
 // -----
 
+// CHECK-LABEL: func @matmul_tensors_iv_indices
+func.func @matmul_tensors_iv_indices(
+  %arg0: tensor<8x16xf32>, %arg1: tensor<16x64xf32>, %arg2: tensor<8x64xf32>)
+    -> tensor<8x64xf32> {
+// CHECK-NOT: linalg
+// CHECK: vector.extract {{.*}} : vector<4xf32> from vector<8x4xf32>
+// CHECK: vector.store {{.*}} : memref<8x64xf32>, vector<4xf32>
+  %0 = linalg.matmul  ins(%arg0, %arg1: tensor<8x16xf32>, tensor<16x64xf32>)
+                     outs(%arg2: tensor<8x64xf32>)
+    -> tensor<8x64xf32>
+  return %0 : tensor<8x64xf32>
+}
+
+module attributes {transform.with_named_sequence} {
+  transform.named_sequence @__transform_main(%module_op: !transform.any_op {transform.consumed}) {
+    %0 = transform.structured.match ops{["linalg.matmul"]} in %module_op : (!transform.any_op) -> !transform.any_op
+    %1, %loops:3 = transform.structured.tile_using_for %0 tile_sizes [8, 4, 2]
+      : (!transform.any_op) -> (!transform.any_op, !transform.any_op, !transform.any_op, !transform.any_op)
+    %2 = transform.get_parent_op %1 {isolated_from_above} : (!transform.any_op) -> !transform.any_op
+    transform.structured.vectorize_children_and_apply_patterns %2 : (!transform.any_op) -> !transform.any_op
+    %b = transform.bufferization.one_shot_bufferize
+        layout{IdentityLayoutMap} %module_op
+        {bufferize_function_boundaries = true, allow_return_allocs = true}
+        : (!transform.any_op) -> !transform.any_op
+
+    %f = transform.structured.match ops{["func.func"]} in %b
+      : (!transform.any_op) -> !transform.any_op
+
+    transform.apply_patterns to %f {
+      transform.apply_patterns.vector.lower_contraction lowering_strategy = "outerproduct"
+    } : !transform.any_op
+
+    transform.apply_patterns to %f {
+      transform.apply_patterns.vector.transfer_permutation_patterns
+    } : !transform.any_op
+
+    transform.apply_patterns to %f {
+      transform.apply_patterns.vector.reorder_multi_reduction_dims lowering_strategy = "innerparallel"
+      transform.apply_patterns.vector.multi_reduction_flattening lowering_strategy = "innerparallel"
+      transform.apply_patterns.vector.multi_reduction_unrolling lowering_strategy = "innerparallel"
+    } : !transform.any_op
+
+    transform.apply_patterns to %f {
+      transform.apply_patterns.vector.split_transfer_full_partial split_transfer_strategy = "linalg-copy"
+    } : !transform.any_op
+
+    transform.apply_patterns to %f {
+      transform.apply_patterns.vector.transfer_to_scf max_transfer_rank = 1 full_unroll = true
+    } : !transform.any_op
+
+    transform.apply_patterns to %f {
+      transform.apply_patterns.vector.lower_transfer max_transfer_rank = 1
+    } : !transform.any_op
+
+    transform.apply_patterns to %f {
+      transform.apply_patterns.vector.lower_shape_cast
+    } : !transform.any_op
+
+    transform.apply_patterns to %f {
+      transform.apply_patterns.vector.lower_transpose lowering_strategy = "shuffle_1d"
+    } : !transform.any_op
+    transform.yield
+  }
+}
+
+// -----
+
 // CHECK-DAG: #[[$map0:.*]] = affine_map<(d0, d1, d2) -> (d0, d2)>
 // CHECK-DAG: #[[$map1:.*]] = affine_map<(d0, d1, d2) -> (d2, d1)>
 // CHECK-DAG: #[[$map2:.*]] = affine_map<(d0, d1, d2) -> (d0, d1)>



More information about the Mlir-commits mailing list