On Wed, 29 Jul 2026, Alfie Richards wrote:
> On 29/07/2026 14:46, Tamar Christina wrote:
> > The example
> >
> > char b[100];
> > int c(int a) {
> > unsigned d = 0;
> > for (; d < a; ++d)
> > {
> > if (b[0] + b[d + 1])
> > return 0;
> > }
> > return 1;
> > }
> >
> > when compiled with a partial masked target, e.g. -march=armv9-a -O3
> > generates
> >
> > ptrue p6.b, all
> > whilelo p15.s, wzr, w3
> > b .L5
> > .L11:
> > ld1b z31.s, p7/z, [x5, x1]
> > add z31.h, z31.h, z30.h
> > uxth z31.s, p6/m, z31.s
> > cmpne p7.s, p7/z, z31.s, #0
> > b.any .L4
> > .L5:
> > mov x2, x1
> > mov p7.b, p15.b
> > incw x1
> > whilelo p15.s, w1, w3
> > b.any .L11
> > mov w1, w2
> > add x3, x4, :lo12:.LANCHOR0
> > b .L8
> >
> > Warped BB rotation due to the incorrect profiles aside (different problem)
> > this loop will exit one iteration early since the IV check (d < a) is
> > checked
> > as (++d < a).
> >
> > This means that when branched to the scalar code we do 2 vector iterations
> > in
> > the worst case rather than 1.
> >
> > This is due to the fact that the loop is essentially a do-while loop coming
> > into
> > the vectorizer. We detect this loop as a
> > LOOP_VINFO_EARLY_BREAKS_VECT_PEELED
> > because it's the same form as a normal while-do loop but where we picked a
> > different exit than the loop latch exit.
> >
> > Because of the versioning and other checks in the preheader we know that if
> > you
> > reach the loop body you'll always do at least 1 iteration of the body (but
> > possibly never the latch). i.e. the d != 0 check is always true should you
> > get
> > to the end of the pre-header.
> >
> > The codegen reflects this but because of the early IV exit check we never
> > get to
> > the body.
> >
> > Alfie's patch to fix this changed the `d++ < a` check to the correct (d < a)
> > check, but due to how the control flow becomes we end up doing
> >
> > mask_1 = PHI <mask_0, next_mask>
> > if (mask_1)
> >
> > and causes us to retest the mask, even though the mask generation which
> > happens
> > in the pre-header has guaranteed that for the first iteration it's
> > non-empty.
> >
> > So we lose information because the check in the header is used by both the
> > value
> > from the pre-header and the latch on loop back.
> >
> > Instead of doing that this patch just changed the control flow to reflect
> > that
> > we can always execute the body at least once. In effect it moves the IV
> > latch
> > check to the end.
> >
> > This generates:
> >
> > ptrue p6.b, all
> > whilelo p7.s, wzr, w4
> > b .L4
> > .p2align 2,,3
> > .L11:
> > whilelo p7.s, w1, w4
> > b.none .L15
> > .L4:
> > mov x2, x1
> > incw x1
> > ld1b z31.s, p7/z, [x5, x1]
> > add z31.h, z31.h, z30.h
> > uxth z31.s, p6/m, z31.s
> > cmpne p7.s, p7/z, z31.s, #0
> > b.none .L11
> > .L7:
> > mov w0, 0
> > ret
> >
> > and when the profiles are fixed (PR117790) we'd get
> >
> > ptrue p6.b, all
> > whilelo p7.s, wzr, w5
> > .p2align 5,,15
> > .L4:
> > mov x2, x1
> > incw x1
> > ld1b z31.s, p7/z, [x3, x1]
> > add z31.h, z31.h, z30.h
> > uxth z31.s, p6/m, z31.s
> > cmpne p7.s, p7/z, z31.s, #0
> > b.any .L7
> > whilelo p7.s, w1, w5
> > b.any .L4
> >
> > Bootstrapped Regtested on aarch64-none-linux-gnu,
> > arm-none-linux-gnueabihf, x86_64-pc-linux-gnu
> > -m32, -m64 and no issues.
> >
> > Any comments?
>
> FWIW LGTM, fixes the issues I was having and markedly improved code gen for
> the cases I was looking at.
>
> There are some minor extra changes needed for FFR but I will submit those with
> FFR series and related changes once I finish squashing bugs.
Sorry for chiming in late - so how can this be valid without actually
peeling up to the IV exit, that is, actually performing loop-ch with
the "correct" exit/latch in mind? And why would that be not
valid for vect_partial_vectors_avx512? IMO the terminology in the
added comments is confusing. We have LOOP_VINFO_EARLY_BREAKS_VECT_PEELED
also for two early exits and the order
if (early)
if (IV-exit)
if (early)
no?
Richard.
> >
> > Thanks,
> > Tamar
> >
> > gcc/ChangeLog:
> >
> > * tree-vect-loop-manip.cc (vect_use_loop_latch_condition_p): New.
> > (vect_set_loop_condition_partial_vectors, vect_set_loop_condition): Use
> > it to rewrite latch and condition.
> > (vect_set_loop_controls_directly): Adjust IV for do-loop conversion.
> >
> > gcc/testsuite/ChangeLog:
> >
> > * gcc.target/aarch64/sve/peeled.c: New test.
> > * gcc.target/aarch64/sve/peeled1.c: New test.
> > * gcc.target/aarch64/sve/peeled1_run.c: New test.
> > * gcc.target/aarch64/sve/peeled2.c: New test.
> > * gcc.target/aarch64/sve/peeled2_run.c: New test.
> > * gcc.target/aarch64/sve/peeled_run.c: New test.
> >
> > ---
> > diff --git a/gcc/testsuite/gcc.target/aarch64/sve/peeled.c
> > b/gcc/testsuite/gcc.target/aarch64/sve/peeled.c
> > new file mode 100644
> > index
> > 0000000000000000000000000000000000000000..f40ffc8f0bcde40aeb754887ed6d5047e9806bc7
> > --- /dev/null
> > +++ b/gcc/testsuite/gcc.target/aarch64/sve/peeled.c
> > @@ -0,0 +1,20 @@
> > +/* { dg-do compile } */
> > +/* { dg-options "-O3 -mautovec-preference=sve-only
> > -msve-vector-bits=scalable" } */
> > +
> > +char b[100];
> > +
> > +int __attribute__ ((noipa))
> > +c (int a)
> > +{
> > + unsigned d = 0;
> > + for (; d < a; ++d)
> > + {
> > + if (b[0] + b[d + 1])
> > + return 0;
> > + }
> > + return 1;
> > +}
> > +
> > +/* { dg-final { scan-assembler-times {\twhilelo\t} 2 } } */
> > +/* { dg-final { scan-assembler-times {\tptest\t} 0 } } */
> > +/* { dg-final { scan-assembler {\tld1b\tz[0-9]+\.s, p[0-9]+/z,} } } */
> > diff --git a/gcc/testsuite/gcc.target/aarch64/sve/peeled1.c
> > b/gcc/testsuite/gcc.target/aarch64/sve/peeled1.c
> > new file mode 100644
> > index
> > 0000000000000000000000000000000000000000..92e5bca44c48cefa20e829510a8914e4852145ed
> > --- /dev/null
> > +++ b/gcc/testsuite/gcc.target/aarch64/sve/peeled1.c
> > @@ -0,0 +1,24 @@
> > +/* { dg-do compile } */
> > +/* { dg-options "-O3 -mautovec-preference=sve-only
> > -msve-vector-bits=scalable" } */
> > +
> > +char b[100];
> > +char e[100];
> > +
> > +int __attribute__ ((noipa))
> > +c (int a)
> > +{
> > + unsigned d = 0;
> > + for (; d < a; ++d)
> > + {
> > + if (b[0] + b[d + 1])
> > + return 0;
> > +
> > + if (e[0] + e[d + 1])
> > + return 0;
> > + }
> > + return 1;
> > +}
> > +
> > +/* { dg-final { scan-assembler-times {\twhilelo\t} 2 } } */
> > +/* { dg-final { scan-assembler-times {\tptest\t} 2 } } */
> > +/* { dg-final { scan-assembler-times {\tld1b\tz[0-9]+\.h, p[0-9]+/z,} 2 } }
> > */
> > diff --git a/gcc/testsuite/gcc.target/aarch64/sve/peeled1_run.c
> > b/gcc/testsuite/gcc.target/aarch64/sve/peeled1_run.c
> > new file mode 100644
> > index
> > 0000000000000000000000000000000000000000..8ec95a187ab0bce43d7bcf9e8d83ea8ddae7ccf5
> > --- /dev/null
> > +++ b/gcc/testsuite/gcc.target/aarch64/sve/peeled1_run.c
> > @@ -0,0 +1,34 @@
> > +/* { dg-do run { target aarch64_sve_hw } } */
> > +/* { dg-options "-O3 -mautovec-preference=sve-only
> > -msve-vector-bits=scalable" } */
> > +
> > +#include "peeled1.c"
> > +
> > +static void
> > +clear_arrays (void)
> > +{
> > + for (int i = 0; i < 100; ++i)
> > + {
> > + b[i] = 0;
> > + e[i] = 0;
> > + }
> > +}
> > +
> > +int
> > +main (void)
> > +{
> > + clear_arrays ();
> > + if (c (99) != 1)
> > + __builtin_abort ();
> > +
> > + clear_arrays ();
> > + b[37] = 1;
> > + if (c (99) != 0)
> > + __builtin_abort ();
> > +
> > + clear_arrays ();
> > + e[45] = 1;
> > + if (c (99) != 0)
> > + __builtin_abort ();
> > +
> > + return 0;
> > +}
> > diff --git a/gcc/testsuite/gcc.target/aarch64/sve/peeled2.c
> > b/gcc/testsuite/gcc.target/aarch64/sve/peeled2.c
> > new file mode 100644
> > index
> > 0000000000000000000000000000000000000000..c29dab4b5536d318b45a0b70c407ef4d9fa32ed5
> > --- /dev/null
> > +++ b/gcc/testsuite/gcc.target/aarch64/sve/peeled2.c
> > @@ -0,0 +1,22 @@
> > +/* { dg-do compile } */
> > +/* { dg-options "-O3 -mautovec-preference=sve-only
> > -msve-vector-bits=scalable" } */
> > +
> > +char b[100];
> > +
> > +int __attribute__ ((noipa))
> > +c (int a)
> > +{
> > + unsigned d = 0;
> > + do
> > + {
> > + if (b[0] + b[d + 1])
> > + return 0;
> > + d++;
> > + }
> > + while (__builtin_expect (d < a, 1));
> > + return 1;
> > +}
> > +
> > +/* { dg-final { scan-assembler-times {\twhilelo\t} 2 } } */
> > +/* { dg-final { scan-assembler-times {\tptest\t} 0 } } */
> > +/* { dg-final { scan-assembler {\tld1b\tz[0-9]+\.s, p[0-9]+/z,} } } */
> > diff --git a/gcc/testsuite/gcc.target/aarch64/sve/peeled2_run.c
> > b/gcc/testsuite/gcc.target/aarch64/sve/peeled2_run.c
> > new file mode 100644
> > index
> > 0000000000000000000000000000000000000000..2075b0803eb0cab43932bc9e60f80f0822ffa83b
> > --- /dev/null
> > +++ b/gcc/testsuite/gcc.target/aarch64/sve/peeled2_run.c
> > @@ -0,0 +1,26 @@
> > +/* { dg-do run { target aarch64_sve_hw } } */
> > +/* { dg-options "-O3 -mautovec-preference=sve-only
> > -msve-vector-bits=scalable" } */
> > +
> > +#include "peeled2.c"
> > +
> > +static void
> > +clear_b (void)
> > +{
> > + for (int i = 0; i < 100; ++i)
> > + b[i] = 0;
> > +}
> > +
> > +int
> > +main (void)
> > +{
> > + clear_b ();
> > + if (c (99) != 1)
> > + __builtin_abort ();
> > +
> > + clear_b ();
> > + b[37] = 1;
> > + if (c (99) != 0)
> > + __builtin_abort ();
> > +
> > + return 0;
> > +}
> > diff --git a/gcc/testsuite/gcc.target/aarch64/sve/peeled3.c
> > b/gcc/testsuite/gcc.target/aarch64/sve/peeled3.c
> > new file mode 100644
> > index
> > 0000000000000000000000000000000000000000..8d82828b770d16868c99930c115571c9128c3fa5
> > --- /dev/null
> > +++ b/gcc/testsuite/gcc.target/aarch64/sve/peeled3.c
> > @@ -0,0 +1,21 @@
> > +/* { dg-do compile } */
> > +/* { dg-options "-O3 -mautovec-preference=sve-only
> > -msve-vector-bits=scalable -fdump-tree-vect-details" } */
> > +
> > +int __attribute__ ((noipa))
> > +c (int *restrict x, int *restrict y, int n)
> > +{
> > + unsigned d = 5;
> > + for (; __builtin_expect (d < n, 1); ++d)
> > + {
> > + if (x[d] != y[d])
> > + return 0;
> > + }
> > + return 1;
> > +}
> > +
> > +/* { dg-final { scan-tree-dump "Both peeling and versioning will be
> > applied" "vect" } } */
> > +/* { dg-final { scan-tree-dump "misalignment for fully-masked loop" "vect"
> > } } */
> > +/* { dg-final { scan-assembler {\tsub\tw[0-9]+, w[0-9]+, #6} } } */
> > +/* { dg-final { scan-assembler-times {\twhilelo\t} 3 } } */
> > +/* { dg-final { scan-assembler-times {\tptest\t} 0 } } */
> > +/* { dg-final { scan-assembler-times {\tld1w\t} 2 } } */
> > diff --git a/gcc/testsuite/gcc.target/aarch64/sve/peeled3_run.c
> > b/gcc/testsuite/gcc.target/aarch64/sve/peeled3_run.c
> > new file mode 100644
> > index
> > 0000000000000000000000000000000000000000..83015b3bfe1861e95171f59f4a8ddd679b9583d2
> > --- /dev/null
> > +++ b/gcc/testsuite/gcc.target/aarch64/sve/peeled3_run.c
> > @@ -0,0 +1,35 @@
> > +/* { dg-do run { target aarch64_sve_hw } } */
> > +/* { dg-options "-O3 -mautovec-preference=sve-only
> > -msve-vector-bits=scalable" } */
> > +
> > +#include "peeled3.c"
> > +
> > +#define N 128
> > +
> > +int a[N] __attribute__ ((aligned (64)));
> > +int b[N] __attribute__ ((aligned (64)));
> > +
> > +static void
> > +clear_arrays (void)
> > +{
> > + for (int i = 0; i < N; ++i)
> > + {
> > + a[i] = 0;
> > + b[i] = 0;
> > + }
> > +}
> > +
> > +int
> > +main (void)
> > +{
> > + clear_arrays ();
> > + b[16] = 1;
> > + if (c (a, b, 16) != 1)
> > + __builtin_abort ();
> > +
> > + clear_arrays ();
> > + b[15] = 1;
> > + if (c (a, b, 16) != 0)
> > + __builtin_abort ();
> > +
> > + return 0;
> > +}
> > diff --git a/gcc/testsuite/gcc.target/aarch64/sve/peeled_run.c
> > b/gcc/testsuite/gcc.target/aarch64/sve/peeled_run.c
> > new file mode 100644
> > index
> > 0000000000000000000000000000000000000000..7e289f3c0c29fda37f3138f3655e79389d76a2d0
> > --- /dev/null
> > +++ b/gcc/testsuite/gcc.target/aarch64/sve/peeled_run.c
> > @@ -0,0 +1,26 @@
> > +/* { dg-do run { target aarch64_sve_hw } } */
> > +/* { dg-options "-O3 -mautovec-preference=sve-only
> > -msve-vector-bits=scalable" } */
> > +
> > +#include "peeled.c"
> > +
> > +static void
> > +clear_b (void)
> > +{
> > + for (int i = 0; i < 100; ++i)
> > + b[i] = 0;
> > +}
> > +
> > +int
> > +main (void)
> > +{
> > + clear_b ();
> > + if (c (99) != 1)
> > + __builtin_abort ();
> > +
> > + clear_b ();
> > + b[37] = 1;
> > + if (c (99) != 0)
> > + __builtin_abort ();
> > +
> > + return 0;
> > +}
> > diff --git a/gcc/tree-vect-loop-manip.cc b/gcc/tree-vect-loop-manip.cc
> > index
> > ec9dec1b7135916ea69fd9eabfdfe1645d6ea11b..96c10340efd94e7db503af208ca21cf3fd807dcd
> > 100644
> > --- a/gcc/tree-vect-loop-manip.cc
> > +++ b/gcc/tree-vect-loop-manip.cc
> > @@ -464,6 +464,19 @@ vect_iv_increment_position (edge loop_exit,
> > @@ gimple_stmt_iterator *bsi,
> > *insert_after = false;
> > }
> >
> > +/* If this is a loop where the latch condition should be rewritten to
> > reflect
> > + a control flow change from a while-do to a do-while loop. */
> > +
> > +static bool
> > +vect_use_loop_latch_condition_p (loop_vec_info loop_vinfo)
> > +{
> > + return (loop_vinfo
> > + && LOOP_VINFO_EARLY_BREAKS_VECT_PEELED (loop_vinfo)
> > + && LOOP_VINFO_USING_PARTIAL_VECTORS_P (loop_vinfo)
> > + && (LOOP_VINFO_PARTIAL_VECTORS_STYLE (loop_vinfo)
> > + != vect_partial_vectors_avx512));
> > +}
> > +
> > /* Helper for vect_set_loop_condition_partial_vectors. Generate
> > definitions
> > for all the rgroup controls in RGC and return a control that is nonzero
> > when the loop needs to iterate. Add any new preheader statements to
> > @@ -744,6 +757,13 @@ 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 (vect_use_loop_latch_condition_p (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;
> > @@ -968,7 +988,62 @@ vect_set_loop_condition_partial_vectors (class loop
> > @@ *loop, edge exit_edge,
> > cond_stmt
> > = gimple_build_cond (code, test_ctrl, zero_ctrl, NULL_TREE, NULL_TREE);
> > }
> > - gsi_insert_before (&loop_cond_gsi, cond_stmt, GSI_SAME_STMT);
> > + edge latch_exit_edge = NULL;
> > + /* Convert the loop into a do-while form similar to what ch_vect would
> > have
> > + done. We know that after the checks and peeling that we have at least
> > one
> > + iteration to perform of the loop because the loop is PEELED. A PEELED
> > loop
> > + has the increment exit before the early ones, i.e. it's a do-while
> > loop but
> > + if we materialize the IV edge in that place we are essentially
> > checking one
> > + iteration ahead so we exit early. Instead when using masks and the
> > loop
> > + is PEELED we remove the existing loop latch and make it a fall through
> > + edge and place the latch back to the end of the loop. So effectively
> > + transform:
> > +
> > + header
> > + |
> > + latch
> > + |
> > + body
> > + |
> > + branch to header
> > +
> > + into
> > +
> > + header
> > + |
> > + body
> > + |
> > + newlatch
> > + |
> > + branch to header
> > +
> > + because the conditions in the pre-header makes it safe to do so for
> > some
> > + cases. */
> > + if (vect_use_loop_latch_condition_p (loop_vinfo))
> > + {
> > + basic_block latch = loop->latch;
> > + edge latch_e = single_succ_edge (latch);
> > + int exit_flags = exit_edge->flags & (EDGE_TRUE_VALUE |
> > EDGE_FALSE_VALUE);
> > +
> > + latch_e->flags &= ~(EDGE_FALLTHRU | EDGE_TRUE_VALUE |
> > EDGE_FALSE_VALUE);
> > + latch_e->flags |= (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE) ^ exit_flags;
> > + latch_exit_edge = make_edge (latch, exit_edge->dest, exit_flags);
> > + latch_exit_edge->probability = exit_edge->probability;
> > + latch_exit_edge->count () = exit_edge->count ();
> > + copy_phi_arg_into_existing_phi (exit_edge, latch_exit_edge);
> > + gimple_stmt_iterator latch_gsi = gsi_last_bb (latch);
> > + gsi_insert_after (&latch_gsi, cond_stmt, GSI_NEW_STMT);
> > + LOOP_VINFO_MAIN_EXIT (loop_vinfo) = latch_exit_edge;
> > +
> > + gcond *old_cond = as_a <gcond *> (gsi_stmt (loop_cond_gsi));
> > + if (exit_edge->flags & EDGE_TRUE_VALUE)
> > + gimple_cond_make_false (old_cond);
> > + else
> > + gimple_cond_make_true (old_cond);
> > + update_stmt (old_cond);
> > + }
> > + else
> > + gsi_insert_before (&loop_cond_gsi, cond_stmt, GSI_SAME_STMT);
> >
> > /* The loop iterates (NITERS - 1) / VF + 1 times.
> > Subtract one from this to get the latch count. */
> > @@ -993,7 +1068,7 @@ vect_set_loop_condition_partial_vectors (class loop
> > @@ *loop, edge exit_edge,
> > }
> > else
> > assign = gimple_build_assign (final_iv, orig_niters);
> > - gsi_insert_on_edge_immediate (exit_edge, assign);
> > + gsi_insert_on_edge_immediate (LOOP_VINFO_MAIN_EXIT (loop_vinfo),
> > assign);
> > }
> >
> > return cond_stmt;
> > @@ -1470,11 +1545,14 @@ vect_set_loop_condition (class loop *loop, edge
> > @@ loop_e, loop_vec_info loop_vinfo
> >
> > /* Remove old loop exit test. */
> > stmt_vec_info orig_cond_info;
> > - if (loop_vinfo
> > - && (orig_cond_info = loop_vinfo->lookup_stmt (orig_cond)))
> > - loop_vinfo->remove_stmt (orig_cond_info);
> > - else
> > - gsi_remove (&loop_cond_gsi, true);
> > + if (!vect_use_loop_latch_condition_p (loop_vinfo))
> > + {
> > + if (loop_vinfo
> > + && (orig_cond_info = loop_vinfo->lookup_stmt (orig_cond)))
> > + loop_vinfo->remove_stmt (orig_cond_info);
> > + else
> > + gsi_remove (&loop_cond_gsi, true);
> > + }
> >
> > if (dump_enabled_p ())
> > dump_printf_loc (MSG_NOTE, vect_location, "New loop exit condition:
> > %G",
> >
> >
>
>
--
Richard Biener <[email protected]>
SUSE Software Solutions Germany GmbH,
Frankenstrasse 146, 90461 Nuernberg, Germany;
GF: Jochen Jaser, Andrew McDonald, Abhinav Puri; (HRB 36809, AG Nuernberg)