[Mlir-commits] [mlir] [mlir][linalg] Add FoldConsecutiveScalarMulPattern canonicalization (PR #209697)
Daniel Christian Mandolang
llvmlistbot at llvm.org
Sun Aug 23 20:19:27 PDT 2026
https://github.com/danielcm585 updated https://github.com/llvm/llvm-project/pull/209697
>From bd6c88bcde0fe8eaa54860f88663a2dd960aa38e Mon Sep 17 00:00:00 2001
From: danielcm585 <danielchristianmandolang at gmail.com>
Date: Wed, 15 Jul 2026 16:31:04 +0800
Subject: [PATCH 1/8] [mlir][linalg] Add FoldConsecutiveScalarMulPattern
canonicalization
---
.../Dialect/Linalg/IR/LinalgStructuredOps.td | 1 +
mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp | 158 ++++++++++++++++++
.../Linalg/fold-consecutive-scalar-mul.mlir | 111 ++++++++++++
3 files changed, 270 insertions(+)
create mode 100644 mlir/test/Dialect/Linalg/fold-consecutive-scalar-mul.mlir
diff --git a/mlir/include/mlir/Dialect/Linalg/IR/LinalgStructuredOps.td b/mlir/include/mlir/Dialect/Linalg/IR/LinalgStructuredOps.td
index fc5c9770d969b..6f556d6072d87 100644
--- a/mlir/include/mlir/Dialect/Linalg/IR/LinalgStructuredOps.td
+++ b/mlir/include/mlir/Dialect/Linalg/IR/LinalgStructuredOps.td
@@ -619,6 +619,7 @@ def ElementwiseOp : LinalgStructuredBase_Op<"elementwise", [
let hasCustomAssemblyFormat = 1;
let hasFolder = 1;
+ let hasCanonicalizer = 1;
let extraClassDeclaration = structuredOpsBaseDecls # [{
/// Get the arity enum corresponding to the kind of op, e.g. if arg is
diff --git a/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp b/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
index 1a56c5a483e73..b17eb95868372 100644
--- a/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
+++ b/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
@@ -5074,6 +5074,164 @@ Speculation::Speculatability ElementwiseOp::getSpeculatability() {
return getGenericSpeculatabilityImpl(cast<LinalgOp>(getOperation()));
}
+/// Check if the given elementwise op is a binary mul.
+static bool isElementwiseMul(ElementwiseOp op) {
+ auto groupAndKind = getArityGroupAndKind(op.getKind());
+ return groupAndKind.arityGroup == ElementwiseArityGroup::Binary &&
+ groupAndKind.kind.binaryFn == BinaryFn::mul;
+}
+
+/// Try to extract the scalar constant value from a Value that is either:
+/// - a dense splat constant (tensor<...xf32> with all same elements), or
+/// - a scalar constant that was broadcast.
+/// Returns std::nullopt if the value is not a recognizable scalar constant.
+static std::optional<TypedAttr> getScalarConstant(Value val) {
+ // Case 1: Dense splat constant.
+ if (auto splatAttr = getScalarConstantAttrFromDenseSplat(val))
+ return splatAttr;
+
+ // Case 2: fill(scalar_constant) - a linalg.fill with a constant scalar.
+ if (auto fillOp = val.getDefiningOp<linalg::FillOp>()) {
+ Value fillVal = fillOp.getInputs()[0];
+ Attribute constAttr;
+ if (matchPattern(fillVal, m_Constant(&constAttr)))
+ return cast<TypedAttr>(constAttr);
+ }
+
+ return std::nullopt;
+}
+
+/// Fold two consecutive scalar multiplications into one:
+///
+/// %c1 = arith.constant dense<s1> : tensor<...>
+/// %mul1 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+/// ins(%x, %c1 : ...) outs(...)
+/// %c2 = arith.constant dense<s2> : tensor<...>
+/// %mul2 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+/// ins(%mul1, %c2 : ...) outs(...)
+///
+/// Into:
+///
+/// %c = arith.constant dense<s1 * s2> : tensor<...>
+/// %mul = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+/// ins(%x, %c : ...) outs(...)
+///
+struct FoldConsecutiveScalarMulPattern
+ : public OpRewritePattern<ElementwiseOp> {
+ using OpRewritePattern<ElementwiseOp>::OpRewritePattern;
+
+ LogicalResult matchAndRewrite(ElementwiseOp outerMul,
+ PatternRewriter &rewriter) const override {
+ if (!outerMul.hasPureTensorSemantics())
+ return failure();
+
+ // Check that the outer op is a mul.
+ if (!isElementwiseMul(outerMul))
+ return failure();
+
+ // The outer op has exactly 2 inputs for binary mul.
+ Value outerLhs = outerMul.getInputs()[0];
+ Value outerRhs = outerMul.getInputs()[1];
+
+ // Find which operand of the outer mul is the scalar constant, and which
+ // is the inner mul.
+ Value outerNonConst = nullptr;
+ std::optional<TypedAttr> outerScalar;
+
+ // Try outerRhs as scalar, outerLhs as inner mul.
+ outerScalar = getScalarConstant(outerRhs);
+ if (outerScalar) {
+ outerNonConst = outerLhs;
+ } else {
+ // Try outerLhs as scalar, outerRhs as inner mul.
+ outerScalar = getScalarConstant(outerLhs);
+ if (!outerScalar)
+ return failure();
+ outerNonConst = outerRhs;
+ }
+
+ // The non-constant operand must be produced by another elementwise mul.
+ auto innerMul =
+ dyn_cast_or_null<ElementwiseOp>(outerNonConst.getDefiningOp());
+ if (!innerMul || !isElementwiseMul(innerMul))
+ return failure();
+
+ if (!innerMul.hasPureTensorSemantics())
+ return failure();
+
+ // The inner mul result should only be used by the outer mul (to avoid
+ // duplicating computation).
+ if (!innerMul->hasOneUse())
+ return failure();
+
+ Value innerLhs = innerMul.getInputs()[0];
+ Value innerRhs = innerMul.getInputs()[1];
+
+ // Find the scalar constant in the inner mul.
+ Value innerNonConst = nullptr;
+ std::optional<TypedAttr> innerScalar;
+
+ innerScalar = getScalarConstant(innerRhs);
+ if (innerScalar) {
+ innerNonConst = innerLhs;
+ } else {
+ innerScalar = getScalarConstant(innerLhs);
+ if (!innerScalar)
+ return failure();
+ innerNonConst = innerRhs;
+ }
+
+ // Fold the two scalar constants: compute s1 * s2 at compile time.
+ Location loc = outerMul.getLoc();
+ auto innerAttr = *innerScalar;
+ auto outerAttr = *outerScalar;
+
+ // Both scalars must have the same element type.
+ if (innerAttr.getType() != outerAttr.getType())
+ return failure();
+
+ TypedAttr foldedAttr;
+ if (isa<FloatType>(innerAttr.getType())) {
+ auto lhs = cast<FloatAttr>(innerAttr);
+ auto rhs = cast<FloatAttr>(outerAttr);
+ foldedAttr = FloatAttr::get(lhs.getType(), lhs.getValue() * rhs.getValue());
+ } else if (isa<IntegerType>(innerAttr.getType())) {
+ auto lhs = cast<IntegerAttr>(innerAttr);
+ auto rhs = cast<IntegerAttr>(outerAttr);
+ foldedAttr = IntegerAttr::get(lhs.getType(), lhs.getValue() * rhs.getValue());
+ } else {
+ return failure();
+ }
+
+ // Create the combined splat constant with the same type as the outer
+ // scalar operand.
+ Value outerScalarOperand =
+ (getScalarConstant(outerRhs)) ? outerRhs : outerLhs;
+ auto scalarOperandType =
+ cast<RankedTensorType>(outerScalarOperand.getType());
+ auto combinedSplat = DenseElementsAttr::get(scalarOperandType, foldedAttr);
+ Value combinedConst =
+ arith::ConstantOp::create(rewriter, loc, scalarOperandType,
+ combinedSplat);
+
+ // Create the new single mul: innerNonConst * combinedConst.
+ // Use the same indexing maps as the outer mul, since both operands have
+ // matching shapes (the inner non-const input may need the inner mul's
+ // indexing map).
+ SmallVector<Value> newInputs = {innerNonConst, combinedConst};
+ rewriter.replaceOpWithNewOp<ElementwiseOp>(
+ outerMul, newInputs, outerMul.getDpsInits(),
+ outerMul.getKindAttr(),
+ rewriter.getAffineMapArrayAttr(outerMul.getIndexingMapsArray()));
+ return success();
+ }
+};
+
+void ElementwiseOp::getCanonicalizationPatterns(RewritePatternSet &results,
+ MLIRContext *context) {
+ results.add<FoldConsecutiveScalarMulPattern>(context);
+}
+
//===----------------------------------------------------------------------===//
// PackOp/UnPackOp Common
//===----------------------------------------------------------------------===//
diff --git a/mlir/test/Dialect/Linalg/fold-consecutive-scalar-mul.mlir b/mlir/test/Dialect/Linalg/fold-consecutive-scalar-mul.mlir
new file mode 100644
index 0000000000000..6e5cbbcb5cb9d
--- /dev/null
+++ b/mlir/test/Dialect/Linalg/fold-consecutive-scalar-mul.mlir
@@ -0,0 +1,111 @@
+// RUN: mlir-opt %s -canonicalize="test-convergence" -split-input-file | FileCheck %s
+
+// CHECK-LABEL: func @fold_consecutive_scalar_mul_f32
+// CHECK-SAME: (%[[ARG:.*]]: tensor<4x8xf32>)
+// CHECK-DAG: %[[COMBINED:.*]] = arith.constant dense<6.000000e+00> : tensor<4x8xf32>
+// CHECK: %[[EMPTY:.*]] = tensor.empty() : tensor<4x8xf32>
+// CHECK: %[[RESULT:.*]] = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+// CHECK-SAME: ins(%[[ARG]], %[[COMBINED]] : tensor<4x8xf32>, tensor<4x8xf32>)
+// CHECK-SAME: outs(%[[EMPTY]] : tensor<4x8xf32>)
+// CHECK: return %[[RESULT]]
+func.func @fold_consecutive_scalar_mul_f32(%arg0: tensor<4x8xf32>) -> tensor<4x8xf32> {
+ %cst2 = arith.constant dense<2.0> : tensor<4x8xf32>
+ %cst3 = arith.constant dense<3.0> : tensor<4x8xf32>
+ %empty = tensor.empty() : tensor<4x8xf32>
+ %mul1 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+ ins(%arg0, %cst2 : tensor<4x8xf32>, tensor<4x8xf32>)
+ outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
+ %mul2 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+ ins(%mul1, %cst3 : tensor<4x8xf32>, tensor<4x8xf32>)
+ outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
+ return %mul2 : tensor<4x8xf32>
+}
+
+// -----
+
+// CHECK-LABEL: func @fold_consecutive_scalar_mul_i32
+// CHECK-SAME: (%[[ARG:.*]]: tensor<4x8xi32>)
+// CHECK-DAG: %[[COMBINED:.*]] = arith.constant dense<15> : tensor<4x8xi32>
+// CHECK: %[[EMPTY:.*]] = tensor.empty() : tensor<4x8xi32>
+// CHECK: %[[RESULT:.*]] = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+// CHECK-SAME: ins(%[[ARG]], %[[COMBINED]] : tensor<4x8xi32>, tensor<4x8xi32>)
+// CHECK-SAME: outs(%[[EMPTY]] : tensor<4x8xi32>)
+// CHECK: return %[[RESULT]]
+func.func @fold_consecutive_scalar_mul_i32(%arg0: tensor<4x8xi32>) -> tensor<4x8xi32> {
+ %cst5 = arith.constant dense<5> : tensor<4x8xi32>
+ %cst3 = arith.constant dense<3> : tensor<4x8xi32>
+ %empty = tensor.empty() : tensor<4x8xi32>
+ %mul1 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+ ins(%arg0, %cst5 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ %mul2 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+ ins(%mul1, %cst3 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ return %mul2 : tensor<4x8xi32>
+}
+
+// -----
+
+// Scalar constant on the left-hand side.
+// CHECK-LABEL: func @fold_scalar_mul_lhs
+// CHECK-SAME: (%[[ARG:.*]]: tensor<4x8xf32>)
+// CHECK-DAG: %[[COMBINED:.*]] = arith.constant dense<1.200000e+01> : tensor<4x8xf32>
+// CHECK: %[[EMPTY:.*]] = tensor.empty() : tensor<4x8xf32>
+// CHECK: %[[RESULT:.*]] = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+// CHECK-SAME: ins(%[[ARG]], %[[COMBINED]] : tensor<4x8xf32>, tensor<4x8xf32>)
+// CHECK-SAME: outs(%[[EMPTY]] : tensor<4x8xf32>)
+// CHECK: return %[[RESULT]]
+func.func @fold_scalar_mul_lhs(%arg0: tensor<4x8xf32>) -> tensor<4x8xf32> {
+ %cst4 = arith.constant dense<4.0> : tensor<4x8xf32>
+ %cst3 = arith.constant dense<3.0> : tensor<4x8xf32>
+ %empty = tensor.empty() : tensor<4x8xf32>
+ %mul1 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+ ins(%cst4, %arg0 : tensor<4x8xf32>, tensor<4x8xf32>)
+ outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
+ %mul2 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+ ins(%cst3, %mul1 : tensor<4x8xf32>, tensor<4x8xf32>)
+ outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
+ return %mul2 : tensor<4x8xf32>
+}
+
+// -----
+
+// Do not fold when the inner mul has multiple uses.
+// CHECK-LABEL: func @no_fold_multi_use
+// CHECK: linalg.elementwise kind=#linalg.elementwise_kind<mul>
+// CHECK: linalg.elementwise kind=#linalg.elementwise_kind<mul>
+// CHECK: linalg.elementwise kind=#linalg.elementwise_kind<add>
+func.func @no_fold_multi_use(%arg0: tensor<4x8xf32>) -> tensor<4x8xf32> {
+ %cst2 = arith.constant dense<2.0> : tensor<4x8xf32>
+ %cst3 = arith.constant dense<3.0> : tensor<4x8xf32>
+ %empty = tensor.empty() : tensor<4x8xf32>
+ %mul1 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+ ins(%arg0, %cst2 : tensor<4x8xf32>, tensor<4x8xf32>)
+ outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
+ %mul2 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+ ins(%mul1, %cst3 : tensor<4x8xf32>, tensor<4x8xf32>)
+ outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
+ // Extra use of mul1 prevents folding.
+ %add = linalg.elementwise kind=#linalg.elementwise_kind<add>
+ ins(%mul2, %mul1 : tensor<4x8xf32>, tensor<4x8xf32>)
+ outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
+ return %add : tensor<4x8xf32>
+}
+
+// -----
+
+// Do not fold when neither operand is a scalar constant.
+// CHECK-LABEL: func @no_fold_non_const
+// CHECK: linalg.elementwise kind=#linalg.elementwise_kind<mul>
+// CHECK: linalg.elementwise kind=#linalg.elementwise_kind<mul>
+func.func @no_fold_non_const(%arg0: tensor<4x8xf32>, %arg1: tensor<4x8xf32>,
+ %arg2: tensor<4x8xf32>) -> tensor<4x8xf32> {
+ %empty = tensor.empty() : tensor<4x8xf32>
+ %mul1 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+ ins(%arg0, %arg1 : tensor<4x8xf32>, tensor<4x8xf32>)
+ outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
+ %mul2 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+ ins(%mul1, %arg2 : tensor<4x8xf32>, tensor<4x8xf32>)
+ outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
+ return %mul2 : tensor<4x8xf32>
+}
>From e215cbb2054528c89cdfbaf02e32a4b015ce7235 Mon Sep 17 00:00:00 2001
From: danielcm585 <danielchristianmandolang at gmail.com>
Date: Wed, 15 Jul 2026 16:37:40 +0800
Subject: [PATCH 2/8] fix formatting
---
mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp b/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
index b17eb95868372..0bdc43b3fd915 100644
--- a/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
+++ b/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
@@ -5194,11 +5194,13 @@ struct FoldConsecutiveScalarMulPattern
if (isa<FloatType>(innerAttr.getType())) {
auto lhs = cast<FloatAttr>(innerAttr);
auto rhs = cast<FloatAttr>(outerAttr);
- foldedAttr = FloatAttr::get(lhs.getType(), lhs.getValue() * rhs.getValue());
+ foldedAttr =
+ FloatAttr::get(lhs.getType(), lhs.getValue() * rhs.getValue());
} else if (isa<IntegerType>(innerAttr.getType())) {
auto lhs = cast<IntegerAttr>(innerAttr);
auto rhs = cast<IntegerAttr>(outerAttr);
- foldedAttr = IntegerAttr::get(lhs.getType(), lhs.getValue() * rhs.getValue());
+ foldedAttr =
+ IntegerAttr::get(lhs.getType(), lhs.getValue() * rhs.getValue());
} else {
return failure();
}
@@ -5210,9 +5212,8 @@ struct FoldConsecutiveScalarMulPattern
auto scalarOperandType =
cast<RankedTensorType>(outerScalarOperand.getType());
auto combinedSplat = DenseElementsAttr::get(scalarOperandType, foldedAttr);
- Value combinedConst =
- arith::ConstantOp::create(rewriter, loc, scalarOperandType,
- combinedSplat);
+ Value combinedConst = arith::ConstantOp::create(
+ rewriter, loc, scalarOperandType, combinedSplat);
// Create the new single mul: innerNonConst * combinedConst.
// Use the same indexing maps as the outer mul, since both operands have
@@ -5220,8 +5221,7 @@ struct FoldConsecutiveScalarMulPattern
// indexing map).
SmallVector<Value> newInputs = {innerNonConst, combinedConst};
rewriter.replaceOpWithNewOp<ElementwiseOp>(
- outerMul, newInputs, outerMul.getDpsInits(),
- outerMul.getKindAttr(),
+ outerMul, newInputs, outerMul.getDpsInits(), outerMul.getKindAttr(),
rewriter.getAffineMapArrayAttr(outerMul.getIndexingMapsArray()));
return success();
}
>From f0d8e7f0538ad7412c07a1ca3e0c69c0a84d8566 Mon Sep 17 00:00:00 2001
From: danielcm585 <danielchristianmandolang at gmail.com>
Date: Tue, 11 Aug 2026 10:53:37 +0800
Subject: [PATCH 3/8] fix
---
mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp | 215 ++++++++++++-----------
1 file changed, 114 insertions(+), 101 deletions(-)
diff --git a/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp b/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
index 0bdc43b3fd915..e02e6f226af82 100644
--- a/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
+++ b/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
@@ -5081,6 +5081,49 @@ static bool isElementwiseMul(ElementwiseOp op) {
groupAndKind.kind.binaryFn == BinaryFn::mul;
}
+/// Check if the given operation is a linalg multiplication operation.
+static bool isLinalgMul(Operation *op) {
+ if (auto elemOp = dyn_cast_or_null<ElementwiseOp>(op))
+ return isElementwiseMul(elemOp);
+ return isa_and_nonnull<linalg::MulOp>(op);
+}
+
+/// Multiply two scalar attributes and return the result.
+/// Returns nullptr if the multiplication fails or types are incompatible.
+static TypedAttr mulScalarAttrs(TypedAttr lhs, TypedAttr rhs) {
+ Type lhsType = lhs.getType();
+ Type rhsType = rhs.getType();
+
+ // Both must be the same type
+ if (lhsType != rhsType)
+ return nullptr;
+
+ // Handle integer types
+ if (auto intType = dyn_cast<IntegerType>(lhsType)) {
+ auto lhsInt = dyn_cast<IntegerAttr>(lhs);
+ auto rhsInt = dyn_cast<IntegerAttr>(rhs);
+ if (!lhsInt || !rhsInt)
+ return nullptr;
+
+ APInt result = lhsInt.getValue() * rhsInt.getValue();
+ return IntegerAttr::get(intType, result);
+ }
+
+ // Handle float types
+ if (isa<FloatType>(lhsType)) {
+ auto lhsFloat = dyn_cast<FloatAttr>(lhs);
+ auto rhsFloat = dyn_cast<FloatAttr>(rhs);
+ if (!lhsFloat || !rhsFloat)
+ return nullptr;
+
+ APFloat result = lhsFloat.getValue();
+ result.multiply(rhsFloat.getValue(), APFloat::rmNearestTiesToEven);
+ return FloatAttr::get(lhsType, result);
+ }
+
+ return nullptr;
+}
+
/// Try to extract the scalar constant value from a Value that is either:
/// - a dense splat constant (tensor<...xf32> with all same elements), or
/// - a scalar constant that was broadcast.
@@ -5101,135 +5144,105 @@ static std::optional<TypedAttr> getScalarConstant(Value val) {
return std::nullopt;
}
-/// Fold two consecutive scalar multiplications into one:
-///
-/// %c1 = arith.constant dense<s1> : tensor<...>
-/// %mul1 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
-/// ins(%x, %c1 : ...) outs(...)
-/// %c2 = arith.constant dense<s2> : tensor<...>
-/// %mul2 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
-/// ins(%mul1, %c2 : ...) outs(...)
+/// Fold two consecutive scalar multiplications into one.
+/// Analogous to arith's muli(muli(x, c0), c1) -> muli(x, c0 * c1).
///
-/// Into:
+/// mul(mul(x, c0), c1) -> mul(x, c0 * c1)
///
-/// %c = arith.constant dense<s1 * s2> : tensor<...>
-/// %mul = linalg.elementwise kind=#linalg.elementwise_kind<mul>
-/// ins(%x, %c : ...) outs(...)
-///
-struct FoldConsecutiveScalarMulPattern
- : public OpRewritePattern<ElementwiseOp> {
- using OpRewritePattern<ElementwiseOp>::OpRewritePattern;
+/// Works for both linalg.mul and linalg.elemwise_binary{fun = mul}.
+template <typename MulOpTy>
+struct FoldConsecutiveScalarMulPattern : public OpRewritePattern<MulOpTy> {
+ using OpRewritePattern<MulOpTy>::OpRewritePattern;
+
+ /// Helper to identify const/non-const operands. Returns {nonConst, scalar, scalarOperand}.
+ static std::tuple<Value, std::optional<TypedAttr>, Value>
+ splitConstOperands(Value lhs, Value rhs) {
+ if (auto scalar = getScalarConstant(rhs))
+ return {lhs, scalar, rhs};
+ if (auto scalar = getScalarConstant(lhs))
+ return {rhs, scalar, lhs};
+ return {Value(), std::nullopt, Value()};
+ }
+
+ /// Create a constant matching the form of the reference operand (scalar or splat tensor).
+ static FailureOr<Value> createMatchingConstant(PatternRewriter &rewriter, Location loc,
+ TypedAttr scalarValue, Value referenceOperand) {
+ if (auto tensorType = dyn_cast<RankedTensorType>(referenceOperand.getType())) {
+ // Reference is a splat tensor: create splat constant.
+ if (scalarValue.getType() != tensorType.getElementType())
+ return failure();
+ auto splatAttr = DenseElementsAttr::get(tensorType, scalarValue);
+ return rewriter.create<arith::ConstantOp>(loc, tensorType, splatAttr).getResult();
+ }
+ // Reference is a raw scalar: create scalar constant.
+ return rewriter.create<arith::ConstantOp>(loc, scalarValue).getResult();
+ }
- LogicalResult matchAndRewrite(ElementwiseOp outerMul,
+ LogicalResult matchAndRewrite(MulOpTy outerMul,
PatternRewriter &rewriter) const override {
if (!outerMul.hasPureTensorSemantics())
return failure();
- // Check that the outer op is a mul.
- if (!isElementwiseMul(outerMul))
- return failure();
-
- // The outer op has exactly 2 inputs for binary mul.
- Value outerLhs = outerMul.getInputs()[0];
- Value outerRhs = outerMul.getInputs()[1];
-
- // Find which operand of the outer mul is the scalar constant, and which
- // is the inner mul.
- Value outerNonConst = nullptr;
- std::optional<TypedAttr> outerScalar;
-
- // Try outerRhs as scalar, outerLhs as inner mul.
- outerScalar = getScalarConstant(outerRhs);
- if (outerScalar) {
- outerNonConst = outerLhs;
- } else {
- // Try outerLhs as scalar, outerRhs as inner mul.
- outerScalar = getScalarConstant(outerLhs);
- if (!outerScalar)
+ // For ElementwiseOp, verify the function is actually mul.
+ if constexpr (std::is_same_v<MulOpTy, ElementwiseOp>) {
+ if (!isElementwiseMul(outerMul))
return failure();
- outerNonConst = outerRhs;
}
- // The non-constant operand must be produced by another elementwise mul.
- auto innerMul =
- dyn_cast_or_null<ElementwiseOp>(outerNonConst.getDefiningOp());
- if (!innerMul || !isElementwiseMul(innerMul))
+ // Split outer mul into const and non-const operands.
+ Value outerNonConst, outerScalarOperand;
+ std::optional<TypedAttr> outerScalar;
+ std::tie(outerNonConst, outerScalar, outerScalarOperand) =
+ splitConstOperands(outerMul.getInputs()[0], outerMul.getInputs()[1]);
+ if (!outerScalar)
return failure();
- if (!innerMul.hasPureTensorSemantics())
+ // The non-constant operand must be another linalg mul.
+ Operation *innerMulOp = outerNonConst.getDefiningOp();
+ if (!isLinalgMul(innerMulOp))
return failure();
-
- // The inner mul result should only be used by the outer mul (to avoid
- // duplicating computation).
- if (!innerMul->hasOneUse())
+ if (!cast<linalg::LinalgOp>(innerMulOp).hasPureTensorSemantics())
+ return failure();
+ if (!innerMulOp->hasOneUse())
return failure();
- Value innerLhs = innerMul.getInputs()[0];
- Value innerRhs = innerMul.getInputs()[1];
-
- // Find the scalar constant in the inner mul.
- Value innerNonConst = nullptr;
+ // Split inner mul into const and non-const operands.
+ Value innerNonConst, innerScalarOperand;
std::optional<TypedAttr> innerScalar;
+ std::tie(innerNonConst, innerScalar, innerScalarOperand) =
+ splitConstOperands(innerMulOp->getOperand(0), innerMulOp->getOperand(1));
+ if (!innerScalar)
+ return failure();
- innerScalar = getScalarConstant(innerRhs);
- if (innerScalar) {
- innerNonConst = innerLhs;
- } else {
- innerScalar = getScalarConstant(innerLhs);
- if (!innerScalar)
- return failure();
- innerNonConst = innerRhs;
- }
-
- // Fold the two scalar constants: compute s1 * s2 at compile time.
- Location loc = outerMul.getLoc();
- auto innerAttr = *innerScalar;
- auto outerAttr = *outerScalar;
+ // Ensure type compatibility: innerNonConst must match the inner mul's result type.
+ // This prevents folding when ElemwiseBinaryOp uses cast semantics.
+ if (innerNonConst.getType() != outerNonConst.getType())
+ return failure();
- // Both scalars must have the same element type.
- if (innerAttr.getType() != outerAttr.getType())
+ // Fold the two scalar constants: c0 * c1.
+ TypedAttr foldedScalar = mulScalarAttrs(*innerScalar, *outerScalar);
+ if (!foldedScalar)
return failure();
- TypedAttr foldedAttr;
- if (isa<FloatType>(innerAttr.getType())) {
- auto lhs = cast<FloatAttr>(innerAttr);
- auto rhs = cast<FloatAttr>(outerAttr);
- foldedAttr =
- FloatAttr::get(lhs.getType(), lhs.getValue() * rhs.getValue());
- } else if (isa<IntegerType>(innerAttr.getType())) {
- auto lhs = cast<IntegerAttr>(innerAttr);
- auto rhs = cast<IntegerAttr>(outerAttr);
- foldedAttr =
- IntegerAttr::get(lhs.getType(), lhs.getValue() * rhs.getValue());
- } else {
+ // Create the combined constant, matching the form of the outer scalar operand.
+ FailureOr<Value> combinedConst =
+ createMatchingConstant(rewriter, outerMul.getLoc(), foldedScalar, outerScalarOperand);
+ if (failed(combinedConst))
return failure();
- }
- // Create the combined splat constant with the same type as the outer
- // scalar operand.
- Value outerScalarOperand =
- (getScalarConstant(outerRhs)) ? outerRhs : outerLhs;
- auto scalarOperandType =
- cast<RankedTensorType>(outerScalarOperand.getType());
- auto combinedSplat = DenseElementsAttr::get(scalarOperandType, foldedAttr);
- Value combinedConst = arith::ConstantOp::create(
- rewriter, loc, scalarOperandType, combinedSplat);
-
- // Create the new single mul: innerNonConst * combinedConst.
- // Use the same indexing maps as the outer mul, since both operands have
- // matching shapes (the inner non-const input may need the inner mul's
- // indexing map).
- SmallVector<Value> newInputs = {innerNonConst, combinedConst};
- rewriter.replaceOpWithNewOp<ElementwiseOp>(
- outerMul, newInputs, outerMul.getDpsInits(), outerMul.getKindAttr(),
- rewriter.getAffineMapArrayAttr(outerMul.getIndexingMapsArray()));
+ // Replace: mul(mul(x, c0), c1) -> mul(x, c0*c1).
+ rewriter.modifyOpInPlace(outerMul, [&]() {
+ outerMul.getDpsInputOperand(0)->set(innerNonConst);
+ outerMul.getDpsInputOperand(1)->set(*combinedConst);
+ });
+ rewriter.eraseOp(innerMulOp);
return success();
}
};
void ElementwiseOp::getCanonicalizationPatterns(RewritePatternSet &results,
MLIRContext *context) {
- results.add<FoldConsecutiveScalarMulPattern>(context);
+ results.add<FoldConsecutiveScalarMulPattern<linalg::ElementwiseOp>>(context);
}
//===----------------------------------------------------------------------===//
>From 7b4a045a7f1bc9c3cf1425d832570174da81d163 Mon Sep 17 00:00:00 2001
From: danielcm585 <danielchristianmandolang at gmail.com>
Date: Tue, 11 Aug 2026 10:56:48 +0800
Subject: [PATCH 4/8] fix formatting
---
mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp | 45 ++++++++++++++----------
1 file changed, 27 insertions(+), 18 deletions(-)
diff --git a/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp b/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
index e02e6f226af82..cab92debe99a8 100644
--- a/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
+++ b/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
@@ -5093,34 +5093,34 @@ static bool isLinalgMul(Operation *op) {
static TypedAttr mulScalarAttrs(TypedAttr lhs, TypedAttr rhs) {
Type lhsType = lhs.getType();
Type rhsType = rhs.getType();
-
+
// Both must be the same type
if (lhsType != rhsType)
return nullptr;
-
+
// Handle integer types
if (auto intType = dyn_cast<IntegerType>(lhsType)) {
auto lhsInt = dyn_cast<IntegerAttr>(lhs);
auto rhsInt = dyn_cast<IntegerAttr>(rhs);
if (!lhsInt || !rhsInt)
return nullptr;
-
+
APInt result = lhsInt.getValue() * rhsInt.getValue();
return IntegerAttr::get(intType, result);
}
-
+
// Handle float types
if (isa<FloatType>(lhsType)) {
auto lhsFloat = dyn_cast<FloatAttr>(lhs);
auto rhsFloat = dyn_cast<FloatAttr>(rhs);
if (!lhsFloat || !rhsFloat)
return nullptr;
-
+
APFloat result = lhsFloat.getValue();
result.multiply(rhsFloat.getValue(), APFloat::rmNearestTiesToEven);
return FloatAttr::get(lhsType, result);
}
-
+
return nullptr;
}
@@ -5154,7 +5154,8 @@ template <typename MulOpTy>
struct FoldConsecutiveScalarMulPattern : public OpRewritePattern<MulOpTy> {
using OpRewritePattern<MulOpTy>::OpRewritePattern;
- /// Helper to identify const/non-const operands. Returns {nonConst, scalar, scalarOperand}.
+ /// Helper to identify const/non-const operands. Returns {nonConst, scalar,
+ /// scalarOperand}.
static std::tuple<Value, std::optional<TypedAttr>, Value>
splitConstOperands(Value lhs, Value rhs) {
if (auto scalar = getScalarConstant(rhs))
@@ -5164,15 +5165,20 @@ struct FoldConsecutiveScalarMulPattern : public OpRewritePattern<MulOpTy> {
return {Value(), std::nullopt, Value()};
}
- /// Create a constant matching the form of the reference operand (scalar or splat tensor).
- static FailureOr<Value> createMatchingConstant(PatternRewriter &rewriter, Location loc,
- TypedAttr scalarValue, Value referenceOperand) {
- if (auto tensorType = dyn_cast<RankedTensorType>(referenceOperand.getType())) {
+ /// Create a constant matching the form of the reference operand (scalar or
+ /// splat tensor).
+ static FailureOr<Value> createMatchingConstant(PatternRewriter &rewriter,
+ Location loc,
+ TypedAttr scalarValue,
+ Value referenceOperand) {
+ if (auto tensorType =
+ dyn_cast<RankedTensorType>(referenceOperand.getType())) {
// Reference is a splat tensor: create splat constant.
if (scalarValue.getType() != tensorType.getElementType())
return failure();
auto splatAttr = DenseElementsAttr::get(tensorType, scalarValue);
- return rewriter.create<arith::ConstantOp>(loc, tensorType, splatAttr).getResult();
+ return rewriter.create<arith::ConstantOp>(loc, tensorType, splatAttr)
+ .getResult();
}
// Reference is a raw scalar: create scalar constant.
return rewriter.create<arith::ConstantOp>(loc, scalarValue).getResult();
@@ -5210,12 +5216,14 @@ struct FoldConsecutiveScalarMulPattern : public OpRewritePattern<MulOpTy> {
Value innerNonConst, innerScalarOperand;
std::optional<TypedAttr> innerScalar;
std::tie(innerNonConst, innerScalar, innerScalarOperand) =
- splitConstOperands(innerMulOp->getOperand(0), innerMulOp->getOperand(1));
+ splitConstOperands(innerMulOp->getOperand(0),
+ innerMulOp->getOperand(1));
if (!innerScalar)
return failure();
- // Ensure type compatibility: innerNonConst must match the inner mul's result type.
- // This prevents folding when ElemwiseBinaryOp uses cast semantics.
+ // Ensure type compatibility: innerNonConst must match the inner mul's
+ // result type. This prevents folding when ElemwiseBinaryOp uses cast
+ // semantics.
if (innerNonConst.getType() != outerNonConst.getType())
return failure();
@@ -5224,9 +5232,10 @@ struct FoldConsecutiveScalarMulPattern : public OpRewritePattern<MulOpTy> {
if (!foldedScalar)
return failure();
- // Create the combined constant, matching the form of the outer scalar operand.
- FailureOr<Value> combinedConst =
- createMatchingConstant(rewriter, outerMul.getLoc(), foldedScalar, outerScalarOperand);
+ // Create the combined constant, matching the form of the outer scalar
+ // operand.
+ FailureOr<Value> combinedConst = createMatchingConstant(
+ rewriter, outerMul.getLoc(), foldedScalar, outerScalarOperand);
if (failed(combinedConst))
return failure();
>From f45e6733bf010a50c8f1042f6ec20a3bd008b0a2 Mon Sep 17 00:00:00 2001
From: danielcm585 <danielchristianmandolang at gmail.com>
Date: Wed, 12 Aug 2026 14:33:10 +0800
Subject: [PATCH 5/8] fix deprecated CI
---
mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp b/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
index cab92debe99a8..bbcd71f344911 100644
--- a/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
+++ b/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
@@ -5177,11 +5177,11 @@ struct FoldConsecutiveScalarMulPattern : public OpRewritePattern<MulOpTy> {
if (scalarValue.getType() != tensorType.getElementType())
return failure();
auto splatAttr = DenseElementsAttr::get(tensorType, scalarValue);
- return rewriter.create<arith::ConstantOp>(loc, tensorType, splatAttr)
+ return arith::ConstantOp::create(loc, tensorType, splatAttr, rewriter)
.getResult();
}
// Reference is a raw scalar: create scalar constant.
- return rewriter.create<arith::ConstantOp>(loc, scalarValue).getResult();
+ return arith::ConstantOp::create(loc, scalarValue, rewriter).getResult();
}
LogicalResult matchAndRewrite(MulOpTy outerMul,
>From fe507763da1e5b0ffe0eeb10d51798a8c97082c2 Mon Sep 17 00:00:00 2001
From: danielcm585 <danielchristianmandolang at gmail.com>
Date: Wed, 12 Aug 2026 14:47:37 +0800
Subject: [PATCH 6/8] fix CI
---
mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp b/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
index bbcd71f344911..43bd26875b343 100644
--- a/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
+++ b/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
@@ -5177,11 +5177,11 @@ struct FoldConsecutiveScalarMulPattern : public OpRewritePattern<MulOpTy> {
if (scalarValue.getType() != tensorType.getElementType())
return failure();
auto splatAttr = DenseElementsAttr::get(tensorType, scalarValue);
- return arith::ConstantOp::create(loc, tensorType, splatAttr, rewriter)
+ return arith::ConstantOp::create(rewriter, loc, splatAttr)
.getResult();
}
// Reference is a raw scalar: create scalar constant.
- return arith::ConstantOp::create(loc, scalarValue, rewriter).getResult();
+ return arith::ConstantOp::create(rewriter, loc, scalarValue).getResult();
}
LogicalResult matchAndRewrite(MulOpTy outerMul,
>From 0ac53e7db58decd4319e3181d515e0de1cdf7f02 Mon Sep 17 00:00:00 2001
From: danielcm585 <danielchristianmandolang at gmail.com>
Date: Wed, 12 Aug 2026 14:58:09 +0800
Subject: [PATCH 7/8] fix formatting
---
mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp b/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
index 43bd26875b343..b5c5f5b568389 100644
--- a/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
+++ b/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
@@ -5177,8 +5177,7 @@ struct FoldConsecutiveScalarMulPattern : public OpRewritePattern<MulOpTy> {
if (scalarValue.getType() != tensorType.getElementType())
return failure();
auto splatAttr = DenseElementsAttr::get(tensorType, scalarValue);
- return arith::ConstantOp::create(rewriter, loc, splatAttr)
- .getResult();
+ return arith::ConstantOp::create(rewriter, loc, splatAttr).getResult();
}
// Reference is a raw scalar: create scalar constant.
return arith::ConstantOp::create(rewriter, loc, scalarValue).getResult();
>From 7ce94e0bcc3aa8884be503cb81e25eae168e81a8 Mon Sep 17 00:00:00 2001
From: danielcm585 <danielchristianmandolang at gmail.com>
Date: Mon, 24 Aug 2026 11:08:50 +0800
Subject: [PATCH 8/8] extend impl for general associative ops
---
mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp | 219 +++++++----
mlir/test/Dialect/Linalg/canonicalize.mlir | 361 +++++++++++++++++-
.../Linalg/fold-consecutive-scalar-mul.mlir | 111 ------
3 files changed, 509 insertions(+), 182 deletions(-)
delete mode 100644 mlir/test/Dialect/Linalg/fold-consecutive-scalar-mul.mlir
diff --git a/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp b/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
index b5c5f5b568389..26b6f60af1ff2 100644
--- a/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
+++ b/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
@@ -5074,42 +5074,81 @@ Speculation::Speculatability ElementwiseOp::getSpeculatability() {
return getGenericSpeculatabilityImpl(cast<LinalgOp>(getOperation()));
}
-/// Check if the given elementwise op is a binary mul.
-static bool isElementwiseMul(ElementwiseOp op) {
+/// Check if the given elementwise op matches a scalar-combinable binary op.
+static bool isElementwiseScalarBinaryFoldable(ElementwiseOp op) {
auto groupAndKind = getArityGroupAndKind(op.getKind());
- return groupAndKind.arityGroup == ElementwiseArityGroup::Binary &&
- groupAndKind.kind.binaryFn == BinaryFn::mul;
+ if (groupAndKind.arityGroup != ElementwiseArityGroup::Binary)
+ return false;
+
+ switch (groupAndKind.kind.binaryFn) {
+ case BinaryFn::add:
+ case BinaryFn::sub:
+ case BinaryFn::mul:
+ case BinaryFn::max_signed:
+ case BinaryFn::min_signed:
+ case BinaryFn::max_unsigned:
+ case BinaryFn::min_unsigned:
+ return true;
+ default:
+ return false;
+ }
}
-/// Check if the given operation is a linalg multiplication operation.
-static bool isLinalgMul(Operation *op) {
+/// Check if the given operation is a Linalg binary op with scalar-combinable
+/// semantics.
+static bool isLinalgScalarBinaryFoldable(Operation *op) {
if (auto elemOp = dyn_cast_or_null<ElementwiseOp>(op))
- return isElementwiseMul(elemOp);
- return isa_and_nonnull<linalg::MulOp>(op);
+ return isElementwiseScalarBinaryFoldable(elemOp);
+ return isa_and_nonnull<linalg::AddOp, linalg::SubOp, linalg::MulOp,
+ linalg::MaxOp, linalg::MinOp>(op);
}
-/// Multiply two scalar attributes and return the result.
-/// Returns nullptr if the multiplication fails or types are incompatible.
-static TypedAttr mulScalarAttrs(TypedAttr lhs, TypedAttr rhs) {
+/// Combine two scalar attributes with a binary Linalg operation.
+/// Returns nullptr if the operation fails or types are incompatible.
+static TypedAttr combineScalarBinaryAttrs(BinaryFn kind, TypedAttr lhs,
+ TypedAttr rhs) {
Type lhsType = lhs.getType();
Type rhsType = rhs.getType();
-
- // Both must be the same type
if (lhsType != rhsType)
return nullptr;
- // Handle integer types
if (auto intType = dyn_cast<IntegerType>(lhsType)) {
auto lhsInt = dyn_cast<IntegerAttr>(lhs);
auto rhsInt = dyn_cast<IntegerAttr>(rhs);
if (!lhsInt || !rhsInt)
return nullptr;
- APInt result = lhsInt.getValue() * rhsInt.getValue();
- return IntegerAttr::get(intType, result);
+ switch (kind) {
+ case BinaryFn::add:
+ case BinaryFn::sub:
+ return IntegerAttr::get(intType, lhsInt.getValue() + rhsInt.getValue());
+ case BinaryFn::mul:
+ return IntegerAttr::get(intType, lhsInt.getValue() * rhsInt.getValue());
+ case BinaryFn::max_signed:
+ return IntegerAttr::get(intType,
+ lhsInt.getValue().slt(rhsInt.getValue())
+ ? rhsInt.getValue()
+ : lhsInt.getValue());
+ case BinaryFn::min_signed:
+ return IntegerAttr::get(intType,
+ lhsInt.getValue().sgt(rhsInt.getValue())
+ ? rhsInt.getValue()
+ : lhsInt.getValue());
+ case BinaryFn::max_unsigned:
+ return IntegerAttr::get(intType,
+ lhsInt.getValue().ult(rhsInt.getValue())
+ ? rhsInt.getValue()
+ : lhsInt.getValue());
+ case BinaryFn::min_unsigned:
+ return IntegerAttr::get(intType,
+ lhsInt.getValue().ugt(rhsInt.getValue())
+ ? rhsInt.getValue()
+ : lhsInt.getValue());
+ default:
+ return nullptr;
+ }
}
- // Handle float types
if (isa<FloatType>(lhsType)) {
auto lhsFloat = dyn_cast<FloatAttr>(lhs);
auto rhsFloat = dyn_cast<FloatAttr>(rhs);
@@ -5117,8 +5156,29 @@ static TypedAttr mulScalarAttrs(TypedAttr lhs, TypedAttr rhs) {
return nullptr;
APFloat result = lhsFloat.getValue();
- result.multiply(rhsFloat.getValue(), APFloat::rmNearestTiesToEven);
- return FloatAttr::get(lhsType, result);
+ switch (kind) {
+ case BinaryFn::add:
+ case BinaryFn::sub:
+ result.add(rhsFloat.getValue(), APFloat::rmNearestTiesToEven);
+ return FloatAttr::get(lhsType, result);
+ case BinaryFn::mul:
+ result.multiply(rhsFloat.getValue(), APFloat::rmNearestTiesToEven);
+ return FloatAttr::get(lhsType, result);
+ case BinaryFn::max_signed:
+ return FloatAttr::get(lhsType,
+ lhsFloat.getValue().compare(rhsFloat.getValue()) ==
+ APFloat::cmpLessThan
+ ? rhsFloat.getValue()
+ : lhsFloat.getValue());
+ case BinaryFn::min_signed:
+ return FloatAttr::get(lhsType,
+ lhsFloat.getValue().compare(rhsFloat.getValue()) ==
+ APFloat::cmpGreaterThan
+ ? rhsFloat.getValue()
+ : lhsFloat.getValue());
+ default:
+ return nullptr;
+ }
}
return nullptr;
@@ -5144,25 +5204,25 @@ static std::optional<TypedAttr> getScalarConstant(Value val) {
return std::nullopt;
}
-/// Fold two consecutive scalar multiplications into one.
-/// Analogous to arith's muli(muli(x, c0), c1) -> muli(x, c0 * c1).
-///
-/// mul(mul(x, c0), c1) -> mul(x, c0 * c1)
+/// Fold two consecutive scalar binary operations into one.
///
-/// Works for both linalg.mul and linalg.elemwise_binary{fun = mul}.
-template <typename MulOpTy>
-struct FoldConsecutiveScalarMulPattern : public OpRewritePattern<MulOpTy> {
- using OpRewritePattern<MulOpTy>::OpRewritePattern;
+/// Works for binary elementwise ops like add/mul/max/min and their named Linalg
+/// counterparts when the scalar constants appear on the same side and the op is
+/// associative w.r.t. the constant combination (with subtraction only handled in
+/// its right-hand-scalar form: sub(sub(x, c0), c1) -> sub(x, c0 + c1)).
+template <typename OpTy, BinaryFn BinaryFnValue>
+struct FoldConsecutiveScalarBinaryPattern : public OpRewritePattern<OpTy> {
+ using OpRewritePattern<OpTy>::OpRewritePattern;
/// Helper to identify const/non-const operands. Returns {nonConst, scalar,
- /// scalarOperand}.
- static std::tuple<Value, std::optional<TypedAttr>, Value>
+ /// scalarOperand, scalarIsOnLeft}.
+ static std::tuple<Value, std::optional<TypedAttr>, Value, bool>
splitConstOperands(Value lhs, Value rhs) {
if (auto scalar = getScalarConstant(rhs))
- return {lhs, scalar, rhs};
+ return {lhs, scalar, rhs, false};
if (auto scalar = getScalarConstant(lhs))
- return {rhs, scalar, lhs};
- return {Value(), std::nullopt, Value()};
+ return {rhs, scalar, lhs, true};
+ return {Value(), std::nullopt, Value(), false};
}
/// Create a constant matching the form of the reference operand (scalar or
@@ -5173,84 +5233,90 @@ struct FoldConsecutiveScalarMulPattern : public OpRewritePattern<MulOpTy> {
Value referenceOperand) {
if (auto tensorType =
dyn_cast<RankedTensorType>(referenceOperand.getType())) {
- // Reference is a splat tensor: create splat constant.
if (scalarValue.getType() != tensorType.getElementType())
return failure();
auto splatAttr = DenseElementsAttr::get(tensorType, scalarValue);
return arith::ConstantOp::create(rewriter, loc, splatAttr).getResult();
}
- // Reference is a raw scalar: create scalar constant.
return arith::ConstantOp::create(rewriter, loc, scalarValue).getResult();
}
- LogicalResult matchAndRewrite(MulOpTy outerMul,
+ LogicalResult matchAndRewrite(OpTy outerOp,
PatternRewriter &rewriter) const override {
- if (!outerMul.hasPureTensorSemantics())
+ if (!outerOp.hasPureTensorSemantics())
return failure();
- // For ElementwiseOp, verify the function is actually mul.
- if constexpr (std::is_same_v<MulOpTy, ElementwiseOp>) {
- if (!isElementwiseMul(outerMul))
+ if constexpr (std::is_same_v<OpTy, ElementwiseOp>) {
+ auto groupAndKind = getArityGroupAndKind(outerOp.getKind());
+ if (groupAndKind.arityGroup != ElementwiseArityGroup::Binary ||
+ groupAndKind.kind.binaryFn != BinaryFnValue)
return failure();
}
- // Split outer mul into const and non-const operands.
Value outerNonConst, outerScalarOperand;
std::optional<TypedAttr> outerScalar;
- std::tie(outerNonConst, outerScalar, outerScalarOperand) =
- splitConstOperands(outerMul.getInputs()[0], outerMul.getInputs()[1]);
+ bool outerScalarOnLeft = false;
+ std::tie(outerNonConst, outerScalar, outerScalarOperand, outerScalarOnLeft) =
+ splitConstOperands(outerOp.getInputs()[0], outerOp.getInputs()[1]);
if (!outerScalar)
return failure();
- // The non-constant operand must be another linalg mul.
- Operation *innerMulOp = outerNonConst.getDefiningOp();
- if (!isLinalgMul(innerMulOp))
+ Operation *innerOp = outerNonConst.getDefiningOp();
+ if (!isLinalgScalarBinaryFoldable(innerOp))
return failure();
- if (!cast<linalg::LinalgOp>(innerMulOp).hasPureTensorSemantics())
+ if (!cast<linalg::LinalgOp>(innerOp).hasPureTensorSemantics())
return failure();
- if (!innerMulOp->hasOneUse())
+ if (!innerOp->hasOneUse())
return failure();
- // Split inner mul into const and non-const operands.
Value innerNonConst, innerScalarOperand;
std::optional<TypedAttr> innerScalar;
- std::tie(innerNonConst, innerScalar, innerScalarOperand) =
- splitConstOperands(innerMulOp->getOperand(0),
- innerMulOp->getOperand(1));
+ bool innerScalarOnLeft = false;
+ std::tie(innerNonConst, innerScalar, innerScalarOperand, innerScalarOnLeft) =
+ splitConstOperands(innerOp->getOperand(0), innerOp->getOperand(1));
if (!innerScalar)
return failure();
-
- // Ensure type compatibility: innerNonConst must match the inner mul's
- // result type. This prevents folding when ElemwiseBinaryOp uses cast
- // semantics.
if (innerNonConst.getType() != outerNonConst.getType())
return failure();
- // Fold the two scalar constants: c0 * c1.
- TypedAttr foldedScalar = mulScalarAttrs(*innerScalar, *outerScalar);
+ if (BinaryFnValue == BinaryFn::sub && (outerScalarOnLeft || innerScalarOnLeft))
+ return failure();
+
+ TypedAttr foldedScalar =
+ combineScalarBinaryAttrs(BinaryFnValue, *innerScalar, *outerScalar);
if (!foldedScalar)
return failure();
- // Create the combined constant, matching the form of the outer scalar
- // operand.
FailureOr<Value> combinedConst = createMatchingConstant(
- rewriter, outerMul.getLoc(), foldedScalar, outerScalarOperand);
+ rewriter, outerOp.getLoc(), foldedScalar, outerScalarOperand);
if (failed(combinedConst))
return failure();
- // Replace: mul(mul(x, c0), c1) -> mul(x, c0*c1).
- rewriter.modifyOpInPlace(outerMul, [&]() {
- outerMul.getDpsInputOperand(0)->set(innerNonConst);
- outerMul.getDpsInputOperand(1)->set(*combinedConst);
+ rewriter.modifyOpInPlace(outerOp, [&]() {
+ outerOp.getDpsInputOperand(0)->set(innerNonConst);
+ outerOp.getDpsInputOperand(1)->set(*combinedConst);
});
- rewriter.eraseOp(innerMulOp);
+ rewriter.eraseOp(innerOp);
return success();
}
};
void ElementwiseOp::getCanonicalizationPatterns(RewritePatternSet &results,
MLIRContext *context) {
- results.add<FoldConsecutiveScalarMulPattern<linalg::ElementwiseOp>>(context);
+ results.add<FoldConsecutiveScalarBinaryPattern<linalg::ElementwiseOp,
+ BinaryFn::add>,
+ FoldConsecutiveScalarBinaryPattern<linalg::ElementwiseOp,
+ BinaryFn::sub>,
+ FoldConsecutiveScalarBinaryPattern<linalg::ElementwiseOp,
+ BinaryFn::mul>,
+ FoldConsecutiveScalarBinaryPattern<linalg::ElementwiseOp,
+ BinaryFn::max_signed>,
+ FoldConsecutiveScalarBinaryPattern<linalg::ElementwiseOp,
+ BinaryFn::min_signed>,
+ FoldConsecutiveScalarBinaryPattern<linalg::ElementwiseOp,
+ BinaryFn::max_unsigned>,
+ FoldConsecutiveScalarBinaryPattern<linalg::ElementwiseOp,
+ BinaryFn::min_unsigned>>(context);
}
//===----------------------------------------------------------------------===//
@@ -6991,7 +7057,26 @@ Speculation::Speculatability BatchReduceMatmulOp::getSpeculatability() {
void LinalgDialect::getCanonicalizationPatterns(
RewritePatternSet &results) const {
results.add<EraseDeadLinalgOp, FoldTensorCastConsumerOp, FoldTensorCastPackOp,
- FoldTensorCastUnPackOp, InferStaticShapeOfOperands>(getContext());
+ FoldTensorCastUnPackOp, InferStaticShapeOfOperands,
+ FoldConsecutiveScalarBinaryPattern<linalg::ElementwiseOp,
+ BinaryFn::add>,
+ FoldConsecutiveScalarBinaryPattern<linalg::ElementwiseOp,
+ BinaryFn::sub>,
+ FoldConsecutiveScalarBinaryPattern<linalg::ElementwiseOp,
+ BinaryFn::mul>,
+ FoldConsecutiveScalarBinaryPattern<linalg::ElementwiseOp,
+ BinaryFn::max_signed>,
+ FoldConsecutiveScalarBinaryPattern<linalg::ElementwiseOp,
+ BinaryFn::min_signed>,
+ FoldConsecutiveScalarBinaryPattern<linalg::ElementwiseOp,
+ BinaryFn::max_unsigned>,
+ FoldConsecutiveScalarBinaryPattern<linalg::ElementwiseOp,
+ BinaryFn::min_unsigned>,
+ FoldConsecutiveScalarBinaryPattern<linalg::AddOp, BinaryFn::add>,
+ FoldConsecutiveScalarBinaryPattern<linalg::SubOp, BinaryFn::sub>,
+ FoldConsecutiveScalarBinaryPattern<linalg::MulOp, BinaryFn::mul>,
+ FoldConsecutiveScalarBinaryPattern<linalg::MaxOp, BinaryFn::max_signed>,
+ FoldConsecutiveScalarBinaryPattern<linalg::MinOp, BinaryFn::min_signed>>(getContext());
}
Operation *LinalgDialect::materializeConstant(OpBuilder &builder,
diff --git a/mlir/test/Dialect/Linalg/canonicalize.mlir b/mlir/test/Dialect/Linalg/canonicalize.mlir
index bb11ce0d4dfb8..411eefa79c784 100644
--- a/mlir/test/Dialect/Linalg/canonicalize.mlir
+++ b/mlir/test/Dialect/Linalg/canonicalize.mlir
@@ -2351,7 +2351,7 @@ func.func @fold_unpack_cast_inner_tile_inlined_mismatch(%arg0: tensor<1x3x8x1xi3
// -----
-// CHECK-LABEL: func.func @no_fold_pack_cast_inner_tile_dynamic_arg
+// CHECK-LABEL: func.func @negative_fold_pack_cast_inner_tile_dynamic_arg
// CHECK-SAME: %[[SRC:.+]]: tensor<8x3xi32>, %[[TILE:.+]]: index, %[[DEST:.+]]: tensor<?x3x?x1xi32>
// CHECK: %[[PACK:.+]] = linalg.pack
// CHECK: padding_value
@@ -2359,7 +2359,7 @@ func.func @fold_unpack_cast_inner_tile_inlined_mismatch(%arg0: tensor<1x3x8x1xi3
// CHECK: inner_tiles = [%[[TILE]], 1]
// CHECK: into %[[DEST]] : tensor
// CHECK: return %[[PACK]] : tensor<?x3x?x1xi32>
-func.func @no_fold_pack_cast_inner_tile_dynamic_arg(%arg0: tensor<8x3xi32>, %arg1: index,
+func.func @negative_fold_pack_cast_inner_tile_dynamic_arg(%arg0: tensor<8x3xi32>, %arg1: index,
%dest: tensor<?x3x?x1xi32>) -> tensor<?x3x?x1xi32> {
%c0 = arith.constant 0 : i32
%cast = tensor.cast %arg0 : tensor<8x3xi32> to tensor<?x?xi32>
@@ -2373,7 +2373,7 @@ func.func @no_fold_pack_cast_inner_tile_dynamic_arg(%arg0: tensor<8x3xi32>, %arg
// -----
-// CHECK-LABEL: func.func @no_fold_pack_cast_inner_tile_inlined_mismatch
+// CHECK-LABEL: func.func @negative_fold_pack_cast_inner_tile_inlined_mismatch
// CHECK-DAG: %[[C256:.+]] = arith.constant 256 : index
// CHECK: %[[PACK:.+]] = linalg.pack
// CHECK: padding_value
@@ -2381,7 +2381,7 @@ func.func @no_fold_pack_cast_inner_tile_dynamic_arg(%arg0: tensor<8x3xi32>, %arg
// CHECK: inner_tiles = [%[[C256]], 1]
// CHECK: into %{{.+}} : tensor
// CHECK: return %[[PACK]] : tensor<?x3x?x1xi32>
-func.func @no_fold_pack_cast_inner_tile_inlined_mismatch(%arg0: tensor<8x3xi32>,
+func.func @negative_fold_pack_cast_inner_tile_inlined_mismatch(%arg0: tensor<8x3xi32>,
%dest: tensor<?x3x?x1xi32>) -> tensor<?x3x?x1xi32> {
%c0 = arith.constant 0 : i32
%c256 = arith.constant 256 : index
@@ -2393,3 +2393,356 @@ func.func @no_fold_pack_cast_inner_tile_inlined_mismatch(%arg0: tensor<8x3xi32>,
into %dest : tensor<?x?xi32> -> tensor<?x3x?x1xi32>
return %pack : tensor<?x3x?x1xi32>
}
+
+// CHECK-LABEL: func @fold_consecutive_scalar_mul_f32
+// CHECK-SAME: (%[[ARG:.*]]: tensor<4x8xf32>)
+// CHECK-DAG: %[[COMBINED:.*]] = arith.constant dense<6.000000e+00> : tensor<4x8xf32>
+// CHECK: %[[EMPTY:.*]] = tensor.empty() : tensor<4x8xf32>
+// CHECK: %[[RESULT:.*]] = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+// CHECK-SAME: ins(%[[ARG]], %[[COMBINED]] : tensor<4x8xf32>, tensor<4x8xf32>)
+// CHECK-SAME: outs(%[[EMPTY]] : tensor<4x8xf32>)
+// CHECK: return %[[RESULT]]
+func.func @fold_consecutive_scalar_mul_f32(%arg0: tensor<4x8xf32>) -> tensor<4x8xf32> {
+ %cst2 = arith.constant dense<2.0> : tensor<4x8xf32>
+ %cst3 = arith.constant dense<3.0> : tensor<4x8xf32>
+ %empty = tensor.empty() : tensor<4x8xf32>
+ %mul1 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+ ins(%arg0, %cst2 : tensor<4x8xf32>, tensor<4x8xf32>)
+ outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
+ %mul2 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+ ins(%mul1, %cst3 : tensor<4x8xf32>, tensor<4x8xf32>)
+ outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
+ return %mul2 : tensor<4x8xf32>
+}
+
+// -----
+
+// CHECK-LABEL: func @fold_consecutive_scalar_mul_i32
+// CHECK-SAME: (%[[ARG:.*]]: tensor<4x8xi32>)
+// CHECK-DAG: %[[COMBINED:.*]] = arith.constant dense<15> : tensor<4x8xi32>
+// CHECK: %[[EMPTY:.*]] = tensor.empty() : tensor<4x8xi32>
+// CHECK: %[[RESULT:.*]] = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+// CHECK-SAME: ins(%[[ARG]], %[[COMBINED]] : tensor<4x8xi32>, tensor<4x8xi32>)
+// CHECK-SAME: outs(%[[EMPTY]] : tensor<4x8xi32>)
+// CHECK: return %[[RESULT]]
+func.func @fold_consecutive_scalar_mul_i32(%arg0: tensor<4x8xi32>) -> tensor<4x8xi32> {
+ %cst5 = arith.constant dense<5> : tensor<4x8xi32>
+ %cst3 = arith.constant dense<3> : tensor<4x8xi32>
+ %empty = tensor.empty() : tensor<4x8xi32>
+ %mul1 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+ ins(%arg0, %cst5 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ %mul2 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+ ins(%mul1, %cst3 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ return %mul2 : tensor<4x8xi32>
+}
+
+// -----
+
+// CHECK-LABEL: func @fold_consecutive_scalar_mul_named_i32
+// CHECK-SAME: (%[[ARG:.*]]: tensor<4x8xi32>)
+// CHECK-DAG: %[[COMBINED:.*]] = arith.constant dense<15> : tensor<4x8xi32>
+// CHECK: %[[EMPTY:.*]] = tensor.empty() : tensor<4x8xi32>
+// CHECK: %[[RESULT:.*]] = linalg.mul
+// CHECK-SAME: ins(%[[ARG]], %[[COMBINED]] : tensor<4x8xi32>, tensor<4x8xi32>)
+// CHECK-SAME: outs(%[[EMPTY]] : tensor<4x8xi32>) -> tensor<4x8xi32>
+// CHECK: return %[[RESULT]]
+func.func @fold_consecutive_scalar_mul_named_i32(%arg0: tensor<4x8xi32>) -> tensor<4x8xi32> {
+ %cst5 = arith.constant dense<5> : tensor<4x8xi32>
+ %cst3 = arith.constant dense<3> : tensor<4x8xi32>
+ %empty = tensor.empty() : tensor<4x8xi32>
+ %mul1 = linalg.mul ins(%arg0, %cst5 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ %mul2 = linalg.mul ins(%mul1, %cst3 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ return %mul2 : tensor<4x8xi32>
+}
+
+// -----
+
+// Scalar constant on the left-hand side.
+// CHECK-LABEL: func @fold_scalar_mul_lhs
+// CHECK-SAME: (%[[ARG:.*]]: tensor<4x8xf32>)
+// CHECK-DAG: %[[COMBINED:.*]] = arith.constant dense<1.200000e+01> : tensor<4x8xf32>
+// CHECK: %[[EMPTY:.*]] = tensor.empty() : tensor<4x8xf32>
+// CHECK: %[[RESULT:.*]] = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+// CHECK-SAME: ins(%[[ARG]], %[[COMBINED]] : tensor<4x8xf32>, tensor<4x8xf32>)
+// CHECK-SAME: outs(%[[EMPTY]] : tensor<4x8xf32>)
+// CHECK: return %[[RESULT]]
+func.func @fold_scalar_mul_lhs(%arg0: tensor<4x8xf32>) -> tensor<4x8xf32> {
+ %cst4 = arith.constant dense<4.0> : tensor<4x8xf32>
+ %cst3 = arith.constant dense<3.0> : tensor<4x8xf32>
+ %empty = tensor.empty() : tensor<4x8xf32>
+ %mul1 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+ ins(%cst4, %arg0 : tensor<4x8xf32>, tensor<4x8xf32>)
+ outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
+ %mul2 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+ ins(%cst3, %mul1 : tensor<4x8xf32>, tensor<4x8xf32>)
+ outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
+ return %mul2 : tensor<4x8xf32>
+}
+
+// -----
+
+// Do not fold when the inner mul has multiple uses.
+// CHECK-LABEL: func @negative_fold_multi_use
+// CHECK: linalg.elementwise kind=#linalg.elementwise_kind<mul>
+// CHECK: linalg.elementwise kind=#linalg.elementwise_kind<mul>
+// CHECK: linalg.elementwise kind=#linalg.elementwise_kind<add>
+func.func @negative_fold_multi_use(%arg0: tensor<4x8xf32>) -> tensor<4x8xf32> {
+ %cst2 = arith.constant dense<2.0> : tensor<4x8xf32>
+ %cst3 = arith.constant dense<3.0> : tensor<4x8xf32>
+ %empty = tensor.empty() : tensor<4x8xf32>
+ %mul1 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+ ins(%arg0, %cst2 : tensor<4x8xf32>, tensor<4x8xf32>)
+ outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
+ %mul2 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+ ins(%mul1, %cst3 : tensor<4x8xf32>, tensor<4x8xf32>)
+ outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
+ // Extra use of mul1 prevents folding.
+ %add = linalg.elementwise kind=#linalg.elementwise_kind<add>
+ ins(%mul2, %mul1 : tensor<4x8xf32>, tensor<4x8xf32>)
+ outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
+ return %add : tensor<4x8xf32>
+}
+
+// -----
+
+// Do not fold when neither operand is a scalar constant.
+// CHECK-LABEL: func @negative_fold_non_const
+// CHECK: linalg.elementwise kind=#linalg.elementwise_kind<mul>
+// CHECK: linalg.elementwise kind=#linalg.elementwise_kind<mul>
+func.func @negative_fold_non_const(%arg0: tensor<4x8xf32>, %arg1: tensor<4x8xf32>,
+ %arg2: tensor<4x8xf32>) -> tensor<4x8xf32> {
+ %empty = tensor.empty() : tensor<4x8xf32>
+ %mul1 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+ ins(%arg0, %arg1 : tensor<4x8xf32>, tensor<4x8xf32>)
+ outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
+ %mul2 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
+ ins(%mul1, %arg2 : tensor<4x8xf32>, tensor<4x8xf32>)
+ outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
+ return %mul2 : tensor<4x8xf32>
+}
+
+// -----
+
+// CHECK-LABEL: func @fold_consecutive_scalar_add_i32
+// CHECK-SAME: (%[[ARG:.*]]: tensor<4x8xi32>)
+// CHECK-DAG: %[[COMBINED:.*]] = arith.constant dense<8> : tensor<4x8xi32>
+// CHECK: %[[EMPTY:.*]] = tensor.empty() : tensor<4x8xi32>
+// CHECK: %[[RESULT:.*]] = linalg.elementwise kind=#linalg.elementwise_kind<add>
+// CHECK-SAME: ins(%[[ARG]], %[[COMBINED]] : tensor<4x8xi32>, tensor<4x8xi32>)
+// CHECK-SAME: outs(%[[EMPTY]] : tensor<4x8xi32>)
+// CHECK: return %[[RESULT]]
+func.func @fold_consecutive_scalar_add_i32(%arg0: tensor<4x8xi32>) -> tensor<4x8xi32> {
+ %cst5 = arith.constant dense<5> : tensor<4x8xi32>
+ %cst3 = arith.constant dense<3> : tensor<4x8xi32>
+ %empty = tensor.empty() : tensor<4x8xi32>
+ %add1 = linalg.elementwise kind=#linalg.elementwise_kind<add>
+ ins(%arg0, %cst5 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ %add2 = linalg.elementwise kind=#linalg.elementwise_kind<add>
+ ins(%add1, %cst3 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ return %add2 : tensor<4x8xi32>
+}
+
+// -----
+
+// CHECK-LABEL: func @fold_consecutive_scalar_add_named_i32
+// CHECK-SAME: (%[[ARG:.*]]: tensor<4x8xi32>)
+// CHECK-DAG: %[[COMBINED:.*]] = arith.constant dense<8> : tensor<4x8xi32>
+// CHECK: %[[EMPTY:.*]] = tensor.empty() : tensor<4x8xi32>
+// CHECK: %[[RESULT:.*]] = linalg.add
+// CHECK-SAME: ins(%[[ARG]], %[[COMBINED]] : tensor<4x8xi32>, tensor<4x8xi32>)
+// CHECK-SAME: outs(%[[EMPTY]] : tensor<4x8xi32>) -> tensor<4x8xi32>
+// CHECK: return %[[RESULT]]
+func.func @fold_consecutive_scalar_add_named_i32(%arg0: tensor<4x8xi32>) -> tensor<4x8xi32> {
+ %cst5 = arith.constant dense<5> : tensor<4x8xi32>
+ %cst3 = arith.constant dense<3> : tensor<4x8xi32>
+ %empty = tensor.empty() : tensor<4x8xi32>
+ %add1 = linalg.add ins(%arg0, %cst5 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ %add2 = linalg.add ins(%add1, %cst3 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ return %add2 : tensor<4x8xi32>
+}
+
+// -----
+
+// CHECK-LABEL: func @fold_consecutive_scalar_max_signed_i32
+// CHECK-SAME: (%[[ARG:.*]]: tensor<4x8xi32>)
+// CHECK-DAG: %[[COMBINED:.*]] = arith.constant dense<9> : tensor<4x8xi32>
+// CHECK: %[[EMPTY:.*]] = tensor.empty() : tensor<4x8xi32>
+// CHECK: %[[RESULT:.*]] = linalg.elementwise kind=#linalg.elementwise_kind<max_signed>
+// CHECK-SAME: ins(%[[ARG]], %[[COMBINED]] : tensor<4x8xi32>, tensor<4x8xi32>)
+// CHECK-SAME: outs(%[[EMPTY]] : tensor<4x8xi32>)
+// CHECK: return %[[RESULT]]
+func.func @fold_consecutive_scalar_max_signed_i32(%arg0: tensor<4x8xi32>) -> tensor<4x8xi32> {
+ %cst5 = arith.constant dense<5> : tensor<4x8xi32>
+ %cst9 = arith.constant dense<9> : tensor<4x8xi32>
+ %empty = tensor.empty() : tensor<4x8xi32>
+ %max1 = linalg.elementwise kind=#linalg.elementwise_kind<max_signed>
+ ins(%arg0, %cst5 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ %max2 = linalg.elementwise kind=#linalg.elementwise_kind<max_signed>
+ ins(%max1, %cst9 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ return %max2 : tensor<4x8xi32>
+}
+
+// -----
+
+// CHECK-LABEL: func @fold_consecutive_scalar_max_named_i32
+// CHECK-SAME: (%[[ARG:.*]]: tensor<4x8xi32>)
+// CHECK-DAG: %[[COMBINED:.*]] = arith.constant dense<9> : tensor<4x8xi32>
+// CHECK: %[[EMPTY:.*]] = tensor.empty() : tensor<4x8xi32>
+// CHECK: %[[RESULT:.*]] = linalg.max
+// CHECK-SAME: ins(%[[ARG]], %[[COMBINED]] : tensor<4x8xi32>, tensor<4x8xi32>)
+// CHECK-SAME: outs(%[[EMPTY]] : tensor<4x8xi32>) -> tensor<4x8xi32>
+// CHECK: return %[[RESULT]]
+func.func @fold_consecutive_scalar_max_named_i32(%arg0: tensor<4x8xi32>) -> tensor<4x8xi32> {
+ %cst5 = arith.constant dense<5> : tensor<4x8xi32>
+ %cst9 = arith.constant dense<9> : tensor<4x8xi32>
+ %empty = tensor.empty() : tensor<4x8xi32>
+ %max1 = linalg.max ins(%arg0, %cst5 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ %max2 = linalg.max ins(%max1, %cst9 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ return %max2 : tensor<4x8xi32>
+}
+
+// -----
+
+// CHECK-LABEL: func @fold_consecutive_scalar_sub_i32
+// CHECK-SAME: (%[[ARG:.*]]: tensor<4x8xi32>)
+// CHECK-DAG: %[[COMBINED:.*]] = arith.constant dense<8> : tensor<4x8xi32>
+// CHECK: %[[EMPTY:.*]] = tensor.empty() : tensor<4x8xi32>
+// CHECK: %[[RESULT:.*]] = linalg.elementwise kind=#linalg.elementwise_kind<sub>
+// CHECK-SAME: ins(%[[ARG]], %[[COMBINED]] : tensor<4x8xi32>, tensor<4x8xi32>)
+// CHECK-SAME: outs(%[[EMPTY]] : tensor<4x8xi32>)
+// CHECK: return %[[RESULT]]
+func.func @fold_consecutive_scalar_sub_i32(%arg0: tensor<4x8xi32>) -> tensor<4x8xi32> {
+ %cst5 = arith.constant dense<5> : tensor<4x8xi32>
+ %cst3 = arith.constant dense<3> : tensor<4x8xi32>
+ %empty = tensor.empty() : tensor<4x8xi32>
+ %sub1 = linalg.elementwise kind=#linalg.elementwise_kind<sub>
+ ins(%arg0, %cst5 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ %sub2 = linalg.elementwise kind=#linalg.elementwise_kind<sub>
+ ins(%sub1, %cst3 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ return %sub2 : tensor<4x8xi32>
+}
+
+// -----
+
+// CHECK-LABEL: func @fold_consecutive_scalar_sub_named_i32
+// CHECK-SAME: (%[[ARG:.*]]: tensor<4x8xi32>)
+// CHECK-DAG: %[[COMBINED:.*]] = arith.constant dense<8> : tensor<4x8xi32>
+// CHECK: %[[EMPTY:.*]] = tensor.empty() : tensor<4x8xi32>
+// CHECK: %[[RESULT:.*]] = linalg.sub
+// CHECK-SAME: ins(%[[ARG]], %[[COMBINED]] : tensor<4x8xi32>, tensor<4x8xi32>)
+// CHECK-SAME: outs(%[[EMPTY]] : tensor<4x8xi32>) -> tensor<4x8xi32>
+// CHECK: return %[[RESULT]]
+func.func @fold_consecutive_scalar_sub_named_i32(%arg0: tensor<4x8xi32>) -> tensor<4x8xi32> {
+ %cst5 = arith.constant dense<5> : tensor<4x8xi32>
+ %cst3 = arith.constant dense<3> : tensor<4x8xi32>
+ %empty = tensor.empty() : tensor<4x8xi32>
+ %sub1 = linalg.sub ins(%arg0, %cst5 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ %sub2 = linalg.sub ins(%sub1, %cst3 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ return %sub2 : tensor<4x8xi32>
+}
+
+// -----
+
+// CHECK-LABEL: func @fold_consecutive_scalar_min_signed_i32
+// CHECK-SAME: (%[[ARG:.*]]: tensor<4x8xi32>)
+// CHECK-DAG: %[[COMBINED:.*]] = arith.constant dense<3> : tensor<4x8xi32>
+// CHECK: %[[EMPTY:.*]] = tensor.empty() : tensor<4x8xi32>
+// CHECK: %[[RESULT:.*]] = linalg.elementwise kind=#linalg.elementwise_kind<min_signed>
+// CHECK-SAME: ins(%[[ARG]], %[[COMBINED]] : tensor<4x8xi32>, tensor<4x8xi32>)
+// CHECK-SAME: outs(%[[EMPTY]] : tensor<4x8xi32>)
+// CHECK: return %[[RESULT]]
+func.func @fold_consecutive_scalar_min_signed_i32(%arg0: tensor<4x8xi32>) -> tensor<4x8xi32> {
+ %cst5 = arith.constant dense<5> : tensor<4x8xi32>
+ %cst3 = arith.constant dense<3> : tensor<4x8xi32>
+ %empty = tensor.empty() : tensor<4x8xi32>
+ %min1 = linalg.elementwise kind=#linalg.elementwise_kind<min_signed>
+ ins(%arg0, %cst5 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ %min2 = linalg.elementwise kind=#linalg.elementwise_kind<min_signed>
+ ins(%min1, %cst3 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ return %min2 : tensor<4x8xi32>
+}
+
+// -----
+
+// CHECK-LABEL: func @fold_consecutive_scalar_min_named_i32
+// CHECK-SAME: (%[[ARG:.*]]: tensor<4x8xi32>)
+// CHECK-DAG: %[[COMBINED:.*]] = arith.constant dense<3> : tensor<4x8xi32>
+// CHECK: %[[EMPTY:.*]] = tensor.empty() : tensor<4x8xi32>
+// CHECK: %[[RESULT:.*]] = linalg.min
+// CHECK-SAME: ins(%[[ARG]], %[[COMBINED]] : tensor<4x8xi32>, tensor<4x8xi32>)
+// CHECK-SAME: outs(%[[EMPTY]] : tensor<4x8xi32>) -> tensor<4x8xi32>
+// CHECK: return %[[RESULT]]
+func.func @fold_consecutive_scalar_min_named_i32(%arg0: tensor<4x8xi32>) -> tensor<4x8xi32> {
+ %cst5 = arith.constant dense<5> : tensor<4x8xi32>
+ %cst3 = arith.constant dense<3> : tensor<4x8xi32>
+ %empty = tensor.empty() : tensor<4x8xi32>
+ %min1 = linalg.min ins(%arg0, %cst5 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ %min2 = linalg.min ins(%min1, %cst3 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ return %min2 : tensor<4x8xi32>
+}
+
+// -----
+
+// CHECK-LABEL: func @fold_consecutive_scalar_max_unsigned_i32
+// CHECK-SAME: (%[[ARG:.*]]: tensor<4x8xi32>)
+// CHECK-DAG: %[[COMBINED:.*]] = arith.constant dense<9> : tensor<4x8xi32>
+// CHECK: %[[EMPTY:.*]] = tensor.empty() : tensor<4x8xi32>
+// CHECK: %[[RESULT:.*]] = linalg.elementwise kind=#linalg.elementwise_kind<max_unsigned>
+// CHECK-SAME: ins(%[[ARG]], %[[COMBINED]] : tensor<4x8xi32>, tensor<4x8xi32>)
+// CHECK-SAME: outs(%[[EMPTY]] : tensor<4x8xi32>)
+// CHECK: return %[[RESULT]]
+func.func @fold_consecutive_scalar_max_unsigned_i32(%arg0: tensor<4x8xi32>) -> tensor<4x8xi32> {
+ %cst5 = arith.constant dense<5> : tensor<4x8xi32>
+ %cst9 = arith.constant dense<9> : tensor<4x8xi32>
+ %empty = tensor.empty() : tensor<4x8xi32>
+ %max1 = linalg.elementwise kind=#linalg.elementwise_kind<max_unsigned>
+ ins(%arg0, %cst5 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ %max2 = linalg.elementwise kind=#linalg.elementwise_kind<max_unsigned>
+ ins(%max1, %cst9 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ return %max2 : tensor<4x8xi32>
+}
+
+// -----
+
+// CHECK-LABEL: func @fold_consecutive_scalar_min_unsigned_i32
+// CHECK-SAME: (%[[ARG:.*]]: tensor<4x8xi32>)
+// CHECK-DAG: %[[COMBINED:.*]] = arith.constant dense<3> : tensor<4x8xi32>
+// CHECK: %[[EMPTY:.*]] = tensor.empty() : tensor<4x8xi32>
+// CHECK: %[[RESULT:.*]] = linalg.elementwise kind=#linalg.elementwise_kind<min_unsigned>
+// CHECK-SAME: ins(%[[ARG]], %[[COMBINED]] : tensor<4x8xi32>, tensor<4x8xi32>)
+// CHECK-SAME: outs(%[[EMPTY]] : tensor<4x8xi32>)
+// CHECK: return %[[RESULT]]
+func.func @fold_consecutive_scalar_min_unsigned_i32(%arg0: tensor<4x8xi32>) -> tensor<4x8xi32> {
+ %cst5 = arith.constant dense<5> : tensor<4x8xi32>
+ %cst3 = arith.constant dense<3> : tensor<4x8xi32>
+ %empty = tensor.empty() : tensor<4x8xi32>
+ %min1 = linalg.elementwise kind=#linalg.elementwise_kind<min_unsigned>
+ ins(%arg0, %cst5 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ %min2 = linalg.elementwise kind=#linalg.elementwise_kind<min_unsigned>
+ ins(%min1, %cst3 : tensor<4x8xi32>, tensor<4x8xi32>)
+ outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
+ return %min2 : tensor<4x8xi32>
+}
diff --git a/mlir/test/Dialect/Linalg/fold-consecutive-scalar-mul.mlir b/mlir/test/Dialect/Linalg/fold-consecutive-scalar-mul.mlir
deleted file mode 100644
index 6e5cbbcb5cb9d..0000000000000
--- a/mlir/test/Dialect/Linalg/fold-consecutive-scalar-mul.mlir
+++ /dev/null
@@ -1,111 +0,0 @@
-// RUN: mlir-opt %s -canonicalize="test-convergence" -split-input-file | FileCheck %s
-
-// CHECK-LABEL: func @fold_consecutive_scalar_mul_f32
-// CHECK-SAME: (%[[ARG:.*]]: tensor<4x8xf32>)
-// CHECK-DAG: %[[COMBINED:.*]] = arith.constant dense<6.000000e+00> : tensor<4x8xf32>
-// CHECK: %[[EMPTY:.*]] = tensor.empty() : tensor<4x8xf32>
-// CHECK: %[[RESULT:.*]] = linalg.elementwise kind=#linalg.elementwise_kind<mul>
-// CHECK-SAME: ins(%[[ARG]], %[[COMBINED]] : tensor<4x8xf32>, tensor<4x8xf32>)
-// CHECK-SAME: outs(%[[EMPTY]] : tensor<4x8xf32>)
-// CHECK: return %[[RESULT]]
-func.func @fold_consecutive_scalar_mul_f32(%arg0: tensor<4x8xf32>) -> tensor<4x8xf32> {
- %cst2 = arith.constant dense<2.0> : tensor<4x8xf32>
- %cst3 = arith.constant dense<3.0> : tensor<4x8xf32>
- %empty = tensor.empty() : tensor<4x8xf32>
- %mul1 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
- ins(%arg0, %cst2 : tensor<4x8xf32>, tensor<4x8xf32>)
- outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
- %mul2 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
- ins(%mul1, %cst3 : tensor<4x8xf32>, tensor<4x8xf32>)
- outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
- return %mul2 : tensor<4x8xf32>
-}
-
-// -----
-
-// CHECK-LABEL: func @fold_consecutive_scalar_mul_i32
-// CHECK-SAME: (%[[ARG:.*]]: tensor<4x8xi32>)
-// CHECK-DAG: %[[COMBINED:.*]] = arith.constant dense<15> : tensor<4x8xi32>
-// CHECK: %[[EMPTY:.*]] = tensor.empty() : tensor<4x8xi32>
-// CHECK: %[[RESULT:.*]] = linalg.elementwise kind=#linalg.elementwise_kind<mul>
-// CHECK-SAME: ins(%[[ARG]], %[[COMBINED]] : tensor<4x8xi32>, tensor<4x8xi32>)
-// CHECK-SAME: outs(%[[EMPTY]] : tensor<4x8xi32>)
-// CHECK: return %[[RESULT]]
-func.func @fold_consecutive_scalar_mul_i32(%arg0: tensor<4x8xi32>) -> tensor<4x8xi32> {
- %cst5 = arith.constant dense<5> : tensor<4x8xi32>
- %cst3 = arith.constant dense<3> : tensor<4x8xi32>
- %empty = tensor.empty() : tensor<4x8xi32>
- %mul1 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
- ins(%arg0, %cst5 : tensor<4x8xi32>, tensor<4x8xi32>)
- outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
- %mul2 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
- ins(%mul1, %cst3 : tensor<4x8xi32>, tensor<4x8xi32>)
- outs(%empty : tensor<4x8xi32>) -> tensor<4x8xi32>
- return %mul2 : tensor<4x8xi32>
-}
-
-// -----
-
-// Scalar constant on the left-hand side.
-// CHECK-LABEL: func @fold_scalar_mul_lhs
-// CHECK-SAME: (%[[ARG:.*]]: tensor<4x8xf32>)
-// CHECK-DAG: %[[COMBINED:.*]] = arith.constant dense<1.200000e+01> : tensor<4x8xf32>
-// CHECK: %[[EMPTY:.*]] = tensor.empty() : tensor<4x8xf32>
-// CHECK: %[[RESULT:.*]] = linalg.elementwise kind=#linalg.elementwise_kind<mul>
-// CHECK-SAME: ins(%[[ARG]], %[[COMBINED]] : tensor<4x8xf32>, tensor<4x8xf32>)
-// CHECK-SAME: outs(%[[EMPTY]] : tensor<4x8xf32>)
-// CHECK: return %[[RESULT]]
-func.func @fold_scalar_mul_lhs(%arg0: tensor<4x8xf32>) -> tensor<4x8xf32> {
- %cst4 = arith.constant dense<4.0> : tensor<4x8xf32>
- %cst3 = arith.constant dense<3.0> : tensor<4x8xf32>
- %empty = tensor.empty() : tensor<4x8xf32>
- %mul1 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
- ins(%cst4, %arg0 : tensor<4x8xf32>, tensor<4x8xf32>)
- outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
- %mul2 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
- ins(%cst3, %mul1 : tensor<4x8xf32>, tensor<4x8xf32>)
- outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
- return %mul2 : tensor<4x8xf32>
-}
-
-// -----
-
-// Do not fold when the inner mul has multiple uses.
-// CHECK-LABEL: func @no_fold_multi_use
-// CHECK: linalg.elementwise kind=#linalg.elementwise_kind<mul>
-// CHECK: linalg.elementwise kind=#linalg.elementwise_kind<mul>
-// CHECK: linalg.elementwise kind=#linalg.elementwise_kind<add>
-func.func @no_fold_multi_use(%arg0: tensor<4x8xf32>) -> tensor<4x8xf32> {
- %cst2 = arith.constant dense<2.0> : tensor<4x8xf32>
- %cst3 = arith.constant dense<3.0> : tensor<4x8xf32>
- %empty = tensor.empty() : tensor<4x8xf32>
- %mul1 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
- ins(%arg0, %cst2 : tensor<4x8xf32>, tensor<4x8xf32>)
- outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
- %mul2 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
- ins(%mul1, %cst3 : tensor<4x8xf32>, tensor<4x8xf32>)
- outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
- // Extra use of mul1 prevents folding.
- %add = linalg.elementwise kind=#linalg.elementwise_kind<add>
- ins(%mul2, %mul1 : tensor<4x8xf32>, tensor<4x8xf32>)
- outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
- return %add : tensor<4x8xf32>
-}
-
-// -----
-
-// Do not fold when neither operand is a scalar constant.
-// CHECK-LABEL: func @no_fold_non_const
-// CHECK: linalg.elementwise kind=#linalg.elementwise_kind<mul>
-// CHECK: linalg.elementwise kind=#linalg.elementwise_kind<mul>
-func.func @no_fold_non_const(%arg0: tensor<4x8xf32>, %arg1: tensor<4x8xf32>,
- %arg2: tensor<4x8xf32>) -> tensor<4x8xf32> {
- %empty = tensor.empty() : tensor<4x8xf32>
- %mul1 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
- ins(%arg0, %arg1 : tensor<4x8xf32>, tensor<4x8xf32>)
- outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
- %mul2 = linalg.elementwise kind=#linalg.elementwise_kind<mul>
- ins(%mul1, %arg2 : tensor<4x8xf32>, tensor<4x8xf32>)
- outs(%empty : tensor<4x8xf32>) -> tensor<4x8xf32>
- return %mul2 : tensor<4x8xf32>
-}
More information about the Mlir-commits
mailing list