From: Kyrylo Tkachov <[email protected]>
REDUC_MAX, REDUC_MIN, REDUC_AND and REDUC_IOR are idempotent. A
reduction of a nonempty duplicate vector with one of these operations is
the duplicated element:
v = [vec_duplicate_expr] x;
res = .REDUC_MAX (v);
becomes:
res = x;
For example, the new SVE test contains:
int __GIMPLE ()
reduc_max (int x)
{
svint32_t v;
int res;
v = svdup_s32 (x);
res = .REDUC_MAX (v);
return res;
}
With -O2 -fgimple -march=armv8.2-a+sve, the code changes from:
reduc_max:
mov z31.s, w0
ptrue p7.b, all
smaxv s31, p7, z31.s
fmov w0, s31
ret
to:
reduc_max:
ret
An XOR reduction of a duplicate vector is zero when the number of lanes
is even. GCC permits variable-length vectors whose lane count has
unknown parity, such as [1, 1]. Fold REDUC_XOR only when multiple_p
proves that every possible lane count is even.
Use the same two paths as IFN_VEC_EXTRACT and IFN_VEC_SHL_INSERT. Match
a nonconstant VEC_DUPLICATE_EXPR directly in match.pd. Handle a
VECTOR_CST in fold_const_reduction. Keep fixed-length constants on the
existing exact-element path.
A scalable VECTOR_CST can store one element from each pattern and repeat
the encoded elements. Reduce one copy of such a nonuniform pattern for
the four idempotent operations. Do not use this path for stepped
constants or operations whose result depends on the number of repeats.
Keep REDUC_MAX and REDUC_MIN when a repeated element can be a signaling
NaN. The reduction can quiet the NaN and raise an invalid-operation
exception.
These folds also let the constant reduction in the PR 127039 rule
simplify for scalable constants. The scalar result cannot match the
inverse reduction-pair rule, so the nontermination cycle stays blocked.
Bootstrapped and tested on aarch64-none-linux-gnu.
Ok for trunk?
Thanks,
Kyrill
gcc/ChangeLog:
PR tree-optimization/127039
* fold-const-call.cc (fold_const_reduction): Fold uniform and
repeating constants when the available lane-count information
determines the result.
* match.pd (reduction of a vector duplicate): New simplifications to
the duplicated element or zero.
gcc/testsuite/ChangeLog:
PR tree-optimization/127039
* gcc.target/aarch64/sve/vec-reduc-dup-1.c: New test.
* gcc.target/aarch64/sve/vec-reduc-pattern-1.c: New test.
* gcc.target/aarch64/sve/vec-reduc-snan-1.c: New test.
* gcc.target/aarch64/sve/vec-reduc-uniform-1.c: New test.
* gcc.target/riscv/rvv/base/reduc-xor-uniform-1.c: New test.
Signed-off-by: Kyrylo Tkachov <[email protected]>
---
gcc/fold-const-call.cc | 35 ++++++-
gcc/match.pd | 14 +++
.../gcc.target/aarch64/sve/vec-reduc-dup-1.c | 63 +++++++++++++
.../aarch64/sve/vec-reduc-pattern-1.c | 93 +++++++++++++++++++
.../gcc.target/aarch64/sve/vec-reduc-snan-1.c | 65 +++++++++++++
.../aarch64/sve/vec-reduc-uniform-1.c | 71 ++++++++++++++
.../riscv/rvv/base/reduc-xor-uniform-1.c | 47 ++++++++++
7 files changed, 385 insertions(+), 3 deletions(-)
create mode 100644 gcc/testsuite/gcc.target/aarch64/sve/vec-reduc-dup-1.c
create mode 100644 gcc/testsuite/gcc.target/aarch64/sve/vec-reduc-pattern-1.c
create mode 100644 gcc/testsuite/gcc.target/aarch64/sve/vec-reduc-snan-1.c
create mode 100644 gcc/testsuite/gcc.target/aarch64/sve/vec-reduc-uniform-1.c
create mode 100644
gcc/testsuite/gcc.target/riscv/rvv/base/reduc-xor-uniform-1.c
diff --git a/gcc/fold-const-call.cc b/gcc/fold-const-call.cc
index c483b80e704..50c08329286 100644
--- a/gcc/fold-const-call.cc
+++ b/gcc/fold-const-call.cc
@@ -622,11 +622,40 @@ fold_const_builtin_nan (tree type, tree arg, bool quiet)
static tree
fold_const_reduction (tree type, tree arg, tree_code code)
{
- unsigned HOST_WIDE_INT nelts;
- if (TREE_CODE (arg) != VECTOR_CST
- || !VECTOR_CST_NELTS (arg).is_constant (&nelts))
+ if (TREE_CODE (arg) != VECTOR_CST)
return NULL_TREE;
+ unsigned HOST_WIDE_INT nelts;
+ if (!VECTOR_CST_NELTS (arg).is_constant (&nelts))
+ {
+ bool idempotent_p = (code == MAX_EXPR
+ || code == MIN_EXPR
+ || code == BIT_AND_EXPR
+ || code == BIT_IOR_EXPR);
+
+ /* Fold uniform reductions when the available lane-count information is
+ sufficient to determine the result. */
+ if (tree elem = uniform_vector_p (arg))
+ {
+ if (idempotent_p
+ && !tree_expr_maybe_signaling_nan_p (elem))
+ return elem;
+
+ if (code == BIT_XOR_EXPR
+ && multiple_p (VECTOR_CST_NELTS (arg), 2))
+ return build_zero_cst (type);
+
+ return NULL_TREE;
+ }
+
+ /* A nonuniform duplicate constant repeats its encoded elements. An
+ idempotent reduction therefore needs to process them only once. */
+ if (!idempotent_p || !VECTOR_CST_DUPLICATE_P (arg))
+ return NULL_TREE;
+
+ nelts = vector_cst_encoded_nelts (arg);
+ }
+
tree res = VECTOR_CST_ELT (arg, 0);
for (unsigned HOST_WIDE_INT i = 1; i < nelts; i++)
{
diff --git a/gcc/match.pd b/gcc/match.pd
index 04a3ee3ab20..dfedb8973e1 100644
--- a/gcc/match.pd
+++ b/gcc/match.pd
@@ -14363,6 +14363,20 @@ and,
(if (elem)
{ build_vector_from_val (type, elem); }))))
+/* A reduction of a duplicate vector with an idempotent operation is the
+ duplicated element. */
+(for reduc (IFN_REDUC_MAX IFN_REDUC_MIN IFN_REDUC_AND IFN_REDUC_IOR)
+ (simplify
+ (reduc (vec_duplicate @0))
+ (if (!tree_expr_maybe_signaling_nan_p (@0))
+ @0)))
+
+/* Fold an XOR reduction of an even-length duplicate vector to zero. */
+(simplify
+ (IFN_REDUC_XOR (vec_duplicate@1 @0))
+ (if (multiple_p (TYPE_VECTOR_SUBPARTS (TREE_TYPE (@1)), 2))
+ { build_zero_cst (type); }))
+
/* Push VEC_PERM earlier if that may help FMA perception (PR101895). */
(simplify
(plus:c (vec_perm:s (mult:c@0 @1 vec_same_elem_p@2) @0 @3) @4)
diff --git a/gcc/testsuite/gcc.target/aarch64/sve/vec-reduc-dup-1.c
b/gcc/testsuite/gcc.target/aarch64/sve/vec-reduc-dup-1.c
new file mode 100644
index 00000000000..9d5581d6811
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/sve/vec-reduc-dup-1.c
@@ -0,0 +1,63 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fgimple -fdump-tree-forwprop1" } */
+
+#include <arm_sve.h>
+
+int __GIMPLE ()
+reduc_max (int x)
+{
+ svint32_t v;
+ int res;
+
+ v = svdup_s32 (x);
+ res = .REDUC_MAX (v);
+ return res;
+}
+
+int __GIMPLE ()
+reduc_min (int x)
+{
+ svint32_t v;
+ int res;
+
+ v = svdup_s32 (x);
+ res = .REDUC_MIN (v);
+ return res;
+}
+
+int __GIMPLE ()
+reduc_and (int x)
+{
+ svint32_t v;
+ int res;
+
+ v = svdup_s32 (x);
+ res = .REDUC_AND (v);
+ return res;
+}
+
+int __GIMPLE ()
+reduc_ior (int x)
+{
+ svint32_t v;
+ int res;
+
+ v = svdup_s32 (x);
+ res = .REDUC_IOR (v);
+ return res;
+}
+
+int __GIMPLE ()
+reduc_xor (int x)
+{
+ svint32_t v;
+ int res;
+
+ v = svdup_s32 (x);
+ res = .REDUC_XOR (v);
+ return res;
+}
+
+/* { dg-final { scan-tree-dump-not {\.REDUC_} "forwprop1" } } */
+/* { dg-final { scan-tree-dump-times {return x_} 4 "forwprop1" } } */
+/* { dg-final { scan-tree-dump-times {return 0;} 1 "forwprop1" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/sve/vec-reduc-pattern-1.c
b/gcc/testsuite/gcc.target/aarch64/sve/vec-reduc-pattern-1.c
new file mode 100644
index 00000000000..f2f6837ca28
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/sve/vec-reduc-pattern-1.c
@@ -0,0 +1,93 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fgimple -fdump-tree-forwprop1" } */
+
+#include <arm_sve.h>
+
+/* Check that idempotent reductions process a repeated pattern once. Keep
+ reductions that depend on the repeat count or use a stepped pattern. */
+
+int __GIMPLE ()
+reduc_max_repeat (void)
+{
+ svint32_t v;
+ int res;
+
+ v = svdupq_s32 (3, 5, 3, 5);
+ res = .REDUC_MAX (v);
+ return res;
+}
+
+int __GIMPLE ()
+reduc_min_repeat (void)
+{
+ svint32_t v;
+ int res;
+
+ v = svdupq_s32 (3, 5, 3, 5);
+ res = .REDUC_MIN (v);
+ return res;
+}
+
+int __GIMPLE ()
+reduc_and_repeat (void)
+{
+ svint32_t v;
+ int res;
+
+ v = svdupq_s32 (3, 5, 3, 5);
+ res = .REDUC_AND (v);
+ return res;
+}
+
+int __GIMPLE ()
+reduc_ior_repeat (void)
+{
+ svint32_t v;
+ int res;
+
+ v = svdupq_s32 (3, 5, 3, 5);
+ res = .REDUC_IOR (v);
+ return res;
+}
+
+int __GIMPLE ()
+reduc_max_series (void)
+{
+ svint32_t v;
+ int res;
+
+ v = svindex_s32 (1, 1);
+ res = .REDUC_MAX (v);
+ return res;
+}
+
+int __GIMPLE ()
+reduc_plus_repeat (void)
+{
+ svint32_t v;
+ int res;
+
+ v = svdupq_s32 (3, 5, 3, 5);
+ res = .REDUC_PLUS (v);
+ return res;
+}
+
+int __GIMPLE ()
+reduc_xor_unknown (void)
+{
+ svint32_t v;
+ int res;
+
+ v = svdupq_s32 (1, 2, 4, 8);
+ res = .REDUC_XOR (v);
+ return res;
+}
+
+/* { dg-final { scan-tree-dump-times {\.REDUC_} 3 "forwprop1" } } */
+/* { dg-final { scan-tree-dump-times {\.REDUC_MAX \(\{ 1, 2, 3, \.\.\. \}\)} 1
"forwprop1" } } */
+/* { dg-final { scan-tree-dump-times {\.REDUC_PLUS \(\{ 3, 5, \.\.\. \}\)} 1
"forwprop1" } } */
+/* { dg-final { scan-tree-dump-times {\.REDUC_XOR \(\{ 1, 2, 4, 8, \.\.\.
\}\)} 1 "forwprop1" } } */
+/* { dg-final { scan-tree-dump-times {return 5;} 1 "forwprop1" } } */
+/* { dg-final { scan-tree-dump-times {return 3;} 1 "forwprop1" } } */
+/* { dg-final { scan-tree-dump-times {return 1;} 1 "forwprop1" } } */
+/* { dg-final { scan-tree-dump-times {return 7;} 1 "forwprop1" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/sve/vec-reduc-snan-1.c
b/gcc/testsuite/gcc.target/aarch64/sve/vec-reduc-snan-1.c
new file mode 100644
index 00000000000..8781380510e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/sve/vec-reduc-snan-1.c
@@ -0,0 +1,65 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fgimple -fsignaling-nans -fdump-tree-forwprop1" } */
+
+#include <arm_sve.h>
+
+#define SNAN_F32_BITS 0x7f800001U
+
+float __GIMPLE ()
+reduc_max_dup (float x)
+{
+ svfloat32_t v;
+ float res;
+
+ v = svdup_f32 (x);
+ res = .REDUC_MAX (v);
+ return res;
+}
+
+float __GIMPLE ()
+reduc_min_dup (float x)
+{
+ svfloat32_t v;
+ float res;
+
+ v = svdup_f32 (x);
+ res = .REDUC_MIN (v);
+ return res;
+}
+
+typedef float v4sf __attribute__ ((vector_size (16)));
+typedef unsigned int v4su __attribute__ ((vector_size (16)));
+
+float __GIMPLE ()
+reduc_max_cst (void)
+{
+ v4su u;
+ v4sf v;
+ float res;
+
+ u = _Literal (v4su) { SNAN_F32_BITS, SNAN_F32_BITS,
+ SNAN_F32_BITS, SNAN_F32_BITS };
+ v = __VIEW_CONVERT <v4sf> (u);
+ res = .REDUC_MAX (v);
+ return res;
+}
+
+float __GIMPLE ()
+reduc_min_cst (void)
+{
+ v4su u;
+ v4sf v;
+ float res;
+
+ u = _Literal (v4su) { SNAN_F32_BITS, SNAN_F32_BITS,
+ SNAN_F32_BITS, SNAN_F32_BITS };
+ v = __VIEW_CONVERT <v4sf> (u);
+ res = .REDUC_MIN (v);
+ return res;
+}
+
+/* { dg-final { scan-tree-dump-times {vec_duplicate_expr} 2 "forwprop1" } } */
+/* { dg-final { scan-tree-dump-times {\.REDUC_MAX} 2 "forwprop1" } } */
+/* { dg-final { scan-tree-dump-times {\.REDUC_MIN} 2 "forwprop1" } } */
+/* { dg-final { scan-tree-dump-times {\.REDUC_MAX \(.*Nan} 1 "forwprop1" } } */
+/* { dg-final { scan-tree-dump-times {\.REDUC_MIN \(.*Nan} 1 "forwprop1" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/sve/vec-reduc-uniform-1.c
b/gcc/testsuite/gcc.target/aarch64/sve/vec-reduc-uniform-1.c
new file mode 100644
index 00000000000..9830511b18d
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/sve/vec-reduc-uniform-1.c
@@ -0,0 +1,71 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -msve-vector-bits=scalable -mautovec-preference=sve-only
-fvect-cost-model=unlimited --param=vect-partial-vector-usage=0
-fdump-tree-vect-details -fdump-tree-forwprop4" } */
+
+/* Check reductions of scalable uniform vector constants. */
+
+char c1, c2, c3, c4, c5;
+short d1, d2, d3, d4, d5;
+int rmax, rmin, r_and, r_ior, r_xor;
+unsigned n;
+
+__attribute__ ((noipa)) void
+max_uniform (void)
+{
+ for (short i = 0; i < n; i += 3)
+ {
+ c1 ^= d1;
+ rmax = rmax > 17 ? rmax : 17;
+ }
+}
+
+__attribute__ ((noipa)) void
+min_uniform (void)
+{
+ for (short i = 0; i < n; i += 3)
+ {
+ c2 ^= d2;
+ rmin = rmin < -19 ? rmin : -19;
+ }
+}
+
+__attribute__ ((noipa)) void
+and_uniform (void)
+{
+ for (short i = 0; i < n; i += 3)
+ {
+ c3 ^= d3;
+ r_and &= 1431655765;
+ }
+}
+
+__attribute__ ((noipa)) void
+ior_uniform (void)
+{
+ for (short i = 0; i < n; i += 3)
+ {
+ c4 ^= d4;
+ r_ior |= 572662306;
+ }
+}
+
+__attribute__ ((noipa)) void
+xor_uniform (void)
+{
+ for (short i = 0; i < n; i += 3)
+ {
+ c5 ^= d5;
+ r_xor ^= 858993459;
+ }
+}
+
+/* { dg-final { scan-tree-dump-times {LOOP VECTORIZED} 5 "vect" } } */
+/* { dg-final { scan-tree-dump-times {\.REDUC_MAX} 1 "forwprop4" } } */
+/* { dg-final { scan-tree-dump-times {\.REDUC_MIN} 1 "forwprop4" } } */
+/* { dg-final { scan-tree-dump-times {\.REDUC_AND} 1 "forwprop4" } } */
+/* { dg-final { scan-tree-dump-times {\.REDUC_IOR} 1 "forwprop4" } } */
+/* { dg-final { scan-tree-dump-times {\.REDUC_MAX \(vect_rmax_lsm} 1
"forwprop4" } } */
+/* { dg-final { scan-tree-dump-times {\.REDUC_MIN \(vect_rmin_lsm} 1
"forwprop4" } } */
+/* { dg-final { scan-tree-dump-times {\.REDUC_AND \(vect_r_and_lsm} 1
"forwprop4" } } */
+/* { dg-final { scan-tree-dump-times {\.REDUC_IOR \(vect_r_ior_lsm} 1
"forwprop4" } } */
+/* { dg-final { scan-tree-dump-times {\.REDUC_XOR} 5 "forwprop4" } } */
+/* { dg-final { scan-tree-dump-not {\.REDUC_XOR \(\{ 0, \.\.\. \}\)}
"forwprop4" } } */
diff --git a/gcc/testsuite/gcc.target/riscv/rvv/base/reduc-xor-uniform-1.c
b/gcc/testsuite/gcc.target/riscv/rvv/base/reduc-xor-uniform-1.c
new file mode 100644
index 00000000000..54339490fd8
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/rvv/base/reduc-xor-uniform-1.c
@@ -0,0 +1,47 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fgimple -march=rv64im_zve64f -mabi=lp64
-fdump-tree-forwprop1" } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+#include "riscv_vector.h"
+
+/*
+** reduc_xor_even:
+** li a0,0
+** ret
+*/
+long __GIMPLE ()
+reduc_xor_even (void)
+{
+ vbool32_t mask;
+ vint64m2_t ones;
+ long res;
+
+ mask = _Literal (vint64m2_t) 0 == _Literal (vint64m2_t) 0;
+ ones = .VEC_CONVERT (mask);
+ res = .REDUC_XOR (ones);
+ return res;
+}
+
+/*
+** reduc_xor_unknown:
+** vsetvli [a-x0-9]+,zero,e64,m1,ta,ma
+** ...
+** vredxor\.vs v[0-9]+,v[0-9]+,v[0-9]+
+** ...
+** ret
+*/
+long __GIMPLE ()
+reduc_xor_unknown (void)
+{
+ vbool64_t mask;
+ vint64m1_t ones;
+ long res;
+
+ mask = _Literal (vint64m1_t) 0 == _Literal (vint64m1_t) 0;
+ ones = .VEC_CONVERT (mask);
+ res = .REDUC_XOR (ones);
+ return res;
+}
+
+/* { dg-final { scan-tree-dump-times {\.REDUC_XOR} 1 "forwprop1" } } */
+/* { dg-final { scan-tree-dump-times {return 0;} 1 "forwprop1" } } */
--
2.50.1 (Apple Git-155)