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
