> -----Original Message-----
> From: Richard Sandiford <[email protected]>
> Sent: 07 July 2026 13:04
> To: Richard Biener <[email protected]>
> Cc: Tamar Christina <[email protected]>; [email protected];
> nd <[email protected]>; Richard Earnshaw <[email protected]>;
> [email protected]; Alex Coplan <[email protected]>;
> [email protected]; Wilco Dijkstra
> <[email protected]>; Alice Carlotti <[email protected]>;
> [email protected]; [email protected]
> Subject: Re: [patch]middle-end: Teach ranger about POLY_INT_CST ranges for
> VRP.
> 
> Richard Biener <[email protected]> writes:
> > On Tue, 7 Jul 2026, Tamar Christina wrote:
> >
> >> Hopefully this isn't controversial :)
> >>
> >> 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_cst_value_range
> 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.
> >>
> >> 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_cst_value_range):
> New.
> >>    (TARGET_POLY_INT_CST_VALUE_RANGE): Implement hook using it.
> >>    * target.def (poly_int_cst_value_range): New.
> >>    * doc/tm.texi.in: Document it.
> >>    * doc/tm.texi: Regenerate.
> >>    * targhooks.cc (default_poly_int_cst_value_range): New.
> >>    * targhooks.h (default_poly_int_cst_value_range): New.
> >>    * 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.
> >>
> >> ---
> >> diff --git a/gcc/config/aarch64/aarch64.cc
> b/gcc/config/aarch64/aarch64.cc
> >> index
> 72822ca621f52afee144bffa76bc75aac28adae1..7e12bb0e2c003b80008dd0
> f3c9fa304c7273ef39 100644
> >> --- a/gcc/config/aarch64/aarch64.cc
> >> +++ b/gcc/config/aarch64/aarch64.cc
> >> @@ -31053,6 +31053,27 @@ aarch64_estimated_poly_value (poly_int64
> val,
> >>    return val.coeffs[0] + val.coeffs[1] * over_128 / 128;
> >>  }
> >>
> >> +/* Implement TARGET_POLY_INT_CST_VALUE_RANGE.  */
> >> +
> >> +static bool
> >> +aarch64_poly_int_cst_value_range (poly_int64 val, HOST_WIDE_INT
> *min_val,
> >> +                            HOST_WIDE_INT *max_val)
> >
> > The interface is a bit unfortunate to be restricted to
> > poly_int64/HWI rather than poly_wide_int/wide_int.  Why did you
> > chose that specifically and why signed poly_int64?  In practice
> > POLY_INT_CSTs will have arbitrary base types.

Mostly since I expected the ranges out of a POLY_INT_CST to be small
since I was only looking at the intrinsics instructions and what we generate
from vect.

But you're right on that.

> >
> > I think the lower bound is obvious without target help(?), only
> > the upper bound is not.  Also isn't it 'N' that is limited in
> > practice rather than base + step * N?
> 
> Yeah, agreed.  Like you say, the lower bound on the indeterminates is
> defined to be zero.  It's the target's responsibility to arrange things
> so that that is true (which is why SVE's byte length is 16 + 16*N rather
> than 0 + 16*N).  And I agree that in this context it would probably be
> better to provide the maximum interdeterminate values.
> 
> The situation regarding lower bounds is different for the cost hooks
> because, when compiling for something like Neoverse V1, the minimum
> "costing" VL can be higher than the minimum "correctness" VL.

Agreed with both. I didn't provide an automatic lower bounds (even though
I could have through constant_lower_bound because I didn't know what the
reception to the patch would be :)

> 
> >> +{
> >> +  unsigned int const_vg;
> >> +  if (aarch64_sve_vg.is_constant (&const_vg))
> >> +    {
> >> +      *min_val = val.coeffs[0];
> >> +      *max_val = *min_val;
> >> +      return true;
> >> +    }
> 
> This shouldn't be necessary as things stand.  We don't use poly_ints
> when the VL is known.  If at some point in the future we allow the
> constant VL to be changed per-function (sounds difficult), we should
> probably have hooks specifically for that.

I must admit I didn't check, but I had assumed we could with something
like FMV.

> 
> >> +
> >> +  *min_val = val.coeffs[0];
> >> +  *max_val = val.coeffs[0] + val.coeffs[1] * 15;
> 
> In a ranger context, we need to be careful about overflow.  The calculation
> should saturate rather than wrap.
> 
> Having a hook based on indeterminate ranges rather than poly_int ranges
> would leave that up to target-independent code, rather than being something
> that AArch64 and RISC-V have to duplicate.
> 

Ok, so just have one hook that returns the indeterminate (N) and move the logic
into the call site in ranger? So that can just return unsigned HOST_WIDE_INT 
with
-1 being VARYING?

Thanks,
Tamar

> Richard
> 
> >> +  if (*min_val > *max_val)
> >> +    std::swap (*min_val, *max_val);
> >> +  return true;
> >> +}
> >> +
> >>
> >>  /* Return true for types that could be supported as SIMD return or
> >>     argument types.  */
> >> @@ -34370,6 +34391,9 @@ aarch64_libgcc_floating_mode_supported_p
> >>  #undef TARGET_ESTIMATED_POLY_VALUE
> >>  #define TARGET_ESTIMATED_POLY_VALUE aarch64_estimated_poly_value
> >>
> >> +#undef TARGET_POLY_INT_CST_VALUE_RANGE
> >> +#define TARGET_POLY_INT_CST_VALUE_RANGE
> aarch64_poly_int_cst_value_range
> >> +
> >>  #undef TARGET_ATTRIBUTE_TABLE
> >>  #define TARGET_ATTRIBUTE_TABLE aarch64_attribute_table
> >>
> >> diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
> >> index
> 6178db74d2fc5d14e8618ee655de05739cb3207c..bfb7e94cf163a75688e6f
> 99473bad4864a57fb24 100644
> >> --- a/gcc/doc/tm.texi
> >> +++ b/gcc/doc/tm.texi
> >> @@ -7509,6 +7509,14 @@ the @code{POLY_VALUE_MIN},
> @code{POLY_VALUE_MAX} and
> >>  implementation returns the lowest possible value of @var{val}.
> >>  @end deftypefn
> >>
> >> +@deftypefn {Target Hook} bool TARGET_POLY_INT_CST_VALUE_RANGE
> (poly_int64 @var{val}, HOST_WIDE_INT *@var{min_val}, HOST_WIDE_INT
> *@var{max_val})
> >> +Return true if the target can give a conservative inclusive range for
> >> +the runtime value of @var{val}, storing the lower bound in @var{min_val}
> >> +and the upper bound in @var{max_val}.  This hook is used for correctness
> >> +rather than for costing, so the returned bounds must be safe for all
> >> +architecturally-valid runtime values.
> >> +@end deftypefn
> >> +
> >>  @deftypefn {Target Hook} bool TARGET_AVOID_STORE_FORWARDING_P
> (vec<store_fwd_info>, @var{rtx}, @var{int}, @var{bool})
> >>  Given a list of stores and a load instruction that reads from the location
> >>  of the stores, this hook decides if it's profitable to emit additional 
> >> code
> >> diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
> >> index
> 0a97b0fa2e44014fa9e27d07a06d407a25e1ecc4..3aae8b2de9a3ecee41803
> a5dcba8f31b4af55b93 100644
> >> --- a/gcc/doc/tm.texi.in
> >> +++ b/gcc/doc/tm.texi.in
> >> @@ -4812,6 +4812,8 @@ Define this macro if a non-short-circuit
> operation produced by
> >>
> >>  @hook TARGET_ESTIMATED_POLY_VALUE
> >>
> >> +@hook TARGET_POLY_INT_CST_VALUE_RANGE
> >> +
> >>  @hook TARGET_AVOID_STORE_FORWARDING_P
> >>
> >>  @node Scheduling
> >> diff --git a/gcc/target.def b/gcc/target.def
> >> index
> 884fe1bd57e17b3ae92cd052a340f02a2559da31..aab20e89bb16fd9bb7d7
> 0d1c403268a65990a3e4 100644
> >> --- a/gcc/target.def
> >> +++ b/gcc/target.def
> >> @@ -4217,6 +4217,16 @@ implementation returns the lowest possible
> value of @var{val}.",
> >>   HOST_WIDE_INT, (poly_int64 val, poly_value_estimate_kind kind),
> >>   default_estimated_poly_value)
> >>
> >> +DEFHOOK
> >> +(poly_int_cst_value_range,
> >> + "Return true if the target can give a conservative inclusive range for\n\
> >> +the runtime value of @var{val}, storing the lower bound in
> @var{min_val}\n\
> >> +and the upper bound in @var{max_val}.  This hook is used for
> correctness\n\
> >> +rather than for costing, so the returned bounds must be safe for all\n\
> >> +architecturally-valid runtime values.",
> >> + bool, (poly_int64 val, HOST_WIDE_INT *min_val, HOST_WIDE_INT
> *max_val),
> >> + default_poly_int_cst_value_range)
> >> +
> >>  /* Permit speculative instructions in delay slots during delayed-branch
> >>     scheduling.  */
> >>  DEFHOOK
> >> diff --git a/gcc/targhooks.cc b/gcc/targhooks.cc
> >> index
> 388f696c8aa1050edbbc7efbdb01db60d84b4fe0..71f6f769e50298e32434e
> e94e87a1f3a3dfeef69 100644
> >> --- a/gcc/targhooks.cc
> >> +++ b/gcc/targhooks.cc
> >> @@ -2164,6 +2164,19 @@ default_estimated_poly_value (poly_int64 x,
> poly_value_estimate_kind)
> >>    return x.coeffs[0];
> >>  }
> >>
> >> +/* The default implementation of TARGET_POLY_INT_CST_VALUE_RANGE.
> */
> >> +
> >> +bool
> >> +default_poly_int_cst_value_range (poly_int64 x, HOST_WIDE_INT
> *min_val,
> >> +                            HOST_WIDE_INT *max_val)
> >> +{
> >> +  if (!x.is_constant (min_val))
> >> +    return false;
> >> +
> >> +  *max_val = *min_val;
> >> +  return true;
> >> +}
> >> +
> >>  /* For hooks which use the MOVE_RATIO macro, this gives the legacy
> default
> >>     behavior.  SPEED_P is true if we are compiling for speed.  */
> >>
> >> diff --git a/gcc/targhooks.h b/gcc/targhooks.h
> >> index
> 86de6ef8a699d72a36c665c697c89ca58314f3fe..90ebc1f514d47ca736c14a
> 641c69ab94f968bb1b 100644
> >> --- a/gcc/targhooks.h
> >> +++ b/gcc/targhooks.h
> >> @@ -246,6 +246,8 @@ extern int default_frame_allocation_cost
> (frame_cost_type,
> >>  extern bool default_slow_unaligned_access (machine_mode, unsigned
> int);
> >>  extern HOST_WIDE_INT default_estimated_poly_value (poly_int64,
> >>                                               poly_value_estimate_kind);
> >> +extern bool default_poly_int_cst_value_range (poly_int64,
> HOST_WIDE_INT *,
> >> +                                        HOST_WIDE_INT *);
> >>
> >>  extern bool default_use_by_pieces_infrastructure_p (unsigned
> HOST_WIDE_INT,
> >>                                                unsigned int,
> >> diff --git a/gcc/testsuite/gcc.target/aarch64/sve/cnt_fold_7.c
> b/gcc/testsuite/gcc.target/aarch64/sve/cnt_fold_7.c
> >> new file mode 100644
> >> index
> 0000000000000000000000000000000000000000..d64933527da7e3e85f
> 37dc231355eb4cd30a1422
> >> --- /dev/null
> >> +++ b/gcc/testsuite/gcc.target/aarch64/sve/cnt_fold_7.c
> >> @@ -0,0 +1,154 @@
> >> +/* { dg-do compile } */
> >> +/* { dg-options "-O2" } */
> >> +/* { dg-final { check-function-bodies "**" "" } } */
> >> +
> >> +#include <arm_sve.h>
> >> +
> >> +/*
> >> +** b_lt_257:
> >> +**        mov     w0, 1
> >> +**        ret
> >> +*/
> >> +int
> >> +b_lt_257 (void)
> >> +{
> >> +  unsigned int vl = svcntb ();
> >> +
> >> +  return vl < 257;
> >> +}
> >> +
> >> +/*
> >> +** h_ge_8:
> >> +**        mov     w0, 1
> >> +**        ret
> >> +*/
> >> +int
> >> +h_ge_8 (void)
> >> +{
> >> +  return svcnth () >= 8;
> >> +}
> >> +
> >> +/*
> >> +** w_le_64:
> >> +**        mov     w0, 1
> >> +**        ret
> >> +*/
> >> +int
> >> +w_le_64 (void)
> >> +{
> >> +  return svcntw () <= 64;
> >> +}
> >> +
> >> +/*
> >> +** d_gt_1:
> >> +**        mov     w0, 1
> >> +**        ret
> >> +*/
> >> +int
> >> +d_gt_1 (void)
> >> +{
> >> +  return svcntd () > 1;
> >> +}
> >> +
> >> +/*
> >> +** b_ne_0:
> >> +**        mov     w0, 1
> >> +**        ret
> >> +*/
> >> +int
> >> +b_ne_0 (void)
> >> +{
> >> +  return svcntb () != 0;
> >> +}
> >> +
> >> +/*
> >> +** b_le_15:
> >> +**        mov     w0, 0
> >> +**        ret
> >> +*/
> >> +int
> >> +b_le_15 (void)
> >> +{
> >> +  return svcntb () <= 15;
> >> +}
> >> +
> >> +/*
> >> +** h_lt_8:
> >> +**        mov     w0, 0
> >> +**        ret
> >> +*/
> >> +int
> >> +h_lt_8 (void)
> >> +{
> >> +  return svcnth () < 8;
> >> +}
> >> +
> >> +/*
> >> +** w_gt_64:
> >> +**        mov     w0, 0
> >> +**        ret
> >> +*/
> >> +int
> >> +w_gt_64 (void)
> >> +{
> >> +  return svcntw () > 64;
> >> +}
> >> +
> >> +/*
> >> +** d_eq_0:
> >> +**        mov     w0, 0
> >> +**        ret
> >> +*/
> >> +int
> >> +d_eq_0 (void)
> >> +{
> >> +  return svcntd () == 0;
> >> +}
> >> +
> >> +/*
> >> +** b_lt_256:
> >> +**        cntb    x0
> >> +**        cmp     x0, 256
> >> +**        cset    w0, cc
> >> +**        ret
> >> +*/
> >> +int
> >> +b_lt_256 (void)
> >> +{
> >> +  return svcntb () < 256;
> >> +}
> >> +
> >> +/*
> >> +** w_gt_4:
> >> +**        cntw    x0
> >> +**        cmp     x0, 4
> >> +**        cset    w0, hi
> >> +**        ret
> >> +*/
> >> +int
> >> +w_gt_4 (void)
> >> +{
> >> +  return svcntw () > 4;
> >> +}
> >> +
> >> +/*
> >> +** b_pat_all_lt_257:
> >> +**        mov     w0, 1
> >> +**        ret
> >> +*/
> >> +int
> >> +b_pat_all_lt_257 (void)
> >> +{
> >> +  return svcntb_pat (SV_ALL) < 257;
> >> +}
> >> +
> >> +/*
> >> +** w_pat_all_le_64:
> >> +**        mov     w0, 1
> >> +**        ret
> >> +*/
> >> +int
> >> +w_pat_all_le_64 (void)
> >> +{
> >> +  return svcntw_pat (SV_ALL) <= 64;
> >> +}
> >> diff --git a/gcc/testsuite/gcc.target/aarch64/sve/cnt_fold_7_run.c
> b/gcc/testsuite/gcc.target/aarch64/sve/cnt_fold_7_run.c
> >> new file mode 100644
> >> index
> 0000000000000000000000000000000000000000..59217ab343ec03509b
> ccc6d12098e6264adaa8b1
> >> --- /dev/null
> >> +++ b/gcc/testsuite/gcc.target/aarch64/sve/cnt_fold_7_run.c
> >> @@ -0,0 +1,47 @@
> >> +/* { dg-do run { target aarch64_sve_hw } } */
> >> +/* { dg-options "-O2" } */
> >> +
> >> +#include <arm_sve.h>
> >> +
> >> +#define CHECK(EXPR) \
> >> +  do \
> >> +    { \
> >> +      if (!(EXPR)) \
> >> +  __builtin_abort (); \
> >> +    } \
> >> +  while (0)
> >> +
> >> +int
> >> +main (void)
> >> +{
> >> +  unsigned int b = svcntb ();
> >> +  unsigned int h = svcnth ();
> >> +  unsigned int w = svcntw ();
> >> +  unsigned int d = svcntd ();
> >> +
> >> +  CHECK (b >= 16 && b <= 256);
> >> +  CHECK (h >= 8 && h <= 128);
> >> +  CHECK (w >= 4 && w <= 64);
> >> +  CHECK (d >= 2 && d <= 32);
> >> +
> >> +  CHECK (b < 257);
> >> +  CHECK (h >= 8);
> >> +  CHECK (w <= 64);
> >> +  CHECK (d > 1);
> >> +  CHECK (b != 0);
> >> +
> >> +  CHECK (!(b <= 15));
> >> +  CHECK (!(h < 8));
> >> +  CHECK (!(w > 64));
> >> +  CHECK (!(d == 0));
> >> +
> >> +  CHECK ((svcntb () < 256) == (b < 256));
> >> +  CHECK ((svcntw () > 4) == (w > 4));
> >> +
> >> +  CHECK (svcntb_pat (SV_ALL) == b);
> >> +  CHECK (svcntw_pat (SV_ALL) == w);
> >> +  CHECK (svcntb_pat (SV_ALL) < 257);
> >> +  CHECK (svcntw_pat (SV_ALL) <= 64);
> >> +
> >> +  return 0;
> >> +}
> >> diff --git a/gcc/testsuite/gcc.target/aarch64/sve/slp_12.c
> b/gcc/testsuite/gcc.target/aarch64/sve/slp_12.c
> >> index
> 0b9f8d9bdfaaf868ba0bd73ae83894c68e1613f8..0b1fef4d23a89b4fa46b45
> a2662924247eedce17 100644
> >> --- a/gcc/testsuite/gcc.target/aarch64/sve/slp_12.c
> >> +++ b/gcc/testsuite/gcc.target/aarch64/sve/slp_12.c
> >> @@ -47,14 +47,12 @@ TEST_ALL (VEC_PERM)
> >>
> >>  /* We should use WHILEs for all accesses.  */
> >>  /* { dg-final { scan-assembler-times {\twhilelo\tp[0-7]\.b} 20 } } */
> >> -/* { dg-final { scan-assembler-times {\twhilelo\tp[0-7]\.h} 20 } } */
> >> -/* { dg-final { scan-assembler-times {\twhilelo\tp[0-7]\.s} 30 } } */
> >> -/* { dg-final { scan-assembler-times {\twhilelo\tp[0-7]\.d} 30 } } */
> >> +/* { dg-final { scan-assembler-times {\twhilelo\tp[0-7]\.h} 18 } } */
> >> +/* { dg-final { scan-assembler-times {\twhilelo\tp[0-7]\.s} 24 } } */
> >> +/* { dg-final { scan-assembler-times {\twhilelo\tp[0-7]\.d} 21 } } */
> >>
> >>  /* 6 for the 8-bit types and 2 for the 16-bit types.  */
> >>  /* { dg-final { scan-assembler-times {\tuqdecb\t} 8 } } */
> >> -/* 4 for the 16-bit types and 3 for the 32-bit types.  */
> >> -/* { dg-final { scan-assembler-times {\tuqdech\t} 7 } } */
> >> -/* 6 for the 32-bit types and 3 for the 64-bit types.  */
> >> -/* { dg-final { scan-assembler-times {\tuqdecw\t} 9 } } */
> >> -/* { dg-final { scan-assembler-times {\tuqdecd\t} 6 } } */
> >> +/* { dg-final { scan-assembler-times {\tuqdech\t} 2 } } */
> >> +/* { dg-final { scan-assembler-times {\tuqdecw\t} 3 } } */
> >> +/* { dg-final { scan-assembler-not {\tuqdecd\t} } } */
> >> diff --git a/gcc/value-query.cc b/gcc/value-query.cc
> >> index
> decf4e4d0ac0eddb2dd6657072bf7c62c2e2ed00..5ce7fac4caeddccd62b587
> d11b2174a1d0979bac 100644
> >> --- a/gcc/value-query.cc
> >> +++ b/gcc/value-query.cc
> >> @@ -32,6 +32,7 @@ along with GCC; see the file COPYING3.  If not see
> >>  #include "alloc-pool.h"
> >>  #include "gimple-range.h"
> >>  #include "value-range-storage.h"
> >> +#include "target.h"
> >>
> >>  // range_query default methods.
> >>
> >> @@ -404,7 +405,20 @@ 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);
> >> +    HOST_WIDE_INT min_val, max_val;
> >> +    if (tree_fits_poly_int64_p (expr)
> >> +        && targetm.poly_int_cst_value_range (tree_to_poly_int64 (expr),
> >> +                                             &min_val, &max_val))
> >> +      {
> >> +        wide_int min = wi::shwi (min_val, precision);
> >> +        wide_int max = wi::shwi (max_val, precision);
> >> +        if (wi::le_p (min, max, TYPE_SIGN (type)))
> >> +          as_a <irange> (r).set (type, min, max);
> >> +        else
> >> +          r.set_varying (type);
> >> +      }
> >> +    else
> >> +      r.set_varying (type);
> >>      r.update_bitmask ({ wi::zero (precision), get_nonzero_bits (expr) });
> >>      return true;
> >>    }
> >>
> >>
> >>

Reply via email to