The 09/29/2026 18:35, Dominic P wrote:
> vect_use_grouped_gather computes the offsets { 0, X, X*2, ... } in the
> unsigned element type of the punned vector, so if that is narrower than
> a pointer a negative step is zero-extended and a large one wraps.
> With -msve-vector-bits=256, a grouped load with a negative run-time
> stride faults (ld1w ..., [x4, z31.s, uxtw]). Ask for a signed offset
> for a negative constant step, and use the gather only if the returned
> offset type is pointer-wide or the constant step's offsets fit it.
> The range of the offsets comes from vect_strided_offset_range, split
> out of vect_truncate_gather_scatter_offset.
> This also fixes vect-alias-check-10.c and -11.c with SVE-256.
>
> Bootstrapped and regression tested on x86_64-pc-linux-gnu; regression
> tested on aarch64-linux-gnu (qemu, default and SVE-256 boards).
>
> Assisted-by: Claude Opus 5 (Anthropic)
>
> gcc/ChangeLog:
>
> * tree-vect-stmts.cc (vect_strided_offset_range): New function,
> split out of...
> (vect_truncate_gather_scatter_offset): ...here. Remove the
> overflow note.
> (vect_use_grouped_gather): Use a signed offset type for a negative
> constant step. Require the offsets not to wrap in the offset type.
>
> gcc/testsuite/ChangeLog:
>
> * gcc.dg/vect/vect-strided-neg-step-1.c: New test.
> * gcc.target/aarch64/sve/strided_grouped_1.c: New test.
> * gcc.target/aarch64/sve/strided_grouped_1_run.c: New test.
>
> Signed-off-by: Dominic P <[email protected]>
> ---
> .../gcc.dg/vect/vect-strided-neg-step-1.c | 82 ++++++++++++++++++
> .../aarch64/sve/strided_grouped_1.c | 8 ++
> .../aarch64/sve/strided_grouped_1_run.c | 4 +
> gcc/tree-vect-stmts.cc | 85 +++++++++++--------
> 4 files changed, 143 insertions(+), 36 deletions(-)
> create mode 100644 gcc/testsuite/gcc.dg/vect/vect-strided-neg-step-1.c
> create mode 100644 gcc/testsuite/gcc.target/aarch64/sve/strided_grouped_1.c
> create mode 100644
> gcc/testsuite/gcc.target/aarch64/sve/strided_grouped_1_run.c
>
> diff --git a/gcc/testsuite/gcc.dg/vect/vect-strided-neg-step-1.c
> b/gcc/testsuite/gcc.dg/vect/vect-strided-neg-step-1.c
> new file mode 100644
> index 00000000000..0d0fe976334
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/vect/vect-strided-neg-step-1.c
> @@ -0,0 +1,82 @@
> +/* Grouped strided accesses with a negative step. */
> +
> +#include "tree-vect.h"
> +
> +typedef unsigned char uc;
> +typedef unsigned short us;
> +
> +#define N 32
> +#define S 40
> +
> +#define TEST(T, G) \
> + void __attribute__ ((noipa))
> \
> + load_##T (T *restrict o, T *restrict p, long stride, int n)
> \
> + { \
> + for (int i = 0; i < n; i++)
> \
> + {
> \
> + for (int k = 0; k < G; k++) \
> + o[i * G + k] = p[k] * 3; \
> + p += stride; \
> + }
> \
> + } \
> + void __attribute__ ((noipa))
> \
> + store_##T (T *restrict q, T *restrict a, long stride, int n)
> \
> + { \
> + for (int i = 0; i < n; i++)
> \
> + {
> \
> + for (int k = 0; k < G; k++) \
> + q[k] = a[i * G + k] * 3; \
> + q += stride; \
> + }
> \
> + } \
> + void __attribute__ ((noipa))
> \
> + store_down_##T (T *restrict q, T *restrict a, int n)
> \
> + { \
> + for (int i = 0; i < n; i++)
> \
> + {
> \
> + for (int k = 0; k < G; k++) \
> + q[k] = a[i * G + k] * 3; \
> + q -= S; \
> + }
> \
> + } \
> + void __attribute__ ((noipa))
> \
> + check_##T (void) \
> + { \
> + static T a[N * G], o[N * G], m[N * S]; \
> + T *last = m + (N - 1) * S;
> \
> + _Pragma ("GCC novector") \
> + for (int i = 0; i < N * S; i++) \
> + m[i] = i * 7 + 1;
> \
> + _Pragma ("GCC novector") \
> + for (int i = 0; i < N * G; i++) \
> + a[i] = i * 5 + 2;
> \
> + load_##T (o, last, -S, N);
> \
> + _Pragma ("GCC novector") \
> + for (int i = 0; i < N * G; i++) \
> + if (o[i] != (T) (last[i % G - i / G * S] * 3)) \
> + __builtin_abort (); \
> + store_##T (last, a, -S, N);
> \
> + _Pragma ("GCC novector") \
> + for (int i = 0; i < N * G; i++) \
> + if (last[i % G - i / G * S] != (T) (a[i] * 3)) \
> + __builtin_abort (); \
> + for (int i = 0; i < N * G; i++) \
> + a[i] += 1; \
> + store_down_##T (last, a, N); \
> + _Pragma ("GCC novector") \
> + for (int i = 0; i < N * G; i++) \
> + if (last[i % G - i / G * S] != (T) (a[i] * 3)) \
> + __builtin_abort (); \
> + }
> +
> +TEST (uc, 4)
> +TEST (us, 2)
> +
> +int
> +main (void)
> +{
> + check_vect ();
> + check_uc ();
> + check_us ();
> + return 0;
> +}
> diff --git a/gcc/testsuite/gcc.target/aarch64/sve/strided_grouped_1.c
> b/gcc/testsuite/gcc.target/aarch64/sve/strided_grouped_1.c
> new file mode 100644
> index 00000000000..662f9753ecc
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/sve/strided_grouped_1.c
> @@ -0,0 +1,8 @@
> +/* { dg-do compile { target lp64 } } */
> +/* { dg-options "-O3 -fno-vect-cost-model -msve-vector-bits=256" } */
> +
> +#include "../../../gcc.dg/vect/vect-strided-neg-step-1.c"
> +
> +/* { dg-final { scan-assembler-not {\tld1w\tz[0-9]+\.s, p[0-7]/z, \[x[0-9]+,
> z[0-9]+\.s, uxtw\]} } } */
> +/* { dg-final { scan-assembler-not {\tst1w\tz[0-9]+\.s, p[0-7], \[x[0-9]+,
> z[0-9]+\.s, uxtw\]} } } */
> +/* { dg-final { scan-assembler-times {\tst1w\tz[0-9]+\.s, p[0-7], \[x[0-9]+,
> z[0-9]+\.s, sxtw\]} 2 } } */
> diff --git a/gcc/testsuite/gcc.target/aarch64/sve/strided_grouped_1_run.c
> b/gcc/testsuite/gcc.target/aarch64/sve/strided_grouped_1_run.c
> new file mode 100644
> index 00000000000..12c245c6ec1
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/sve/strided_grouped_1_run.c
> @@ -0,0 +1,4 @@
> +/* { dg-do run { target { aarch64_sve256_hw && lp64 } } } */
> +/* { dg-options "-O3 -fno-vect-cost-model -msve-vector-bits=256" } */
> +
> +#include "../../../gcc.dg/vect/vect-strided-neg-step-1.c"
> diff --git a/gcc/tree-vect-stmts.cc b/gcc/tree-vect-stmts.cc
> index 1db6786078e..7c96e9c8f55 100644
> --- a/gcc/tree-vect-stmts.cc
> +++ b/gcc/tree-vect-stmts.cc
> @@ -1613,6 +1613,36 @@ prepare_vec_mask (loop_vec_info loop_vinfo, tree
> mask_type, tree loop_mask,
> return and_res;
> }
>
> +/* Store in *RANGE the last offset of { 0, X, X*2, X*3, ... } for a strided
> + access in LOOP_VINFO, where X is the constant step STEP divided by SCALE.
> + Return false if STEP is not a multiple of SCALE or *RANGE overflows. */
> +
> +static bool
> +vect_strided_offset_range (loop_vec_info loop_vinfo, tree step,
> + unsigned int scale, widest_int *range)
> +{
> + /* Set COUNT to the upper limit on the number of elements - 1.
> + Start with the maximum vectorization factor. */
> + unsigned HOST_WIDE_INT count = vect_max_vf (loop_vinfo) - 1;
> +
> + /* Try lowering COUNT to the number of scalar latch iterations.
> + Peeling for alignment by masking shifts the active lanes up. */
> + class loop *loop = LOOP_VINFO_LOOP (loop_vinfo);
> + widest_int max_iters;
> + if (!LOOP_VINFO_PEELING_FOR_ALIGNMENT (loop_vinfo)
Needs updating from the previous patch.
> + && max_loop_iterations (loop, &max_iters)
> + && max_iters < count)
> + count = max_iters.to_shwi ();
> +
> + widest_int factor;
> + if (!wi::multiple_of_p (wi::to_widest (step), scale, SIGNED, &factor))
> + return false;
> +
> + wi::overflow_type overflow;
> + *range = wi::mul (count, factor, SIGNED, &overflow);
> + return !overflow;
> +}
> +
> /* Determine whether we can use a gather load or scatter store to vectorize
> strided load or store STMT_INFO by truncating the current offset to a
> smaller width. We need to be able to construct an offset vector:
> @@ -1645,37 +1675,16 @@ vect_truncate_gather_scatter_offset (stmt_vec_info
> stmt_info, tree vectype,
> return false;
> }
>
> - /* Get the number of bits in an element. */
> - scalar_mode element_mode = SCALAR_TYPE_MODE (TREE_TYPE (vectype));
> - unsigned int element_bits = GET_MODE_BITSIZE (element_mode);
> -
> - /* Set COUNT to the upper limit on the number of elements - 1.
> - Start with the maximum vectorization factor. */
> - unsigned HOST_WIDE_INT count = vect_max_vf (loop_vinfo) - 1;
> -
> - /* Try lowering COUNT to the number of scalar latch iterations.
> - Peeling for alignment by masking shifts the active lanes up. */
> - class loop *loop = LOOP_VINFO_LOOP (loop_vinfo);
> - widest_int max_iters;
> - if (!LOOP_VINFO_PEELING_FOR_ALIGNMENT (loop_vinfo)
> - && max_loop_iterations (loop, &max_iters)
> - && max_iters < count)
> - count = max_iters.to_shwi ();
> -
> /* Try scales of 1 and the element size. */
> unsigned int scales[] = { 1, vect_get_scalar_dr_size (dr_info) };
> - wi::overflow_type overflow = wi::OVF_NONE;
> for (int i = 0; i < 2; ++i)
> {
> unsigned int scale = scales[i];
> - widest_int factor;
> - if (!wi::multiple_of_p (wi::to_widest (step), scale, SIGNED, &factor))
> + widest_int range;
> + if (!vect_strided_offset_range (loop_vinfo, step, scale, &range))
> continue;
>
> - /* Determine the minimum precision of (COUNT - 1) * STEP / SCALE. */
> - widest_int range = wi::mul (count, factor, SIGNED, &overflow);
> - if (overflow)
> - continue;
> + /* Determine the minimum precision of RANGE. */
> signop sign = range >= 0 ? UNSIGNED : SIGNED;
> unsigned int min_offset_bits = wi::min_precision (range, sign);
>
> @@ -1711,11 +1720,6 @@ vect_truncate_gather_scatter_offset (stmt_vec_info
> stmt_info, tree vectype,
> return true;
> }
>
> - if (overflow && dump_enabled_p ())
> - dump_printf_loc (MSG_NOTE, vect_location,
> - "truncating gather/scatter offset to %d bits"
> - " might change its value.\n", element_bits);
> -
We still want the diagnostics here, likely you want to pass overflow as an
argument
to vect_strided_offset_range.
Thanks,
Tamar
> return false;
> }
>
> @@ -1762,22 +1766,31 @@ vect_use_grouped_gather (dr_vec_info *dr_info, tree
> vectype,
>
> internal_fn ifn;
> tree offset_vectype = *pun_vectype;
> + tree step = DR_STEP (dr);
> + if (TREE_CODE (step) == INTEGER_CST && tree_int_cst_sgn (step) < 0)
> + offset_vectype = signed_type_for (offset_vectype);
>
> internal_fn strided_ifn = DR_IS_READ (dr)
> ? IFN_MASK_LEN_STRIDED_LOAD : IFN_MASK_LEN_STRIDED_STORE;
>
> - /* Check if we have a gather/scatter with the new type. We're just trying
> - with the type itself as offset for now. If not, check if we have a
> - strided load/store. These have fewer constraints (for example no offset
> - type must exist) so it is possible that even though a gather/scatter is
> - not available we still have a strided load/store. */
> + /* Check if we have a gather/scatter with the new type as offset whose
> + offsets { 0, X, X*2, ... } for DR_STEP X do not wrap. If not, check
> + if we have a strided load/store. These have fewer constraints (for
> + example no offset type must exist) so it is possible that even though
> + a gather/scatter is not available we still have a strided
> + load/store. */
> bool ok = false;
> tree tmp_vectype;
> int tmp_scale;
> + widest_int range;
> if (vect_gather_scatter_fn_p
> (loop_vinfo, DR_IS_READ (dr), masked_p, *pun_vectype,
> - TREE_TYPE (*pun_vectype), *pun_vectype, 1, &tmp_scale, &ifn,
> - &offset_vectype, &tmp_vectype, elsvals))
> + TREE_TYPE (*pun_vectype), offset_vectype, 1, &tmp_scale, &ifn,
> + &offset_vectype, &tmp_vectype, elsvals)
> + && (TYPE_PRECISION (TREE_TYPE (offset_vectype)) >= POINTER_SIZE
> + || (TREE_CODE (step) == INTEGER_CST
> + && vect_strided_offset_range (loop_vinfo, step, 1, &range)
> + && wi::fits_to_tree_p (range, TREE_TYPE (offset_vectype)))))
> ok = true;
> else if (internal_strided_fn_supported_p (strided_ifn, *pun_vectype,
> elsvals))
> --
> 2.55.0
>
--