[gcc r17-3054] match.pd: fold the overflow-free average idiom

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

commit r17-3054-gb59ff31043ae6b1a5ce8582dd375e2ef9a938d79
Author: Kyrylo Tkachov <[email protected]>
Date:   Fri Aug 7 07:36:58 2026 +0200

    match.pd: fold the overflow-free average idiom
    
    The average of two integers is often written so that it cannot overflow:
    
      ((x >> 1) + (y >> 1)) + (x & y & 1)
    
    Since x + y is 2 * (x & y) + (x ^ y), the same value is (x & y) +
    ((x ^ y) >> 1), which uses four operations instead of six.  Both forms
    are exact for signed and unsigned types, and neither can overflow,
    because the result is always between the two inputs.
    
      int f (int a, int b) { return ((a >> 1) + (b >> 1)) + (a & b & 1); }
    
    aarch64 -O2 before:
    
            lsr     w2, w1, 1
            add     w2, w2, w0, lsr 1
            and     w0, w0, w1
            and     w0, w0, 1
            add     w0, w2, w0
    
    after:
    
            eor     w2, w0, w1
            and     w0, w0, w1
            add     w0, w0, w2, lsr 1
    
    The vectoriser emits the six-operation form itself when the target has
    no halving add, so the same reduction applies there.  On SVE without
    SVE2 the loop body of pr89007-2.c goes from six vector operations to
    four, and that test is updated to the shorter sequence.  Targets where
    IFN_AVG_FLOOR is recognised on the scalar form before this rule are
    unaffected, so NEON and SVE2 keep their uhadd.  RISC-V also keeps its
    vector vaadd and vaaddu instructions for the pixel_avg case.
    
    Do not commute the identical inner shift forms.  Reuse the matched
    conjunction in the result.
    
    Bootstrapped and tested on aarch64-none-linux-gnu.
    
    gcc/ChangeLog:
    
            * match.pd (((x >> 1) + (y >> 1)) + (x & y & 1)): New simplification
            to (x & y) + ((x ^ y) >> 1).
    
    gcc/testsuite/ChangeLog:
    
            * gcc.dg/tree-ssa/avg-1.c: New test.
            * gcc.target/aarch64/sve/pr89007-2.c: Update the expected loop body.
    
    Signed-off-by: Kyrylo Tkachov <[email protected]>

Diff:
---
 gcc/match.pd                                     |  9 ++++++++
 gcc/testsuite/gcc.dg/tree-ssa/avg-1.c            | 27 ++++++++++++++++++++++++
 gcc/testsuite/gcc.target/aarch64/sve/pr89007-2.c | 10 ++++-----
 3 files changed, 40 insertions(+), 6 deletions(-)

diff --git a/gcc/match.pd b/gcc/match.pd
index 62cc01380bcc..a2a48e1b4753 100644
--- a/gcc/match.pd
+++ b/gcc/match.pd
@@ -1986,6 +1986,15 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
   (op:c (bit_and @0 @1) (bit_xor @0 @1))
   (bit_ior @0 @1)))
 
+/* ((x >> 1) + (y >> 1)) + (x & y & 1) -> (x & y) + ((x ^ y) >> 1).
+   Both are the average of x and y computed without overflowing, since
+   x + y is 2 * (x & y) + (x ^ y), but the second form needs four
+   operations instead of six.  */
+(simplify
+ (plus:c (plus (rshift @0 integer_onep@2) (rshift @1 @2))
+	 (bit_and (bit_and:c@3 @0 @1) integer_onep))
+ (plus @3 (rshift (bit_xor @0 @1) @2)))
+
 /* (x & y) + (x | y) -> x + y */
 (simplify
  (plus:c (bit_and @0 @1) (bit_ior @0 @1))
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/avg-1.c b/gcc/testsuite/gcc.dg/tree-ssa/avg-1.c
new file mode 100644
index 000000000000..d1fde110a746
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/avg-1.c
@@ -0,0 +1,27 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+/* ((x >> 1) + (y >> 1)) + (x & y & 1) is the average of x and y without
+   overflow.  It must fold to (x & y) + ((x ^ y) >> 1), which needs three
+   operations instead of five.  */
+
+int
+f (int a, int b)
+{
+  return ((a >> 1) + (b >> 1)) + (a & b & 1);
+}
+
+unsigned
+g (unsigned a, unsigned b)
+{
+  return (a & b & 1) + ((a >> 1) + (b >> 1));
+}
+
+long
+h (long a, long b)
+{
+  return ((a >> 1) + (b >> 1)) + (1 & b & a);
+}
+
+/* { dg-final { scan-tree-dump-times " \\^ " 3 "optimized" } } */
+/* { dg-final { scan-tree-dump-not " & 1;" "optimized" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/sve/pr89007-2.c b/gcc/testsuite/gcc.target/aarch64/sve/pr89007-2.c
index 1de44df96c92..26f00dc2258b 100644
--- a/gcc/testsuite/gcc.target/aarch64/sve/pr89007-2.c
+++ b/gcc/testsuite/gcc.target/aarch64/sve/pr89007-2.c
@@ -10,12 +10,10 @@ unsigned char in2[N];
 /*
 **  foo: 
 **	...
-**	lsr	(z[0-9]+\.b), z[0-9]+\.b, #1
-**	lsr	(z[0-9]+\.b), z[0-9]+\.b, #1
-**	add	(z[0-9]+\.b), (\1, \2|\2, \1)
-**	and	(z[0-9]+)\.d, z[0-9]+\.d, z[0-9]+\.d
-**	and	(z[0-9]+\.b), \5\.b, #0x1
-**	add	z[0-9]+\.b, (\3, \6|\6, \3)
+**	eor	z[0-9]+\.d, z[0-9]+\.d, z[0-9]+\.d
+**	lsr	z[0-9]+\.b, z[0-9]+\.b, #1
+**	and	z[0-9]+\.d, z[0-9]+\.d, z[0-9]+\.d
+**	add	z[0-9]+\.b, z[0-9]+\.b, z[0-9]+\.b
 **	...
 */
 void
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.