[v2,2/2] middle-end: Do not assume signed POLY_INT_CSTs cannot wrap

Message ID 20261002184121.1378137-3-gcc@gcc.dp11.uk
State New
Headers
Series Do not fold possibly wrapping POLY_INT_CSTs |

Commit Message

DominicP Oct. 2, 2026, 6:41 p.m. UTC
  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 (<87fr1vknvk.fsf@googlemail.com>); 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 <gcc@gcc.dp11.uk>
---
 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
  

Patch

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;