On Sun, Jul 26, 2026 at 9:23 PM T G <[email protected]> wrote:
>
> Apologies, I sent this patch without the intended cover note. Adding
> the context here.
>
> Generative AI was used in preparing this patch. I found the missed
> optimization through a differential TSVC sweep against clang, diagnosed
> the recognition and transform failures, and ran all of the verification
> below myself.

Can you clarify whether the AI produced the patch?

> What the patch does: it teaches the vectorizer to handle chained
> first-order recurrences, the shape in TSVC s255:
>
>   for (i) { a[i] = (b[i] + x + y) * c; y = x; x = b[i]; }
>
> Here x carries a value from one iteration back and y from two, so y's
> latch definition is another header PHI.
> vect_phi_first_order_recurrence_p rejects that outright, which is why
> GCC leaves s255 scalar while clang vectorizes it.
>
> The patch adds vect_recurrence_chain_p, which accepts an acyclic chain
> of header PHIs terminating in a non-PHI definition inside the loop, and
> rejects cycles (two PHIs swapping values), chains containing a PHI
> already classified as another kind of scalar cycle (the induction PHI
> in the loop of PR 107302), and chains whose terminating definition
> feeds back into a chain member. On the transform side, the vectorized
> latch value of a chained recurrence is the permute emitted for the
> latch child, so the permute is inserted after that child's last
> vectorized def rather than after the scalar latch definition, which for
> a chain is a PHI we cannot insert after.
>
> Testing: bootstrapped and regtested on x86_64-linux-gnu at the
> 2026-06-10 trunk revision with no regressions;
> gcc.dg/vect/tsvc/vect-tsvc-s255.c and vect-tsvc-s292.c newly vectorize.
> The patch rebases cleanly onto current trunk; on the rebased tree I
> re-verified on riscv64 with a cross-compiler under QEMU, where the s255
> chain, a depth-three chain and a plain single recurrence all produce
> output bit-identical to the scalar reference with slide counts scaling
> with chain depth, the swap case compiles to scalar code, and PR 107302
> no longer ICEs. I have not re-run a full bootstrap and regtest on
> current trunk.
>
> Feedback on the approach is welcome, in particular on whether the chain
> walk belongs in vect_phi_first_order_recurrence_p or somewhere else.

I think it belongs there.  But given that PHI nodes are un-ordered I wonder
if you can rely on vect_first_order_recurrence - that's why you also allow
unknown_def_type, right?

You'll analyze each PHI entry to the recurrence chain, so quite some redundant
work.  I wonder if we can classify the whole chain as
vect_first_order_recurrence
and restrict to analyzing PHIs with the latch def not being a PHI via
vect_phi_first_order_recurrence_p and walk "backwards" from there?

Thanks,
Richard.

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

Reply via email to