On Tue, 13 Oct 2026, [email protected] wrote:

> From: Kyrylo Tkachov <[email protected]>
> 
> vect_recog_popcount_clz_ctz_ffs_pattern checks the second argument of a
> two-argument .CLZ or .CTZ against the value of the target.  It does
> this only when the precisions of the argument and the result differ.
> For equal precisions it builds the pattern call with the value of the
> target.  On AArch64
> 
>   _5 = .CLZ (_1, 5);
>   _9 = (unsigned int) _5;
> 
> becomes
> 
>   vect_patt_18.11_23 = .CLZ (vect__1.10_22, 32);
> 
> and zero lanes give 32 instead of 5.  The expansion implements any value
> at zero of a scalar call, so such scalar calls are valid.
> 
> Do the check after the switch, for all precisions.  The calls of the
> example then stay scalar.
> 
> Bootstrapped and tested on aarch64-none-linux-gnu and x86_64-linux.

I think that's OK (correct), but can we do better?  We should
be able to, since at least for 'unsupported' we dispatch to
vect_recog_ctz_ffs_pattern which eventually does

  if (defined_at_zero
      && (!defined_at_zero_new || val != val_cmp))
    {
      append_pattern_def_seq (vinfo, stmt_vinfo, pattern_stmt, vec_type);
      tree ret_var = vect_recog_temp_ssa_var (lhs_type, NULL);
      rhs_oprnd = gimple_call_arg (call_stmt, 0);
      rhs_type = TREE_TYPE (rhs_oprnd);
      tree cmp = vect_recog_temp_ssa_var (boolean_type_node, NULL);
      pattern_stmt = gimple_build_assign (cmp, NE_EXPR, rhs_oprnd,
                                          build_zero_cst (rhs_type));
      append_pattern_def_seq (vinfo, stmt_vinfo, pattern_stmt,
                              truth_type_for (vec_type), rhs_type);
      pattern_stmt = gimple_build_assign (ret_var, COND_EXPR, cmp,
                                          new_var,
                                          build_int_cst (lhs_type, val));


> gcc/ChangeLog:
> 
>       * tree-vect-patterns.cc (vect_recog_popcount_clz_ctz_ffs_pattern):
>       Check the value at zero of a two-argument .CLZ or .CTZ for all
>       precisions.
> 
> gcc/testsuite/ChangeLog:
> 
>       * gcc.dg/vect/bb-slp-clz-ctz-value-at-zero-1.c: New test.
> 
> Signed-off-by: Kyrylo Tkachov <[email protected]>
> ---
>  .../vect/bb-slp-clz-ctz-value-at-zero-1.c     | 182 ++++++++++++++++++
>  gcc/tree-vect-patterns.cc                     |  43 ++---
>  2 files changed, 199 insertions(+), 26 deletions(-)
>  create mode 100644 gcc/testsuite/gcc.dg/vect/bb-slp-clz-ctz-value-at-zero-1.c
> 
> diff --git a/gcc/testsuite/gcc.dg/vect/bb-slp-clz-ctz-value-at-zero-1.c 
> b/gcc/testsuite/gcc.dg/vect/bb-slp-clz-ctz-value-at-zero-1.c
> new file mode 100644
> index 00000000000..64404dc01e0
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/vect/bb-slp-clz-ctz-value-at-zero-1.c
> @@ -0,0 +1,182 @@
> +/* Lanes of .CLZ and .CTZ whose value at zero is not the value of the
> +   target must keep that value.  */
> +/* { dg-require-effective-target int32 } */
> +/* { dg-require-effective-target clz_ifn } */
> +/* { dg-require-effective-target ctz_ifn } */
> +/* { dg-additional-options "-fgimple -fdump-tree-optimized" } */
> +
> +#include "tree-vect.h"
> +
> +unsigned int a[4];
> +int r[4];
> +unsigned int u[4];
> +
> +void __GIMPLE (ssa,startwith ("fix_loops")) __attribute__((noipa))
> +clz5 (void)
> +{
> +  unsigned int _1;
> +  unsigned int _2;
> +  unsigned int _3;
> +  unsigned int _4;
> +  int _5;
> +  int _6;
> +  int _7;
> +  int _8;
> +
> +  __BB(2):
> +  _1 = a[0];
> +  _2 = a[1];
> +  _3 = a[2];
> +  _4 = a[3];
> +  _5 = .CLZ (_1, 5);
> +  _6 = .CLZ (_2, 5);
> +  _7 = .CLZ (_3, 5);
> +  _8 = .CLZ (_4, 5);
> +  r[0] = _5;
> +  r[1] = _6;
> +  r[2] = _7;
> +  r[3] = _8;
> +  return;
> +}
> +
> +/* The conversion makes the call part of a vectorizer pattern.  */
> +
> +void __GIMPLE (ssa,startwith ("fix_loops")) __attribute__((noipa))
> +clz5u (void)
> +{
> +  unsigned int _1;
> +  unsigned int _2;
> +  unsigned int _3;
> +  unsigned int _4;
> +  int _5;
> +  int _6;
> +  int _7;
> +  int _8;
> +  unsigned int _9;
> +  unsigned int _10;
> +  unsigned int _11;
> +  unsigned int _12;
> +
> +  __BB(2):
> +  _1 = a[0];
> +  _2 = a[1];
> +  _3 = a[2];
> +  _4 = a[3];
> +  _5 = .CLZ (_1, 5);
> +  _6 = .CLZ (_2, 5);
> +  _7 = .CLZ (_3, 5);
> +  _8 = .CLZ (_4, 5);
> +  _9 = (unsigned int) _5;
> +  _10 = (unsigned int) _6;
> +  _11 = (unsigned int) _7;
> +  _12 = (unsigned int) _8;
> +  u[0] = _9;
> +  u[1] = _10;
> +  u[2] = _11;
> +  u[3] = _12;
> +  return;
> +}
> +
> +void __GIMPLE (ssa,startwith ("fix_loops")) __attribute__((noipa))
> +ctz5u (void)
> +{
> +  unsigned int _1;
> +  unsigned int _2;
> +  unsigned int _3;
> +  unsigned int _4;
> +  int _5;
> +  int _6;
> +  int _7;
> +  int _8;
> +  unsigned int _9;
> +  unsigned int _10;
> +  unsigned int _11;
> +  unsigned int _12;
> +
> +  __BB(2):
> +  _1 = a[0];
> +  _2 = a[1];
> +  _3 = a[2];
> +  _4 = a[3];
> +  _5 = .CTZ (_1, 5);
> +  _6 = .CTZ (_2, 5);
> +  _7 = .CTZ (_3, 5);
> +  _8 = .CTZ (_4, 5);
> +  _9 = (unsigned int) _5;
> +  _10 = (unsigned int) _6;
> +  _11 = (unsigned int) _7;
> +  _12 = (unsigned int) _8;
> +  u[0] = _9;
> +  u[1] = _10;
> +  u[2] = _11;
> +  u[3] = _12;
> +  return;
> +}
> +
> +/* 32 is the value of the target on AArch64, so this vectorizes there.  */
> +
> +void __GIMPLE (ssa,startwith ("fix_loops")) __attribute__((noipa))
> +clz32u (void)
> +{
> +  unsigned int _1;
> +  unsigned int _2;
> +  unsigned int _3;
> +  unsigned int _4;
> +  int _5;
> +  int _6;
> +  int _7;
> +  int _8;
> +  unsigned int _9;
> +  unsigned int _10;
> +  unsigned int _11;
> +  unsigned int _12;
> +
> +  __BB(2):
> +  _1 = a[0];
> +  _2 = a[1];
> +  _3 = a[2];
> +  _4 = a[3];
> +  _5 = .CLZ (_1, 32);
> +  _6 = .CLZ (_2, 32);
> +  _7 = .CLZ (_3, 32);
> +  _8 = .CLZ (_4, 32);
> +  _9 = (unsigned int) _5;
> +  _10 = (unsigned int) _6;
> +  _11 = (unsigned int) _7;
> +  _12 = (unsigned int) _8;
> +  u[0] = _9;
> +  u[1] = _10;
> +  u[2] = _11;
> +  u[3] = _12;
> +  return;
> +}
> +
> +static void __attribute__((noipa))
> +check (const unsigned int *p, unsigned int e0, unsigned int e1,
> +       unsigned int e2, unsigned int e3)
> +{
> +  if (p[0] != e0 || p[1] != e1 || p[2] != e2 || p[3] != e3)
> +    __builtin_abort ();
> +}
> +
> +int
> +main (void)
> +{
> +  check_vect ();
> +
> +  /* Write A at run time, so that LTO cannot fold the loads.  */
> +  a[1] = 1;
> +  a[3] = 0x80000000u;
> +  clz5 ();
> +  check ((const unsigned int *) r, 5, 31, 5, 0);
> +  clz5u ();
> +  check (u, 5, 31, 5, 0);
> +  ctz5u ();
> +  check (u, 5, 0, 5, 31);
> +  clz32u ();
> +  check (u, 32, 31, 32, 0);
> +  return 0;
> +}
> +
> +/* { dg-final { scan-tree-dump-times {\.CLZ \(vect_} 1 "optimized" { target 
> aarch64*-*-* } } } */
> +/* { dg-final { scan-tree-dump-not {\.CTZ \(vect_} "optimized" { target 
> aarch64*-*-* } } } */
> diff --git a/gcc/tree-vect-patterns.cc b/gcc/tree-vect-patterns.cc
> index e9d2870feb4..7c435adeb98 100644
> --- a/gcc/tree-vect-patterns.cc
> +++ b/gcc/tree-vect-patterns.cc
> @@ -2260,9 +2260,7 @@ vect_recog_popcount_clz_ctz_ffs_pattern (vec_info 
> *vinfo,
>         return NULL;
>       break;
>        case IFN_CLZ:
> -     /* clzll (x) == clz (x) + 32 for unsigned x != 0, so ok
> -        if it is undefined at zero or if it matches also for the
> -        defined value there.  */
> +     /* clzll (x) == clz (x) + 32 for unsigned x != 0.  */
>       if (!TYPE_UNSIGNED (unprom_diff.type))
>         return NULL;
>       if (!type_has_mode_precision_p (lhs_type)
> @@ -2270,31 +2268,9 @@ vect_recog_popcount_clz_ctz_ffs_pattern (vec_info 
> *vinfo,
>         return NULL;
>       addend = (TYPE_PRECISION (TREE_TYPE (rhs_oprnd))
>                 - TYPE_PRECISION (lhs_type));
> -     if (gimple_call_internal_p (call_stmt)
> -         && gimple_call_num_args (call_stmt) == 2)
> -       {
> -         int val1, val2;
> -         val1 = tree_to_shwi (gimple_call_arg (call_stmt, 1));
> -         int d2
> -           = CLZ_DEFINED_VALUE_AT_ZERO (TYPE_MODE (vec_type), val2);
> -         if (d2 != 2 || val1 != val2 + addend)
> -           return NULL;
> -       }
>       break;
>        case IFN_CTZ:
> -     /* ctzll (x) == ctz (x) for unsigned or signed x != 0, so ok
> -        if it is undefined at zero or if it matches also for the
> -        defined value there.  */
> -     if (gimple_call_internal_p (call_stmt)
> -         && gimple_call_num_args (call_stmt) == 2)
> -       {
> -         int val1, val2;
> -         val1 = tree_to_shwi (gimple_call_arg (call_stmt, 1));
> -         int d2
> -           = CTZ_DEFINED_VALUE_AT_ZERO (TYPE_MODE (vec_type), val2);
> -         if (d2 != 2 || val1 != val2)
> -           return NULL;
> -       }
> +     /* ctzll (x) == ctz (x) for unsigned or signed x != 0.  */
>       break;
>        case IFN_FFS:
>       /* ffsll (x) == ffs (x) for unsigned or signed x.  */
> @@ -2303,6 +2279,21 @@ vect_recog_popcount_clz_ctz_ffs_pattern (vec_info 
> *vinfo,
>       gcc_unreachable ();
>        }
>  
> +  /* For zero the pattern gives the value of the target for VEC_TYPE plus
> +     ADDEND.  Punt if the call has another value at zero.  */
> +  if ((ifn == IFN_CLZ || ifn == IFN_CTZ)
> +      && gimple_call_internal_p (call_stmt)
> +      && gimple_call_num_args (call_stmt) == 2)
> +    {
> +      int val;
> +      int d = (ifn == IFN_CLZ
> +            ? CLZ_DEFINED_VALUE_AT_ZERO (TYPE_MODE (vec_type), val)
> +            : CTZ_DEFINED_VALUE_AT_ZERO (TYPE_MODE (vec_type), val));
> +      if (d != 2
> +       || tree_to_shwi (gimple_call_arg (call_stmt, 1)) != val + addend)
> +     return NULL;
> +    }
> +
>    /* Do it only if the backend has popcount<vector_mode>2 etc. pattern.  */
>    bool supported
>      = direct_internal_fn_supported_p (ifn, vec_type, OPTIMIZE_FOR_SPEED);
> 

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

Reply via email to