[PATCH] match.pd: combine a pair of vector reductions
<[email protected]> Tue, 4 Aug 2026 12:01:30 +0200
| Newsgroups | gmane.comp.gcc.patches |
|---|---|
| Message-ID | <[email protected]> |
From: Kyrylo Tkachov <[email protected]> Two reductions feeding the matching scalar operation are one reduction of the elementwise operation: REDUC (a) op REDUC (b) -> REDUC (a op b) The two sides accumulate the same multiset of lanes, just grouped differently, so for the associative and commutative reductions the result is identical. This trades a lane-crossing reduction, which is the expensive part, for an elementwise operation. MIN, MAX, AND, IOR and XOR need no flag. The sum needs one only for floating point, where the regrouping is a reassociation; the integer sum is done in the unsigned type, since the reduction wraps while the signed vector type has undefined overflow. #include <arm_neon.h> int f (int32x4_t a, int32x4_t b) { return vaddvq_s32 (a) + vaddvq_s32 (b); } aarch64 -O3 before: addv s1, v1.4s addv s0, v0.4s fmov w1, s1 fmov w0, s0 add w0, w0, w1 after: add v0.4s, v0.4s, v1.4s addv s0, v0.4s fmov w0, s0 A pair of maxima goes from 8 instructions to 3. On a target without a horizontal reduce instruction the reduction is open coded as a chain of shuffles, so the saving is larger there, never smaller. For integers, preserve trapping and sanitized scalar overflow. For floating point, require reassociation, insignificant zero signs, and non-trapping exceptions. Restrict the reassociative alternative to floating-point types. Floating-point options must not license fixed-point saturation changes. Bootstrapped and tested on aarch64-none-linux-gnu. Ok for trunk? Thanks, Kyrill gcc/ChangeLog: * match.pd (REDUC (a) op REDUC (b)): New simplifications combining two reductions into one. gcc/testsuite/ChangeLog: * gcc.dg/tree-ssa/vec-reduc-pair-1.c: New test. * gcc.dg/tree-ssa/vec-reduc-pair-2.c: New test. * gcc.dg/tree-ssa/vec-reduc-pair-3.c: New test. * gcc.dg/tree-ssa/vec-reduc-pair-4.c: New test. Signed-off-by: Kyrylo Tkachov <[email protected]> --- gcc/match.pd | 35 +++++++++++++++++++ .../gcc.dg/tree-ssa/vec-reduc-pair-1.c | 11 ++++++ .../gcc.dg/tree-ssa/vec-reduc-pair-2.c | 13 +++++++ .../gcc.dg/tree-ssa/vec-reduc-pair-3.c | 12 +++++++ .../gcc.dg/tree-ssa/vec-reduc-pair-4.c | 13 +++++++ 5 files changed, 84 insertions(+) create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vec-reduc-pair-1.c create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vec-reduc-pair-2.c create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vec-reduc-pair-3.c create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vec-reduc-pair-4.c diff --git a/gcc/match.pd b/gcc/match.pd index fe058d8c2a8..85fea9fa18f 100644 --- a/gcc/match.pd +++ b/gcc/match.pd @@ -12038,6 +12038,41 @@ and, (simplify (reduc (op @0 VECTOR_CST@1)) (op (reduc:type @0) (reduc:type @1)))) +/* Fold REDUC_PLUS (@0) +- REDUC_PLUS (@1) as REDUC_PLUS (@0 +- @1). This + trades one lane-crossing reduction for one elementwise operation. The + elementwise operation is done in the unsigned type because the reduction + itself wraps and the signed vector type has undefined overflow. */ +(for op (plus minus) + (simplify + (op (IFN_REDUC_PLUS:s @0) (IFN_REDUC_PLUS:s @1)) + (if (types_match (TREE_TYPE (@0), TREE_TYPE (@1)) + && ((ANY_INTEGRAL_TYPE_P (type) + && !TYPE_OVERFLOW_TRAPS (type) + && !TYPE_OVERFLOW_SANITIZED (type)) + || (FLOAT_TYPE_P (type) + && flag_associative_math + && !HONOR_SIGNED_ZEROS (type) + && !flag_trapping_math)) + && target_supports_op_p (TREE_TYPE (@0), op, optab_vector)) + (with { tree vtype = (ANY_INTEGRAL_TYPE_P (type) + ? unsigned_type_for (TREE_TYPE (@0)) + : TREE_TYPE (@0)); + tree stype = TREE_TYPE (vtype); } + (convert (IFN_REDUC_PLUS:stype + (op (view_convert:vtype @0) (view_convert:vtype @1)))))))) + +/* Fold REDUC (@0) op REDUC (@1) as REDUC (@0 op @1) for the reductions whose + scalar operation is associative and commutative with no flag needed. This + removes one lane-crossing reduction. */ +(for reduc (IFN_REDUC_MAX IFN_REDUC_MIN IFN_REDUC_AND IFN_REDUC_IOR + IFN_REDUC_XOR) + op (max min bit_and bit_ior bit_xor) + (simplify + (op (reduc:s @0) (reduc:s @1)) + (if (types_match (TREE_TYPE (@0), TREE_TYPE (@1)) + && target_supports_op_p (TREE_TYPE (@0), op, optab_vector)) + (reduc (op @0 @1))))) + /* Simplify .REDUC_IOR (@0) ==/!= 0 to @0 ==/!= 0. */ (for cmp (eq ne) (simplify diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vec-reduc-pair-1.c b/gcc/testsuite/gcc.dg/tree-ssa/vec-reduc-pair-1.c new file mode 100644 index 00000000000..01da5c2da34 --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/vec-reduc-pair-1.c @@ -0,0 +1,11 @@ +/* { dg-do compile { target aarch64*-*-* } } */ +/* { dg-options "-O2 -fdump-tree-optimized" } */ +/* Two reductions feeding the matching scalar operation are one reduction of + the elementwise operation, which trades a lane-crossing reduction for an + elementwise one. */ +#include <arm_neon.h> +int f (int32x4_t a, int32x4_t b) { return vaddvq_s32 (a) + vaddvq_s32 (b); } +int g (int32x4_t a, int32x4_t b) { return vaddvq_s32 (a) - vaddvq_s32 (b); } +int h (int32x4_t a, int32x4_t b) { int x = vmaxvq_s32 (a), y = vmaxvq_s32 (b); return x > y ? x : y; } +/* { dg-final { scan-tree-dump-times "REDUC_PLUS" 2 "optimized" } } */ +/* { dg-final { scan-tree-dump-times "REDUC_MAX" 1 "optimized" } } */ diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vec-reduc-pair-2.c b/gcc/testsuite/gcc.dg/tree-ssa/vec-reduc-pair-2.c new file mode 100644 index 00000000000..75d4369fc9f --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/vec-reduc-pair-2.c @@ -0,0 +1,13 @@ +/* { dg-do compile { target aarch64*-*-* } } */ +/* { dg-options "-O2 -ftrapv -fdump-tree-optimized" } */ + +#include <arm_neon.h> + +int +f (int32x4_t a, int32x4_t b) +{ + return vaddvq_s32 (a) + vaddvq_s32 (b); +} + +/* Trapping scalar addition must remain outside the reductions. */ +/* { dg-final { scan-tree-dump-times "REDUC_PLUS" 2 "optimized" } } */ diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vec-reduc-pair-3.c b/gcc/testsuite/gcc.dg/tree-ssa/vec-reduc-pair-3.c new file mode 100644 index 00000000000..4a9a3639b93 --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/vec-reduc-pair-3.c @@ -0,0 +1,12 @@ +/* { dg-do compile { target aarch64*-*-* } } */ +/* { dg-options "-O2 -fassociative-math -fno-signed-zeros -fno-trapping-math -fdump-tree-optimized" } */ + +#include <arm_neon.h> + +double +f (float64x2_t a, float64x2_t b) +{ + return vaddvq_f64 (a) - vaddvq_f64 (b); +} + +/* { dg-final { scan-tree-dump-times "REDUC_PLUS" 1 "optimized" } } */ diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vec-reduc-pair-4.c b/gcc/testsuite/gcc.dg/tree-ssa/vec-reduc-pair-4.c new file mode 100644 index 00000000000..cf0b0b6d44f --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/vec-reduc-pair-4.c @@ -0,0 +1,13 @@ +/* { dg-do compile { target aarch64*-*-* } } */ +/* { dg-options "-O2 -ftrapping-math -fdump-tree-optimized" } */ + +#include <arm_neon.h> + +__attribute__((optimize ("associative-math,no-signed-zeros"))) +double +f (float64x2_t a, float64x2_t b) +{ + return vaddvq_f64 (a) - vaddvq_f64 (b); +} + +/* { dg-final { scan-tree-dump-times "REDUC_PLUS" 2 "optimized" } } */ -- 2.50.1 (Apple Git-155)