The Linaro precommit CI flagged 4 "regressions" on aarch64, but all four
are XPASSes on the two tests this patch is meant to fix:
XPASS: gcc.dg/vect/tsvc/vect-tsvc-s255.c scan-tree-dump vect "vectorized
1 loops"
XPASS: gcc.dg/vect/tsvc/vect-tsvc-s292.c scan-tree-dump vect "vectorized
1 loops"
(and the same two under -flto -ffat-lto-objects)
Both currently carry an unconditional marker:
/* { dg-final { scan-tree-dump "vectorized 1 loops" "vect" { xfail *-*-*
} } } */
so the XPASSes are the intended effect rather than a regression, and it
is useful confirmation the patch works on aarch64 as well as x86_64 and
riscv64.
I would rather narrow the marker than remove it, since both files are
gated only on vect_float and the transform needs a permute:
/* { dg-final { scan-tree-dump "vectorized 1 loops" "vect" { xfail { !
vect_perm } } } } */
That matches the { target vect_perm } guard on the new testcases in this
patch. I have not run the testsuite on a target without permute support
to confirm that is the right selector for both, so I am happy to be
corrected.
Shall I fold this into a v2, or would you prefer it as a separate
testsuite patch?
On Mon, Jul 27, 2026 at 12:47 AM Tanmay Gulhane <[email protected]>
wrote:
> First-order recurrence vectorization rejects a loop in which the latch
> definition of a header PHI is itself a header PHI. That is the shape
> of TSVC s255, where two scalars carry values from one and two
> iterations back. Such a chain of header PHIs, each the latch
> definition of the previous one and terminating in a non-PHI definition
> inside the loop, is a series of first-order recurrences that can all
> be vectorized. A cyclic chain, for example two PHIs swapping values,
> cannot, so the new vect_recurrence_chain_p rejects cycles. It also
> rejects chains involving a PHI classified as another kind of scalar
> cycle, like the induction PHI in the loop of PR 107302, and chains
> whose terminating definition uses a chain PHI, a value cycle as in a
> reduction.
>
> For the transform, the vectorized latch value of a chained recurrence
> is the permute emitted for the latch child, which the SLP scheduler
> has already transformed. Insert the chained permute after the last
> vectorized def of that child instead of after the scalar latch
> definition, which for a chain is a PHI we cannot insert after.
>
> PR tree-optimization/116338
>
> gcc/ChangeLog:
>
> * tree-vect-loop.cc (vect_recurrence_chain_p): New function.
> (vect_phi_first_order_recurrence_p): Accept a latch definition
> that is a header PHI starting an acyclic recurrence chain. Do
> not require dominance for a use as the latch argument of
> another header PHI.
> (vectorizable_recurr): For a chained recurrence insert the
> permute after the last vectorized def of the latch child.
>
> gcc/testsuite/ChangeLog:
>
> * gcc.dg/vect/pr116338-1.c: New testcase.
> * gcc.dg/vect/pr116338-2.c: New testcase.
> * gcc.dg/vect/pr116338-3.c: New testcase.
>
> Signed-off-by: Tanmay Gulhane <[email protected]>
> ---
> gcc/testsuite/gcc.dg/vect/pr116338-1.c | 43 +++++++++
> gcc/testsuite/gcc.dg/vect/pr116338-2.c | 43 +++++++++
> gcc/testsuite/gcc.dg/vect/pr116338-3.c | 23 +++++
> gcc/tree-vect-loop.cc | 121 ++++++++++++++++++++++---
> 4 files changed, 218 insertions(+), 12 deletions(-)
> create mode 100644 gcc/testsuite/gcc.dg/vect/pr116338-1.c
> create mode 100644 gcc/testsuite/gcc.dg/vect/pr116338-2.c
> create mode 100644 gcc/testsuite/gcc.dg/vect/pr116338-3.c
>
> diff --git a/gcc/testsuite/gcc.dg/vect/pr116338-1.c
> b/gcc/testsuite/gcc.dg/vect/pr116338-1.c
> new file mode 100644
> index 000000000..940b2b56d
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/vect/pr116338-1.c
> @@ -0,0 +1,43 @@
> +/* { dg-do run } */
> +/* { dg-require-effective-target vect_int } */
> +
> +#include "tree-vect.h"
> +
> +int a[64], b[64];
> +
> +void __attribute__((noipa))
> +foo (void)
> +{
> + int x = b[63];
> + int y = b[62];
> + for (int i = 0; i < 64; ++i)
> + {
> + a[i] = b[i] + x + y;
> + y = x;
> + x = b[i];
> + }
> +}
> +
> +int
> +main ()
> +{
> + check_vect ();
> + for (int i = 0; i < 64; ++i)
> + {
> + b[i] = i * 7 + 3;
> + __asm__ volatile ("" ::: "memory");
> + }
> + foo ();
> + int x = b[63], y = b[62];
> +#pragma GCC novector
> + for (int i = 0; i < 64; ++i)
> + {
> + if (a[i] != b[i] + x + y)
> + __builtin_abort ();
> + y = x;
> + x = b[i];
> + }
> + return 0;
> +}
> +
> +/* { dg-final { scan-tree-dump "vectorized 1 loops" "vect" { target
> vect_perm } } } */
> diff --git a/gcc/testsuite/gcc.dg/vect/pr116338-2.c
> b/gcc/testsuite/gcc.dg/vect/pr116338-2.c
> new file mode 100644
> index 000000000..889eaab08
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/vect/pr116338-2.c
> @@ -0,0 +1,43 @@
> +/* { dg-do run } */
> +/* { dg-require-effective-target vect_float } */
> +
> +#include "tree-vect.h"
> +
> +float a[64], b[64];
> +
> +void __attribute__((noipa))
> +foo (void)
> +{
> + float x = b[63];
> + float y = b[62];
> + for (int i = 0; i < 64; ++i)
> + {
> + a[i] = (b[i] + x + y) * 0.5f;
> + y = x;
> + x = b[i];
> + }
> +}
> +
> +int
> +main ()
> +{
> + check_vect ();
> + for (int i = 0; i < 64; ++i)
> + {
> + b[i] = i * 7 + 3;
> + __asm__ volatile ("" ::: "memory");
> + }
> + foo ();
> + float x = b[63], y = b[62];
> +#pragma GCC novector
> + for (int i = 0; i < 64; ++i)
> + {
> + if (a[i] != (b[i] + x + y) * 0.5f)
> + __builtin_abort ();
> + y = x;
> + x = b[i];
> + }
> + return 0;
> +}
> +
> +/* { dg-final { scan-tree-dump "vectorized 1 loops" "vect" { target
> vect_perm } } } */
> diff --git a/gcc/testsuite/gcc.dg/vect/pr116338-3.c
> b/gcc/testsuite/gcc.dg/vect/pr116338-3.c
> new file mode 100644
> index 000000000..2de4e1764
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/vect/pr116338-3.c
> @@ -0,0 +1,23 @@
> +/* { dg-do compile } */
> +/* { dg-require-effective-target vect_int } */
> +
> +/* PR tree-optimization/116338. A cycle of header PHIs, here two values
> + swapping every iteration, is not a chain of first-order recurrences
> + and must not be treated as one. Verify we neither vectorize this
> + nor ICE on it. */
> +
> +void
> +swap (int * __restrict__ a, int * __restrict__ b, int n)
> +{
> + int x = b[n-1];
> + int y = b[n-2];
> + for (int i = 0; i < n; ++i)
> + {
> + a[i] = b[i] + 2 * x - y;
> + int t = x;
> + x = y;
> + y = t;
> + }
> +}
> +
> +/* { dg-final { scan-tree-dump-not "vectorized 1 loops" "vect" } } */
> diff --git a/gcc/tree-vect-loop.cc b/gcc/tree-vect-loop.cc
> index a9335ed68..7f1c05ad7 100644
> --- a/gcc/tree-vect-loop.cc
> +++ b/gcc/tree-vect-loop.cc
> @@ -289,6 +289,67 @@ vect_is_nonlinear_iv_evolution (class loop* loop,
> stmt_vec_info stmt_info,
> return true;
> }
>
> +/* PHI is a loop header PHI of LOOP, the loop vectorized as LOOP_VINFO,
> + whose latch definition is the header PHI CHAIN. Return true if
> + CHAIN starts an acyclic chain of header
> + PHIs, each being the latch definition of the previous one, terminating
> + in a non-PHI definition inside LOOP. Such a chain represents a series
> + of recurrences each delayed by one more iteration and can be
> + vectorized as chained first-order recurrences. A cyclic chain, for
> + example two PHIs swapping values, cannot, and neither can a chain
> + that involves another kind of scalar cycle such as an induction
> + or reduction. */
> +
> +static bool
> +vect_recurrence_chain_p (loop_vec_info loop_vinfo, class loop *loop,
> + gphi *phi, gphi *chain)
> +{
> + /* Bound the walk by the number of PHIs in the header; a longer walk
> + means we revisited a PHI, i.e. the chain is cyclic. */
> + unsigned limit = 0;
> + for (gphi_iterator gsi = gsi_start_phis (loop->header); !gsi_end_p
> (gsi);
> + gsi_next (&gsi))
> + limit++;
> + edge latch = loop_latch_edge (loop);
> + auto_vec<gphi *, 4> members;
> + members.safe_push (phi);
> + gimple *def_stmt = chain;
> + while (gphi *p = dyn_cast <gphi *> (def_stmt))
> + {
> + if (gimple_bb (p) != loop->header
> + || p == phi
> + || limit-- == 0)
> + return false;
> + /* A PHI already classified as another kind of scalar cycle,
> + for example the induction PHI of the loop in PR 107302,
> + cannot be a member of a recurrence chain. */
> + stmt_vec_info p_info = loop_vinfo->lookup_stmt (p);
> + if (!p_info
> + || (STMT_VINFO_DEF_TYPE (p_info) != vect_unknown_def_type
> + && STMT_VINFO_DEF_TYPE (p_info)
> + != vect_first_order_recurrence))
> + return false;
> + members.safe_push (p);
> + tree ldef = PHI_ARG_DEF_FROM_EDGE (p, latch);
> + if (TREE_CODE (ldef) != SSA_NAME
> + || SSA_NAME_IS_DEFAULT_DEF (ldef)
> + || !flow_bb_inside_loop_p (loop,
> + gimple_bb (SSA_NAME_DEF_STMT (ldef))))
> + return false;
> + def_stmt = SSA_NAME_DEF_STMT (ldef);
> + }
> + /* Also reject a value cycle: if the terminating definition uses
> + any PHI of the chain the values feed back into themselves, as
> + in a reduction, rather than forming delayed recurrences. */
> + ssa_op_iter iter;
> + use_operand_p use_p;
> + FOR_EACH_SSA_USE_OPERAND (use_p, def_stmt, iter, SSA_OP_USE)
> + for (gphi *m : members)
> + if (USE_FROM_PTR (use_p) == gimple_phi_result (m))
> + return false;
> + return true;
> +}
> +
> /* Returns true if Phi is a first-order recurrence. A first-order
> recurrence is a non-reduction recurrence relation in which the value of
> the recurrence in the current loop iteration equals a value defined in
> @@ -307,7 +368,12 @@ vect_phi_first_order_recurrence_p (loop_vec_info
> loop_vinfo, class loop *loop,
> tree ldef = PHI_ARG_DEF_FROM_EDGE (phi, latch);
> if (TREE_CODE (ldef) != SSA_NAME
> || SSA_NAME_IS_DEFAULT_DEF (ldef)
> - || is_a <gphi *> (SSA_NAME_DEF_STMT (ldef))
> + /* The latch definition may itself be a header PHI when it forms
> + an acyclic chain of recurrences (PR tree-optimization/116338). */
> + || (is_a <gphi *> (SSA_NAME_DEF_STMT (ldef))
> + && !vect_recurrence_chain_p (loop_vinfo, loop, phi,
> + as_a <gphi *>
> + (SSA_NAME_DEF_STMT (ldef))))
> || !flow_bb_inside_loop_p (loop, gimple_bb (SSA_NAME_DEF_STMT
> (ldef))))
> return false;
>
> @@ -318,11 +384,25 @@ vect_phi_first_order_recurrence_p (loop_vec_info
> loop_vinfo, class loop *loop,
> imm_use_iterator imm_iter;
> use_operand_p use_p;
> FOR_EACH_IMM_USE_FAST (use_p, imm_iter, def)
> - if (!is_gimple_debug (USE_STMT (use_p))
> - && (SSA_NAME_DEF_STMT (ldef) == USE_STMT (use_p)
> - || !vect_stmt_dominates_stmt_p (SSA_NAME_DEF_STMT (ldef),
> - USE_STMT (use_p))))
> - return false;
> + {
> + gimple *use_stmt = USE_STMT (use_p);
> + if (is_gimple_debug (use_stmt))
> + continue;
> + /* A use as the latch argument of another header PHI is realized
> + by vectorizing that PHI as a chained first-order recurrence.
> + If that PHI does not qualify the loop fails to vectorize as a
> + whole, so this exemption cannot produce wrong code. */
> + if (gphi *q = dyn_cast <gphi *> (use_stmt))
> + {
> + if (gimple_bb (q) == loop->header
> + && PHI_ARG_DEF_FROM_EDGE (q, latch) == def)
> + continue;
> + }
> + if (SSA_NAME_DEF_STMT (ldef) == use_stmt
> + || !vect_stmt_dominates_stmt_p (SSA_NAME_DEF_STMT (ldef),
> + use_stmt))
> + return false;
> + }
>
> /* First-order recurrence autovectorization needs shuffle vector. */
> tree scalar_type = TREE_TYPE (def);
> @@ -8852,14 +8932,31 @@ vectorizable_recurr (loop_vec_info loop_vinfo,
> stmt_vec_info stmt_info,
> vectorized the latch definition. */
> edge le = loop_latch_edge (LOOP_VINFO_LOOP (loop_vinfo));
> gimple *latch_def = SSA_NAME_DEF_STMT (PHI_ARG_DEF_FROM_EDGE (phi, le));
> - gimple_stmt_iterator gsi2 = gsi_for_stmt (latch_def);
> - do
> - {
> + gimple_stmt_iterator gsi2;
> + if (is_a <gphi *> (latch_def))
> + {
> + /* For a chained recurrence the latch value is itself a recurrence
> + PHI. We cannot insert after a PHI; insert after the last
> + vectorized def of the latch child, its permute, which has been
> + emitted already since the SCC scheduler transforms children
> + first and an acyclic chain is a DAG. */
> + slp_tree latch_child = SLP_TREE_CHILDREN (slp_node)[le->dest_idx];
> + gcc_assert (SLP_TREE_VEC_DEFS (latch_child).length () == ncopies);
> + tree last = SLP_TREE_VEC_DEFS (latch_child)[ncopies - 1];
> + gsi2 = gsi_for_stmt (SSA_NAME_DEF_STMT (last));
> gsi_next (&gsi2);
> }
> - /* Skip inserted vectorized stmts for the latch definition. We have to
> - insert after those. */
> - while (gsi_stmt (gsi2) && gimple_uid (gsi_stmt (gsi2)) == 0);
> + else
> + {
> + gsi2 = gsi_for_stmt (latch_def);
> + do
> + {
> + gsi_next (&gsi2);
> + }
> + /* Skip inserted vectorized stmts for the latch definition. We
> + have to insert after those. */
> + while (gsi_stmt (gsi2) && gimple_uid (gsi_stmt (gsi2)) == 0);
> + }
>
> for (unsigned i = 0; i < ncopies; ++i)
> {
> --
> 2.53.0
>
>