A signed POLY_INT_CST can wrap without undefined behaviour:
fold_convert_const narrows coefficient-wise, so (int8_t) svcntb () is
POLY_INT_CST [16, 16] but -128 for 1024-bit vectors, and GCC does not
treat signed << overflow as undefined.  get_tree_range saturates a
signed bound that overflows, as Richard Sandiford asked in the review of
the POLY_INT_CST range support (<[email protected]>); that
is right for a value that cannot wrap, but here it gives [16, 127], so
(int8_t) svcntb () < 5 folds to false.  Use poly_int_cst_bounds there
and give VARYING when the target has no bound or a bound does not fit
the type, and let can_min_p punt on signed POLY_INT_CSTs whose bounds
do not fit.
Pointer-typed POLY_INT_CSTs, such as (char *) svcntb (), now get VARYING
instead of an ICE.

Bootstrapped and regression tested on x86_64-pc-linux-gnu; regression
tested on aarch64-linux-gnu under qemu (the SVE ACLE, vect, aarch64-sve
and execute tests at VL 128, and at VL 2048 with -march=armv8.2-a+sve).

Assisted-by: Claude Opus 5 (Anthropic)

gcc/ChangeLog:

        * fold-const.cc (poly_int_cst_bounds): Export.
        (maybe_wrapping_poly_p): Handle signed POLY_INT_CSTs.
        * fold-const.h (poly_int_cst_bounds): Declare.
        * value-query.cc: Do not include target.h.
        (range_query::get_tree_range): Use poly_int_cst_bounds.  Give
        VARYING rather than a saturated range when the bounds are unknown
        or do not fit, and for pointer types.

gcc/testsuite/ChangeLog:

        * gcc.target/aarch64/sve/acle/general/cntb_ptr_1.c: New test.
        * gcc.target/aarch64/sve/acle/general/min_poly_2.c: New test.
        * gcc.target/aarch64/sve/acle/general/min_poly_2_run.c: New test.
        * gcc.target/aarch64/sve/acle/general/min_poly_3.c: New test.
        * gcc.target/aarch64/sve/acle/general/min_poly_3_run.c: New test.

Signed-off-by: Dominic P <[email protected]>
---
 gcc/fold-const.cc                             | 16 ++--
 gcc/fold-const.h                              |  1 +
 .../aarch64/sve/acle/general/cntb_ptr_1.c     | 11 +++
 .../aarch64/sve/acle/general/min_poly_2.c     | 88 +++++++++++++++++
 .../aarch64/sve/acle/general/min_poly_2_run.c | 24 +++++
 .../aarch64/sve/acle/general/min_poly_3.c     | 50 ++++++++++
 .../aarch64/sve/acle/general/min_poly_3_run.c | 20 ++++
 gcc/value-query.cc                            | 96 +++----------------
 8 files changed, 214 insertions(+), 92 deletions(-)
 create mode 100644 
gcc/testsuite/gcc.target/aarch64/sve/acle/general/cntb_ptr_1.c
 create mode 100644 
gcc/testsuite/gcc.target/aarch64/sve/acle/general/min_poly_2.c
 create mode 100644 
gcc/testsuite/gcc.target/aarch64/sve/acle/general/min_poly_2_run.c
 create mode 100644 
gcc/testsuite/gcc.target/aarch64/sve/acle/general/min_poly_3.c
 create mode 100644 
gcc/testsuite/gcc.target/aarch64/sve/acle/general/min_poly_3_run.c

diff --git a/gcc/fold-const.cc b/gcc/fold-const.cc
index 6795c15b906..5e2a06c71d1 100644
--- a/gcc/fold-const.cc
+++ b/gcc/fold-const.cc
@@ -1149,7 +1149,7 @@ wide_int_binop (wide_int &res,
    bounds, computed in infinite precision, and return true.  Otherwise
    return false.  */
 
-static bool
+bool
 poly_int_cst_bounds (const_tree arg, widest_int *lo, widest_int *hi)
 {
   if (!targetm.poly_int_indeterminate_bound)
@@ -1165,21 +1165,21 @@ poly_int_cst_bounds (const_tree arg, widest_int *lo, 
widest_int *hi)
   return true;
 }
 
-/* Return true if ARG is an unsigned POLY_INT_CST that may wrap for some
-   runtime value of its indeterminates.  Without
-   TARGET_POLY_INT_INDETERMINATE_BOUND, only catch coefficients that are
-   negative when read as signed.  */
+/* Return true if ARG is a POLY_INT_CST that may wrap for some runtime
+   value of its indeterminates.  Without TARGET_POLY_INT_INDETERMINATE_BOUND,
+   only catch unsigned POLY_INT_CSTs that may be negative when read as
+   signed.  */
 
 static bool
 maybe_wrapping_poly_p (const_tree arg)
 {
-  if (!POLY_INT_CST_P (arg) || !TYPE_UNSIGNED (TREE_TYPE (arg)))
+  if (!POLY_INT_CST_P (arg))
     return false;
   tree type = TREE_TYPE (arg);
   widest_int lo, hi;
   if (!poly_int_cst_bounds (arg, &lo, &hi))
-    return maybe_lt (tree_to_poly_int64 (arg), 0);
-  return !wi::fits_to_tree_p (hi, type);
+    return TYPE_UNSIGNED (type) && maybe_lt (tree_to_poly_int64 (arg), 0);
+  return !wi::fits_to_tree_p (lo, type) || !wi::fits_to_tree_p (hi, type);
 }
 
 /* Returns true if we know who is smaller or equal, ARG1 or ARG2, and set the
diff --git a/gcc/fold-const.h b/gcc/fold-const.h
index ab03bf0aa35..d9f0c373e39 100644
--- a/gcc/fold-const.h
+++ b/gcc/fold-const.h
@@ -125,6 +125,7 @@ extern tree fold_vec_perm (tree, tree, tree, const 
vec_perm_indices &);
 extern bool wide_int_binop (wide_int &res, enum tree_code,
                            const wide_int &arg1, const wide_int &arg2,
                            signop, wi::overflow_type *);
+extern bool poly_int_cst_bounds (const_tree, widest_int *, widest_int *);
 extern bool poly_int_binop (poly_wide_int &res, enum tree_code,
                            const_tree, const_tree, signop,
                            wi::overflow_type *);
diff --git a/gcc/testsuite/gcc.target/aarch64/sve/acle/general/cntb_ptr_1.c 
b/gcc/testsuite/gcc.target/aarch64/sve/acle/general/cntb_ptr_1.c
new file mode 100644
index 00000000000..d208920096e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/sve/acle/general/cntb_ptr_1.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+#include <arm_sve.h>
+
+int
+f (void)
+{
+  char *p = (char *) svcntb ();
+  return p != 0;
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/sve/acle/general/min_poly_2.c 
b/gcc/testsuite/gcc.target/aarch64/sve/acle/general/min_poly_2.c
new file mode 100644
index 00000000000..bface1b739a
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/sve/acle/general/min_poly_2.c
@@ -0,0 +1,88 @@
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+#include <arm_sve.h>
+
+/*
+** min_b:
+**     (cnt[bhwd]|rdvl)        [^\n]+
+**     ...
+**     csel    [^\n]+
+**     ret
+*/
+int8_t __attribute__ ((noipa))
+min_b (void)
+{
+  int8_t n = svcntb ();
+  return n < 5 ? n : 5;
+}
+
+/*
+** max_h:
+**     (cnt[bhwd]|rdvl)        [^\n]+
+**     ...
+**     csel    [^\n]+
+**     ...
+**     ret
+*/
+int16_t __attribute__ ((noipa))
+max_h (void)
+{
+  int16_t n = svcnth () * 1024;
+  return n > 7 ? n : 7;
+}
+
+/*
+** gt_w:
+**     (cnt[bhwd]|rdvl)        [^\n]+
+**     ...
+**     cset    [^\n]+
+**     ret
+*/
+int __attribute__ ((noipa))
+gt_w (void)
+{
+  int32_t n = svcntw () * 0x2000000;
+  return n > 0;
+}
+
+/*
+** ne_d:
+**     (cnt[bhwd]|rdvl)        [^\n]+
+**     ...
+**     cset    [^\n]+
+**     ret
+*/
+int __attribute__ ((noipa))
+ne_d (void)
+{
+  int8_t n = svcntd () * 16;
+  return n != 0;
+}
+
+/*
+** lt_neg:
+**     (cnt[bhwd]|rdvl)        [^\n]+
+**     ...
+**     ret
+*/
+int __attribute__ ((noipa))
+lt_neg (void)
+{
+  int8_t n = -svcntb ();
+  return n < 0;
+}
+
+/*
+** ge_d:
+**     (cnt[bhwd]|rdvl)        [^\n]+
+**     ...
+**     ret
+*/
+int __attribute__ ((noipa))
+ge_d (void)
+{
+  int64_t n = (int64_t) svcntd () << 58;
+  return n >= 0;
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/sve/acle/general/min_poly_2_run.c 
b/gcc/testsuite/gcc.target/aarch64/sve/acle/general/min_poly_2_run.c
new file mode 100644
index 00000000000..c3129f591ea
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/sve/acle/general/min_poly_2_run.c
@@ -0,0 +1,24 @@
+/* { dg-do run { target aarch64_sve_hw } } */
+/* { dg-options "-O2" } */
+
+#include "min_poly_2.c"
+
+volatile uint64_t vcnt;
+
+int
+main (void)
+{
+  vcnt = svcntd ();
+  int8_t b = vcnt * 8, d = vcnt * 16, neg = -(vcnt * 8);
+  int16_t h = vcnt * 4 * 1024;
+  int32_t w = vcnt * 2 * 0x2000000;
+  int64_t d58 = vcnt << 58;
+  if (min_b () != (b < 5 ? b : 5)
+      || max_h () != (h > 7 ? h : 7)
+      || gt_w () != (w > 0)
+      || ne_d () != (d != 0)
+      || lt_neg () != (neg < 0)
+      || ge_d () != (d58 >= 0))
+    __builtin_abort ();
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/sve/acle/general/min_poly_3.c 
b/gcc/testsuite/gcc.target/aarch64/sve/acle/general/min_poly_3.c
new file mode 100644
index 00000000000..ff5a0ea82a7
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/sve/acle/general/min_poly_3.c
@@ -0,0 +1,50 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fwrapv" } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+#include <arm_sve.h>
+
+/*
+** min_16:
+**     (cnt[bhwd]|rdvl)        [^\n]+
+**     ...
+**     csel    [^\n]+
+**     ret
+*/
+int16_t __attribute__ ((noipa))
+min_16 (void)
+{
+  int16_t n = svcntb ();
+  n *= 0x100;
+  return n < 5 ? n : 5;
+}
+
+/*
+** min_32:
+**     (cnt[bhwd]|rdvl)        [^\n]+
+**     ...
+**     csel    [^\n]+
+**     ret
+*/
+int32_t __attribute__ ((noipa))
+min_32 (void)
+{
+  int32_t n = svcntb ();
+  n *= 0x1000000;
+  return n < 5 ? n : 5;
+}
+
+/*
+** min_64:
+**     (cnt[bhwd]|rdvl)        [^\n]+
+**     ...
+**     csel    [^\n]+
+**     ret
+*/
+int64_t __attribute__ ((noipa))
+min_64 (void)
+{
+  int64_t n = svcntb ();
+  n *= 0x80000000000000;
+  return n < 5 ? n : 5;
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/sve/acle/general/min_poly_3_run.c 
b/gcc/testsuite/gcc.target/aarch64/sve/acle/general/min_poly_3_run.c
new file mode 100644
index 00000000000..a2677567c39
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/sve/acle/general/min_poly_3_run.c
@@ -0,0 +1,20 @@
+/* { dg-do run { target aarch64_sve_hw } } */
+/* { dg-options "-O2 -fwrapv" } */
+
+#include "min_poly_3.c"
+
+volatile int64_t vcnt;
+
+int
+main (void)
+{
+  vcnt = svcntb ();
+  int16_t h = vcnt * 0x100;
+  int32_t w = vcnt * 0x1000000;
+  int64_t d = vcnt * 0x80000000000000;
+  if (min_16 () != (h < 5 ? h : 5)
+      || min_32 () != (w < 5 ? w : 5)
+      || min_64 () != (d < 5 ? d : 5))
+    __builtin_abort ();
+  return 0;
+}
diff --git a/gcc/value-query.cc b/gcc/value-query.cc
index 78976e21f25..425afa4982e 100644
--- a/gcc/value-query.cc
+++ b/gcc/value-query.cc
@@ -32,7 +32,6 @@ 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"
 
 // Attempt to make Q the current range query for FUN.  If it is already the
 // current range query, return NULL.
@@ -442,94 +441,23 @@ range_query::get_tree_range (vrange &r, tree expr, gimple 
*stmt,
       if (POLY_INT_CST_P (expr))
        {
          unsigned int precision = TYPE_PRECISION (type);
-         signop sign = TYPE_SIGN (type);
-         bool have_poly_bound = targetm.poly_int_indeterminate_bound;
-         poly_uint64 indeterminate_bound;
-
-         if (have_poly_bound)
-           indeterminate_bound = targetm.poly_int_indeterminate_bound ();
-
-         auto val = wi::to_poly_wide (expr);
-         auto type_min = wi::to_wide (TYPE_MIN_VALUE (type));
-         auto type_max = wi::to_wide (TYPE_MAX_VALUE (type));
-
-         /* Start with the invariant part of the poly-int, then account
-            for each coefficient below.
-
-            The target hook gives a per-coefficient upper bound for the
-            indeterminate.  Since those indeterminates are unsigned and
-            nonnegative, a positive coefficient can only increase the upper
-            bound and a negative coefficient can only decrease the lower
-            bound.  The opposite bound is unaffected by that coefficient:
-
-               [A, +C] with C >= 0  => max += C * bound
-               [A, -C] with C >= 0  => min -= C * bound.  */
-         wide_int bounds[2] = { val.coeffs[0], val.coeffs[0] };
-         bool ovf[2] = { false, false };
-
-         for (unsigned int i = 1; i < NUM_POLY_INT_COEFFS; ++i)
-           {
-             const auto &coeff = val.coeffs[i];
-             if (wi::eq_p (coeff, 0))
-               continue;
-
-             /* Select the only bound affected by this coefficient.  A
-                negative coefficient contributes to the minimum and a positive
-                coefficient contributes to the maximum.  */
-             bool coeff_neg = wi::neg_p (coeff, sign);
-             wide_int &bound = bounds[coeff_neg ? 0 : 1];
-             bool &bound_ovf = ovf[coeff_neg ? 0 : 1];
-
-             if (bound_ovf)
-               continue;
-
-             /* A missing hook, or a -1 bound for this coefficient, means the
-                indeterminate has no finite target-specific limit.  Treat that
-                like an overflow of the affected bound.  */
-             if (!have_poly_bound
-                 || indeterminate_bound.coeffs[i] == HOST_WIDE_INT_M1U)
-               bound_ovf = true;
-             else
-               {
-                 auto indeterminate
-                   = wi::uhwi (indeterminate_bound.coeffs[i], precision);
-                 wi::overflow_type mul_ovf = wi::OVF_NONE;
-                 auto term = wi::mul (coeff, indeterminate, sign, &mul_ovf);
-                 wi::overflow_type add_ovf = wi::OVF_NONE;
-                 bound = wi::add (bound, term, sign, &add_ovf);
-                 bound_ovf = (mul_ovf != wi::OVF_NONE
-                              || add_ovf != wi::OVF_NONE);
-               }
-
-             if (TYPE_OVERFLOW_WRAPS (type) && bound_ovf)
-               {
-                 r.set_varying (type);
-                 r.update_bitmask (irange_bitmask (wi::zero (precision),
-                                                   get_nonzero_bits (expr)));
-                 return true;
-               }
-
-             if (bound_ovf)
-               {
-                 if (coeff_neg)
-                   bounds[0] = type_min;
-                 else
-                   bounds[1] = type_max;
-               }
-           }
-
-         /* Check that the target filled in sensible bounds information.  */
-         gcc_assert (wi::le_p (bounds[0], bounds[1], sign));
-
-         irange &ir = as_a <irange> (r);
-         ir.set (type, bounds[0], bounds[1]);
+         widest_int lo, hi;
+         if (is_a <irange> (r)
+             && poly_int_cst_bounds (expr, &lo, &hi)
+             && wi::fits_to_tree_p (lo, type)
+             && wi::fits_to_tree_p (hi, type))
+           as_a <irange> (r).set (type,
+                                  wide_int::from (lo, precision, SIGNED),
+                                  wide_int::from (hi, precision, SIGNED));
+         else
+           r.set_varying (type);
 
          /* Preserve alignment/step information that is not visible in the
             intervals.  For example, a poly-int like [8, 8] can
             only produce multiples of 8, but the interval range might be
             [8, 136], which also contains values with low bits set.  */
-         ir.update_bitmask (irange_bitmask (wi::zero (precision),
-                                            get_nonzero_bits (expr)));
+         r.update_bitmask (irange_bitmask (wi::zero (precision),
+                                           get_nonzero_bits (expr)));
          return true;
        }
       break;
-- 
2.56.0

Reply via email to