[Mlir-commits] [mlir] [mlir][arith] Canonicalize `addf(negf(x), y)` to `subf(y, x)` (PR #209277)
Victor Perez
llvmlistbot at llvm.org
Mon Jul 13 12:48:23 PDT 2026
https://github.com/victor-eds updated https://github.com/llvm/llvm-project/pull/209277
>From bcc58dcd530ad06ab73ee6c164ea16ce455804a6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?V=C3=ADctor=20P=C3=A9rez=20Carrasco?= <victorperez at fb.com>
Date: Mon, 13 Jul 2026 07:25:08 -0700
Subject: [PATCH] [mlir][arith] Canonicalize addf(negf(x), y) to subf(y, x)
arith.addf had no canonicalization patterns at all. This adds a
canonicalizer rewriting addf(negf(x), y) -> subf(y, x) (and the commuted
operand order).
The rewrite is valid under any rounding mode, which is propagated to the
subf: negf is an exact sign flip, IEEE addition is commutative, and y - x
is the correctly rounded y + (-x), so subf(y, x) and addf(negf(x), y)
are bit-identical with identical exception behavior for every rounding
mode. Fast-math flags are propagated to the new subf.
---
.../include/mlir/Dialect/Arith/IR/ArithOps.td | 1 +
.../Dialect/Arith/IR/ArithCanonicalization.td | 18 +++++
mlir/lib/Dialect/Arith/IR/ArithOps.cpp | 5 ++
mlir/test/Dialect/Arith/canonicalize.mlir | 74 +++++++++++++++++++
4 files changed, 98 insertions(+)
diff --git a/mlir/include/mlir/Dialect/Arith/IR/ArithOps.td b/mlir/include/mlir/Dialect/Arith/IR/ArithOps.td
index 423948c8734af..54481d3232483 100644
--- a/mlir/include/mlir/Dialect/Arith/IR/ArithOps.td
+++ b/mlir/include/mlir/Dialect/Arith/IR/ArithOps.td
@@ -1113,6 +1113,7 @@ def Arith_AddFOp : Arith_FloatBinaryOpWithRoundingMode<"addf", [Commutative]> {
%a = arith.addf %b, %c to_nearest_even : f64
```
}];
+ let hasCanonicalizer = 1;
let hasFolder = 1;
}
diff --git a/mlir/lib/Dialect/Arith/IR/ArithCanonicalization.td b/mlir/lib/Dialect/Arith/IR/ArithCanonicalization.td
index 86c9b6d3551d8..84deb2b920a85 100644
--- a/mlir/lib/Dialect/Arith/IR/ArithCanonicalization.td
+++ b/mlir/lib/Dialect/Arith/IR/ArithCanonicalization.td
@@ -521,6 +521,24 @@ def UIToFPOfExtUI :
Pat<(Arith_UIToFPOp (Arith_ExtUIOp $x, $nneg1), $nneg2),
(Arith_UIToFPOp $x, $nneg1)>;
+//===----------------------------------------------------------------------===//
+// AddFOp
+//===----------------------------------------------------------------------===//
+
+// addf(negf(x), y) -> subf(y, x)
+// addf(y, negf(x)) -> subf(y, x)
+//
+// Valid for any rounding mode, which is propagated to the subf: negf is an
+// exact sign flip, IEEE addition is commutative, and y - x is the correctly
+// rounded y + (-x), so subf(y, x) and addf(negf(x), y) are bit-identical.
+def AddFOfNegFLhs :
+ Pat<(Arith_AddFOp (Arith_NegFOp $x, $_), $y, $fmf, $rm),
+ (Arith_SubFOp $y, $x, $fmf, $rm)>;
+
+def AddFOfNegFRhs :
+ Pat<(Arith_AddFOp $y, (Arith_NegFOp $x, $_), $fmf, $rm),
+ (Arith_SubFOp $y, $x, $fmf, $rm)>;
+
//===----------------------------------------------------------------------===//
// SubFOp
//===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Dialect/Arith/IR/ArithOps.cpp b/mlir/lib/Dialect/Arith/IR/ArithOps.cpp
index 397fa28227831..674577e5764d8 100644
--- a/mlir/lib/Dialect/Arith/IR/ArithOps.cpp
+++ b/mlir/lib/Dialect/Arith/IR/ArithOps.cpp
@@ -1235,6 +1235,11 @@ OpFoldResult arith::AddFOp::fold(FoldAdaptor adaptor) {
});
}
+void arith::AddFOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
+ MLIRContext *context) {
+ patterns.add<AddFOfNegFLhs, AddFOfNegFRhs>(context);
+}
+
//===----------------------------------------------------------------------===//
// SubFOp
//===----------------------------------------------------------------------===//
diff --git a/mlir/test/Dialect/Arith/canonicalize.mlir b/mlir/test/Dialect/Arith/canonicalize.mlir
index c12befc76675a..e273ff58921ef 100644
--- a/mlir/test/Dialect/Arith/canonicalize.mlir
+++ b/mlir/test/Dialect/Arith/canonicalize.mlir
@@ -3005,6 +3005,80 @@ func.func @test_addf_rounding_mode(%arg0 : f32) -> (f32, f32, f32) {
// -----
+// CHECK-LABEL: @test_addf_negf(
+// CHECK-SAME: %[[ARG0:.+]]: f32, %[[ARG1:.+]]: f32
+func.func @test_addf_negf(%arg0 : f32, %arg1 : f32) -> (f32, f32) {
+ // CHECK-DAG: %[[X:.+]] = arith.subf %[[ARG1]], %[[ARG0]] : f32
+ // CHECK-DAG: %[[Y:.+]] = arith.subf %[[ARG0]], %[[ARG1]] : f32
+ // CHECK: return %[[X]], %[[Y]]
+ %0 = arith.negf %arg0 : f32
+ %1 = arith.addf %0, %arg1 : f32
+ %2 = arith.negf %arg1 : f32
+ %3 = arith.addf %arg0, %2 : f32
+ return %1, %3 : f32, f32
+}
+
+// -----
+
+// CHECK-LABEL: @test_addf_negf_types
+// CHECK-SAME: %[[F16:.+]]: f16, %[[G16:.+]]: f16, %[[F64:.+]]: f64, %[[G64:.+]]: f64, %[[BF:.+]]: bf16, %[[GBF:.+]]: bf16,
+// CHECK-SAME: %[[VEC:.+]]: vector<4xf32>, %[[GVEC:.+]]: vector<4xf32>,
+// CHECK-SAME: %[[TEN:.+]]: tensor<2x3xf32>, %[[GTEN:.+]]: tensor<2x3xf32>
+func.func @test_addf_negf_types(
+ %f16 : f16, %g16 : f16, %f64 : f64, %g64 : f64, %bf : bf16, %gbf : bf16,
+ %vec : vector<4xf32>, %gvec : vector<4xf32>,
+ %ten : tensor<2x3xf32>, %gten : tensor<2x3xf32>)
+ -> (f16, f64, bf16, vector<4xf32>, tensor<2x3xf32>) {
+ // CHECK-DAG: arith.subf %[[G16]], %[[F16]] : f16
+ // CHECK-DAG: arith.subf %[[G64]], %[[F64]] : f64
+ // CHECK-DAG: arith.subf %[[GBF]], %[[BF]] : bf16
+ // CHECK-DAG: arith.subf %[[GVEC]], %[[VEC]] : vector<4xf32>
+ // CHECK-DAG: arith.subf %[[GTEN]], %[[TEN]] : tensor<2x3xf32>
+ // CHECK-NOT: arith.negf
+ // CHECK-NOT: arith.addf
+ %n16 = arith.negf %f16 : f16
+ %0 = arith.addf %n16, %g16 : f16
+ %n64 = arith.negf %f64 : f64
+ %1 = arith.addf %g64, %n64 : f64
+ %nbf = arith.negf %bf : bf16
+ %2 = arith.addf %nbf, %gbf : bf16
+ %nvec = arith.negf %vec : vector<4xf32>
+ %3 = arith.addf %gvec, %nvec : vector<4xf32>
+ %nten = arith.negf %ten : tensor<2x3xf32>
+ %4 = arith.addf %nten, %gten : tensor<2x3xf32>
+ return %0, %1, %2, %3, %4 : f16, f64, bf16, vector<4xf32>, tensor<2x3xf32>
+}
+
+// -----
+
+// CHECK-LABEL: @test_addf_negf_fastmath(
+// CHECK-SAME: %[[X:.+]]: f32, %[[Y:.+]]: f32
+func.func @test_addf_negf_fastmath(%x : f32, %y : f32) -> (f32, f32) {
+ // CHECK-DAG: %[[A:.+]] = arith.subf %[[Y]], %[[X]] fastmath<nnan,nsz> : f32
+ // CHECK-DAG: %[[B:.+]] = arith.subf %[[Y]], %[[X]] fastmath<reassoc> : f32
+ // CHECK: return %[[A]], %[[B]]
+ %n = arith.negf %x : f32
+ %0 = arith.addf %n, %y fastmath<nnan,nsz> : f32
+ %1 = arith.addf %y, %n fastmath<reassoc> : f32
+ return %0, %1 : f32, f32
+}
+
+// -----
+
+// CHECK-LABEL: @test_addf_negf_rounding_mode(
+// CHECK-SAME: %[[X:.+]]: f32, %[[Y:.+]]: f32
+func.func @test_addf_negf_rounding_mode(%x : f32, %y : f32) -> (f32, f32) {
+ // CHECK-DAG: %[[A:.+]] = arith.subf %[[Y]], %[[X]] downward : f32
+ // CHECK-DAG: %[[B:.+]] = arith.subf %[[Y]], %[[X]] upward : f32
+ // CHECK: return %[[A]], %[[B]]
+ %n = arith.negf %x : f32
+ %0 = arith.addf %n, %y downward : f32
+ %1 = arith.addf %y, %n upward : f32
+ return %0, %1 : f32, f32
+}
+
+// -----
+
// CHECK-LABEL: @test_subf_rounding_mode(
// CHECK-SAME: %[[ARG0:.+]]: f32
func.func @test_subf_rounding_mode(%arg0 : f32) -> (f32, f32, f32, f32) {
More information about the Mlir-commits
mailing list