The floor rule added by g:b59ff31043a turns
((x >> 1) + (y >> 1)) + (x & y & 1) into (x & y) + ((x ^ y) >> 1)
using x + y == 2 * (x & y) + (x ^ y). The rounded-up average has the same
shape through the dual identity x + y == 2 * (x | y) - (x ^ y), so
((x >> 1) + (y >> 1)) + ((x | y) & 1) into (x | y) - ((x ^ y) >> 1)
is the matching four-operation form. This is the spelling used by the
rounding motion-compensation averages in media codecs.
For vector types this is the scalar counterpart of the .AVG_CEIL matcher
added by g:72f0b446d2c: that matcher already recognises both the
six-operation source form and exactly this minus form, so on targets with
the averaging instruction the late vector pattern still gets its
IFN_AVG_CEIL whichever spelling reaches it, and on targets without one a
vector drops from six elementwise operations to four.
The identity was checked exhaustively over every pair of 16-bit values, for
both the unsigned and the signed (arithmetic shift) case: no mismatches.
On arm the six-operation form went from five instructions to three; five
near-miss shapes (a mask of 2 rather than 1, a shift of 2, an xor in place
of the ior, a third operand, mismatched shift counts) are all left alone.
Bootstrapped on x86_64-pc-linux-gnu at trunk 95dd5ea480d with the
stage2/stage3 comparison successful, and regtested there with gcc.dg/dg.exp
and gcc.dg/tree-ssa/tree-ssa.exp: no unexpected results. With the fold
reverted and the tests kept, the first test's shift, xor and mask scans
fail (the ior count is the same in either form); the second test is an
execution check of the identity at the extremes of both ranges and passes
either way.
Assisted-by: Claude Opus 5 (Anthropic)
gcc/ChangeLog:
* match.pd (((x >> 1) + (y >> 1)) + ((x | y) & 1)): New pattern.
gcc/testsuite/ChangeLog:
* gcc.dg/tree-ssa/avg-ceil-1.c: New test.
* gcc.dg/tree-ssa/avg-ceil-2.c: New test.
Signed-off-by: Dominic P <[email protected]>
---
gcc/match.pd | 10 ++++
gcc/testsuite/gcc.dg/tree-ssa/avg-ceil-1.c | 25 ++++++++++
gcc/testsuite/gcc.dg/tree-ssa/avg-ceil-2.c | 53 ++++++++++++++++++++++
3 files changed, 88 insertions(+)
create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/avg-ceil-1.c
create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/avg-ceil-2.c
diff --git a/gcc/match.pd b/gcc/match.pd
index 02684d8a302..99084158e1f 100644
--- a/gcc/match.pd
+++ b/gcc/match.pd
@@ -2049,6 +2049,16 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
(bit_and (bit_and:c@3 @0 @1) integer_onep))
(plus @3 (rshift (bit_xor @0 @1) @2)))
+/* ((x >> 1) + (y >> 1)) + ((x | y) & 1) -> (x | y) - ((x ^ y) >> 1).
+ The rounded-up dual of the rule above: x + y is 2 * (x | y) - (x ^ y), so
+ the average rounded towards +inf is (x | y) - ((x ^ y) >> 1), again four
+ operations instead of six. This is the form used by the rounding
+ motion-compensation averages in media codecs. */
+(simplify
+ (plus:c (plus (rshift @0 integer_onep@2) (rshift @1 @2))
+ (bit_and (bit_ior:c@3 @0 @1) integer_onep))
+ (minus @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-ceil-1.c
b/gcc/testsuite/gcc.dg/tree-ssa/avg-ceil-1.c
new file mode 100644
index 00000000000..bb66c4415d3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/avg-ceil-1.c
@@ -0,0 +1,25 @@
+/* The six-operation rounded (ceiling) average should collapse to the
+ four-operation form, the dual of the floor rule:
+ ((x >> 1) + (y >> 1)) + ((x | y) & 1) -> (x | y) - ((x ^ y) >> 1)
+ x + y is 2 * (x | y) - (x ^ y), so halving the sum rounded up needs only
+ an ior, an xor, a shift and a subtract. */
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+unsigned
+avg_ceil (unsigned x, unsigned y)
+{
+ return ((x >> 1) + (y >> 1)) + ((x | y) & 1u);
+}
+
+int
+avg_ceil_signed (int x, int y)
+{
+ return ((x >> 1) + (y >> 1)) + ((x | y) & 1);
+}
+
+/* Both shifts of the operands must be gone, leaving one shift of the xor. */
+/* { dg-final { scan-tree-dump-times " >> 1;" 2 "optimized" } } */
+/* { dg-final { scan-tree-dump-times " \\^ " 2 "optimized" } } */
+/* { dg-final { scan-tree-dump-times " \\| " 2 "optimized" } } */
+/* { dg-final { scan-tree-dump-not " & 1;" "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/avg-ceil-2.c
b/gcc/testsuite/gcc.dg/tree-ssa/avg-ceil-2.c
new file mode 100644
index 00000000000..f1ca20b49d0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/avg-ceil-2.c
@@ -0,0 +1,53 @@
+/* Execution test for the rounded-average fold
+ ((x >> 1) + (y >> 1)) + ((x | y) & 1) -> (x | y) - ((x ^ y) >> 1)
+ at the extremes of the signed and unsigned ranges. The subtraction in
+ the folded form cannot overflow: its result is the ceiling average,
+ which always lies between min (x, y) and max (x, y). Compare against
+ the source expression evaluated in a wider type. */
+/* { dg-do run } */
+/* { dg-options "-O2" } */
+/* { dg-require-effective-target int32plus } */
+
+#define INT_MIN (-__INT_MAX__ - 1)
+#define INT_MAX __INT_MAX__
+
+__attribute__ ((noipa)) int
+avg_ceil_signed (int x, int y)
+{
+ return ((x >> 1) + (y >> 1)) + ((x | y) & 1);
+}
+
+__attribute__ ((noipa)) unsigned
+avg_ceil_unsigned (unsigned x, unsigned y)
+{
+ return ((x >> 1) + (y >> 1)) + ((x | y) & 1u);
+}
+
+int
+main (void)
+{
+ static const int sv[] = { INT_MIN, INT_MIN + 1, -3, -2, -1, 0, 1, 2, 3,
+ INT_MAX - 1, INT_MAX };
+ static const unsigned uv[] = { 0, 1, 2, 3, 0x7ffffffe, 0x7fffffff,
+ 0x80000000u, 0xfffffffeu, 0xffffffffu };
+ unsigned i, j;
+
+ for (i = 0; i < sizeof sv / sizeof sv[0]; i++)
+ for (j = 0; j < sizeof sv / sizeof sv[0]; j++)
+ {
+ long long x = sv[i], y = sv[j];
+ long long ref = ((x >> 1) + (y >> 1)) + ((x | y) & 1);
+ if (avg_ceil_signed (sv[i], sv[j]) != (int) ref)
+ __builtin_abort ();
+ }
+
+ for (i = 0; i < sizeof uv / sizeof uv[0]; i++)
+ for (j = 0; j < sizeof uv / sizeof uv[0]; j++)
+ {
+ unsigned long long x = uv[i], y = uv[j];
+ unsigned long long ref = ((x >> 1) + (y >> 1)) + ((x | y) & 1);
+ if (avg_ceil_unsigned (uv[i], uv[j]) != (unsigned) ref)
+ __builtin_abort ();
+ }
+ return 0;
+}
--
2.55.0