middle-end: Teach ranger about POLY_INT_CST ranges for VRP.
Checks
Commit Message
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.
---
--
Comments
Hi Tamar,
I like the idea and approach. A code review comment inline...
> On 7 Jul 2026, at 12:17, Tamar Christina <tamar.christina@arm.com> 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..7e12bb0e2c003b80008dd0f3c9fa304c7273ef39 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. */
This sounds very similar to aarch64_estimated_poly_value, though I guess this has to be always conservatively correct. I’m okay with your approach, but have you considered extending the TARGET_ESTIMATED_POLY_VALUE hook to take a “STRICT” requirement?
> +
> +static bool
> +aarch64_poly_int_cst_value_range (poly_int64 val, HOST_WIDE_INT *min_val,
> + HOST_WIDE_INT *max_val)
> +{
> + unsigned int const_vg;
> + if (aarch64_sve_vg.is_constant (&const_vg))
> + {
> + *min_val = val.coeffs[0];
> + *max_val = *min_val;
> + return true;
> + }
> +
> + *min_val = val.coeffs[0];
> + *max_val = val.coeffs[0] + val.coeffs[1] * 15;
> + 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..bfb7e94cf163a75688e6f99473bad4864a57fb24 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..3aae8b2de9a3ecee41803a5dcba8f31b4af55b93 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..aab20e89bb16fd9bb7d70d1c403268a65990a3e4 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..71f6f769e50298e32434ee94e87a1f3a3dfeef69 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))
Should be *min_val?
> + return false;
> +
> + *max_val = *min_val;
In the use of this code in value-query.cc you’re passing uninitialized variables so this assignment looks dangerous.
I think it should at least check/assert for NULL pointers for min_val and max_val.
Thanks,
Kyrill
> + 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..90ebc1f514d47ca736c14a641c69ab94f968bb1b 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..d64933527da7e3e85f37dc231355eb4cd30a1422
> --- /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..59217ab343ec03509bccc6d12098e6264adaa8b1
> --- /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..0b1fef4d23a89b4fa46b45a2662924247eedce17 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..5ce7fac4caeddccd62b587d11b2174a1d0979bac 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;
> }
>
>
> --
> <rb20685.patch>
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..7e12bb0e2c003b80008dd0f3c9fa304c7273ef39 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.
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?
> +{
> + unsigned int const_vg;
> + if (aarch64_sve_vg.is_constant (&const_vg))
> + {
> + *min_val = val.coeffs[0];
> + *max_val = *min_val;
> + return true;
> + }
> +
> + *min_val = val.coeffs[0];
> + *max_val = val.coeffs[0] + val.coeffs[1] * 15;
> + 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..bfb7e94cf163a75688e6f99473bad4864a57fb24 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..3aae8b2de9a3ecee41803a5dcba8f31b4af55b93 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..aab20e89bb16fd9bb7d70d1c403268a65990a3e4 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..71f6f769e50298e32434ee94e87a1f3a3dfeef69 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..90ebc1f514d47ca736c14a641c69ab94f968bb1b 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..d64933527da7e3e85f37dc231355eb4cd30a1422
> --- /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..59217ab343ec03509bccc6d12098e6264adaa8b1
> --- /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..0b1fef4d23a89b4fa46b45a2662924247eedce17 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..5ce7fac4caeddccd62b587d11b2174a1d0979bac 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;
> }
>
>
>
Richard Biener <rguenther@suse.de> 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..7e12bb0e2c003b80008dd0f3c9fa304c7273ef39 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.
>
> 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.
>> +{
>> + 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.
>> +
>> + *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.
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..bfb7e94cf163a75688e6f99473bad4864a57fb24 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..3aae8b2de9a3ecee41803a5dcba8f31b4af55b93 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..aab20e89bb16fd9bb7d70d1c403268a65990a3e4 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..71f6f769e50298e32434ee94e87a1f3a3dfeef69 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..90ebc1f514d47ca736c14a641c69ab94f968bb1b 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..d64933527da7e3e85f37dc231355eb4cd30a1422
>> --- /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..59217ab343ec03509bccc6d12098e6264adaa8b1
>> --- /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..0b1fef4d23a89b4fa46b45a2662924247eedce17 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..5ce7fac4caeddccd62b587d11b2174a1d0979bac 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;
>> }
>>
>>
>>
> -----Original Message-----
> From: Richard Sandiford <rdsandiford@googlemail.com>
> Sent: 07 July 2026 13:04
> To: Richard Biener <rguenther@suse.de>
> Cc: Tamar Christina <Tamar.Christina@arm.com>; gcc-patches@gcc.gnu.org;
> nd <nd@arm.com>; Richard Earnshaw <Richard.Earnshaw@arm.com>;
> ktkachov@gcc.gnu.org; Alex Coplan <Alex.Coplan@arm.com>;
> andrew.pinski@oss.qualcomm.com; Wilco Dijkstra
> <Wilco.Dijkstra@arm.com>; Alice Carlotti <Alice.Carlotti@arm.com>;
> aldy@quesejoda.com; amacleod@redhat.com
> Subject: Re: [patch]middle-end: Teach ranger about POLY_INT_CST ranges for
> VRP.
>
> Richard Biener <rguenther@suse.de> 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;
> >> }
> >>
> >>
> >>
Tamar Christina <Tamar.Christina@arm.com> writes:
>> >> +{
>> >> + 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.
Not that I'm aware. FMV can't do anything that a target attribute
couldn't do.
>> >> +
>> >> + *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?
Yeah, sounds good. Using -1 as a wildcard matches what we do for
the size/range poly-int.h functions.
But maybe the hook should return a poly_uint64, with coefficient 0 being
ignored. That would cope with multiple indeterminates (which most code
does try to support, even though that hasn't been used yet).
Richard
On 07/07/2026 14:02, Richard Sandiford wrote:
> Tamar Christina <Tamar.Christina@arm.com> writes:
>>>>> +{
>>>>> + 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.
>
> Not that I'm aware. FMV can't do anything that a target attribute
> couldn't do.
FMV for specific vector lengths is something we are interested in
adding, this is important for intrinsics such as svmmla_f64 which
require certain vector lengths.
However, I agree that it likely will be challenging...
>
>>>>> +
>>>>> + *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?
>
> Yeah, sounds good. Using -1 as a wildcard matches what we do for
> the size/range poly-int.h functions.
>
> But maybe the hook should return a poly_uint64, with coefficient 0 being
> ignored. That would cope with multiple indeterminates (which most code
> does try to support, even though that hasn't been used yet).
>
> Richard
On Tue, Jul 7, 2026 at 3:18 AM Tamar Christina <tamar.christina@arm.com> 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?
I think min is already detected correctly. It is just max that is needed here.
So maybe the interface should just handle max.
On the other hand, I noticed things like `POLY_INT_CST [16, 16] > 15`
are not optimized right now either.
This is because poly_int_binop does not handle GT_EXPR.
The question becomes for the cases need to handle are formed together
and poly_int_binop will be called?
If so then maybe the interface should just be called from there
instead of the VRP?
Thanks,
Andrea
>
>
> 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..7e12bb0e2c003b80008dd0f3c9fa304c7273ef39 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)
> +{
> + unsigned int const_vg;
> + if (aarch64_sve_vg.is_constant (&const_vg))
> + {
> + *min_val = val.coeffs[0];
> + *max_val = *min_val;
> + return true;
> + }
> +
> + *min_val = val.coeffs[0];
> + *max_val = val.coeffs[0] + val.coeffs[1] * 15;
> + 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..bfb7e94cf163a75688e6f99473bad4864a57fb24 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..3aae8b2de9a3ecee41803a5dcba8f31b4af55b93 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..aab20e89bb16fd9bb7d70d1c403268a65990a3e4 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..71f6f769e50298e32434ee94e87a1f3a3dfeef69 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..90ebc1f514d47ca736c14a641c69ab94f968bb1b 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..d64933527da7e3e85f37dc231355eb4cd30a1422
> --- /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..59217ab343ec03509bccc6d12098e6264adaa8b1
> --- /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..0b1fef4d23a89b4fa46b45a2662924247eedce17 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..5ce7fac4caeddccd62b587d11b2174a1d0979bac 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;
> }
>
>
> --
> -----Original Message-----
> From: Andrea Pinski <andrew.pinski@oss.qualcomm.com>
> Sent: 07 July 2026 16:43
> To: Tamar Christina <Tamar.Christina@arm.com>
> Cc: gcc-patches@gcc.gnu.org; nd <nd@arm.com>; rguenther@suse.de;
> Richard Earnshaw <Richard.Earnshaw@arm.com>; ktkachov@gcc.gnu.org;
> Alex Coplan <Alex.Coplan@arm.com>; Wilco Dijkstra
> <Wilco.Dijkstra@arm.com>; Alice Carlotti <Alice.Carlotti@arm.com>;
> aldy@quesejoda.com; amacleod@redhat.com
> Subject: Re: [patch]middle-end: Teach ranger about POLY_INT_CST ranges for
> VRP.
>
> On Tue, Jul 7, 2026 at 3:18 AM Tamar Christina <tamar.christina@arm.com>
> 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?
>
> I think min is already detected correctly. It is just max that is needed here.
> So maybe the interface should just handle max.
> On the other hand, I noticed things like `POLY_INT_CST [16, 16] > 15`
> are not optimized right now either.
> This is because poly_int_binop does not handle GT_EXPR.
> The question becomes for the cases need to handle are formed together
> and poly_int_binop will be called?
> If so then maybe the interface should just be called from there
> instead of the VRP?
With the patch this fold correctly though. I do prefer this in VRP rather than
a specific folding interface since I'd imagine the range information to be
in general a useful thing to have which can be used for other optimizations.
Thanks,
Tamar
>
> Thanks,
> Andrea
>
> >
>
> >
> > 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)
> > +{
> > + unsigned int const_vg;
> > + if (aarch64_sve_vg.is_constant (&const_vg))
> > + {
> > + *min_val = val.coeffs[0];
> > + *max_val = *min_val;
> > + return true;
> > + }
> > +
> > + *min_val = val.coeffs[0];
> > + *max_val = val.coeffs[0] + val.coeffs[1] * 15;
> > + 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;
> > }
> >
> >
> > --
Tamar Christina <tamar.christina@arm.com> writes:
> 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_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.
>
> ---
>
> diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
> index a1f91dd425efb6c21675b05c909f0fae4a9f49d4..a56c8170c6988d8c75ab3b4059b1b1363e35fccd 100644
> --- a/gcc/config/aarch64/aarch64.cc
> +++ b/gcc/config/aarch64/aarch64.cc
> @@ -31099,6 +31099,21 @@ aarch64_estimated_poly_value (poly_int64 val,
> return val.coeffs[0] + val.coeffs[1] * over_128 / 128;
> }
>
> +/* Implement TARGET_POLY_INT_INDETERMINATE_BOUND. */
> +
> +static poly_uint64
> +aarch64_poly_int_indeterminate_bound ()
> +{
> + unsigned int const_vg;
> + if (aarch64_sve_vg.is_constant (&const_vg))
> + {
> + gcc_assert (const_vg >= 2);
> + return poly_uint64 (0, (const_vg - 2) / 2);
> + }
Could you run a test run with an assert to see whether this ever triggers?
It still looks wrong to me. At the moment we should never use poly_int
for fixed VL, and even if we're trying to be future-proof, the effect
of the patch is to define a reduced range (e.g. [0, 1] for VL256) rather
than a strict constant.
Also, the hook without this test has the advantage of being safe for IPA,
whereas anything that wanted to take per-function VLs into account would
need to take the function into account as well.
I suspect replacing poly_ints with constants is something that would
need to be done (carefully) in some other way, and would be complicated
enough that it would be better not to anticipate it here.
LGTM for the non-ranger parts otherwise, but some comments about the
ranger part below.
> +
> + return poly_uint64 (0, 15);
> +}
> +
>
> /* Return true for types that could be supported as SIMD return or
> argument types. */
> [...]
> diff --git a/gcc/value-query.cc b/gcc/value-query.cc
> index decf4e4d0ac0eddb2dd6657072bf7c62c2e2ed00..01d2aa669f524cd81aa4eb15c6d93f13f67c8491 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,36 @@ 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);
> + /* I wonder if it's useful to set the min bounds for targets that
> + return -1 here. But unsure if a range with only a known lower
> + bound can be specified. */
I think you can just use the TYPE_MAX_VALUE for the upper bound in that case.
> + if (targetm.poly_int_indeterminate_bound
> + && tree_fits_poly_int64_p (expr))
As Richi mentioned earlier, it would be good to make this operate on
wide-ints rather than HWIs.
> + {
> + poly_uint64 indeterminate_bound
> + = targetm.poly_int_indeterminate_bound ();
> + poly_int64 val = tree_to_poly_int64 (expr);
> + HOST_WIDE_INT min_val = val.coeffs[0];
> + HOST_WIDE_INT max_val = val.coeffs[0];
> + for (unsigned int i = 1; i < NUM_POLY_INT_COEFFS; ++i)
> + {
> + HOST_WIDE_INT coeff = val.coeffs[i];
> + unsigned HOST_WIDE_INT bound
> + = indeterminate_bound.coeffs[i];
> + if (coeff < 0)
> + min_val = add_sat_hwi (min_val, coeff * bound);
> + else
> + max_val = add_sat_hwi (max_val, coeff * bound);
add_sat_hwi assumes that the type is int64_t, but tree_fits_poly_int64_p
tests only whether the value (not the full type range) fits in int64_t.
Using wide_ints and TYPE_SIGN, and checking for overflow, would fix that.
We need to account for saturation in the multiplication too.
For wrapping (unsigned) types, saturation of the maximum can affect the
minimum.
E.g. if the 64-bit SVE poly-int:
[INT64_MAX - 1, INT64_MAX - 1]
is unsigned, its range should be varying (unless we want to calculate
every possible coefficient value, or do fancy maths). If the poly-int
is signed, its range should become [0, INT64_MAX].
Thanks,
Richard
> + }
> + 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;
> }
> -----Original Message-----
> From: Richard Sandiford <rdsandiford@googlemail.com>
> Sent: 23 July 2026 10:46
> To: Tamar Christina <Tamar.Christina@arm.com>
> Cc: gcc-patches@gcc.gnu.org; nd <nd@arm.com>; rguenther@suse.de;
> ktkachov@gcc.gnu.org; aldy@quesejoda.com; amacleod@redhat.com
> Subject: Re: [patch]middle-end: Teach ranger about POLY_INT_CST ranges for
> VRP.
>
> Tamar Christina <tamar.christina@arm.com> writes:
> > 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_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.
> >
> > ---
> >
> > diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
> > index
> a1f91dd425efb6c21675b05c909f0fae4a9f49d4..a56c8170c6988d8c75ab3b
> 4059b1b1363e35fccd 100644
> > --- a/gcc/config/aarch64/aarch64.cc
> > +++ b/gcc/config/aarch64/aarch64.cc
> > @@ -31099,6 +31099,21 @@ aarch64_estimated_poly_value (poly_int64
> val,
> > return val.coeffs[0] + val.coeffs[1] * over_128 / 128;
> > }
> >
> > +/* Implement TARGET_POLY_INT_INDETERMINATE_BOUND. */
> > +
> > +static poly_uint64
> > +aarch64_poly_int_indeterminate_bound ()
> > +{
> > + unsigned int const_vg;
> > + if (aarch64_sve_vg.is_constant (&const_vg))
> > + {
> > + gcc_assert (const_vg >= 2);
> > + return poly_uint64 (0, (const_vg - 2) / 2);
> > + }
>
> Could you run a test run with an assert to see whether this ever triggers?
> It still looks wrong to me. At the moment we should never use poly_int
> for fixed VL, and even if we're trying to be future-proof, the effect
> of the patch is to define a reduced range (e.g. [0, 1] for VL256) rather
> than a strict constant.
>
So this is a part I really wasn't sure about. I did remember your previous
comment on this but didn't know how to convey VLS correctly here.
> Also, the hook without this test has the advantage of being safe for IPA,
> whereas anything that wanted to take per-function VLs into account would
> need to take the function into account as well.
So my goal here wasn't to do per function VLS tbf, I was just trying to make
the hook return the right thing for VLS. When I looked around the other
functions in the backend they all seemed to be using aarch64_sve_vg
to determine VLS vs VLA and so I copied the same approach.
I'm aware that for VLS today we already due the foldings since the POLY
become constants so as you say the above is essentially dead code atm.
So perhaps I should just update the documentation for the hook to explicitly
mention it only returns a correct value to use when VLA?
My worry was someone using the hook in a context other than the current
use not knowing/expecting it to only reflect the architectural bounds.
But happy to make it a documentation issue and drop the hunk if you prefer :)
Thanks,
Tamar
>
> I suspect replacing poly_ints with constants is something that would
> need to be done (carefully) in some other way, and would be complicated
> enough that it would be better not to anticipate it here.
>
> LGTM for the non-ranger parts otherwise, but some comments about the
> ranger part below.
>
> > +
> > + return poly_uint64 (0, 15);
> > +}
> > +
> >
> > /* Return true for types that could be supported as SIMD return or
> > argument types. */
> > [...]
> > diff --git a/gcc/value-query.cc b/gcc/value-query.cc
> > index
> decf4e4d0ac0eddb2dd6657072bf7c62c2e2ed00..01d2aa669f524cd81aa4e
> b15c6d93f13f67c8491 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,36 @@ 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);
> > + /* I wonder if it's useful to set the min bounds for targets that
> > + return -1 here. But unsure if a range with only a known lower
> > + bound can be specified. */
>
> I think you can just use the TYPE_MAX_VALUE for the upper bound in that
> case.
>
> > + if (targetm.poly_int_indeterminate_bound
> > + && tree_fits_poly_int64_p (expr))
>
> As Richi mentioned earlier, it would be good to make this operate on
> wide-ints rather than HWIs.
>
> > + {
> > + poly_uint64 indeterminate_bound
> > + = targetm.poly_int_indeterminate_bound ();
> > + poly_int64 val = tree_to_poly_int64 (expr);
> > + HOST_WIDE_INT min_val = val.coeffs[0];
> > + HOST_WIDE_INT max_val = val.coeffs[0];
> > + for (unsigned int i = 1; i < NUM_POLY_INT_COEFFS; ++i)
> > + {
> > + HOST_WIDE_INT coeff = val.coeffs[i];
> > + unsigned HOST_WIDE_INT bound
> > + = indeterminate_bound.coeffs[i];
> > + if (coeff < 0)
> > + min_val = add_sat_hwi (min_val, coeff * bound);
> > + else
> > + max_val = add_sat_hwi (max_val, coeff * bound);
>
> add_sat_hwi assumes that the type is int64_t, but tree_fits_poly_int64_p
> tests only whether the value (not the full type range) fits in int64_t.
> Using wide_ints and TYPE_SIGN, and checking for overflow, would fix that.
>
> We need to account for saturation in the multiplication too.
>
> For wrapping (unsigned) types, saturation of the maximum can affect the
> minimum.
>
> E.g. if the 64-bit SVE poly-int:
>
> [INT64_MAX - 1, INT64_MAX - 1]
>
> is unsigned, its range should be varying (unless we want to calculate
> every possible coefficient value, or do fancy maths). If the poly-int
> is signed, its range should become [0, INT64_MAX].
>
> Thanks,
> Richard
>
> > + }
> > + 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;
> > }
Tamar Christina <Tamar.Christina@arm.com> writes:
>> -----Original Message-----
>> From: Richard Sandiford <rdsandiford@googlemail.com>
>> Sent: 23 July 2026 10:46
>> To: Tamar Christina <Tamar.Christina@arm.com>
>> Cc: gcc-patches@gcc.gnu.org; nd <nd@arm.com>; rguenther@suse.de;
>> ktkachov@gcc.gnu.org; aldy@quesejoda.com; amacleod@redhat.com
>> Subject: Re: [patch]middle-end: Teach ranger about POLY_INT_CST ranges for
>> VRP.
>>
>> Tamar Christina <tamar.christina@arm.com> writes:
>> > 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_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.
>> >
>> > ---
>> >
>> > diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
>> > index
>> a1f91dd425efb6c21675b05c909f0fae4a9f49d4..a56c8170c6988d8c75ab3b
>> 4059b1b1363e35fccd 100644
>> > --- a/gcc/config/aarch64/aarch64.cc
>> > +++ b/gcc/config/aarch64/aarch64.cc
>> > @@ -31099,6 +31099,21 @@ aarch64_estimated_poly_value (poly_int64
>> val,
>> > return val.coeffs[0] + val.coeffs[1] * over_128 / 128;
>> > }
>> >
>> > +/* Implement TARGET_POLY_INT_INDETERMINATE_BOUND. */
>> > +
>> > +static poly_uint64
>> > +aarch64_poly_int_indeterminate_bound ()
>> > +{
>> > + unsigned int const_vg;
>> > + if (aarch64_sve_vg.is_constant (&const_vg))
>> > + {
>> > + gcc_assert (const_vg >= 2);
>> > + return poly_uint64 (0, (const_vg - 2) / 2);
>> > + }
>>
>> Could you run a test run with an assert to see whether this ever triggers?
>> It still looks wrong to me. At the moment we should never use poly_int
>> for fixed VL, and even if we're trying to be future-proof, the effect
>> of the patch is to define a reduced range (e.g. [0, 1] for VL256) rather
>> than a strict constant.
>>
>
> So this is a part I really wasn't sure about. I did remember your previous
> comment on this but didn't know how to convey VLS correctly here.
>
>> Also, the hook without this test has the advantage of being safe for IPA,
>> whereas anything that wanted to take per-function VLs into account would
>> need to take the function into account as well.
>
> So my goal here wasn't to do per function VLS tbf,
Ah, ok. In the below, I'll just stick to the current all-or-nothing VLS then.
> I was just trying to make
> the hook return the right thing for VLS. When I looked around the other
> functions in the backend they all seemed to be using aarch64_sve_vg
> to determine VLS vs VLA and so I copied the same approach.
>
> I'm aware that for VLS today we already due the foldings since the POLY
> become constants so as you say the above is essentially dead code atm.
Unless this has changed: it's not a case of non-constant poly_ints
being folded for VLS. Non-constant poly_ints should never be used at all.
Everything should be a constant from the outset, including things like
mode sizes. VLS should essentially behave like Adv SIMD.
Of course, on AArch64, everything that is stored as a poly_int has
two coefficients. But that's true of Adv SIMD as well. So for VLS,
aarch64_sve_vg is only a poly_int in the same way that GET_MODE_SIZE
(V16QImode) is a poly_int. Both store two coefficients while only
using the first.
This means that we shouldn't need this hook for VLS any more than we do
for Adv SIMD. VLS and Adv SIMD should never generate a POLY_INT_CST
or a CONST_POLY_INT. All the poly_ints they deal with should be constants.
> So perhaps I should just update the documentation for the hook to explicitly
> mention it only returns a correct value to use when VLA?
>
> My worry was someone using the hook in a context other than the current
> use not knowing/expecting it to only reflect the architectural bounds.
>
> But happy to make it a documentation issue and drop the hunk if you prefer :)
The way I see it, the hook is correct for all cases if it returns
{ 0, 16 } unconditionally. That describes the architectural range.
If this hook is used in a context where (a) coefficient 1 is nonzero
but (b) the SVE VL is known, then IMO the problem isn't with the hook,
but with whatever generated that poly_int in the first place.
The behaviour will still be functionally correct. It's just that the
code that generated the poly_int did something unexpected. It should
have generated a constant instead.
So my preference would be to drop the hunk but keep the documentation as-is.
Of course, this would change with per-function VL, but I think we should
design that when we come to it.
Thanks,
Richard
@@ -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)
+{
+ unsigned int const_vg;
+ if (aarch64_sve_vg.is_constant (&const_vg))
+ {
+ *min_val = val.coeffs[0];
+ *max_val = *min_val;
+ return true;
+ }
+
+ *min_val = val.coeffs[0];
+ *max_val = val.coeffs[0] + val.coeffs[1] * 15;
+ 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
@@ -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
@@ -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
@@ -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
@@ -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. */
@@ -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,
new file mode 100644
@@ -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;
+}
new file mode 100644
@@ -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;
+}
@@ -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} } } */
@@ -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;
}