[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