Richard Biener <[email protected]> writes:
> On Tue, 7 Jul 2026, Tamar Christina wrote:
>
>> Hopefully this isn't controversial :)
>>
>> A an area where LLVM generates much better code than GCC today is in
>> optimization of VLA branches guarded by VLA constants.
>>
>> Normally in GIMPLE POLY_INT_CSTs have no upport and lower bound because well,
>> they're poly. However SVE has defined minimum and maximum vector sizes in
>> the architecture[1] and so for us we do have bounds on these constants.
>>
>> [1] https://developer.arm.com/documentation/102476/0101/Introducing-SVE
>>
>> Today LLVM optimizes these simple expressions
>>
>> #include <arm_sve.h>
>>
>> int g (void)
>> {
>> unsigned int vl = svcntb ();
>> return vl < 257;
>> }
>>
>> int h (void)
>> {
>> return svcntw () <= 64;
>> }
>>
>> away to
>>
>> g():
>> mov w0, #1
>> ret
>>
>> h():
>> mov w0, #1
>> ret
>>
>> which is right because both are always true for any SVE vector length.
>>
>> while GCC generates:
>>
>> g():
>> cntb x0
>> cmp w0, 257
>> cset w0, cc
>> ret
>> h():
>> cntw x0
>> cmp x0, 65
>> cset w0, cc
>> ret
>>
>> This patch extends ranger with a target hook poly_int_cst_value_range which
>> is different from the current costing only hooks used in the vectorizer
>> because
>> those hooks can't be relied upon for correctness.
>>
>> This new hook allows us specify the minimum and maximum bounds of a
>> POLY_INT_CST
>> and have ranger use it in range_query::get_tree_range.
>>
>> This gets GCC to fold away many known true or known false comparisons in
>> codegen today and we generate much simpler loop pre-headers.
>>
>> I have kept the AArch64 parts in this patch to get some extra eyes on it, but
>> will split them out on commit.
>>
>> Bootstrapped Regtested on aarch64-none-linux-gnu,
>> arm-none-linux-gnueabihf, x86_64-pc-linux-gnu
>> -m32, -m64 and no issues.
>>
>> Ok for master?
>>
>> Thanks,
>> Tamar
>>
>> gcc/ChangeLog:
>>
>> * config/aarch64/aarch64.cc (aarch64_poly_int_cst_value_range): New.
>> (TARGET_POLY_INT_CST_VALUE_RANGE): Implement hook using it.
>> * target.def (poly_int_cst_value_range): New.
>> * doc/tm.texi.in: Document it.
>> * doc/tm.texi: Regenerate.
>> * targhooks.cc (default_poly_int_cst_value_range): New.
>> * targhooks.h (default_poly_int_cst_value_range): New.
>> * value-query.cc (range_query::get_tree_range): Use it.
>>
>> gcc/testsuite/ChangeLog:
>>
>> * gcc.target/aarch64/sve/slp_12.c: Update testcases
>> * gcc.target/aarch64/sve/cnt_fold_7.c: New test.
>> * gcc.target/aarch64/sve/cnt_fold_7_run.c: New test.
>>
>> ---
>> diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
>> index
>> 72822ca621f52afee144bffa76bc75aac28adae1..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;
>> }
>>
>>
>>