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;
> > }
> >
> >
> > --
@@ -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;
}