tree-optimization: Query ranger for non-SSA niter bound expressions
Checks
| Context |
Check |
Description |
| linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_simplebootstrap_build--master-aarch64-bootstrap |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_gcc_build--master-arm |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_gcc_check--master-aarch64 |
success
|
Test passed
|
| linaro-tcwg-bot/tcwg_gcc_check--master-arm |
success
|
Test passed
|
| linaro-tcwg-bot/tcwg_simplebootstrap_build--master-arm-bootstrap |
success
|
Build passed
|
Commit Message
determine_value_range only queried ranger when VAR was an SSA_NAME.
However, number_of_iterations_exit_assumptions calls
expand_simple_operations on the IV bases before asking for the range.
This can expose simple GENERIC expressions, including conversions, even
when the original value had an SSA_NAME with useful range information.
Ranger can analyze such expressions at a given program point, so query it
for integral bound expressions using the loop header as context rather
than restricting the query to SSA_NAMEs. This lets niter analysis
recover the narrower range for value-preserving conversions such as
uint8_t to int, while still handling non-value-preserving conversions and
wrapping expressions conservatively.
The new test covers a direct converted uint8_t bound, a masked uint16_t
bound whose useful range comes from ranger, a guarded uint16_t bound
whose range is context-sensitive, a non-value-preserving uint8_t to
int8_t conversion, and a wrapping unsigned expression.
Bootstrapped and regression tested on aarch64-unknown-linux-gnu.
gcc/ChangeLog:
* tree-ssa-loop-niter.cc (determine_value_range): Query ranger
for integral expressions, not only SSA_NAMEs. Pass the loop
header context to range queries.
gcc/testsuite/ChangeLog:
* gcc.target/aarch64/sve2/niter-convert-range.c: New test.
---
.../aarch64/sve2/niter-convert-range.c | 68 +++++++++++++++++++
gcc/tree-ssa-loop-niter.cc | 10 +--
2 files changed, 74 insertions(+), 4 deletions(-)
create mode 100644 gcc/testsuite/gcc.target/aarch64/sve2/niter-convert-range.c
Comments
On Tue, 2 Jun 2026, Abhishek Kaushik wrote:
> determine_value_range only queried ranger when VAR was an SSA_NAME.
> However, number_of_iterations_exit_assumptions calls
> expand_simple_operations on the IV bases before asking for the range.
> This can expose simple GENERIC expressions, including conversions, even
> when the original value had an SSA_NAME with useful range information.
>
> Ranger can analyze such expressions at a given program point, so query it
> for integral bound expressions using the loop header as context rather
> than restricting the query to SSA_NAMEs. This lets niter analysis
> recover the narrower range for value-preserving conversions such as
> uint8_t to int, while still handling non-value-preserving conversions and
> wrapping expressions conservatively.
>
> The new test covers a direct converted uint8_t bound, a masked uint16_t
> bound whose useful range comes from ranger, a guarded uint16_t bound
> whose range is context-sensitive, a non-value-preserving uint8_t to
> int8_t conversion, and a wrapping unsigned expression.
>
> Bootstrapped and regression tested on aarch64-unknown-linux-gnu.
>
> gcc/ChangeLog:
>
> * tree-ssa-loop-niter.cc (determine_value_range): Query ranger
> for integral expressions, not only SSA_NAMEs. Pass the loop
> header context to range queries.
>
> gcc/testsuite/ChangeLog:
>
> * gcc.target/aarch64/sve2/niter-convert-range.c: New test.
> ---
> .../aarch64/sve2/niter-convert-range.c | 68 +++++++++++++++++++
> gcc/tree-ssa-loop-niter.cc | 10 +--
> 2 files changed, 74 insertions(+), 4 deletions(-)
> create mode 100644 gcc/testsuite/gcc.target/aarch64/sve2/niter-convert-range.c
>
> diff --git a/gcc/testsuite/gcc.target/aarch64/sve2/niter-convert-range.c b/gcc/testsuite/gcc.target/aarch64/sve2/niter-convert-range.c
> new file mode 100644
> index 00000000000..c96b2f3999c
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/sve2/niter-convert-range.c
> @@ -0,0 +1,68 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -march=armv8.3-a+sve2 -fdump-tree-vect-details" } */
> +
> +#include <stdint.h>
> +
> +int
> +foo (char *buf, uint8_t len)
> +{
> + int x = 0;
> +
> + for (int16_t i = 0, y = 0; i < len; i++, y = i * 10)
> + x += (int) y * (buf[i] - '0');
> +
> + return x;
> +}
> +
> +int
> +masked_len_foo (unsigned char *buf, uint16_t len)
> +{
> + uint16_t n = len & 255;
> + int x = 0;
> +
> + for (int16_t i = 0, y = 0; i < n; i++, y = i * 10)
> + x += (int) y * (buf[i] - '0');
> +
> + return x;
> +}
> +
> +int
> +guarded_len_foo (char *buf, uint16_t len)
> +{
> + if (len > 255)
> + return 0;
> +
> + int x = 0;
> +
> + for (int16_t i = 0, y = 0; i < len; i++, y = i * 10)
> + x += (int) y * (buf[i] - '0');
> +
> + return x;
> +}
> +
> +int
> +non_value_preserving (unsigned char *buf, uint8_t len)
> +{
> + int x = 0;
> +
> + for (int16_t i = 0, y = 0; i < (int) (int8_t) len; i++, y = i * 10)
> + x += (int) y * (buf[i] - '0');
> +
> + return x;
> +}
> +
> +unsigned
> +niter_convert_range_wrap (unsigned char *buf, uint8_t len)
> +{
> + unsigned x = 0;
> +
> + for (unsigned i = 0, y = 0; i < (unsigned) len - 1; i++, y = i * 10)
> + x += y * (buf[i] - '0');
> +
> + return x;
> +}
> +
> +/* { dg-final { scan-tree-dump-times {bounds on difference of bases: 0 [.][.][.] 126} 3 "vect" } } */
> +/* { dg-final { scan-tree-dump-times {bounds on difference of bases: 0 [.][.][.] 254} 9 "vect" } } */
> +/* { dg-final { scan-tree-dump-times {bounds on difference of bases: -1 [.][.][.] 4294967294} 3 "vect" } } */
> +/* { dg-final { scan-tree-dump-times "loop vectorized using variable length vectors" 5 "vect" } } */
> diff --git a/gcc/tree-ssa-loop-niter.cc b/gcc/tree-ssa-loop-niter.cc
> index f179be1ffc7..b3a0aebfba2 100644
> --- a/gcc/tree-ssa-loop-niter.cc
> +++ b/gcc/tree-ssa-loop-niter.cc
> @@ -356,15 +356,16 @@ determine_value_range (class loop *loop, tree type, tree var, mpz_t off,
> get_type_static_bounds (type, min, max);
>
> /* See if we have some range info from VRP. */
> - if (TREE_CODE (var) == SSA_NAME && INTEGRAL_TYPE_P (type))
> + if (INTEGRAL_TYPE_P (type))
> {
> edge e = loop_preheader_edge (loop);
> signop sgn = TYPE_SIGN (type);
> gphi_iterator gsi;
> + gimple *ctx = last_nondebug_stmt (loop->header);
>
> /* Either for VAR itself... */
> int_range_max var_range (TREE_TYPE (var));
> - get_range_query (cfun)->range_of_expr (var_range, var);
> + get_range_query (cfun)->range_of_expr (var_range, var, ctx);
> if (var_range.varying_p () || var_range.undefined_p ())
> rtype = VR_VARYING;
> else
> @@ -383,7 +384,8 @@ determine_value_range (class loop *loop, tree type, tree var, mpz_t off,
> gphi *phi = gsi.phi ();
> if (PHI_ARG_DEF_FROM_EDGE (phi, e) == var
> && get_range_query (cfun)->range_of_expr (phi_range,
> - gimple_phi_result (phi))
> + gimple_phi_result (phi),
> + ctx)
I believe you can use ->range_on_edge here, so no need to compute 'ctx'
but instead pass in the preheader edge as context (last_nondebug_stmt
of the loop header looks not exactly correct).
> && !phi_range.varying_p ()
> && !phi_range.undefined_p ())
> {
> @@ -404,7 +406,7 @@ determine_value_range (class loop *loop, tree type, tree var, mpz_t off,
> if (wi::gt_p (minv, maxv, sgn))
> {
> int_range_max vr (TREE_TYPE (var));
> - get_range_query (cfun)->range_of_expr (vr, var);
> + get_range_query (cfun)->range_of_expr (vr, var, ctx);
Similar here. Would be nice to amend the functions documentation
that the range computation is valid on the entry edge of the loop.
Thanks,
Richard.
> if (vr.varying_p () || vr.undefined_p ())
> rtype = VR_VARYING;
> else
>
new file mode 100644
@@ -0,0 +1,68 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -march=armv8.3-a+sve2 -fdump-tree-vect-details" } */
+
+#include <stdint.h>
+
+int
+foo (char *buf, uint8_t len)
+{
+ int x = 0;
+
+ for (int16_t i = 0, y = 0; i < len; i++, y = i * 10)
+ x += (int) y * (buf[i] - '0');
+
+ return x;
+}
+
+int
+masked_len_foo (unsigned char *buf, uint16_t len)
+{
+ uint16_t n = len & 255;
+ int x = 0;
+
+ for (int16_t i = 0, y = 0; i < n; i++, y = i * 10)
+ x += (int) y * (buf[i] - '0');
+
+ return x;
+}
+
+int
+guarded_len_foo (char *buf, uint16_t len)
+{
+ if (len > 255)
+ return 0;
+
+ int x = 0;
+
+ for (int16_t i = 0, y = 0; i < len; i++, y = i * 10)
+ x += (int) y * (buf[i] - '0');
+
+ return x;
+}
+
+int
+non_value_preserving (unsigned char *buf, uint8_t len)
+{
+ int x = 0;
+
+ for (int16_t i = 0, y = 0; i < (int) (int8_t) len; i++, y = i * 10)
+ x += (int) y * (buf[i] - '0');
+
+ return x;
+}
+
+unsigned
+niter_convert_range_wrap (unsigned char *buf, uint8_t len)
+{
+ unsigned x = 0;
+
+ for (unsigned i = 0, y = 0; i < (unsigned) len - 1; i++, y = i * 10)
+ x += y * (buf[i] - '0');
+
+ return x;
+}
+
+/* { dg-final { scan-tree-dump-times {bounds on difference of bases: 0 [.][.][.] 126} 3 "vect" } } */
+/* { dg-final { scan-tree-dump-times {bounds on difference of bases: 0 [.][.][.] 254} 9 "vect" } } */
+/* { dg-final { scan-tree-dump-times {bounds on difference of bases: -1 [.][.][.] 4294967294} 3 "vect" } } */
+/* { dg-final { scan-tree-dump-times "loop vectorized using variable length vectors" 5 "vect" } } */
@@ -356,15 +356,16 @@ determine_value_range (class loop *loop, tree type, tree var, mpz_t off,
get_type_static_bounds (type, min, max);
/* See if we have some range info from VRP. */
- if (TREE_CODE (var) == SSA_NAME && INTEGRAL_TYPE_P (type))
+ if (INTEGRAL_TYPE_P (type))
{
edge e = loop_preheader_edge (loop);
signop sgn = TYPE_SIGN (type);
gphi_iterator gsi;
+ gimple *ctx = last_nondebug_stmt (loop->header);
/* Either for VAR itself... */
int_range_max var_range (TREE_TYPE (var));
- get_range_query (cfun)->range_of_expr (var_range, var);
+ get_range_query (cfun)->range_of_expr (var_range, var, ctx);
if (var_range.varying_p () || var_range.undefined_p ())
rtype = VR_VARYING;
else
@@ -383,7 +384,8 @@ determine_value_range (class loop *loop, tree type, tree var, mpz_t off,
gphi *phi = gsi.phi ();
if (PHI_ARG_DEF_FROM_EDGE (phi, e) == var
&& get_range_query (cfun)->range_of_expr (phi_range,
- gimple_phi_result (phi))
+ gimple_phi_result (phi),
+ ctx)
&& !phi_range.varying_p ()
&& !phi_range.undefined_p ())
{
@@ -404,7 +406,7 @@ determine_value_range (class loop *loop, tree type, tree var, mpz_t off,
if (wi::gt_p (minv, maxv, sgn))
{
int_range_max vr (TREE_TYPE (var));
- get_range_query (cfun)->range_of_expr (vr, var);
+ get_range_query (cfun)->range_of_expr (vr, var, ctx);
if (vr.varying_p () || vr.undefined_p ())
rtype = VR_VARYING;
else