On 28/10/2026 14:36, Tamar Christina wrote:
Hi Alfie,

-----Original Message-----
From: Alfie Richards <[email protected]>
Sent: 23 October 2026 13:34
To: [email protected]
Cc: Richard Biener <[email protected]>; Tamar Christina
<[email protected]>; Robin Dapp <[email protected]>; Richard
Sandiford <[email protected]>; [email protected];
[email protected]; Alfie Richards <[email protected]>
Subject: [PATCH 2/2] vect: Fix final iterations counts and latch values for
peeled early break [PR 127805]

After recent changes to how we accomplish peeling for early break the
final iv updates and the niters calculations were wrong.

This rewrites and simplifies some of that logic fixing the issue.

gcc/ChangeLog:

        PR tree-optimization/127805

        * tree-vect-loop-manip.cc (vect_set_loop_controls_directly):
        Remove niter catch adjustment.
        (vect_set_loop_condition_partial_vectors): Update final_iv
        calculation.
        (vect_set_loop_condition_partial_vectors_avx512): Likewise.
        (vect_set_loop_condition_normal): Change final_iv calculation
        to properly take into account the different loop forms for
        peeled early break.
        (vect_gen_vector_loop_niters): Update to account for do-loop
        transformation when peeling for early break.
        (vect_gen_vector_loop_niters_mult_vf): Remove unused early
        break restart.
        (vect_do_peeling): Force skip vectors true for all peeled
        early break cases, and don't overrule final_iv when peeling
        for early break (instead only when uncounted).
        * tree-vect-loop.cc (vect_transform_loop): Update compile time
        known niters case for early break possibility.


Sorry for the delay in reviewing. I had to go through the examples to
figure out what this was doing.

I largely agree with the patch, as you mentioned, by moving the header
rotation to the loop controls it now means the loop manip code shouldn't
do it itself anymore.

Which is why you removed the niters_vector_mult_vf adjustment.  However
you left the adjustments to vect_update_ivs_after_vectorizer and instead
added a specific fixup using skip_vector, and you then fix it up in  the
control code.  This is why you still have the odd fixup code here and there.

Ah interesting, my understanding of this was that I was removing the "restart 1 VL back" from the cases where we exit due to the number of iterations latch.

But I thought we would still need that for the early exits (which I believe are handled by vect_update_ivs_after_vectorizer), where we exit because of an early break. As in those situations we would not know what element had exited (until your other patch series I suppose).

Is that not the case? I suppose not as otherwise it wouldn't be specifically for PEELED...

Really interesting, thanks for the review!

But if we move the iteration adjustment code to loop control we don't need
any of the special handling for PEELED loops anymore in loop manip.

This means that indeed LOOP_VINFO_NITERS_UNCOUNTED_P stays the only
actual special case.  So the entire thing can be much simpler.

I've tested a patch that takes this cleanup started here further and will post
that in a bit since I think we can now avoid treating PEELED special here,
and in turn also cleanup the various loop_condition_* functions.

Look forward to seeing the patch!

Thanks,
Alfie>
Thanks,
Tamar

gcc/testsuite/ChangeLog:

        * vect/vect-early-break_147-pr128028.c: New test.

-- >8 --

Hi all,

Sorry for the delay on this fix, it really sent me on a whole journey,
but I learnt a lot! For instance I learnt my understanding of the
early break peeling and how it interacts with niters and final_iv was
completely off :)

I'm pretty sure this is now correct, and cleans up some of the weirdness
in the code.

Unfortunately we stil have pretty poor test coverage for early break and
especially early-break peeled.  However the gmp test suite serves as a
pretty excellent test of it in my expereince now!

Bootstrapped and regr-tested on x86_64-linux-gnu and aarch64-linux-gnu.

GMP testsuite run with a bunch of different vectorization settings
for x86_64 and aarch64.

Okay for master?
Alfie Richards

---
  .../vect/vect-early-break_147-pr128028.c      | 117 ++++++++++++++++++
  gcc/tree-vect-loop-manip.cc                   |  88 +++++--------
  gcc/tree-vect-loop.cc                         |   5 +-
  3 files changed, 151 insertions(+), 59 deletions(-)
  create mode 100644 gcc/testsuite/gcc.dg/vect/vect-early-break_147-
pr128028.c

diff --git a/gcc/testsuite/gcc.dg/vect/vect-early-break_147-pr128028.c
b/gcc/testsuite/gcc.dg/vect/vect-early-break_147-pr128028.c
new file mode 100644
index 00000000000..c7b1c3ad4b2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/vect/vect-early-break_147-pr128028.c
@@ -0,0 +1,117 @@
+/* PR tree-optimization/126778 */
+/* { dg-do run } */
+/* { dg-add-options vect_early_break } */
+/* { dg-require-effective-target vect_early_break_hw } */
+/* { dg-require-effective-target vect_int } */
+
+#define N 64
+
+/* The counting exit is tested before the early exit, so the loop body runs
+   one time less than the loop header.  The vector loop must not process a
+   lane for the iteration that only tests the counter, as that lane reads the
+   element next to the arrays.  */
+
+__attribute__((optimize("no-tree-vectorize")))
+__attribute__ ((noipa)) int
+differs (const long *a, const long *b, unsigned long n)
+{
+  while (n != 0)
+    {
+      n--;
+      if (a[n] != b[n])
+       return 1;
+    }
+  return 0;
+}
+__attribute__((optimize("no-tree-vectorize")))
+__attribute__ ((noipa)) int
+differs_signed (const long *a, const long *b, long n)
+{
+  if (n < 0)
+    return 2;
+  while (--n >= 0)
+    {
+      if (a[n] != b[n])
+       return 1;
+    }
+  return 0;
+}
+
+__attribute__ ((noipa)) int
+differs_up (const long *a, const long *b, unsigned long n)
+{
+  unsigned long i = 0;
+  while (i != n)
+    {
+      unsigned long j = i;
+      i++;
+      if (a[j] != b[j])
+       return 1;
+    }
+  return 0;
+}
+
+/* The store before the early exit must not write the extra lane and must run
+   once for every element.  */
+
+__attribute__((optimize("no-tree-vectorize")))
+__attribute__ ((noipa)) int
+bump (const long *restrict a, const long *restrict b, long *restrict c,
+      unsigned long n)
+{
+  while (n != 0)
+    {
+      n--;
+      c[n] += 1;
+      if (a[n] != b[n])
+       return 1;
+    }
+  return 0;
+}
+static long abuf[N + 16] __attribute__ ((aligned (64)));
+static long bbuf[N + 16] __attribute__ ((aligned (64)));
+static long cbuf[N + 16] __attribute__ ((aligned (64)));
+
+__attribute__((optimize("no-tree-vectorize")))
+int
+main (void)
+{
+
+  for (unsigned long n = 0; n <= N; n++)
+    for (int off = 0; off < 8; off++)
+      {
+       /* Start the arrays at every alignment of a 64 byte vector and give
+          the elements around them different values.  */
+       long *a = abuf + 8 + off;
+       long *b = bbuf + 8 + off;
+       long *c = cbuf + 8 + off;
+       for (unsigned long i = 0; i < n; i++)
+         a[i] = b[i] = c[i] = i * 3 + 1;
+       a[-1] = 1;
+       b[-1] = 2;
+       c[-1] = 5;
+       a[n] = 3;
+       b[n] = 4;
+       c[n] = 6;
+
+       if (differs (a, b, n) != 0 || differs_signed (a, b, n) != 0
+           || differs_up (a, b, n) != 0)
+         __builtin_abort ();
+
+       if (bump (a, b, c, n) != 0 || c[-1] != 5 || c[n] != 6)
+         __builtin_abort ();
+       for (unsigned long i = 0; i < n; i++)
+         if (c[i] != i * 3 + 2)
+           __builtin_abort ();
+
+       if (n > 0)
+         {
+           b[n / 2]++;
+           if (differs (a, b, n) != 1 || differs_signed (a, b, n) != 1
+               || differs_up (a, b, n) != 1)
+             __builtin_abort ();
+         }
+      }
+
+  return 0;
+}
diff --git a/gcc/tree-vect-loop-manip.cc b/gcc/tree-vect-loop-manip.cc
index 4dfef69de87..1154b6edf50 100644
--- a/gcc/tree-vect-loop-manip.cc
+++ b/gcc/tree-vect-loop-manip.cc
@@ -789,13 +789,6 @@ vect_set_loop_controls_directly (class loop *loop,
loop_vec_info loop_vinfo,
                                          bias_tree);
        }

-      /* A do-while loop always executes the body once, as such the limit
-        the end counter should be lowered by 1 iteration.  */
-      if (LOOP_VINFO_EARLY_BREAKS_VECT_PEELED (loop_vinfo))
-       this_test_limit = gimple_build (preheader_seq, MINUS_EXPR,
-                                       compare_type, this_test_limit,
-                                       build_one_cst (compare_type));
-
        /* Create the initial control.  First include all items that
         are within the loop limit.  */
        tree init_ctrl = NULL_TREE;
@@ -1097,17 +1090,7 @@ vect_set_loop_condition_partial_vectors (class
loop *loop, edge exit_edge,
    if (final_iv)
      {
        gassign *assign;
-      /* If vectorizing an inverted early break loop we have to restart the
-        scalar loop at niters - vf.  This matches what we do in
-        vect_gen_vector_loop_niters_mult_vf for non-masked loops.  */
-      if (LOOP_VINFO_EARLY_BREAKS_VECT_PEELED (loop_vinfo))
-       {
-         tree ftype = TREE_TYPE (orig_niters);
-         tree vf = build_int_cst (ftype, LOOP_VINFO_VECT_FACTOR
(loop_vinfo));
-         assign = gimple_build_assign (final_iv, MINUS_EXPR, orig_niters, vf);
-       }
-       else
-       assign = gimple_build_assign (final_iv, orig_niters);
+      assign = gimple_build_assign (final_iv, orig_niters);
        gsi_insert_on_edge_immediate (LOOP_VINFO_MAIN_EXIT (loop_vinfo),
assign);
      }

@@ -1354,19 +1337,8 @@ vect_set_loop_condition_partial_vectors_avx512
(class loop *loop,

    if (final_iv)
      {
-      gassign *assign;
-      /* If vectorizing an inverted early break loop we have to restart the
-        scalar loop at niters - vf.  This matches what we do in
-        vect_gen_vector_loop_niters_mult_vf for non-masked loops.  */
-      if (LOOP_VINFO_EARLY_BREAKS_VECT_PEELED (loop_vinfo))
-       {
-         tree ftype = TREE_TYPE (orig_niters);
-         tree vf = build_int_cst (ftype, LOOP_VINFO_VECT_FACTOR
(loop_vinfo));
-         assign = gimple_build_assign (final_iv, MINUS_EXPR, orig_niters, vf);
-       }
-       else
-       assign = gimple_build_assign (final_iv, orig_niters);
-       gsi_insert_on_edge_immediate (LOOP_VINFO_MAIN_EXIT (loop_vinfo),
assign);
+      gassign *assign = gimple_build_assign (final_iv, orig_niters);
+      gsi_insert_on_edge_immediate (LOOP_VINFO_MAIN_EXIT (loop_vinfo),
assign);
      }

    return cond_stmt;
@@ -1499,29 +1471,27 @@ vect_set_loop_condition_normal (loop_vec_info
loop_vinfo, edge exit_edge,

    if (final_iv)
      {
+      /* If we are using step=1 catch=niters/vf then we should be using the
+         logic in vect_gen_vector_loop_niters_mult_vf for the final iv and this
+         branch should never be taken.  */
+      gcc_assert (!integer_onep (step));
+
        gassign *assign;
-      tree phi_dest
-       = integer_zerop (init) ? final_iv : copy_ssa_name (indx_after_incr);
+      tree iv_end = LOOP_VINFO_EARLY_BREAKS_VECT_PEELED (loop_vinfo)
+                     ? indx_before_incr
+                     : indx_after_incr;
+
+      tree phi_dest = integer_zerop (init) ? final_iv : copy_ssa_name (iv_end);
+
        /* Make sure to maintain LC SSA form here and elide the subtraction
         if the value is zero.  */
        gphi *phi = create_phi_node (phi_dest, exit_edge->dest);
-      add_phi_arg (phi, indx_after_incr, exit_edge, UNKNOWN_LOCATION);
+      add_phi_arg (phi, iv_end, exit_edge, UNKNOWN_LOCATION);
+
        if (!integer_zerop (init))
        {
-         /* If vectorizing an inverted early break loop we have to restart the
-            scalar loop at niters - vf.  This matches what we do in
-            vect_gen_vector_loop_niters_mult_vf for non-masked loops.  */
-         if (LOOP_VINFO_EARLY_BREAKS_VECT_PEELED (loop_vinfo))
-           {
-             tree ftype = TREE_TYPE (final_iv);
-             tree vf
-               = build_int_cst (ftype, LOOP_VINFO_VECT_FACTOR
(loop_vinfo));
-             assign = gimple_build_assign (final_iv, MINUS_EXPR, init, vf);
-           }
-         else
-           assign = gimple_build_assign (final_iv, MINUS_EXPR, phi_dest, init);
-         gimple_stmt_iterator gsi
-           = gsi_after_labels (LOOP_VINFO_MAIN_EXIT (loop_vinfo)->dest);
+         gimple_stmt_iterator gsi = gsi_after_labels (LOOP_VINFO_MAIN_EXIT
(loop_vinfo)->dest);
+         assign = gimple_build_assign (final_iv, MINUS_EXPR, phi_dest, init);
          gsi_insert_before (&gsi, assign, GSI_SAME_STMT);
        }
      }
@@ -3110,8 +3080,12 @@ vect_gen_vector_loop_niters (loop_vec_info
loop_vinfo, tree niters,

    /* If epilogue loop is required because of data accesses with gaps, we
       subtract one iteration from the total number of iterations here for
-     correct calculation of RATIO.  */
-  if (LOOP_VINFO_PEELING_FOR_GAPS (loop_vinfo))
+     correct calculation of RATIO.
+
+     Alternatively, if we are peeled for early breaks, then loop niters
+     is 1 too large as we convert the loop into a do-loop.  */
+  if (LOOP_VINFO_PEELING_FOR_GAPS (loop_vinfo)
+      || LOOP_VINFO_EARLY_BREAKS_VECT_PEELED (loop_vinfo))
      {
        ni_minus_gap = fold_build2 (MINUS_EXPR, type, niters,
                                  build_one_cst (type));
@@ -3250,12 +3224,6 @@ vect_gen_vector_loop_niters_mult_vf
(loop_vec_info loop_vinfo,
    tree niters_vector_mult_vf = fold_build2 (MULT_EXPR, type,
                                            niters_vector, tree_vf);

-  /* If we've peeled a vector iteration then subtract one full vector
-     iteration.  */
-  if (LOOP_VINFO_EARLY_BREAKS_VECT_PEELED (loop_vinfo))
-    niters_vector_mult_vf = fold_build2 (MINUS_EXPR, type,
-                                        niters_vector_mult_vf, tree_vf);
-
    if (!is_gimple_val (niters_vector_mult_vf))
      {
        tree var = create_tmp_var (type, "niters_vector_mult_vf");
@@ -3633,7 +3601,8 @@ vect_do_peeling (loop_vec_info loop_vinfo, tree
niters, tree nitersm1,
                   ? maybe_lt (LOOP_VINFO_INT_NITERS (loop_vinfo),
                               bound_prolog + bound_epilog)
                   : (!LOOP_VINFO_USE_VERSIONING_WITHOUT_PEELING
(loop_vinfo)
-                     || vect_epilogues));
+                    || vect_epilogues
+                    || LOOP_VINFO_EARLY_BREAKS_VECT_PEELED
(loop_vinfo)));

    /* Epilog loop must be executed if the number of iterations for epilog
       loop is known at compile time, otherwise we need to add a check at
@@ -4017,7 +3986,10 @@ vect_do_peeling (loop_vec_info loop_vinfo, tree
niters, tree nitersm1,
            vect_update_ivs_after_vectorizer (loop_vinfo, tmp_niters_vf,
                                              update_e, true);
          }
-         if (LOOP_VINFO_EARLY_BREAKS_VECT_PEELED (loop_vinfo))
+         /* If the loop is uncounted we do not have a loop control to set
+            the final niters for the main exit. Therefore, we need to use
+            the early break exit niters for all exits.  */
+         if (LOOP_VINFO_NITERS_UNCOUNTED_P (loop_vinfo))
            vector_iters_vf = tmp_niters_vf;

          LOOP_VINFO_EARLY_BRK_NITERS_VAR (loop_vinfo) = tmp_niters_vf;
diff --git a/gcc/tree-vect-loop.cc b/gcc/tree-vect-loop.cc
index 7ed3f58bf23..9feea2d2e9c 100644
--- a/gcc/tree-vect-loop.cc
+++ b/gcc/tree-vect-loop.cc
@@ -11529,9 +11529,12 @@ vect_transform_loop (loop_vec_info
loop_vinfo, gimple *loop_vectorized_call)
          && !LOOP_VINFO_USING_PARTIAL_VECTORS_P (loop_vinfo)
          && known_eq (lowest_vf, vf))
        {
+         /* Make sure to use niters here, as if we have peeled for
+            early break then this will be updated to remove the extra
+            iteration for the latch.  */
          niters_vector
            = build_int_cst (TREE_TYPE (LOOP_VINFO_NITERS (loop_vinfo)),
-                            LOOP_VINFO_INT_NITERS (loop_vinfo) /
lowest_vf);
+                            TREE_INT_CST_LOW (niters) / lowest_vf);
          step_vector = build_one_cst (TREE_TYPE (niters));
        }
        else if (vect_use_loop_mask_for_alignment_p (loop_vinfo))
--
2.55.0


Reply via email to