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;
 	}

