[Mlir-commits] [mlir] [mlir][tensor] Add more tensor.extract_slice canonicalization (PR #212974)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Thu Jul 30 02:41:18 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir-tensor
Author: Tuomas Kärnä (tkarna)
<details>
<summary>Changes</summary>
Adds more canonicalization patterns for tensor.extract_slice op.
### 1. Fold full-slice rank-reducing extract of expand_shape
Before:
%expanded = tensor.expand_shape %src [[0, 1]] output_shape [4096, 1]
: tensor<4096xf32> into tensor<4096x1xf32>
%slice = tensor.extract_slice %expanded[0, 0] [4096, 1] [1, 1]
: tensor<4096x1xf32> to tensor<4096xf32>
After:
// %slice replaced by:
%src : tensor<4096xf32>
### 2. Fold extract_slice of tensor.empty to a smaller tensor.empty
Before:
%empty = tensor.empty() : tensor<4096x1xf32>
%slice = tensor.extract_slice %empty[0, 0] [4096, 1] [1, 1]
: tensor<4096x1xf32> to tensor<4096xf32>
After:
%new_empty = tensor.empty() : tensor<4096xf32>
### 3. Fold extract_slice of linalg.fill over tensor.empty by shrinking destination
Before:
%empty = tensor.empty() : tensor<64x96xf32>
%filled = linalg.fill ins(%cst : f32) outs(%empty : tensor<64x96xf32>) -> tensor<64x96xf32>
%slice = tensor.extract_slice %filled[0, 0] [32, 48] [1, 1]
: tensor<64x96xf32> to tensor<32x48xf32>
After:
%new_empty = tensor.empty() : tensor<32x48xf32>
%new_filled = linalg.fill ins(%cst : f32) outs(%new_empty : tensor<32x48xf32>) -> tensor<32x48xf32>
Assisted-by: GPT-5.3-Codex
---
Full diff: https://github.com/llvm/llvm-project/pull/212974.diff
4 Files Affected:
- (modified) mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp (+36-1)
- (modified) mlir/lib/Dialect/Tensor/IR/TensorOps.cpp (+68-1)
- (modified) mlir/test/Dialect/Linalg/canonicalize.mlir (+30)
- (modified) mlir/test/Dialect/Tensor/canonicalize.mlir (+43)
``````````diff
diff --git a/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp b/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
index 170e1edf8a55d..7240d38f24c6d 100644
--- a/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
+++ b/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
@@ -973,6 +973,41 @@ struct FoldFillWithTensorExtract : public OpRewritePattern<tensor::ExtractOp> {
}
};
+/// Fold tensor.extract_slice(linalg.fill(..., tensor.empty)) by shrinking the
+/// tensor.empty and rebuilding linalg.fill on top of it.
+struct FoldExtractSliceOfFillOfEmpty
+ : public OpRewritePattern<tensor::ExtractSliceOp> {
+public:
+ using OpRewritePattern<tensor::ExtractSliceOp>::OpRewritePattern;
+
+ LogicalResult matchAndRewrite(tensor::ExtractSliceOp extractSliceOp,
+ PatternRewriter &rewriter) const override {
+ // See if tensor input of tensor.extract_slice op is the result of a
+ // linalg.fill op.
+ auto fillOp = extractSliceOp.getSource().getDefiningOp<FillOp>();
+ if (!fillOp)
+ return failure();
+
+ // Ensure the fill op has a single use.
+ if (!fillOp->hasOneUse())
+ return failure();
+
+ // See if the output of the fill op is created by a tensor.empty op.
+ if (!fillOp.getOutputs()[0].getDefiningOp<tensor::EmptyOp>())
+ return failure();
+
+ // Create a new tensor.empty op with the smaller size of the extract_slice.
+ Value smallerEmpty = tensor::EmptyOp::create(
+ rewriter, extractSliceOp.getLoc(), extractSliceOp.getType(),
+ extractSliceOp.getSizes());
+ // Create a new linalg.fill op with the same value and the smaller empty.
+ auto newFill = FillOp::create(rewriter, extractSliceOp.getLoc(),
+ fillOp.getInputs(), smallerEmpty);
+ rewriter.replaceOp(extractSliceOp, newFill.getResult(0));
+ return success();
+ }
+};
+
/// Folds pack(fill) into a single fill op if
/// 1. The pack op does not have padding value, or
/// 2. The filled value and padding value are the same.
@@ -1103,7 +1138,7 @@ struct FoldConcatsOfFill : public OpRewritePattern<tensor::ConcatOp> {
void FillOp::getCanonicalizationPatterns(RewritePatternSet &results,
MLIRContext *context) {
results.add<FoldConcatsOfFill, FoldFillWithCopy, FoldFillWithTensorExtract,
- FoldFillWithPack, FoldFillWithPad,
+ FoldExtractSliceOfFillOfEmpty, FoldFillWithPack, FoldFillWithPad,
FoldFillWithTensorReshape<tensor::CollapseShapeOp>,
FoldFillWithTensorReshape<tensor::ExpandShapeOp>,
FoldInsertPadIntoFill, FoldFillWithTranspose>(context);
diff --git a/mlir/lib/Dialect/Tensor/IR/TensorOps.cpp b/mlir/lib/Dialect/Tensor/IR/TensorOps.cpp
index 637366a289ac9..998f8595a5f7c 100644
--- a/mlir/lib/Dialect/Tensor/IR/TensorOps.cpp
+++ b/mlir/lib/Dialect/Tensor/IR/TensorOps.cpp
@@ -2612,6 +2612,72 @@ class ExtractSliceOpCastFolder final : public OpRewritePattern<ExtractSliceOp> {
}
};
+/// Fold a full-slice rank-reducing extract_slice of an expand_shape back to
+/// the expand_shape source when the expanded and sliced dimensions match.
+///
+/// Example:
+/// ```
+/// %expanded = tensor.expand_shape %src [[0, 1]] output_shape [4096, 1]
+/// : tensor<4096xf32> into tensor<4096x1xf32>
+/// %slice = tensor.extract_slice %expanded[0, 0] [4096, 1] [1, 1]
+/// : tensor<4096x1xf32> to tensor<4096xf32>
+/// ```
+///
+class FoldExtractSliceOfExpandShape final
+ : public OpRewritePattern<ExtractSliceOp> {
+public:
+ using OpRewritePattern<ExtractSliceOp>::OpRewritePattern;
+
+ LogicalResult matchAndRewrite(ExtractSliceOp sliceOp,
+ PatternRewriter &rewriter) const override {
+ auto expandOp = sliceOp.getSource().getDefiningOp<ExpandShapeOp>();
+ if (!expandOp)
+ return failure();
+
+ if (sliceOp.getType() != expandOp.getSrcType())
+ return failure();
+
+ SmallVector<OpFoldResult> mixedExpandedSizes = expandOp.getMixedOutputShape();
+ if (mixedExpandedSizes.size() != sliceOp.getMixedSizes().size())
+ return failure();
+
+ for (auto [offset, size, stride, expandedSize] : llvm::zip_equal(
+ sliceOp.getMixedOffsets(), sliceOp.getMixedSizes(),
+ sliceOp.getMixedStrides(), mixedExpandedSizes)) {
+ if (getConstantIntValue(offset) != static_cast<int64_t>(0) ||
+ getConstantIntValue(stride) != static_cast<int64_t>(1))
+ return failure();
+ if (size != expandedSize)
+ return failure();
+ }
+
+ rewriter.replaceOp(sliceOp, expandOp.getSrc());
+ return success();
+ }
+};
+
+/// Fold extract_slice of tensor.empty to a smaller tensor.empty.
+class FoldExtractSliceOfEmpty final
+ : public OpRewritePattern<ExtractSliceOp> {
+public:
+ using OpRewritePattern<ExtractSliceOp>::OpRewritePattern;
+
+ LogicalResult matchAndRewrite(ExtractSliceOp sliceOp,
+ PatternRewriter &rewriter) const override {
+ auto makeSmallerEmpty = [&]() -> Value {
+ return EmptyOp::create(rewriter, sliceOp.getLoc(), sliceOp.getType(),
+ sliceOp.getSizes())
+ .getResult();
+ };
+
+ if (sliceOp.getSource().getDefiningOp<EmptyOp>()) {
+ rewriter.replaceOp(sliceOp, makeSmallerEmpty());
+ return success();
+ }
+ return failure();
+ }
+};
+
/// Slice elements from `values` into `outValues`. `counts` represents the
/// numbers of elements to stride in the original values for each dimension.
/// The output values can be used to construct a DenseElementsAttr.
@@ -2767,7 +2833,8 @@ void ExtractSliceOp::getCanonicalizationPatterns(RewritePatternSet &results,
MLIRContext *context) {
results.add<
OpWithOffsetSizesAndStridesConstantArgumentFolder<
- ExtractSliceOp, SliceReturnTypeCanonicalizer, SliceCanonicalizer>,
+ ExtractSliceOp, SliceReturnTypeCanonicalizer, SliceCanonicalizer>,
+ FoldExtractSliceOfEmpty, FoldExtractSliceOfExpandShape,
ExtractSliceOpCastFolder>(context);
}
diff --git a/mlir/test/Dialect/Linalg/canonicalize.mlir b/mlir/test/Dialect/Linalg/canonicalize.mlir
index bb11ce0d4dfb8..074f662d34245 100644
--- a/mlir/test/Dialect/Linalg/canonicalize.mlir
+++ b/mlir/test/Dialect/Linalg/canonicalize.mlir
@@ -117,6 +117,36 @@ func.func @linalg_effects(
// -----
+// CHECK-LABEL: func @fold_extract_slice_of_fill_of_empty
+// CHECK-NOT: tensor.extract_slice
+// CHECK: %[[EMPTY:.*]] = tensor.empty() : tensor<4096xf32>
+// CHECK: %[[FILL:.*]] = linalg.fill ins(%[[CST:.*]] : f32) outs(%[[EMPTY]] : tensor<4096xf32>) -> tensor<4096xf32>
+// CHECK: return %[[FILL]] : tensor<4096xf32>
+func.func @fold_extract_slice_of_fill_of_empty(%cst : f32) -> tensor<4096xf32> {
+ %empty = tensor.empty() : tensor<4096x1xf32>
+ %filled = linalg.fill ins(%cst : f32) outs(%empty : tensor<4096x1xf32>) -> tensor<4096x1xf32>
+ %slice = tensor.extract_slice %filled[0, 0] [4096, 1] [1, 1]
+ : tensor<4096x1xf32> to tensor<4096xf32>
+ return %slice : tensor<4096xf32>
+}
+
+// -----
+
+// CHECK-LABEL: func @fold_extract_slice_of_fill_of_empty_2d
+// CHECK-NOT: tensor.extract_slice
+// CHECK: %[[EMPTY:.*]] = tensor.empty() : tensor<32x48xf32>
+// CHECK: %[[FILL:.*]] = linalg.fill ins(%[[CST:.*]] : f32) outs(%[[EMPTY]] : tensor<32x48xf32>) -> tensor<32x48xf32>
+// CHECK: return %[[FILL]] : tensor<32x48xf32>
+func.func @fold_extract_slice_of_fill_of_empty_2d(%cst : f32) -> tensor<32x48xf32> {
+ %empty = tensor.empty() : tensor<64x96xf32>
+ %filled = linalg.fill ins(%cst : f32) outs(%empty : tensor<64x96xf32>) -> tensor<64x96xf32>
+ %slice = tensor.extract_slice %filled[0, 0] [32, 48] [1, 1]
+ : tensor<64x96xf32> to tensor<32x48xf32>
+ return %slice : tensor<32x48xf32>
+}
+
+// -----
+
#map = affine_map<(d0, d1, d2) -> (d0, d1, d2)>
func.func @remove_no_op(%arg0 : tensor<?x?x?xf32>, %arg1 : tensor<?x?x?xf32>)
-> (tensor<?x?x?xf32>, tensor<?x?x?xf32>) {
diff --git a/mlir/test/Dialect/Tensor/canonicalize.mlir b/mlir/test/Dialect/Tensor/canonicalize.mlir
index 67b7ab99c5d18..adcfe5b97b558 100644
--- a/mlir/test/Dialect/Tensor/canonicalize.mlir
+++ b/mlir/test/Dialect/Tensor/canonicalize.mlir
@@ -623,6 +623,49 @@ func.func @trivial_slice(%arg0 : tensor<4x6x16x32xi8>) -> tensor<4x6x16x32xi8> {
// -----
+// CHECK-LABEL: func @fold_extract_slice_of_expand_shape
+// CHECK-SAME: %[[ARG0:.*]]: tensor<4096xf32>
+// CHECK-NOT: tensor.expand_shape
+// CHECK-NOT: tensor.extract_slice
+// CHECK: return %[[ARG0]] : tensor<4096xf32>
+func.func @fold_extract_slice_of_expand_shape(
+ %arg0 : tensor<4096xf32>) -> tensor<4096xf32> {
+ %expanded = tensor.expand_shape %arg0 [[0, 1]] output_shape [4096, 1]
+ : tensor<4096xf32> into tensor<4096x1xf32>
+ %slice = tensor.extract_slice %expanded[0, 0] [4096, 1] [1, 1]
+ : tensor<4096x1xf32> to tensor<4096xf32>
+ return %slice : tensor<4096xf32>
+}
+
+// -----
+
+// CHECK-LABEL: func @dont_fold_extract_slice_of_expand_shape_with_different_sizes
+// CHECK: tensor.expand_shape
+// CHECK: tensor.extract_slice
+func.func @dont_fold_extract_slice_of_expand_shape_with_different_sizes(
+ %arg0 : tensor<4096xf32>) -> tensor<1024xf32> {
+ %expanded = tensor.expand_shape %arg0 [[0, 1]] output_shape [4096, 1]
+ : tensor<4096xf32> into tensor<4096x1xf32>
+ %slice = tensor.extract_slice %expanded[0, 0] [1024, 1] [1, 1]
+ : tensor<4096x1xf32> to tensor<1024xf32>
+ return %slice : tensor<1024xf32>
+}
+
+// -----
+
+// CHECK-LABEL: func @fold_extract_slice_of_empty
+// CHECK-NOT: tensor.extract_slice
+// CHECK: %[[EMPTY:.*]] = tensor.empty() : tensor<4096xf32>
+// CHECK: return %[[EMPTY]] : tensor<4096xf32>
+func.func @fold_extract_slice_of_empty() -> tensor<4096xf32> {
+ %empty = tensor.empty() : tensor<4096x1xf32>
+ %slice = tensor.extract_slice %empty[0, 0] [4096, 1] [1, 1]
+ : tensor<4096x1xf32> to tensor<4096xf32>
+ return %slice : tensor<4096xf32>
+}
+
+// -----
+
// CHECK-LABEL: func @trivial_insert_slice
// CHECK-SAME: %[[ARG0:.[a-z0-9A-Z_]+]]: tensor<4x6x16x32xi8>
// CHECK-NOT: tensor.extract_slice
``````````
</details>
https://github.com/llvm/llvm-project/pull/212974
More information about the Mlir-commits
mailing list