RE: [patch][vect]: fix loop exit profiles with early break [PR118407]

Tamar Christina <[email protected]> Wed, 5 Aug 2026 13:54:10 +0000
Newsgroups gmane.comp.gcc.patches
Message-ID <VI0PR08MB10392035272BDB34C89255D4FFFD32@VI0PR08MB10392.eurprd08.prod.outlook.com>
> -----Original Message-----
> From: Richard Biener <[email protected]>
> Sent: 05 August 2026 13:57
> To: Tamar Christina <[email protected]>
> Cc: [email protected]; nd <[email protected]>; [email protected]; Jan
> Hubicka <[email protected]>
> Subject: Re: [patch][vect]: fix loop exit profiles with early break [PR118407]
> 
> On Wed, 5 Aug 2026, Tamar Christina wrote:
> 
> > It's been a few years since the patches for PR118407 but have been stuck on
> a
> > review.
> >
> > Those patches attempted to make an framework for supporting early break
> profiles
> > in not just the vectorizer but in other passes as well.
> >
> > This patch takes a different approach. We know that the vectorizer is the one
> > introducing the inaccurate profile information, so have it fix it up.  There are
> > other places in the vectorizer where this happens for non-early break too.
> >
> > As an example this loop
> >
> > #ifndef N
> > #define N 800
> > #endif
> > unsigned vect_a[N];
> > unsigned vect_b[N];
> >
> > unsigned test4(unsigned x)
> > {
> >  unsigned ret = 0;
> >  for (int i = 0; i < N; i++)
> >  {
> >    vect_b[i] = x + i;
> >    if (vect_a[i]*2 != x)
> >      break;
> >    vect_a[i] = x;
> >
> >  }
> >  return ret;
> > }
> >
> > generates with -O3 -march=armv9-a the following loop:
> >
> > test4:
> >         cntw    x1
> >         cmp     w1, 799
> >         bhi     .L7
> >         adrp    x4, .LANCHOR0
> >         add     x3, x4, :lo12:.LANCHOR0
> >         ptrue   p7.b, all
> >         add     x5, x3, 3200
> >         mov     w1, 0
> >         mov     z30.s, w0
> >         mov     w6, 800
> >         index   z29.s, #0, #1
> >         b       .L4
> > .L3:
> >         st1w    z28.s, p7, [x5, x1, lsl 2]
> >         st1w    z30.s, p7, [x3, x1, lsl 2]
> >         incw    z29.s
> >         incw    x1
> >         whilelo p7.s, w1, w6
> >         b.none  .L5
> > .L4:
> >         ld1w    z31.s, p7/z, [x3, x1, lsl 2]
> >         add     z28.s, z29.s, z30.s
> >         add     z31.s, z31.s, z31.s
> >         cmpne   p15.s, p7/z, z31.s, z30.s
> >         b.none  .L3
> >         mov     w5, 800
> >         mov     w2, w1
> >         sub     w5, w5, w1
> > .L2:
> >
> > notice the pointless indirect branch to L4, and the commonly taken branch of
> .L3
> > has to do a branch back to the latch instead of a fall through.  The CFG
> > becomes increasingly branchy as the number of exits increase.
> >
> > With this patch we generate:
> >
> > test4:
> >         cntw    x1
> >         cmp     w1, 799
> >         bhi     .L7
> >         adrp    x3, .LANCHOR0
> >         add     x3, x3, :lo12:.LANCHOR0
> >         ptrue   p7.b, all
> >         add     x4, x3, 3200
> >         mov     w1, 0
> >         mov     z30.s, w0
> >         mov     w5, 800
> >         index   z29.s, #0, #1
> >         .p2align 5,,15
> > .L4:
> >         ld1w    z31.s, p7/z, [x3, x1, lsl 2]
> >         add     z28.s, z29.s, z30.s
> >         add     z31.s, z31.s, z31.s
> >         cmpne   p15.s, p7/z, z31.s, z30.s
> >         b.any   .L12
> >         st1w    z28.s, p7, [x4, x1, lsl 2]
> >         st1w    z30.s, p7, [x3, x1, lsl 2]
> >         incw    z29.s
> >         incw    x1
> >         whilelo p7.s, w1, w5
> >         b.any   .L4
> > .L5:
> >         mov     w0, 0
> >         ret
> >
> > which is a more natural layout because it now knows the exit is unlikely since
> > they have been scaled correctly.
> >
> > The scaling is done by:
> >
> > let P be the scalar probability that one iteration takes an early exit edge.
> > 1 - P is then the probabiliy that the exit is not taken.
> >
> > Since every vector iteration handles VF scalar iterations, the early exit
> > branch can be taken when any of the lanes are true.
> >
> > We invert this and first calculate the probablity that you reach the latch,
> > i.e. the probability that the vector code does not exit is the probability
> > Q that none of the lanes are true.
> >
> > Q = (1 - P) * (1 - P) * ... * (1 - P)
> >   = (1 - P) ^ VF
> >
> > and so the probability that at least one lane breaks is 1 - Q,
> > or rather 1 - (i - P) ^ VF.
> >
> > For example, if the scalar break probability is 10% and VF is 4:
> >
> > P = 0.1
> > 1 - P = 0.9
> > (1 - P) ^ VF = 0.9 ^ 4 = 0.6561
> > 1 - (1 - p) ^ VF = 1 - 0.6561 = 0.3439
> >
> > So a scalar early-exit edge that is taken 10% of the time becomes a vector
> > early-exit edge that is taken about 34.4% of the time, because each vector
> > iteration gives the break condition four chances to trigger.
> >
> > NOTE: This does not fully fix PR118407 but only fixes part of the issue.
> >       there are other adjustments needed but this is the most glaring one.
> >
> > Bootstrapped Regtested on aarch64-none-linux-gnu,
> > arm-none-linux-gnueabihf, x86_64-pc-linux-gnu
> > -m32, -m64 and no issues.
> >
> > Any comments?
> >
> > Thanks,
> > Tamar
> >
> > gcc/ChangeLog:
> >
> > 	PR tree-optimization/118407
> > 	* tree-vect-stmts.cc (vect_update_early_break_profiles): New.
> > 	(vect_update_ivs_after_vectorizer_for_early_breaks): Use it.
> >
> > gcc/testsuite/ChangeLog:
> >
> > 	PR tree-optimization/118407
> > 	* gcc.target/aarch64/sve/noeffect2.c: Update test.
> > 	* gcc.target/aarch64/sve/noeffect3.c: Likewise.
> >
> > ---
> > diff --git a/gcc/testsuite/gcc.target/aarch64/sve/noeffect2.c
> b/gcc/testsuite/gcc.target/aarch64/sve/noeffect2.c
> > index
> 5bb1badde3c4e07d06ba68b2c73ba591e031f611..7169ede3cda7108ce662
> 8c147d014dd03c5d57bf 100644
> > --- a/gcc/testsuite/gcc.target/aarch64/sve/noeffect2.c
> > +++ b/gcc/testsuite/gcc.target/aarch64/sve/noeffect2.c
> > @@ -9,14 +9,19 @@ int b[N] = {0};
> >  /*
> >  ** foo:
> >  **	...
> > +**	ptrue	p[0-9]+\.b, all
> > +**	...
> >  **	whilelo	p[0-9]+\.s, w[0-9]+, w[0-9]+
> > +**	b\.none	\.L[0-9]+
> >  **	...
> >  **	ld1w	z[0-9]+\.s, p[0-9]+/z, \[x[0-9]+, x[0-9]+, lsl 2\]
> >  **	ld1w	z[0-9]+\.s, p[0-9]+/z, \[x[0-9]+, x[0-9]+, lsl 2\]
> >  **	cmpgt	p[0-9]+\.s, p[0-9]+/z, z[0-9]+\.s, z[0-9]+\.s
> >  **	b\.none	\.L[0-9]+
> > +**	...
> >  **	mov	w0, 1
> >  **	ret
> > +**	...
> >  **	mov	w0, 0
> >  **	ret
> >  */
> > diff --git a/gcc/testsuite/gcc.target/aarch64/sve/noeffect3.c
> b/gcc/testsuite/gcc.target/aarch64/sve/noeffect3.c
> > index
> c5f81bb0d303a070b482ec40d2921f6e6236ce67..8c20f39d2a467e636cfec
> 71acacb2da654a49d67 100644
> > --- a/gcc/testsuite/gcc.target/aarch64/sve/noeffect3.c
> > +++ b/gcc/testsuite/gcc.target/aarch64/sve/noeffect3.c
> > @@ -9,11 +9,14 @@ int b[N] = {0};
> >  /*
> >  ** foo:
> >  **	...
> > -**	whilelo	p[0-9]+\.s, w[0-9]+, w[0-9]+
> > +**	ptrue	p[0-9]+\.b, all
> >  **	...
> >  **	ld1w	z[0-9]+\.s, p[0-9]+/z, \[x[0-9]+, x[0-9]+, lsl 2\]
> >  **	ld1w	z[0-9]+\.s, p[0-9]+/z, \[x[0-9]+, x[0-9]+, lsl 2\]
> >  **	cmpgt	p[0-9]+\.s, p[0-9]+/z, z[0-9]+\.s, z[0-9]+\.s
> > +**	b\.any	\.L[0-9]+
> > +**	...
> > +**	whilelo	p[0-9]+\.s, w[0-9]+, w[0-9]+
> >  **	...
> >  **	ldr	w[0-9]+, \[x[0-9]+, x[0-9]+, lsl 2\]
> >  **	ldr	w[0-9]+, \[x[0-9]+, x[0-9]+, lsl 2\]
> > diff --git a/gcc/tree-vect-loop.cc b/gcc/tree-vect-loop.cc
> > index
> 31de29c036be5da2a94173e98dccc40a092099be..309f06d2d386769f152c
> 97c70b41f43a43181a79 100644
> > --- a/gcc/tree-vect-loop.cc
> > +++ b/gcc/tree-vect-loop.cc
> > @@ -11113,6 +11113,80 @@ move_early_exit_stmts (loop_vec_info
> loop_vinfo)
> >  	SET_PHI_ARG_DEF_ON_EDGE (phi, e, last_seen_vuse);
> >  }
> >
> > +/* Update the exit profile counts scaling them from scalar to vector counts.
> > +
> > +   The calculations are as follows.
> > +
> > +   let P be the scalar probability that one iteration takes an early exit edge.
> > +   1 - P is then the probabiliy that the exit is not taken.
> > +
> > +   Since every vector iteration handles VF scalar iterations, the early exit
> > +   branch can be taken when any of the lanes are true.
> > +
> > +   We invert this and first calculate the probablity that you reach the latch,
> > +   i.e. the probability that the vector code does not exit is the probability
> > +   Q that none of the lanes are true.
> > +
> > +   Q = (1 - P) * (1 - P) * ... * (1 - P)
> > +     = (1 - P) ^ VF
> > +
> > +   and so the probability that at least one lane breaks is 1 - Q,
> > +   or rather 1 - (i - P) ^ VF.
> > +
> > +   For example, if the scalar break probability is 10% and VF is 4:
> > +
> > +   P = 0.1
> > +   1 - P = 0.9
> > +   (1 - P) ^ VF = 0.9 ^ 4 = 0.6561
> > +   1 - (1 - p) ^ VF = 1 - 0.6561 = 0.3439
> > +
> > +   So a scalar early-exit edge that is taken 10% of the time becomes a vector
> > +   early-exit edge that is taken about 34.4% of the time, because each vector
> > +   iteration gives the break condition four chances to trigger.  */
> > +
> > +static void
> > +vect_update_early_break_profiles (loop_vec_info loop_vinfo)
> > +{
> > +  if (!LOOP_VINFO_EARLY_BREAKS (loop_vinfo))
> > +    return;
> > +
> > +  class loop *loop = LOOP_VINFO_LOOP (loop_vinfo);
> > +  unsigned int vf = vect_vf_for_cost (loop_vinfo);
> > +
> > +  auto_vec<std::pair<edge, profile_probability>, 8> updates;
> > +  for (edge e : get_loop_exit_edges (loop))
> > +    {
> > +      if (e == LOOP_VINFO_MAIN_EXIT (loop_vinfo)
> > +	  || !e->probability.initialized_p ())
> > +	continue;
> > +
> > +      /* For any early break exits, the probablity of exiting are based on if
> > +	 any lane is true, and so are tied to VF since you have VF chances.
> > +	 As such scale the scalar profile by VF to the the probability for the
> > +	 vector edges using 1 - (1 - p) ^ VF.  */
> > +      profile_probability probability
> > +	= profile_probability::always () - e->probability.invert ().pow (vf);
> > +      updates.safe_push ({e, probability});
> > +    }
> > +
> > +  /* Save all old scalar probabilities before changing any edges.  Then apply
> > +     all probability updates before changing successor counts, since earlier
> > +     count changes can affect the source counts used by later early exits.  */
> > +  for (auto update : updates)
> > +    {
> > +      edge e = update.first;
> > +      profile_probability probability = update.second;
> > +      profile_count old_count = e->count ();
> > +      set_edge_probability_and_rescale_others (e, probability);
> > +      e->dest->count += e->count () - old_count;
> > +    }
> > +
> > +  for (auto update : updates)
> > +    for (edge e : update.first->src->succs)
> > +      if (single_pred_p (e->dest))
> > +	e->dest->count = e->count ();
> > +}
> > +
> >  /* Generate adjustment code for early break scalar IVs filling in the value
> >     we created earlier on for LOOP_VINFO_EARLY_BRK_NITERS_VAR.  */
> >
> > @@ -11121,9 +11195,13 @@
> vect_update_ivs_after_vectorizer_for_early_breaks (loop_vec_info loop_vinfo)
> >  {
> >    DUMP_VECT_SCOPE
> ("vect_update_ivs_after_vectorizer_for_early_breaks");
> >
> > -  if (!LOOP_VINFO_EARLY_BREAKS (loop_vinfo)
> > -      /* If no peeling was done then we have no IV to update.  */
> > -      || !LOOP_VINFO_EARLY_BRK_NITERS_VAR (loop_vinfo))
> > +  if (!LOOP_VINFO_EARLY_BREAKS (loop_vinfo))
> > +    return;
> > +
> > +  vect_update_early_break_profiles (loop_vinfo);
> > +
> 
> My comment would be that we should do this in the caller,
> where we already apply some scaling to the main vector loop
> (the argument for the main exit would be similar, no?).

I've moved it to scale_profile_for_vect_loop.  Will send v2 in a bit.

> It's also a bit odd to scale for the main exit separately?

No because the main exit is a counted exit, it's probabilities are not
influenced by the lanes of a comparison, since it's usually just checking
the counter value.

That said, uncounted loops likely need all exit calculated this way.
I'll need to double check. Today since get_likely_max_loop_iterations_int
would return -1 you'd just get just basic loop scaling.

PEELED loops are handled correctly with this patch (and I've tested by
looking at the peeled*.c testcases)

So I think for !uncounted loops this is correct, for uncounted loops it's
slightly better than before and likely needs

if ((!LOOP_VINFO_NITERS_UNCOUNTED_P (loop_vinfo)
       && e == LOOP_VINFO_MAIN_EXIT (loop_vinfo))
      || !e->probability.initialized_p ())
    continue;

to be correct, and in scale_profile_for_vect_loop we should not scale
the main exit for uncounted loops.

Let me respin.

Thanks,
Tamar


> 
> > +  /* If no peeling was done then we have no IV to update.  */
> > +  if (!LOOP_VINFO_EARLY_BRK_NITERS_VAR (loop_vinfo))
> >      return;
> >
> >    tree phi_var = LOOP_VINFO_EARLY_BRK_NITERS_VAR (loop_vinfo);
> >
> >
> >
> 
> --
> 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)