On Tue, 22 Sep 2026, Dominic P wrote:
> get_load_store_type elides a consecutive load permutation of a strided
> group, records that in subchain_p, and vectorizable_load re-derives the
> group from the flag. Elide it in remove_redundant_permutations instead:
> a load permutation indexes the whole group, and a node without one is its
> own group. That also covers a run that never had a permutation, which
> the flag could not see: in the half-pel SAD kernel p2[1..8] got one wide
> load while p2[0..7], starting at lane 0, kept DR_GROUP_SIZE 9 and was
> built from per-element loads. The rule in vectorizable_load is wider
> than the elision: it also covers a permutation-free node on the
> negative-constant-step route to VMAT_STRIDED_SLP.
>
> On aarch64 that loop goes from 50 to 23 instructions, and
> gcc.target/aarch64/sad-halfpel-2.c, which has failed since r17-4180,
> passes. On x86_64 the function grows, 148 to 167 instructions at -O3
> and 131 to 144 with -mavx2, and gets faster: measured natively, 8-row
> blocks are unchanged at -O3 and 14% faster with -mavx2, 64-row blocks
> 26 to 30% faster at -O3, -msse4.2 and -mavx2.
>
> Bootstrapped and regression tested on x86_64-pc-linux-gnu; regression
> tested on aarch64-linux-gnu under qemu.
>
> Assisted-by: Claude Opus 5 (Anthropic)
>
> gcc/ChangeLog:
>
> * tree-vect-slp.cc
> (vect_optimize_slp_pass::remove_redundant_permutations): Release a
> consecutive load permutation of a strided group.
> * tree-vect-stmts.cc (has_consecutive_load_permutation): Remove.
> (get_load_store_type): Take the group size from the SLP node when
> the node has no load permutation.
> (vectorizable_load): Likewise, and take the first statement of the
> group from the node too.
> * tree-vectorizer.h (vect_load_store_data): Remove subchain_p.
>
> gcc/testsuite/ChangeLog:
>
> * gcc.dg/vect/vect-strided-subrun-1.c: New test.
>
> Signed-off-by: Dominic P <[email protected]>
> ---
> v2: rebased on trunk 2cb78d1a0e5. The vectorizer change is unchanged from
> v1; sad-halfpel-2.c went in with r17-4180 already, which is why Linaro's CI
> reports it failing on aarch64 (GNU-2036), and this fixes that. New: a
> run-time test for sub-runs of a strided group, and x86_64 numbers measured
> again on this base (v1 quoted one block height only).
>
> .../gcc.dg/vect/vect-strided-subrun-1.c | 102 ++++++++++++++++++
> gcc/tree-vect-slp.cc | 11 ++
> gcc/tree-vect-stmts.cc | 67 +++---------
> gcc/tree-vectorizer.h | 2 -
> 4 files changed, 128 insertions(+), 54 deletions(-)
> create mode 100644 gcc/testsuite/gcc.dg/vect/vect-strided-subrun-1.c
>
> diff --git a/gcc/testsuite/gcc.dg/vect/vect-strided-subrun-1.c
> b/gcc/testsuite/gcc.dg/vect/vect-strided-subrun-1.c
> new file mode 100644
> index 00000000000..1882c63d75e
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/vect/vect-strided-subrun-1.c
> @@ -0,0 +1,102 @@
> +/* { dg-require-effective-target vect_int } */
> +
> +/* Consecutive runs of a strided load group that do not start at its first
> + element, overlap, run at a negative stride or feed two consumers. */
> +
> +#include "tree-vect.h"
> +
> +#define N 4096
> +unsigned char buf[N];
> +
> +#define KERNELS(NAME, ATTR) \
> +ATTR int \
> +NAME##_halfpel (const unsigned char *p1, const unsigned char *p2, \
> + long stride, int h) \
> +{ \
> + int s = 0; \
> + for (int i = 0; i < h; i++)
> \
> + {
> \
> + for (int k = 0; k < 8; k++) \
> + s += __builtin_abs (p1[k] - ((p2[k] + p2[k + 1] + 1) >> 1)); \
> + p1 += stride; \
> + p2 += stride; \
> + }
> \
> + return s; \
> +} \
> + \
> +ATTR int \
> +NAME##_run_at_5 (const unsigned char *p, long stride, int h) \
> +{ \
> + int s = 0; \
> + for (int i = 0; i < h; i++)
> \
> + {
> \
> + for (int k = 0; k < 4; k++) \
> + s += p[k + 5] * 3 + p[k + 8]; \
> + s += p[0] + p[11]; \
> + p += stride; \
> + }
> \
> + return s; \
> +} \
> + \
> +ATTR int \
> +NAME##_negstep (const unsigned char *p, long stride, int h) \
> +{ \
> + int s = 0; \
> + for (int i = 0; i < h; i++)
> \
> + {
> \
> + for (int k = 0; k < 8; k++) \
> + s += __builtin_abs (p[k] - ((p[k + 1] + 1) >> 1)); \
> + p -= stride; \
> + }
> \
> + return s; \
> +} \
> + \
> +ATTR int \
> +NAME##_shared (const unsigned char *p, long stride, int h) \
> +{ \
> + int s = 0, t = 0; \
> + for (int i = 0; i < h; i++)
> \
> + {
> \
> + for (int k = 0; k < 8; k++) \
> + { \
> + int v = p[k + 1]; \
> + s += v * 3; \
> + t += v ^ p[k]; \
> + } \
> + p += stride; \
> + }
> \
> + return s - t;
> \
> +}
> +
> +KERNELS (vec, __attribute__ ((noipa)))
> +KERNELS (ref, __attribute__ ((noipa, optimize ("O0"))))
> +
> +int
> +main (void)
> +{
> + check_vect ();
> +
> + unsigned x = 1;
> + for (int i = 0; i < N; i++)
> + {
> + x = x * 1103515245u + 12345u;
> + buf[i] = x >> 16;
> + }
> +
> + for (long stride = 13; stride <= 40; stride += 27)
> + for (int h = 1; h <= 33; h += 8)
> + {
> + if (vec_halfpel (buf + 3, buf + 700, stride, h)
> + != ref_halfpel (buf + 3, buf + 700, stride, h))
> + __builtin_abort ();
> + if (vec_run_at_5 (buf + 1, stride, h)
> + != ref_run_at_5 (buf + 1, stride, h))
> + __builtin_abort ();
> + if (vec_negstep (buf + 2000, stride, h)
> + != ref_negstep (buf + 2000, stride, h))
> + __builtin_abort ();
> + if (vec_shared (buf + 5, stride, h) != ref_shared (buf + 5, stride, h))
> + __builtin_abort ();
> + }
> + return 0;
> +}
> diff --git a/gcc/tree-vect-slp.cc b/gcc/tree-vect-slp.cc
> index 12b1abe3d78..77b5e260b44 100644
> --- a/gcc/tree-vect-slp.cc
> +++ b/gcc/tree-vect-slp.cc
> @@ -8204,6 +8204,17 @@ vect_optimize_slp_pass::remove_redundant_permutations
> ()
> }
> stmt_vec_info first_stmt_info
> = DR_GROUP_FIRST_ELEMENT (SLP_TREE_SCALAR_STMTS (node)[0]);
> + /* For a strided group the group is only a lane numbering, so a
> + consecutive run of it is itself a group with the same DR_STEP
> + and the permutation selecting the run is redundant. */
> + if (STMT_VINFO_STRIDED_P (first_stmt_info)
> + && SLP_TREE_LANES (node) > 1
> + && pow2p_hwi (SLP_TREE_LANES (node))
why this?
> + && vect_load_perm_consecutive_p (node))
I think this might go wrong when SLP_TREE_LANES != group_size, that is,
when the load permutation selects a subset of the group starting at
lane zero. For BB vectorization we allow _any_ consecutive sub-chain
(the preceeding code handles this). So I think the BB case should
be extendable to the loop case for STMT_VINFO_STRIDED_P with the
argument you rise here.
I'll note that some checks which are at the moment based on
the existence of a SLP_TREE_LOAD_PERMUTATION might be mis-using that
and could now fail to trigger.
> + {
> + SLP_TREE_LOAD_PERMUTATION (node).release ();
> + continue;
> + }
> if (!this_load_permuted
> /* The load requires permutation when unrolling exposes
> a gap either because the group is larger than the SLP
> diff --git a/gcc/tree-vect-stmts.cc b/gcc/tree-vect-stmts.cc
> index eb78fd2eba3..53f863cb3ed 100644
> --- a/gcc/tree-vect-stmts.cc
> +++ b/gcc/tree-vect-stmts.cc
> @@ -2033,25 +2033,6 @@ vector_vector_composition_type (tree vtype,
> poly_uint64 nelts, tree *ptype,
> return NULL_TREE;
> }
>
> -/* Check if the load permutation of NODE only refers to a consecutive
> - subset of the group indices. We also require the length of the
> - permutation to be a power of two.
> - Such load permutations can be elided in strided access schemes as
> - we can "jump over" the gap they leave. */
> -
> -static bool
> -has_consecutive_load_permutation (slp_tree node)
> -{
> - load_permutation_t perm = SLP_TREE_LOAD_PERMUTATION (node);
> - if (!perm.exists ()
> - || perm.length () <= 1
> - || !pow2p_hwi (perm.length ()))
> - return false;
> -
> - return vect_load_perm_consecutive_p (node);
> -}
> -
> -
> /* Analyze load or store SLP_NODE of type VLS_TYPE. Return true
> if there is a memory access type that the vectorized form can use,
> storing it in *MEMORY_ACCESS_TYPE if so. If we decide to use gathers
> @@ -2101,7 +2082,6 @@ get_load_store_type (vec_info *vinfo, stmt_vec_info
> stmt_info,
> *slp_perm = false;
> *n_perms = -1U;
> *n_loads = -1U;
> - ls->subchain_p = false;
>
> bool perm_ok = true;
> poly_int64 vf = loop_vinfo ? LOOP_VINFO_VECT_FACTOR (loop_vinfo) : 1;
> @@ -2154,15 +2134,10 @@ get_load_store_type (vec_info *vinfo, stmt_vec_info
> stmt_info,
> Fall back to scalar accesses if that isn't possible. */
> *memory_access_type = VMAT_STRIDED_SLP;
>
> - /* If the load permutation is consecutive we can reduce the group to
> - the elements the permutation accesses. Then we release the
> - permutation. */
> - if (has_consecutive_load_permutation (slp_node))
> - {
> - ls->subchain_p = true;
> - group_size = SLP_TREE_LANES (slp_node);
> - SLP_TREE_LOAD_PERMUTATION (slp_node).release ();
> - }
> + /* A load permutation indexes the whole group, so we have to load
> + all of it; without one the node's lanes are the group. */
> + if (!SLP_TREE_LOAD_PERMUTATION (slp_node).exists ())
> + group_size = SLP_TREE_LANES (slp_node);
> }
> else if (STMT_VINFO_GATHER_SCATTER_P (stmt_info))
> {
> @@ -10190,16 +10165,14 @@ vectorizable_load (vec_info *vinfo,
> gcc_assert (!LOOP_VINFO_USING_PARTIAL_VECTORS_P (loop_vinfo));
> gcc_assert (!nested_in_vect_loop);
>
> + /* A load permutation indexes the whole group; without one the
> + node's own statements are the group. */
> + bool perm_p = SLP_TREE_LOAD_PERMUTATION (slp_node).exists ();
> if (grouped_load)
> {
> - /* If we elided a consecutive load permutation, don't
> - use the original first statement (which could be elided)
> - but the one the load permutation starts with.
> - This ensures the stride_base below is correct. */
> - if (!ls.subchain_p)
> - first_stmt_info = DR_GROUP_FIRST_ELEMENT (stmt_info);
> - else
> - first_stmt_info = SLP_TREE_SCALAR_STMTS (slp_node)[0];
> + first_stmt_info = (perm_p
> + ? DR_GROUP_FIRST_ELEMENT (stmt_info)
> + : SLP_TREE_SCALAR_STMTS (slp_node)[0]);
> first_dr_info = STMT_VINFO_DR_INFO (first_stmt_info);
> ref_type = get_group_alias_ptr_type (first_stmt_info);
> }
> @@ -10210,22 +10183,12 @@ vectorizable_load (vec_info *vinfo,
> ref_type = reference_alias_ptr_type (DR_REF (dr_info->dr));
> }
>
> - if (grouped_load)
> - {
> - if (memory_access_type == VMAT_STRIDED_SLP)
> - {
> - /* If we elided a consecutive load permutation, adjust
> - the group size here. */
> - if (!ls.subchain_p)
> - group_size = DR_GROUP_SIZE (first_stmt_info);
> - else
> - group_size = SLP_TREE_LANES (slp_node);
> - }
> - else /* VMAT_ELEMENTWISE */
> - group_size = SLP_TREE_LANES (slp_node);
> - }
> - else
> + if (!grouped_load)
> group_size = 1;
> + else if (perm_p && memory_access_type == VMAT_STRIDED_SLP)
> + group_size = DR_GROUP_SIZE (first_stmt_info);
> + else
> + group_size = SLP_TREE_LANES (slp_node);
So I _think_ this should work for sub-chains not starting at lane 0.
Richard.
> if (!costing_p)
> {
> diff --git a/gcc/tree-vectorizer.h b/gcc/tree-vectorizer.h
> index abd9edc2743..26df0ad526e 100644
> --- a/gcc/tree-vectorizer.h
> +++ b/gcc/tree-vectorizer.h
> @@ -312,8 +312,6 @@ struct vect_load_store_data : vect_data {
> bool slp_perm; // SLP_TREE_LOAD_PERMUTATION
> unsigned n_perms; // SLP_TREE_LOAD_PERMUTATION
> unsigned n_loads; // SLP_TREE_LOAD_PERMUTATION
> - /* Whether the load permutation is consecutive and simple. */
> - bool subchain_p; // VMAT_STRIDED_SLP and VMAT_GATHER_SCATTER
> };
>
> struct match_elt_t {
>
--
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)