RE: [patch v3]middle-end: Teach ranger about POLY_INT_CST ranges for VRP.

Tamar Christina <[email protected]>
Newsgroups gmane.comp.gcc.patches
Message-ID <VI0PR08MB103921B721E984BF51A993629FFA42@VI0PR08MB10392.eurprd08.prod.outlook.com>
> -----Original Message-----
> From: Richard Sandiford <[email protected]>
> Sent: 20 August 2026 11:52
> To: Tamar Christina <[email protected]>
> Cc: [email protected]; nd <[email protected]>; [email protected];
> [email protected]; [email protected]; [email protected]
> Subject: Re: [patch v3]middle-end: Teach ranger about POLY_INT_CST ranges
> for VRP.
> 
> Sorry for the slow reply.
> 
> Tamar Christina <[email protected]> writes:
> > A an area where LLVM generates much better code than GCC today is in
> > optimization of VLA branches guarded by VLA constants.
> >
> > Normally in GIMPLE POLY_INT_CSTs have no upport and lower bound
> because well,
> > they're poly.  However SVE has defined minimum and maximum vector sizes
> in
> > the architecture[1] and so for us we do have bounds on these constants.
> >
> > [1] https://developer.arm.com/documentation/102476/0101/Introducing-
> SVE
> >
> > Today LLVM optimizes these simple expressions
> >
> > #include <arm_sve.h>
> >
> > int g (void)
> > {
> >   unsigned int vl = svcntb ();
> >   return vl < 257;
> > }
> >
> > int h (void)
> > {
> >   return svcntw () <= 64;
> > }
> >
> > away to
> >
> > g():
> >         mov     w0, #1
> >         ret
> >
> > h():
> >         mov     w0, #1
> >         ret
> >
> > which is right because both are always true for any SVE vector length.
> >
> > while GCC generates:
> >
> > g():
> >         cntb    x0
> >         cmp     w0, 257
> >         cset    w0, cc
> >         ret
> > h():
> >         cntw    x0
> >         cmp     x0, 65
> >         cset    w0, cc
> >         ret
> >
> > This patch extends ranger with a target hook poly_int_indeterminate_bound
> which
> > is different from the current costing only hooks used in the vectorizer
> because
> > those hooks can't be relied upon for correctness.
> >
> > This new hook allows us specify the minimum and maximum bounds of a
> POLY_INT_CST
> > and have ranger use it in range_query::get_tree_range though specifying the
> > indeterminate bound of a POLY target.
> >
> > This gets GCC to fold away many known true or known false comparisons in
> > codegen today and we generate much simpler loop pre-headers.
> >
> > I have kept the AArch64 parts in this patch to get some extra eyes on it, but
> > will split them out on commit.
> >
> > Bootstrapped Regtested on aarch64-none-linux-gnu,
> > arm-none-linux-gnueabihf, x86_64-pc-linux-gnu
> > -m32, -m64 and no issues.
> >
> > Ok for master?
> >
> > Thanks,
> > Tamar
> >
> > gcc/ChangeLog:
> >
> > 	* config/aarch64/aarch64.cc
> (aarch64_poly_int_indeterminate_bound): New.
> > 	(TARGET_POLY_INT_INDETERMINATE_BOUND ): Implement hook
> using it.
> > 	* target.def (poly_int_indeterminate_bound): New.
> > 	* doc/tm.texi.in: Document it.
> > 	* doc/tm.texi: Regenerate.
> > 	* value-query.cc (range_query::get_tree_range): Use it.
> >
> > gcc/testsuite/ChangeLog:
> >
> > 	* gcc.target/aarch64/sve/slp_12.c: Update testcases
> > 	* gcc.target/aarch64/sve/cnt_fold_7.c: New test.
> > 	* gcc.target/aarch64/sve/cnt_fold_7_run.c: New test.
> 
> This mostly LGTM, but some comments about the wide_int usage below.
> 
> > @@ -404,8 +406,98 @@ range_query::get_tree_range (vrange &r, tree expr,
> gimple *stmt,
> >        if (POLY_INT_CST_P (expr))
> >  	{
> >  	  unsigned int precision = TYPE_PRECISION (type);
> > -	  r.set_varying (type);
> > -	  r.update_bitmask ({ wi::zero (precision), get_nonzero_bits (expr) });
> > +	  signop sign = TYPE_SIGN (type);
> > +	  bool have_poly_bound = targetm.poly_int_indeterminate_bound;
> > +	  poly_uint64 indeterminate_bound;
> > +
> > +	  if (have_poly_bound)
> > +	    indeterminate_bound = targetm.poly_int_indeterminate_bound ();
> > +
> > +	  poly_wide_int val = wi::to_poly_wide (expr);
> > +	  wide_int type_min = wi::to_wide (TYPE_MIN_VALUE (type));
> > +	  wide_int type_max = wi::to_wide (TYPE_MAX_VALUE (type));
> 
> Variables should usually only be declared as "wide_int" if they specifically
> need to be written to later.  Otherwise it's best to use "auto".  The types
> returned by wi::to_wide and the like are more efficient than temporary
> wide_ints.
> 
> One of the requirements for wide_int being accepted was that using trees
> and rtxes as "wide_int-like" should have low overhead.  We tend to lose
> that in practice by using wide_int temporaries where they aren't needed.
> 

Ah I see. I always get confused here when looking at the comment in wide-int.h
but this makes sense.  Thanks.

> I know this isn't exactly hot code, but I'm going to make this point
> whenever I review wide_int stuff, since the idiom might be copied
> elsewhere.
> 

Ack.

> > +
> > +	  /* Start with the invariant part of the poly-int, then account
> > +	     for each coefficient below.
> > +
> > +	     The target hook gives a per-coefficient upper bound for the
> > +	     indeterminate.  Since those indeterminates are unsigned and
> > +	     nonnegative, a positive coefficient can only increase the upper
> > +	     bound and a negative coefficient can only decrease the lower
> > +	     bound.  The opposite bound is unaffected by that coefficient:
> > +
> > +		[A, +C] with C >= 0  => max += C * bound
> > +		[A, -C] with C >= 0  => min -= C * bound.  */
> > +	  wide_int bounds[2]
> > +	    = { wide_int::from (val.coeffs[0], precision, sign),
> > +		wide_int::from (val.coeffs[0], precision, sign) };
> 
> These wide_int::froms seem unnecessary.  val.coeffs[0] is already a
> wide_int of the right precision.
> 
> > +	  bool ovf[2] = { false, false };
> > +
> > +	  for (unsigned int i = 1; i < NUM_POLY_INT_COEFFS; ++i)
> > +	    {
> > +	      wide_int coeff = wide_int::from (val.coeffs[i], precision, sign);
> 
> Similarly here.
> 
> > +	      if (wi::eq_p (coeff, 0))
> > +		continue;
> > +
> > +	      /* Select the only bound affected by this coefficient.  A
> > +		 negative coefficient contributes to the minimum and a
> positive
> > +		 coefficient contributes to the maximum.  */
> > +	      bool coeff_neg = wi::neg_p (coeff, sign);
> > +	      wide_int &bound = bounds[coeff_neg ? 0 : 1];
> > +	      bool &bound_ovf = ovf[coeff_neg ? 0 : 1];
> > +
> > +	      if (bound_ovf)
> > +		continue;
> > +
> > +	      /* A missing hook, or a -1 bound for this coefficient, means the
> > +		 indeterminate has no finite target-specific limit.  Treat that
> > +		 like an overflow of the affected bound.  */
> > +	      if (!have_poly_bound
> > +		  || indeterminate_bound.coeffs[i] == HOST_WIDE_INT_M1U)
> > +		bound_ovf = true;
> > +	      else
> > +		{
> > +		  wide_int indeterminate
> > +		    = wi::uhwi (indeterminate_bound.coeffs[i], precision);
> 
> Similarly here about not using wide_int.
> 
> > +		  wi::overflow_type mul_ovf = wi::OVF_NONE;
> > +		  wide_int term = wi::mul (coeff, indeterminate, sign,
> > +					   &mul_ovf);
> > +		  wi::overflow_type add_ovf = wi::OVF_NONE;
> > +		  bound = wi::add (bound, term, sign, &add_ovf);
> > +		  bound_ovf = (mul_ovf != wi::OVF_NONE
> > +			       || add_ovf != wi::OVF_NONE);
> > +		}
> > +
> > +	      if (TYPE_OVERFLOW_WRAPS (type) && bound_ovf)
> > +		{
> > +		  r.set_varying (type);
> > +		  return true;
> > +		}
> > +
> > +	      if (bound_ovf)
> > +		{
> > +		  if (coeff_neg)
> > +		    bounds[0] = type_min;
> > +		  else
> > +		    bounds[1] = type_max;
> > +		}
> > +	    }
> 
> I've attached the comments above in patch form since I wanted to check
> that they worked.  It's trivial stuff, so doesn't count as co-authorship.
> 

Thanks!

> > +
> > +	  if (!wi::le_p (bounds[0], bounds[1], sign))
> > +	    {
> > +	      r.set_varying (type);
> > +	      return true;
> > +	    }
> 
> Is this possible after the above?  I would hope that we could either drop
> this or turn it into an assert.  Either way is ok with me.

You're right, this is mostly a defensive check for a broken target bounds.
but an assert is 100% more appropriate.

> 
> OK with those changes, thanks.  I think the patch has been around long
> enough that more active folks would have commented by now if they wanted
> to.
> But please say if you think the above le_p is still needed.

No I think it can be an assert.  OK with you to make it that, apply your patch and
push or do you want to see the final version?

Thanks,
Tamar

> 
> Richard
> 
> > +
> > +	  irange &ir = as_a <irange> (r);
> > +	  ir.set (type, bounds[0], bounds[1]);
> > +
> > +	  /* Preserve alignment/step information that is not visible in the
> > +	     intervals.  For example, a poly-int like [8, 8] can
> > +	     only produce multiples of 8, but the interval range might be
> > +	     [8, 136], which also contains values with low bits set.  */
> > +	  ir.update_bitmask (irange_bitmask (wi::zero (precision),
> > +					     get_nonzero_bits (expr)));
> >  	  return true;
> >  	}
> >        break;
> 
> 
> diff --git a/gcc/value-query.cc b/gcc/value-query.cc
> index 53728addefc..da93aea4a14 100644
> --- a/gcc/value-query.cc
> +++ b/gcc/value-query.cc
> @@ -413,9 +413,9 @@ range_query::get_tree_range (vrange &r, tree expr,
> gimple *stmt,
>  	  if (have_poly_bound)
>  	    indeterminate_bound = targetm.poly_int_indeterminate_bound ();
> 
> -	  poly_wide_int val = wi::to_poly_wide (expr);
> -	  wide_int type_min = wi::to_wide (TYPE_MIN_VALUE (type));
> -	  wide_int type_max = wi::to_wide (TYPE_MAX_VALUE (type));
> +	  auto val = wi::to_poly_wide (expr);
> +	  auto type_min = wi::to_wide (TYPE_MIN_VALUE (type));
> +	  auto type_max = wi::to_wide (TYPE_MAX_VALUE (type));
> 
>  	  /* Start with the invariant part of the poly-int, then account
>  	     for each coefficient below.
> @@ -428,14 +428,12 @@ range_query::get_tree_range (vrange &r, tree expr,
> gimple *stmt,
> 
>  		[A, +C] with C >= 0  => max += C * bound
>  		[A, -C] with C >= 0  => min -= C * bound.  */
> -	  wide_int bounds[2]
> -	    = { wide_int::from (val.coeffs[0], precision, sign),
> -		wide_int::from (val.coeffs[0], precision, sign) };
> +	  wide_int bounds[2] = { val.coeffs[0], val.coeffs[0] };
>  	  bool ovf[2] = { false, false };
> 
>  	  for (unsigned int i = 1; i < NUM_POLY_INT_COEFFS; ++i)
>  	    {
> -	      wide_int coeff = wide_int::from (val.coeffs[i], precision, sign);
> +	      const auto &coeff = val.coeffs[i];
>  	      if (wi::eq_p (coeff, 0))
>  		continue;
> 
> @@ -457,11 +455,10 @@ range_query::get_tree_range (vrange &r, tree expr,
> gimple *stmt,
>  		bound_ovf = true;
>  	      else
>  		{
> -		  wide_int indeterminate
> +		  auto indeterminate
>  		    = wi::uhwi (indeterminate_bound.coeffs[i], precision);
>  		  wi::overflow_type mul_ovf = wi::OVF_NONE;
> -		  wide_int term = wi::mul (coeff, indeterminate, sign,
> -					   &mul_ovf);
> +		  auto term = wi::mul (coeff, indeterminate, sign, &mul_ovf);
>  		  wi::overflow_type add_ovf = wi::OVF_NONE;
>  		  bound = wi::add (bound, term, sign, &add_ovf);
>  		  bound_ovf = (mul_ovf != wi::OVF_NONE
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.