On Sat, 14 Nov 2026 at 00:05, Alfie Richards <[email protected]> wrote:
> Hi Andrea and Richard,
>
> I changed this to be perhaps less ambitious.
>
> Instead of trying to modify the existing poly_int API's and make them
> some level of target dependent I instead just added a new API for
> target aware poly_int comparisons and upper/lower bounds.
>
> Which I think makes a lot more sense.
>
> As a benefit, it means I didn't have to change loads of uses of maybe_lt and
> similar.
>
> I'm not sure about the names I chose though.
>
> Is this any better?

Yeah, this definitely looks better to me FWIW.  Some high-level comments below,
but I'd like to hear what others think before doing a detailed review.

> Bootstrapped and regr tested on aarch64-linux-gnu, x86_64-linux-gnu, 
> armhf-linux-gnu.
>
> Thanks,
> Alfie
>
> -- >8 --
>
> gcc/ChangeLog:
>
>         * Makefile.in: Add target dependency on new
>         poly-int-target-bounds.h..
>         * target.h: Add include of poly-int-target-bounds.h.
>         * value-query.cc (range_query::get_tree_range): Use new
>         functions for poly int bounds.
>         * poly-int-target-bounds.h: New file.
>
> ---
>  gcc/Makefile.in              |   2 +-
>  gcc/poly-int-target-bounds.h | 264 +++++++++++++++++++++++++++++++++++
>  gcc/target.h                 |   2 +
>  gcc/value-query.cc           |  85 +++--------
>  4 files changed, 283 insertions(+), 70 deletions(-)
>  create mode 100644 gcc/poly-int-target-bounds.h
>
> diff --git a/gcc/Makefile.in b/gcc/Makefile.in
> index c6059206a95..c2e5a05c770 100644
> --- a/gcc/Makefile.in
> +++ b/gcc/Makefile.in
> @@ -995,7 +995,7 @@ COMMON_TARGET_DEF = common/common-target.def 
> target-hooks-macros.h
>  D_TARGET_DEF = d/d-target.def target-hooks-macros.h
>  RUST_TARGET_DEF = rust/rust-target.def target-hooks-macros.h
>  JIT_TARGET_DEF = jit/jit-target.def target-hooks-macros.h
> -TARGET_H = $(TM_H) target.h $(TARGET_DEF) insn-modes.h insn-codes.h
> +TARGET_H = $(TM_H) target.h $(TARGET_DEF) insn-modes.h insn-codes.h 
> poly-int-target-bounds.h
>  C_TARGET_H = c-family/c-target.h $(C_TARGET_DEF)
>  COMMON_TARGET_H = common/common-target.h $(INPUT_H) $(COMMON_TARGET_DEF)
>  D_TARGET_H = d/d-target.h $(D_TARGET_DEF)
> diff --git a/gcc/poly-int-target-bounds.h b/gcc/poly-int-target-bounds.h
> new file mode 100644
> index 00000000000..c6b1e6e1bd5
> --- /dev/null
> +++ b/gcc/poly-int-target-bounds.h

How about poly-int-target.h, so that we can put other target-specific
routines there in future?

> @@ -0,0 +1,264 @@
> +/* Operations with very long integers.
> +   Copyright (C) 2026-2026 Free Software Foundation, Inc.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify it
> +under the terms of the GNU General Public License as published by the
> +Free Software Foundation; either version 3, or (at your option) any
> +later version.
> +
> +GCC is distributed in the hope that it will be useful, but WITHOUT
> +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> +FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
> +for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GCC; see the file COPYING3.  If not see
> +<http://www.gnu.org/licenses/>.  */
> +
> +/* Functions for reasoning about poly_int's with information from
> +   targetm.indeterminate_bound hook.  */
> +
> +#ifndef HAVE_POLY_INT_TARGET_BOUNDS_H
> +#define HAVE_POLY_INT_TARGET_BOUNDS_H
> +
> +/* Attempts to find an upper bound for poly-int A, potentially using the 
> target
> +   poly_int_indeterminate_bound hook if defined. On success stores the result
> +   in RES and returns true.  */
> +
> +template <typename Ca>
> +inline bool
> +maybe_upper_bound (const poly_int<NUM_POLY_INT_COEFFS, Ca> &a,
> +                         WI_BINARY_RESULT
> +                           (Ca, WI_BINARY_RESULT
> +                                  (Ca, unsigned HOST_WIDE_INT)) * res,
> +                         signop sgn)

Despite the templating, I think the implementation essentially requires
some poly_wide_int-compatible type, so "a" can be poly_wide_int_ref instead.
The result can similarly be wide_int.

Taking that into account, I think the interface makes an artifical distinction
between cases where a bound can be computed and happens to be the maximum vs
cases where a bound can't be computed and the maximum has to be assumed.

So maybe it would be better to remove the "res" pointer and return a
wide_int unconditionally instead?  The name could then be target_upper_bound,
for consistency with the other routines.

> +{
> +  typedef WI_BINARY_RESULT (Ca, unsigned HOST_WIDE_INT) C1;
> +  typedef WI_BINARY_RESULT (Ca, C1) Cr;
> +
> +  wi::overflow_type ovf = wi::OVF_NONE;
> +  if (NUM_POLY_INT_COEFFS < 2 || wi::neg_p (a.coeffs[1], sgn)
> +      || wi::eq_p (a.coeffs[1], wi::ints_for<Ca>::zero (a.coeffs[1])))

Even target code shouldn't assume NUM_POLY_INT_COEFFS <= 2 unless
there's a specific need (in which case there should be a static assert).
The existing code was right to iterate over each coefficient.

Thanks,
Richard

> +    {
> +      Cr temp2 = wi::add (a.coeffs[0], wi::ints_for<C1>::zero (a.coeffs[0]),
> +                         sgn, &ovf);
> +      gcc_assert (ovf == wi::OVF_NONE);
> +      *res = temp2;
> +      return true;
> +    }
> +
> +  const poly_uint64 indeterminate_bound
> +    = targetm.poly_int_indeterminate_bound ();
> +
> +  if (indeterminate_bound.coeffs[1] == HOST_WIDE_INT_M1U)
> +    return false;
> +
> +  C1 temp1 = wi::mul (a.coeffs[1], indeterminate_bound.coeffs[1], sgn, &ovf);
> +  if (ovf != wi::OVF_NONE)
> +    return false;
> +  Cr temp2 = wi::add (a.coeffs[0], temp1, sgn, &ovf);
> +  if (ovf != wi::OVF_NONE)
> +    return false;
> +
> +  *res = temp2;
> +  return true;
> +}
> +
> +/* Attempts to find a lower bound for poly-int A, potentially using the 
> target
> +   poly_int_indeterminate_bound hook if defined. On success stores the result
> +   in RES and returns true.  */
> +
> +template <typename Ca>
> +inline bool
> +maybe_lower_bound (const poly_int<NUM_POLY_INT_COEFFS, Ca> &a,
> +                  WI_BINARY_RESULT (Ca, WI_BINARY_RESULT (Ca, unsigned 
> HOST_WIDE_INT))
> +                    * res,
> +                  signop sgn)
> +{
> +  typedef WI_BINARY_RESULT (Ca, unsigned HOST_WIDE_INT) C1;
> +  typedef WI_BINARY_RESULT (Ca, C1) Cr;
> +
> +  wi::overflow_type ovf = wi::OVF_NONE;
> +  if (NUM_POLY_INT_COEFFS < 2 || !wi::neg_p (a.coeffs[1], sgn))
> +    {
> +      Cr temp2 = wi::add (a.coeffs[0], wi::ints_for<C1>::zero (a.coeffs[0]),
> +                         sgn, &ovf);
> +      gcc_assert (ovf == wi::OVF_NONE);
> +      *res = temp2;
> +      return true;
> +    }
> +
> +  const poly_uint64 indeterminate_bound
> +    = targetm.poly_int_indeterminate_bound ();
> +
> +  if (indeterminate_bound.coeffs[1] == HOST_WIDE_INT_M1U)
> +    return false;
> +
> +  C1 temp1 = wi::mul (a.coeffs[1], indeterminate_bound.coeffs[1], sgn, &ovf);
> +  if (ovf != wi::OVF_NONE)
> +    return false;
> +  Cr temp2 = wi::add (a.coeffs[0], temp1, sgn, &ovf);
> +  if (ovf != wi::OVF_NONE)
> +    return false;
> +
> +  *res = temp2;
> +  return true;
> +}
> +
> +/* Similar to maybe_le but also uses information from maybe_upper_bound and
> +   maybe_lower_bound.  */
> +template <typename Ca, typename Cb>
> +inline bool
> +target_maybe_le (const poly_int<NUM_POLY_INT_COEFFS, Ca> &a,
> +                const poly_int<NUM_POLY_INT_COEFFS, Cb> &b)
> +{
> +  typedef WI_BINARY_RESULT (Ca, unsigned int) type_a;
> +  typedef WI_BINARY_RESULT (Cb, unsigned int) type_b;
> +
> +  signop sgn = (poly_coeff_traits<type_a>::signedness == 1
> +                   || poly_coeff_traits<type_b>::signedness == 1
> +                 ? SIGNED
> +                 : UNSIGNED);
> +
> +  type_a a_lower_bound = 0;
> +  type_b b_upper_bound = 0;
> +  if (maybe_lower_bound (a, &a_lower_bound, sgn)
> +      && maybe_upper_bound (b, &b_upper_bound, sgn)
> +      && wi::gt_p (a_lower_bound, b_upper_bound, sgn))
> +    return false;
> +
> +  return maybe_le (a, b);
> +}
> +
> +/* Similar to maybe_le but also uses information from maybe_upper_bound and
> +   maybe_lower_bound.  */
> +template <typename Ca, typename Cb>
> +inline typename if_nonpoly<Cb, bool>::type
> +target_maybe_le (const poly_int<NUM_POLY_INT_COEFFS, Ca> &a, const Cb &b
> +         )
> +{
> +  typedef WI_BINARY_RESULT (Ca, unsigned int) type_a;
> +  signop sgn = (poly_coeff_traits<type_a>::signedness == 1 ? SIGNED : 
> UNSIGNED);
> +
> +  type_a a_lower_bound = 0;
> +  if (maybe_lower_bound (a, &a_lower_bound, sgn)
> +      && wi::gt_p (a_lower_bound, b, sgn))
> +    return false;
> +
> +  return maybe_le (a, b);
> +}
> +
> +/* Similar to maybe_le but also uses information from maybe_upper_bound and
> +   maybe_lower_bound.  */
> +template <typename Ca, typename Cb>
> +inline typename if_nonpoly<Ca, bool>::type
> +target_maybe_le (const Ca &a, const poly_int<NUM_POLY_INT_COEFFS, Cb> &b
> +         )
> +{
> +  typedef WI_BINARY_RESULT (Cb, unsigned int) type_b;
> +  signop sgn = (poly_coeff_traits<type_b>::signedness == 1 ? SIGNED : 
> UNSIGNED);
> +
> +  type_b b_upper_bound = 0;
> +  if (maybe_upper_bound (b, &b_upper_bound, sgn)
> +      && wi::gt_p (a, b_upper_bound, sgn))
> +    return false;
> +
> +  return maybe_le (a, b);
> +}
> +
> +/* Similar to maybe_le but also uses information from maybe_upper_bound and
> +   maybe_lower_bound.  */
> +template <typename Ca, typename Cb>
> +inline typename if_nonpoly2<Ca, Cb, bool>::type
> +target_maybe_le (const Ca &a, const Cb &b)
> +{
> +  return maybe_le (a, b);
> +}
> +
> +/* Similar to maybe_lt but also uses information from maybe_upper_bound and
> +   maybe_lower_bound.  */
> +template <typename Ca, typename Cb>
> +inline bool
> +target_maybe_lt (const poly_int<NUM_POLY_INT_COEFFS, Ca> &a, const 
> poly_int<NUM_POLY_INT_COEFFS, Cb> &b)
> +{
> +  typedef WI_BINARY_RESULT (Ca, unsigned int) type_a;
> +  typedef WI_BINARY_RESULT (Cb, unsigned int) type_b;
> +  signop sgn = (poly_coeff_traits<type_a>::signedness == 1
> +                   || poly_coeff_traits<type_b>::signedness == 1
> +                 ? SIGNED
> +                 : UNSIGNED);
> +
> +  type_a a_lower_bound = 0;
> +  type_b b_upper_bound = 0;
> +  if (maybe_lower_bound (a, &a_lower_bound, sgn)
> +      && maybe_upper_bound (b, &b_upper_bound, sgn)
> +      && wi::ge_p (a_lower_bound, b_upper_bound, sgn))
> +    return false;
> +
> +  return maybe_lt (a, b);
> +}
> +
> +/* Similar to maybe_lt but also uses information from maybe_upper_bound and
> +   maybe_lower_bound.  */
> +template <typename Ca, typename Cb>
> +inline typename if_nonpoly<Cb, bool>::type
> +target_maybe_lt (const poly_int<NUM_POLY_INT_COEFFS, Ca> &a, const Cb &b
> +         )
> +{
> +  typedef WI_BINARY_RESULT (Ca, unsigned int) type_a;
> +  signop sgn = (poly_coeff_traits<type_a>::signedness == 1 ? SIGNED : 
> UNSIGNED);
> +
> +  type_a a_lower_bound = 0;
> +  if (maybe_lower_bound (a, &a_lower_bound, sgn)
> +      && wi::ge_p (a_lower_bound, b, sgn))
> +    return false;
> +
> +  return maybe_lt (a, b);
> +}
> +
> +/* Similar to maybe_lt but also uses information from maybe_upper_bound and
> +   maybe_lower_bound.  */
> +template <typename Ca, typename Cb>
> +inline typename if_nonpoly<Ca, bool>::type
> +target_maybe_lt (const Ca &a, const poly_int<NUM_POLY_INT_COEFFS, Cb> &b)
> +{
> +  typedef WI_BINARY_RESULT (Cb, unsigned int) type_b;
> +  signop sgn = (poly_coeff_traits<type_b>::signedness == 1 ? SIGNED : 
> UNSIGNED);
> +
> +  type_b b_upper_bound = 0;
> +  if (maybe_upper_bound (b, &b_upper_bound, sgn)
> +      && wi::ge_p (a, b_upper_bound, sgn))
> +    return false;
> +
> +  return maybe_lt (a, b);
> +}
> +
> +/* Similar to maybe_lt but also uses information from maybe_upper_bound and
> +   maybe_lower_bound.  */
> +template<typename Ca, typename Cb>
> +inline typename if_nonpoly2<Ca, Cb, bool>::type
> +target_maybe_lt (const Ca &a, const Cb &b)
> +{
> +  return maybe_lt (a, b);
> +}
> +
> +/* Return true if A may be greater than or equal to B.  */
> +#define target_maybe_ge(A, B) target_maybe_le (B, A)
> +
> +/* Return true if A may be greater than B.  */
> +#define target_maybe_gt(A, B) target_maybe_lt (B, A)
> +
> +/* Return true if A is known to be less than or equal to B.  */
> +#define target_known_le(A, B) (!target_maybe_gt (A, B))
> +
> +/* Return true if A is known to be less than B.  */
> +#define target_known_lt(A, B) (!target_maybe_ge (A, B))
> +
> +/* Return true if A is known to be greater than B.  */
> +#define target_known_gt(A, B) (!target_maybe_le (A, B))
> +
> +/* Return true if A is known to be greater than or equal to B.  */
> +#define target_known_ge(A, B) (!target_maybe_lt (A, B))
> +#endif
> diff --git a/gcc/target.h b/gcc/target.h
> index 67b04f90f45..bc9239bfaa8 100644
> --- a/gcc/target.h
> +++ b/gcc/target.h
> @@ -337,6 +337,8 @@ extern bool verify_type_context (location_t, 
> type_context_kind, const_tree,
>
>  extern struct gcc_target targetm;
>
> +#include "poly-int-target-bounds.h"
> +
>  /* Return an estimate of the runtime value of X, for use in things
>     like cost calculations or profiling frequencies.  Note that this
>     function should never be used in situations where the actual
> diff --git a/gcc/value-query.cc b/gcc/value-query.cc
> index 0a6de62d763..8b3d607f0e0 100644
> --- a/gcc/value-query.cc
> +++ b/gcc/value-query.cc
> @@ -442,86 +442,33 @@ range_query::get_tree_range (vrange &r, tree expr, 
> gimple *stmt,
>         {
>           unsigned int precision = TYPE_PRECISION (type);
>           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 ();
>
>           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.
> -
> -            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] = { val.coeffs[0], val.coeffs[0] };
> -         bool ovf[2] = { false, false };
> +         wide_int lower_bound = type_min;
> +         wide_int upper_bound = type_max;
>
> -         for (unsigned int i = 1; i < NUM_POLY_INT_COEFFS; ++i)
> +         if (!maybe_lower_bound (val, &lower_bound, sign)
> +             || wi::lt_p (lower_bound, type_min, sign)
> +             || wi::gt_p (lower_bound, type_max, sign)
> +             || !maybe_upper_bound (val, &upper_bound, sign)
> +             || wi::lt_p (upper_bound, type_min, sign)
> +             || wi::gt_p (upper_bound, type_max, sign))
>             {
> -             const auto &coeff = val.coeffs[i];
> -             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
> -               {
> -                 auto indeterminate
> -                   = wi::uhwi (indeterminate_bound.coeffs[i], precision);
> -                 wi::overflow_type mul_ovf = wi::OVF_NONE;
> -                 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
> -                              || add_ovf != wi::OVF_NONE);
> -               }
> -
> -             if (TYPE_OVERFLOW_WRAPS (type) && bound_ovf)
> -               {
> -                 r.set_varying (type);
> -                 r.update_bitmask (irange_bitmask (wi::zero (precision),
> -                                                   get_nonzero_bits (expr)));
> -                 return true;
> -               }
> -
> -             if (bound_ovf)
> -               {
> -                 if (coeff_neg)
> -                   bounds[0] = type_min;
> -                 else
> -                   bounds[1] = type_max;
> -               }
> +             /* Overflow risk, so no known bounds.  */
> +             r.set_varying (type);
> +             r.update_bitmask (
> +               irange_bitmask (wi::zero (precision), get_nonzero_bits 
> (expr)));
> +             return true;
>             }
>
>           /* Check that the target filled in sensible bounds information.  */
> -         gcc_assert (wi::le_p (bounds[0], bounds[1], sign));
> +         gcc_assert (wi::le_p (lower_bound, upper_bound, sign));
>
> -         irange &ir = as_a <irange> (r);
> -         ir.set (type, bounds[0], bounds[1]);
> +         irange &ir = as_a<irange> (r);
> +         ir.set (type, lower_bound, upper_bound);
>
>           /* Preserve alignment/step information that is not visible in the
>              intervals.  For example, a poly-int like [8, 8] can

Reply via email to