On Tue, Jul 7, 2026 at 3:18 AM Tamar Christina <[email protected]> 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;
> }
>
>
> --