On Fri, 11 Sep 2026, Dominic P wrote:

> vect_widened_op_tree accepts an operand that is as wide as the result only
> if it is itself the demotion of a narrower value, so a loop invariant that
> merely has a narrow range blocks every widening recogniser.
> 
> x265's angular intra prediction is the motivating case
> (source/common/intrapred.cpp:186):
> 
>   f = angleSum & 31;
>   dst[x] = ((32 - f) * ref[x] + f * ref[x + 1] + 16) >> 5;
> 
> with ref unsigned char.  over_widening narrows the chain to unsigned
> short, and widen_mult then rejects the (unsigned short) f operand even
> though its range is [0, 31].  Spelling f as unsigned char in the source
> makes widen_mult fire at once, so only the range query is missing.
> 
> Query the range of such an operand and record the narrower type when the
> range fits at most half the result precision.  vect_get_range_info is the
> helper the over-widening recogniser already uses; the operand is an
> external def, so its definition dominates the loop and the range holds
> throughout it.  The narrowed type keeps the sign of the original, as
> vect_joust_widened_integer does for constants, so the value is
> representable in it.  The recognisers check target support for the widened
> operation themselves, and for loop vectorization vect_convert_input
> places the demoting cast on the incoming edge, outside the loop.
> 
> Restricting this to vect_external_def is profitability, not correctness:
> the range of an induction variable is equally usable, but the demoting
> cast is then executed every iteration.  An earlier version admitted
> induction defs and narrowed the induction variable of
> gcc.dg/vect/slp-widen-mult-half.c on its [0, 15] range, changing the
> pattern counts there for byte-identical code.
> 
> An operand whose range is known only after over_widening has cast it to a
> new unsigned type is still missed: for signed char data and a non-negative
> invariant, the cast operand has no range and the unsigned narrowing that
> the range would give clashes with the signed data anyway.  Fixing that
> needs the sign choice to be made where the operands are jousted, not here.
> 
> On the x265 kernel above, aarch64 -mcpu=cortex-a76 -O2 goes from 77
> instructions to 68: 3 mul + 2 mla + 2 zip1 + 2 zip2 become umull + umull2
> + umlal + umlal2 + 1 mul.  x86_64 -O2 -mavx2 goes from 119 to 123, the
> five extra instructions all in the inner loop's preheader, because x86 has
> no unsigned char widening multiply and emulates it; that is what GCC
> already emits there when the multiplier is spelled unsigned char in the
> source, so the change makes the two spellings agree.
> 
> Bootstrapped and regression tested on x86_64-pc-linux-gnu.
> 
> Assisted-by: Claude Opus 5 (Anthropic)
> 
> gcc/ChangeLog:
> 
>       * tree-vect-patterns.cc (vect_narrow_invariant_to_range): New
>       function.
>       (vect_widened_op_tree): Use it.
> 
> gcc/testsuite/ChangeLog:
> 
>       * gcc.dg/vect/vect-widen-invariant-1.c: New test.
>       * gcc.target/aarch64/widen-invariant-1.c: New test.
> 
> Signed-off-by: Dominic P <[email protected]>
> ---
>  .../gcc.dg/vect/vect-widen-invariant-1.c      | 58 +++++++++++++++++++
>  .../gcc.target/aarch64/widen-invariant-1.c    | 33 +++++++++++
>  gcc/tree-vect-patterns.cc                     | 28 ++++++++-
>  3 files changed, 118 insertions(+), 1 deletion(-)
>  create mode 100644 gcc/testsuite/gcc.dg/vect/vect-widen-invariant-1.c
>  create mode 100644 gcc/testsuite/gcc.target/aarch64/widen-invariant-1.c
> 
> diff --git a/gcc/testsuite/gcc.dg/vect/vect-widen-invariant-1.c 
> b/gcc/testsuite/gcc.dg/vect/vect-widen-invariant-1.c
> new file mode 100644
> index 00000000000..1cf9f2a97ce
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/vect/vect-widen-invariant-1.c
> @@ -0,0 +1,58 @@
> +/* { dg-require-effective-target vect_int } */
> +
> +#include "tree-vect.h"
> +
> +#define N 64
> +
> +unsigned char dst[N], src[N + 1];
> +short sub[N];
> +
> +__attribute__ ((noipa)) void
> +ang_row (unsigned char *__restrict d, const unsigned char *__restrict s,
> +      int angle, int n)
> +{
> +  int f = angle & 31;
> +  int a = 32 - f;
> +  for (int x = 0; x < n; x++)
> +    d[x] = (unsigned char) ((a * s[x] + f * s[x + 1] + 16) >> 5);
> +}
> +
> +__attribute__ ((noipa)) void
> +sub_row (short *__restrict d, const unsigned char *__restrict s,
> +      int angle, int n)
> +{
> +  int f = angle & 31;
> +  /* Walk D with a pointer: indexing it would scale the index by 2 and give
> +     the test a widening multiply that has nothing to do with the patch.  */
> +  for (const unsigned char *e = s + n; s < e; s++)
> +    *d++ = (short) (f - *s);
> +}
> +
> +int
> +main (void)
> +{
> +  int i;
> +
> +  check_vect ();
> +
> +#pragma GCC novector
> +  for (i = 0; i < N + 1; i++)
> +    src[i] = (i * 37 + 11) & 0xff;
> +
> +  ang_row (dst, src, 0x123, N);
> +  sub_row (sub, src, 0x123, N);
> +
> +#pragma GCC novector
> +  for (i = 0; i < N; i++)
> +    {
> +      int f = 0x123 & 31, a = 32 - f;
> +      if (dst[i] != (unsigned char) ((a * src[i] + f * src[i + 1] + 16) >> 
> 5))
> +     abort ();
> +      if (sub[i] != (short) (f - src[i]))
> +     abort ();
> +    }
> +  return 0;
> +}
> +
> +/* { dg-final { scan-tree-dump "vect_recog_widen_mult_pattern: detected" 
> "vect" { target vect_widen_mult_qi_to_hi } } } */
> +/* { dg-final { scan-tree-dump "vect_recog_widen_minus_pattern: detected" 
> "vect" { target vect_widen_mult_qi_to_hi } } } */
> diff --git a/gcc/testsuite/gcc.target/aarch64/widen-invariant-1.c 
> b/gcc/testsuite/gcc.target/aarch64/widen-invariant-1.c
> new file mode 100644
> index 00000000000..24d413d71fb
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/widen-invariant-1.c
> @@ -0,0 +1,33 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fno-schedule-insns -fno-schedule-insns2" } */
> +/* { dg-final { check-function-bodies "**" "" {} } } */
> +
> +/*
> +** ang_row:
> +**   and     w2, w2, 31
> +**   mov     w[0-9]+, 32
> +**   sub     w[0-9]+, w[0-9]+, w2
> +**   dup     v[0-9]+\.16b, w[0-9]+
> +**   dup     v[0-9]+\.16b, w2
> +**   movi    v[0-9]+\.8h, 0x10
> +**   mov     v[0-9]+\.16b, v[0-9]+\.16b
> +**   ldr     q[0-9]+, \[x1\]
> +**   ldr     q[0-9]+, \[x1, 1\]
> +**   umlal   v[0-9]+\.8h, v[0-9]+\.8b, v[0-9]+\.8b
> +**   umlal   v[0-9]+\.8h, v[0-9]+\.8b, v[0-9]+\.8b
> +**   umlal2  v[0-9]+\.8h, v[0-9]+\.16b, v[0-9]+\.16b
> +**   umlal2  v[0-9]+\.8h, v[0-9]+\.16b, v[0-9]+\.16b
> +**   shrn    v[0-9]+\.8b, v[0-9]+\.8h, 5
> +**   shrn2   v[0-9]+\.16b, v[0-9]+\.8h, 5
> +**   str     q[0-9]+, \[x0\]
> +**   ret
> +*/
> +void
> +ang_row (unsigned char *__restrict d, const unsigned char *__restrict s,
> +      int angle)
> +{
> +  int f = angle & 31;
> +  int a = 32 - f;
> +  for (int x = 0; x < 16; x++)
> +    d[x] = (unsigned char) ((a * s[x] + f * s[x + 1] + 16) >> 5);
> +}
> diff --git a/gcc/tree-vect-patterns.cc b/gcc/tree-vect-patterns.cc
> index 58710b2a5fd..98fc1be8a54 100644
> --- a/gcc/tree-vect-patterns.cc
> +++ b/gcc/tree-vect-patterns.cc
> @@ -567,6 +567,31 @@ vect_joust_widened_type (tree type, tree new_type, tree 
> *common_type)
>    return true;
>  }
>  
> +/* UNPROM describes an operand of an operation whose result has type TYPE,
> +   and the operand is as wide as TYPE.  If the operand is loop-invariant and
> +   its value range fits a type of at most half the precision of TYPE, narrow
> +   UNPROM->TYPE to such a type and return true.  */
> +
> +static bool
> +vect_narrow_invariant_to_range (vect_unpromoted_value *unprom, tree type)
> +{
> +  wide_int min_value, max_value;
> +  if (unprom->dt != vect_external_def
> +      || TREE_CODE (unprom->op) != SSA_NAME
> +      || !vect_get_range_info (unprom->op, &min_value, &max_value))
> +    return false;
> +

Can you avoid new uses of vect_get_range_info and instead use
range_of_expr directly (passing last_stmt as context) and
for the precision use lower/upper_bound () of the range?

The restriction to unprom->dt != vect_external_def makes this
less usable as a general utility so I'd prefer that check to be
done at the caller, possibly also that we apply this to
vect_unpromoted_value, though that's probably OK.

Otherwise this looks good.

> +  signop sgn = TYPE_SIGN (unprom->type);
> +  unsigned int precision = MAX (wi::min_precision (min_value, sgn),
> +                             wi::min_precision (max_value, sgn));
> +  if (precision * 2 > TYPE_PRECISION (type))
> +    return false;
> +
> +  precision = vect_element_precision (precision);
> +  unprom->type = build_nonstandard_integer_type (precision, sgn == UNSIGNED);
> +  return true;
> +}
> +
>  /* Check whether STMT_INFO can be viewed as a tree of integer operations
>     in which each node either performs CODE or WIDENED_CODE, and where
>     each leaf operand is narrower than the result of STMT_INFO.  MAX_NOPS
> @@ -659,7 +684,8 @@ vect_widened_op_tree (vec_info *vinfo, stmt_vec_info 
> stmt_info, tree_code code,
>                                                         this_unprom))
>           return 0;
>  
> -       if (TYPE_PRECISION (this_unprom->type) == TYPE_PRECISION (type))
> +       if (TYPE_PRECISION (this_unprom->type) == TYPE_PRECISION (type)
> +           && !vect_narrow_invariant_to_range (this_unprom, type))
>           {
>             /* The operand isn't widened.  If STMT_INFO has the code
>                for an unwidened operation, recursively check whether
> 

-- 
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