On Wed, 26 Aug 2026, [email protected] wrote:

> From: Kyrylo Tkachov <[email protected]>
> 
> REDUC_MAX, REDUC_MIN, REDUC_AND and REDUC_IOR are idempotent.  Reducing
> a nonempty uniform vector with one of these operations gives the element
> that the vector contains.  Fold this form to that element:
> 
>   v = _Literal (v4si) { x, x, x, x };
>   res = .REDUC_MAX (v);
>   return res;
> 
> to:
> 
>   return x;
> 
> An XOR reduction of a uniform 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:

Note GCC also permits single-element vectors.

> 
>   v = _Literal (v4si) { x, x, x, x };
>   res = .REDUC_XOR (v);
>   return res;
> 
> to:
> 
>   return 0;
> 
> Use vec_same_elem_p to match the uniform vector and
> ssa_uniform_vector_p to get its element.  Keep a floating-point
> reduction when the element can be a signaling NaN because the reduction
> can quiet the NaN and raise an exception.
> 
> These folds also let the constant reduction in the PR 127039 rule
> simplify for scalable uniform constants.  The scalar result cannot match
> the inverse reduction-pair rule, so the nontermination cycle stays
> blocked.
> 
> The assembly comparison uses this function from the new test:
> 
>   typedef int v4si __attribute__ ((vector_size (16)));
> 
>   int __GIMPLE ()
>   reduc_max (int x)
>   {
>     v4si v;
>     int res;
> 
>     v = _Literal (v4si) { x, x, x, x };
>     res = .REDUC_MAX (v);
>     return res;
>   }
> 
> With -O2 -fgimple on AArch64, the code changes from:
> 
>   reduc_max:
>           dup     v31.4s, w0
>           smaxv   s31, v31.4s
>           fmov    w0, s31
>           ret
> 
> to:
> 
>   reduc_max:
>           ret
> 
> Bootstrapped and tested on aarch64-unknown-linux-gnu.
> Ok for trunk?

LGTM.

On x86 where we have SSE, AVX and AVX512 vectors a reduction
of a vector like { x, x, x, x, y, y, y, y } could possibly
be split up, doing a scalar reduction of x and y.  Likewise
(given .REDUC_ allows association) for { x, y, x, y }.  So
like we now have uniform_vector_difference_p maybe we want
a more general uniform_vector_p, for this particular case
a bool twoval_vector_p (vector, val[2]&) (or
unsigned nval_vector_p (vector, val[n]&, unsigned n) for the more general
case).

Just some food for thought.

Richard.

> Thanks,
> Kyrill
> 
> gcc/ChangeLog:
> 
>       PR tree-optimization/127039
>       * match.pd (reduction of a uniform vector): New simplifications to
>       the splatted element or zero.
> 
> gcc/testsuite/ChangeLog:
> 
>       PR tree-optimization/127039
>       * gcc.dg/tree-ssa/vec-reduc-splat-1.c: New test.
>       * gcc.target/aarch64/sve/vec-reduc-uniform-1.c: New test.
>       * gcc.target/aarch64/vec-reduc-splat-float-1.c: New test.
>       * gcc.target/riscv/rvv/base/reduc-xor-uniform-1.c: New test.
> 
> Signed-off-by: Kyrylo Tkachov <[email protected]>
> ---
>  gcc/match.pd                                  | 17 +++++
>  .../gcc.dg/tree-ssa/vec-reduc-splat-1.c       | 69 ++++++++++++++++++
>  .../aarch64/sve/vec-reduc-uniform-1.c         | 71 +++++++++++++++++++
>  .../aarch64/vec-reduc-splat-float-1.c         | 31 ++++++++
>  .../riscv/rvv/base/reduc-xor-uniform-1.c      | 47 ++++++++++++
>  5 files changed, 235 insertions(+)
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vec-reduc-splat-1.c
>  create mode 100644 gcc/testsuite/gcc.target/aarch64/sve/vec-reduc-uniform-1.c
>  create mode 100644 gcc/testsuite/gcc.target/aarch64/vec-reduc-splat-float-1.c
>  create mode 100644 
> gcc/testsuite/gcc.target/riscv/rvv/base/reduc-xor-uniform-1.c
> 
> diff --git a/gcc/match.pd b/gcc/match.pd
> index c7841cab459..529e3d5ff60 100644
> --- a/gcc/match.pd
> +++ b/gcc/match.pd
> @@ -12536,6 +12536,23 @@ and,
>     (if (elem)
>      { build_vector_from_val (type, elem); }))))
>  
> +/* A reduction of a uniform vector with an idempotent operation is the
> +   element it is a splat of.  */
> +(for reduc (IFN_REDUC_MAX IFN_REDUC_MIN IFN_REDUC_AND IFN_REDUC_IOR)
> + (simplify
> +  (reduc vec_same_elem_p@0)
> +  (with { tree elem = ssa_uniform_vector_p (@0); }
> +   (if (elem
> +     && types_match (type, TREE_TYPE (elem))
> +     && !tree_expr_maybe_signaling_nan_p (elem))
> +    { elem; }))))
> +
> +/* Fold an XOR reduction of an even-length uniform vector to zero.  */
> +(simplify
> + (IFN_REDUC_XOR vec_same_elem_p@0)
> + (if (multiple_p (TYPE_VECTOR_SUBPARTS (TREE_TYPE (@0)), 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.dg/tree-ssa/vec-reduc-splat-1.c 
> b/gcc/testsuite/gcc.dg/tree-ssa/vec-reduc-splat-1.c
> new file mode 100644
> index 00000000000..2ad23472a06
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/vec-reduc-splat-1.c
> @@ -0,0 +1,69 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fgimple -Wno-psabi -fdump-tree-forwprop1" } */
> +
> +/* Check reductions of a variable splat.  */
> +
> +typedef int v4si __attribute__ ((vector_size (16)));
> +
> +int __GIMPLE ()
> +reduc_max (int x)
> +{
> +  v4si v;
> +  int res;
> +
> +  v = _Literal (v4si) { x, x, x, x };
> +  res = .REDUC_MAX (v);
> +  return res;
> +}
> +
> +int __GIMPLE ()
> +reduc_min (int x)
> +{
> +  v4si v;
> +  int res;
> +
> +  v = _Literal (v4si) { x, x, x, x };
> +  res = .REDUC_MIN (v);
> +  return res;
> +}
> +
> +int __GIMPLE ()
> +reduc_and (int x)
> +{
> +  v4si v;
> +  int res;
> +
> +  v = _Literal (v4si) { x, x, x, x };
> +  res = .REDUC_AND (v);
> +  return res;
> +}
> +
> +int __GIMPLE ()
> +reduc_ior (int x)
> +{
> +  v4si v;
> +  int res;
> +
> +  v = _Literal (v4si) { x, x, x, x };
> +  res = .REDUC_IOR (v);
> +  return res;
> +}
> +
> +int __GIMPLE ()
> +reduc_xor_even (int x)
> +{
> +  v4si v;
> +  int res;
> +
> +  v = _Literal (v4si) { x, x, x, x };
> +  res = .REDUC_XOR (v);
> +  return res;
> +}
> +
> +/* { dg-final { scan-tree-dump-not {\.REDUC_MAX} "forwprop1" } } */
> +/* { dg-final { scan-tree-dump-not {\.REDUC_MIN} "forwprop1" } } */
> +/* { dg-final { scan-tree-dump-not {\.REDUC_AND} "forwprop1" } } */
> +/* { dg-final { scan-tree-dump-not {\.REDUC_IOR} "forwprop1" } } */
> +/* { dg-final { scan-tree-dump-not {\.REDUC_XOR} "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-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/aarch64/vec-reduc-splat-float-1.c 
> b/gcc/testsuite/gcc.target/aarch64/vec-reduc-splat-float-1.c
> new file mode 100644
> index 00000000000..ca0764997a0
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/vec-reduc-splat-float-1.c
> @@ -0,0 +1,31 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fgimple -fsignaling-nans -fdump-tree-forwprop1" } */
> +
> +/* Keep reductions of a splat that can contain a signaling NaN.  */
> +
> +typedef float v4sf __attribute__ ((vector_size (16)));
> +
> +float __GIMPLE ()
> +reduc_max (float x)
> +{
> +  v4sf v;
> +  float res;
> +
> +  v = _Literal (v4sf) { x, x, x, x };
> +  res = .REDUC_MAX (v);
> +  return res;
> +}
> +
> +float __GIMPLE ()
> +reduc_min (float x)
> +{
> +  v4sf v;
> +  float res;
> +
> +  v = _Literal (v4sf) { x, x, x, x };
> +  res = .REDUC_MIN (v);
> +  return res;
> +}
> +
> +/* { dg-final { scan-tree-dump-times {\.REDUC_MAX} 1 "forwprop1" } } */
> +/* { dg-final { scan-tree-dump-times {\.REDUC_MIN} 1 "forwprop1" } } */
> 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" } } */
> 

-- 
Richard Biener <[email protected]>
SUSE Software Solutions Germany GmbH,
Frankenstrasse 146, 90461 Nuernberg, Germany;
GF: Jochen Jaser, Andrew McDonald, Abhinav Puri; (HRB 36809, AG Nuernberg)

Reply via email to