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

Reply via email to