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

Richard Biener <[email protected]> Thu, 6 Aug 2026 12:55:08 +0200 (CEST)
Newsgroups gmane.comp.gcc.patches
Message-ID <[email protected]>
On Thu, 6 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.
> 
> For counted loops we need to exclude the main exit from the calculations
> as for those the main exit is counted and so does not have this per-lane
> probability.  For uncounted loops, all exits are based on vector compare,
> i.e. none of the exits are counted and we have a different loop control.
> That means for uncounted loops all exits should be using the per-lane
> probability and we should skip the counted exit adjustments in
> scale_profile_for_vect_loop.
> 
> 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.
> 	(scale_profile_for_vect_loop): Use it and pass loop_vinfo.
> 	(vect_transform_loop): Pass loop_vinfo to scale_profile_for_vect_loop.
> 
> 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..04b7eb993e36fbae27ff3849957b2e7e25595c9c 100644
> --- a/gcc/testsuite/gcc.target/aarch64/sve/noeffect2.c
> +++ b/gcc/testsuite/gcc.target/aarch64/sve/noeffect2.c
> @@ -9,16 +9,21 @@ 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\.none	\.L[0-9]+
> -**	mov	w0, 1
> -**	ret
> +**	b\.any	\.L[0-9]+
> +**	...
> +**	incw	x[0-9]+
> +**	whilelo	p[0-9]+\.s, w[0-9]+, w[0-9]+
> +**	b\.any	\.L[0-9]+
>  **	mov	w0, 0
>  **	ret
> +**	...
> +**	mov	w0, 1
> +**	ret
>  */
>  __attribute__ ((noipa, noinline))
>  int foo (void)
> diff --git a/gcc/testsuite/gcc.target/aarch64/sve/noeffect3.c b/gcc/testsuite/gcc.target/aarch64/sve/noeffect3.c
> index c5f81bb0d303a070b482ec40d2921f6e6236ce67..8c20f39d2a467e636cfec71acacb2da654a49d67 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..c2b04149bf05a1a7def6fa41331dc53674d38e51 100644
> --- a/gcc/tree-vect-loop.cc
> +++ b/gcc/tree-vect-loop.cc
> @@ -10890,14 +10890,94 @@ vect_gen_loop_len_mask (loop_vec_info loop_vinfo, gimple_stmt_iterator *gsi,
>    return len_mask;
>  }
>  
> +/* 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)
> +	   && !LOOP_VINFO_NITERS_UNCOUNTED_P (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.  */

Note that get_loop_exit_edges () returns exits unsorted, so with multiple
exits it isn't guaranteed that you visit earlier exits first.

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

Doesn't this result in profile mismatches outside of the loop?  I'd
have expected exit->dest->count to be unchanged, since the number
of times we exit a loop should be unchanged.  So don't you have to
adjust the in-loop counts instead keeping that in mind?

> +    }
> +
> +  for (auto update : updates)
> +    for (edge e : update.first->src->succs)
> +      if (single_pred_p (e->dest))
> +	e->dest->count = e->count ();
> +}
> +
>  /* Scale profiling counters by estimation for LOOP which is vectorized
>     by factor VF.
>     If FLAT is true, the loop we started with had unrealistically flat
>     profile.  */
>  
>  static void
> -scale_profile_for_vect_loop (class loop *loop, edge exit_e, unsigned vf, bool flat)
> +scale_profile_for_vect_loop (loop_vec_info loop_vinfo,
> +			     class loop *loop, edge exit_e, unsigned vf,
> +			     bool flat)
>  {
> +  /* First scale any early exits.  */
> +  vect_update_early_break_profiles (loop_vinfo);
> +
>    /* For flat profiles do not scale down proportionally by VF and only
>       cap by known iteration count bounds.  */
>    if (flat)
> @@ -10927,14 +11007,19 @@ scale_profile_for_vect_loop (class loop *loop, edge exit_e, unsigned vf, bool fl
>        vf /= 2;
>      }
>  
> -  if (entry_count.nonzero_p ())
> -    set_edge_probability_and_rescale_others
> +  if (!LOOP_VINFO_NITERS_UNCOUNTED_P (loop_vinfo))
> +    {
> +      if (entry_count.nonzero_p ())
> +	set_edge_probability_and_rescale_others
>  	    (exit_e,
>  	     entry_count.probability_in (loop->header->count / vf));
> -  /* Avoid producing very large exit probability when we do not have
> -     sensible profile.  */
> -  else if (exit_e->probability < profile_probability::always () / (vf * 2))
> -    set_edge_probability_and_rescale_others (exit_e, exit_e->probability * vf);
> +      /* Avoid producing very large exit probability when we do not have
> +	 sensible profile.  */
> +      else if (exit_e->probability < profile_probability::always () / (vf * 2))
> +	set_edge_probability_and_rescale_others (exit_e,
> +						 exit_e->probability * vf);
> +    }
> +
>    loop->latch->count = single_pred_edge (loop->latch)->count ();
>  
>    scale_loop_profile (loop, profile_probability::always () / vf,
> @@ -11555,8 +11640,9 @@ vect_transform_loop (loop_vec_info loop_vinfo, gimple *loop_vectorized_call)
>  			  assumed_vf) - 1
>  	 : wi::udiv_floor (loop->nb_iterations_estimate + bias_for_assumed,
>  			   assumed_vf) - 1);
> -  scale_profile_for_vect_loop (loop, LOOP_VINFO_MAIN_EXIT (loop_vinfo),
> -			       assumed_vf, flat);
> +  scale_profile_for_vect_loop (loop_vinfo, loop,
> +			       LOOP_VINFO_MAIN_EXIT (loop_vinfo), assumed_vf,
> +			       flat);
>  
>    if (dump_enabled_p ())
>      {
> 
> 
> 

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