[gcc r17-3159] match.pd: turn a product of two quotients into one division

Kyrylo Tkachov via Gcc-cvs <[email protected]>
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
https://gcc.gnu.org/g:22d833315ea8ed0069be1a5d920532e9771613d2

commit r17-3159-g22d833315ea8ed0069be1a5d920532e9771613d2
Author: Kyrylo Tkachov <[email protected]>
Date:   Fri Jul 31 05:13:39 2026 +0200

    match.pd: turn a product of two quotients into one division
    
    (A / B) * (C / D) is (A * C) / (B * D), which replaces one of the two
    divisions with a multiply.  A division costs several multiplies on every
    target.
    
      double f (double a, double b, double c)
      { return (a / b) * (1.0 / c); }
    
    AArch64 -Ofast before:
    
            fdiv    d0, d0, d1
            fdiv    d0, d0, d2
    
    After:
    
            fmul    d1, d1, d2
            fdiv    d0, d0, d1
    
    The rule needs infinities and NaNs excluded.  It forms two products, and each
    is a new place for the exponent to leave the range.  It also needs
    non-trapping math because it removes one division.  A function option test
    restores trapping math and verifies that both divisions remain.
    
    Both quotients must be singly used.  This excludes a shared reciprocal and
    the X * X form of one reciprocal square root, where the rewrite can add a
    division after its products fold.  Complex values also require signed zeros
    to be ignored because reassociation can change the sign of an imaginary zero.
    
    Bootstrapped and tested on aarch64-none-linux-gnu.
    
    gcc/ChangeLog:
    
            * match.pd ((A / B) * (C / D)): New simplification.
    
    gcc/testsuite/ChangeLog:
    
            * gcc.dg/tree-ssa/recip-mult-div-1.c: New test.
            * gcc.dg/tree-ssa/recip-mult-div-2.c: New test.
    
    Signed-off-by: Kyrylo Tkachov <[email protected]>

Diff:
---
 gcc/match.pd                                     | 29 ++++++++++++++++
 gcc/testsuite/gcc.dg/tree-ssa/recip-mult-div-1.c | 43 ++++++++++++++++++++++++
 gcc/testsuite/gcc.dg/tree-ssa/recip-mult-div-2.c | 15 +++++++++
 3 files changed, 87 insertions(+)

diff --git a/gcc/match.pd b/gcc/match.pd
index 1ea46fd17264..c2f411001a03 100644
--- a/gcc/match.pd
+++ b/gcc/match.pd
@@ -811,6 +811,35 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
   (rdiv (rdiv:s @0 @1) @2)
   (rdiv @0 (mult @1 @2)))
 
+ /* Convert (A/B) * (C/D) to (A*C) / (B*D).  Two divisions become one
+    multiply and one division, and a division costs several multiplies on
+    every target.
+
+    The two products are new places for the exponent to leave the range, so
+    the rule needs more than the rounding licence: with B and D both large
+    B * D is an infinity and the quotient becomes inf / inf, and with both
+    small it is a zero and the quotient becomes 0 / 0.  Either way a finite
+    result turns into a NaN, so the rule is restricted to the case where
+    infinities and NaNs are excluded, as the cancellation rules above are.
+
+    Both quotients have to be dead outside the product, otherwise a division
+    would be added rather than removed, and a shared reciprocal is better
+    left alone for the multiplications to reuse.  That is a hard requirement
+    rather than a :s marker, because :s only forbids emitting new statements
+    and both products can fold away to nothing, as they do for X * X where X
+    is one reciprocal square root.  Requiring two singly used quotients also
+    excludes that case, since a value feeding both operands of the product
+    has two uses.  */
+ (simplify
+  (mult (rdiv@4 @0 @1) (rdiv@5 @2 @3))
+  (if (flag_associative_math
+       && !HONOR_NANS (type) && !HONOR_INFINITIES (type)
+       && !flag_trapping_math
+       && (TREE_CODE (type) != COMPLEX_TYPE
+	   || !HONOR_SIGNED_ZEROS (type))
+       && single_use (@4) && single_use (@5))
+   (rdiv (mult @0 @2) (mult @1 @3))))
+
  /* Canonicalize x / (C1 * y) to (x * C2) / y.  */
  (simplify
   (rdiv @0 (mult:s @1 REAL_CST@2))
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/recip-mult-div-1.c b/gcc/testsuite/gcc.dg/tree-ssa/recip-mult-div-1.c
new file mode 100644
index 000000000000..56d2d9a7feb3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/recip-mult-div-1.c
@@ -0,0 +1,43 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -freciprocal-math -ffinite-math-only -fdump-tree-optimized" } */
+/* { dg-additional-options "-fassociative-math -fno-signed-zeros -fno-trapping-math" } */
+
+/* (A / B) * (C / D) is (A * C) / (B * D): one of the two divisions becomes
+   a multiply.  The rule also needs infinities and NaNs excluded, because the
+   two products it forms can leave the range of the type.  */
+
+double f1 (double a, double b, double c)
+{
+  return (a / b) * (1.0 / c);
+}
+
+double f2 (double a, double b, double c, double d)
+{
+  return (a / b) * (c / d);
+}
+
+float f3 (float a, float b, float c, float d)
+{
+  return (a / b) * (c / d);
+}
+
+/* Trapping math must preserve both divisions.  */
+__attribute__((optimize ("trapping-math")))
+double trapping (double a, double b, double c, double d)
+{
+  return (a / b) * (c / d);
+}
+
+/* Must not fold: the reciprocal is shared, so the multiplications should
+   reuse it rather than pay for a second division.  */
+double keep (double a, double b, double c, double *r)
+{
+  double t = 1.0 / c;
+  r[0] = (a / b) * t;
+  r[1] = t;
+  return t;
+}
+
+/* Must not fold without -ffinite-math-only: see recip-mult-div-2.c.  */
+
+/* { dg-final { scan-tree-dump-times " / " 7 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/recip-mult-div-2.c b/gcc/testsuite/gcc.dg/tree-ssa/recip-mult-div-2.c
new file mode 100644
index 000000000000..e866d217ee7a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/recip-mult-div-2.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -freciprocal-math -fdump-tree-optimized" } */
+
+/* (A / B) * (C / D) -> (A * C) / (B * D) forms two products that can leave
+   the range of the type.  With B and D both large B * D is an infinity and
+   the quotient becomes inf / inf; with both small it is a zero and the
+   quotient becomes 0 / 0.  Either turns a finite result into a NaN, so
+   -freciprocal-math on its own must not enable the rule.  */
+
+double f (double a, double b, double c, double d)
+{
+  return (a / b) * (c / d);
+}
+
+/* { dg-final { scan-tree-dump-times " / " 2 "optimized" } } */
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.