[COMMITTED,02/23] Implement basic prange class.

Message ID 20240504083056.139719-3-aldyh@redhat.com
State New
Headers
Series prange: pointer ranges |

Commit Message

Aldy Hernandez May 4, 2024, 8:30 a.m. UTC
  This provides a bare prange class with bounds and bitmasks.  It will
be a drop-in replacement for pointer ranges, so we can pull their
support from irange.  The range-op code will be contributed as a
follow-up.

The code is disabled by default, as irange::supports_p still accepts
pointers:

inline bool
irange::supports_p (const_tree type)
{
  return INTEGRAL_TYPE_P (type) || POINTER_TYPE_P (type);
}

Once the prange operators are implemented in range-ops, pointer
support will be removed from irange to activate pranges.

gcc/ChangeLog:

	* value-range-pretty-print.cc (vrange_printer::visit): New.
	* value-range-pretty-print.h: Declare prange visit() method.
	* value-range.cc (vrange::operator=): Add prange support.
	(vrange::operator==): Same.
	(prange::accept): New.
	(prange::set_nonnegative): New.
	(prange::set): New.
	(prange::contains_p): New.
	(prange::singleton_p): New.
	(prange::lbound): New.
	(prange::ubound): New.
	(prange::union_): New.
	(prange::intersect): New.
	(prange::operator=): New.
	(prange::operator==): New.
	(prange::invert): New.
	(prange::verify_range): New.
	(prange::update_bitmask): New.
	(range_tests_misc): Use prange.
	* value-range.h (enum value_range_discriminator): Add VR_PRANGE.
	(class prange): New.
	(Value_Range::init): Add prange support.
	(Value_Range::operator=): Same.
	(Value_Range::supports_type_p): Same.
	(prange::prange):  New.
	(prange::supports_p): New.
	(prange::supports_type_p): New.
	(prange::set_undefined): New.
	(prange::set_varying): New.
	(prange::set_nonzero): New.
	(prange::set_zero): New.
	(prange::contains_p): New.
	(prange::zero_p): New.
	(prange::nonzero_p): New.
	(prange::type): New.
	(prange::lower_bound): New.
	(prange::upper_bound): New.
	(prange::varying_compatible_p): New.
	(prange::get_bitmask): New.
	(prange::fits_p): New.
---
 gcc/value-range-pretty-print.cc |  25 +++
 gcc/value-range-pretty-print.h  |   1 +
 gcc/value-range.cc              | 303 +++++++++++++++++++++++++++++++-
 gcc/value-range.h               | 199 ++++++++++++++++++---
 4 files changed, 500 insertions(+), 28 deletions(-)
  

Patch

diff --git a/gcc/value-range-pretty-print.cc b/gcc/value-range-pretty-print.cc
index b6d23dce6d2..b11d6494774 100644
--- a/gcc/value-range-pretty-print.cc
+++ b/gcc/value-range-pretty-print.cc
@@ -112,6 +112,31 @@  vrange_printer::visit (const irange &r) const
   print_irange_bitmasks (pp, r.m_bitmask);
 }
 
+void
+vrange_printer::visit (const prange &r) const
+{
+  pp_string (pp, "[prange] ");
+  if (r.undefined_p ())
+    {
+      pp_string (pp, "UNDEFINED");
+      return;
+    }
+  dump_generic_node (pp, r.type (), 0, TDF_NONE | TDF_NOUID, false);
+  pp_character (pp, ' ');
+  if (r.varying_p ())
+    {
+      pp_string (pp, "VARYING");
+      return;
+    }
+
+  pp_character (pp, '[');
+  print_int_bound (pp, r.lower_bound (), r.type ());
+  pp_string (pp, ", ");
+  print_int_bound (pp, r.upper_bound (), r.type ());
+  pp_character (pp, ']');
+  print_irange_bitmasks (pp, r.m_bitmask);
+}
+
 void
 vrange_printer::print_real_value (tree type, const REAL_VALUE_TYPE &r) const
 {
diff --git a/gcc/value-range-pretty-print.h b/gcc/value-range-pretty-print.h
index 44cd6e81298..5522aad0673 100644
--- a/gcc/value-range-pretty-print.h
+++ b/gcc/value-range-pretty-print.h
@@ -27,6 +27,7 @@  public:
   vrange_printer (pretty_printer *pp_) : pp (pp_) { }
   void visit (const unsupported_range &) const override;
   void visit (const irange &) const override;
+  void visit (const prange &) const override;
   void visit (const frange &) const override;
 private:
   void print_frange_nan (const frange &) const;
diff --git a/gcc/value-range.cc b/gcc/value-range.cc
index 7250115261f..84113ccfbd0 100644
--- a/gcc/value-range.cc
+++ b/gcc/value-range.cc
@@ -251,6 +251,8 @@  vrange::operator= (const vrange &src)
 {
   if (is_a <irange> (src))
     as_a <irange> (*this) = as_a <irange> (src);
+  else if (is_a <prange> (src))
+    as_a <prange> (*this) = as_a <prange> (src);
   else if (is_a <frange> (src))
     as_a <frange> (*this) = as_a <frange> (src);
   else
@@ -268,6 +270,8 @@  vrange::operator== (const vrange &src) const
 {
   if (is_a <irange> (src))
     return as_a <irange> (*this) == as_a <irange> (src);
+  if (is_a <prange> (src))
+    return as_a <prange> (*this) == as_a <prange> (src);
   if (is_a <frange> (src))
     return as_a <frange> (*this) == as_a <frange> (src);
   gcc_unreachable ();
@@ -397,6 +401,294 @@  irange::set_nonnegative (tree type)
        wi::to_wide (TYPE_MAX_VALUE (type)));
 }
 
+// Prange implementation.
+
+void
+prange::accept (const vrange_visitor &v) const
+{
+  v.visit (*this);
+}
+
+void
+prange::set_nonnegative (tree type)
+{
+  set (type,
+       wi::zero (TYPE_PRECISION (type)),
+       wi::max_value (TYPE_PRECISION (type), UNSIGNED));
+}
+
+void
+prange::set (tree min, tree max, value_range_kind kind)
+{
+  return set (TREE_TYPE (min), wi::to_wide (min), wi::to_wide (max), kind);
+}
+
+void
+prange::set (tree type, const wide_int &min, const wide_int &max,
+	     value_range_kind kind)
+{
+  if (kind == VR_UNDEFINED)
+    {
+      set_undefined ();
+      return;
+    }
+  if (kind == VR_VARYING)
+    {
+      set_varying (type);
+      return;
+    }
+  if (kind == VR_ANTI_RANGE)
+    {
+      gcc_checking_assert (min == 0 && max == 0);
+      set_nonzero (type);
+      return;
+    }
+  m_type = type;
+  m_min = min;
+  m_max = max;
+  if (m_min == 0 && m_max == -1)
+    {
+      m_kind = VR_VARYING;
+      m_bitmask.set_unknown (TYPE_PRECISION (type));
+      if (flag_checking)
+	verify_range ();
+      return;
+    }
+
+  m_kind = VR_RANGE;
+  m_bitmask = get_bitmask_from_range (type, min, max);
+  if (flag_checking)
+    verify_range ();
+}
+
+bool
+prange::contains_p (const wide_int &w) const
+{
+  if (undefined_p ())
+    return false;
+
+  if (varying_p ())
+    return true;
+
+  return (wi::le_p (lower_bound (), w, UNSIGNED)
+	  && wi::ge_p (upper_bound (), w, UNSIGNED));
+}
+
+bool
+prange::singleton_p (tree *result) const
+{
+  if (m_kind == VR_RANGE && lower_bound () == upper_bound ())
+    {
+      if (result)
+	*result = wide_int_to_tree (type (), m_min);
+      return true;
+    }
+  return false;
+}
+
+tree
+prange::lbound () const
+{
+  return wide_int_to_tree (type (), m_min);
+}
+
+tree
+prange::ubound () const
+{
+  return wide_int_to_tree (type (), m_max);
+}
+
+bool
+prange::union_ (const vrange &v)
+{
+  const prange &r = as_a <prange> (v);
+
+  if (r.undefined_p ())
+    return false;
+  if (undefined_p ())
+    {
+      *this = r;
+      if (flag_checking)
+	verify_range ();
+      return true;
+    }
+  if (varying_p ())
+    return false;
+  if (r.varying_p ())
+    {
+      set_varying (type ());
+      return true;
+    }
+
+  wide_int new_lb = wi::min (r.lower_bound (), lower_bound (), UNSIGNED);
+  wide_int new_ub = wi::max (r.upper_bound (), upper_bound (), UNSIGNED);
+  prange new_range (type (), new_lb, new_ub);
+  new_range.m_bitmask.union_ (m_bitmask);
+  new_range.m_bitmask.union_ (r.m_bitmask);
+  if (new_range.varying_compatible_p ())
+    {
+      set_varying (type ());
+      return true;
+    }
+  if (flag_checking)
+    new_range.verify_range ();
+  if (new_range == *this)
+    return false;
+  *this = new_range;
+  return true;
+}
+
+bool
+prange::intersect (const vrange &v)
+{
+  const prange &r = as_a <prange> (v);
+  gcc_checking_assert (undefined_p () || r.undefined_p ()
+		       || range_compatible_p (type (), r.type ()));
+
+  if (undefined_p ())
+    return false;
+  if (r.undefined_p ())
+    {
+      set_undefined ();
+      return true;
+    }
+  if (r.varying_p ())
+    return false;
+  if (varying_p ())
+    {
+      *this = r;
+      return true;
+    }
+
+  prange save = *this;
+  m_min = wi::max (r.lower_bound (), lower_bound (), UNSIGNED);
+  m_max = wi::min (r.upper_bound (), upper_bound (), UNSIGNED);
+  if (wi::gt_p (m_min, m_max, UNSIGNED))
+    {
+      set_undefined ();
+      return true;
+    }
+
+  // Intersect all bitmasks: the old one, the new one, and the other operand's.
+  irange_bitmask new_bitmask = get_bitmask_from_range (m_type, m_min, m_max);
+  m_bitmask.intersect (new_bitmask);
+  m_bitmask.intersect (r.m_bitmask);
+
+  if (flag_checking)
+    verify_range ();
+  if (*this == save)
+    return false;
+  return true;
+}
+
+prange &
+prange::operator= (const prange &src)
+{
+  m_type = src.m_type;
+  m_kind = src.m_kind;
+  m_min = src.m_min;
+  m_max = src.m_max;
+  m_bitmask = src.m_bitmask;
+  if (flag_checking)
+    verify_range ();
+  return *this;
+}
+
+bool
+prange::operator== (const prange &src) const
+{
+  if (m_kind == src.m_kind)
+    {
+      if (undefined_p ())
+	return true;
+
+      if (varying_p ())
+	return types_compatible_p (type (), src.type ());
+
+      return (m_min == src.m_min && m_max == src.m_max
+	      && m_bitmask == src.m_bitmask);
+    }
+  return false;
+}
+
+void
+prange::invert ()
+{
+  gcc_checking_assert (!undefined_p () && !varying_p ());
+
+  wide_int new_lb, new_ub;
+  unsigned prec = TYPE_PRECISION (type ());
+  wide_int type_min = wi::zero (prec);
+  wide_int type_max = wi::max_value (prec, UNSIGNED);
+  wi::overflow_type ovf;
+
+  if (lower_bound () == type_min)
+    {
+      new_lb = wi::add (upper_bound (), 1, UNSIGNED, &ovf);
+      if (ovf)
+	new_lb = type_min;
+      new_ub = type_max;
+      set (type (), new_lb, new_ub);
+    }
+  else if (upper_bound () == type_max)
+    {
+      wi::overflow_type ovf;
+      new_lb = type_min;
+      new_ub = wi::sub (lower_bound (), 1, UNSIGNED, &ovf);
+      if (ovf)
+	new_ub = type_max;
+      set (type (), new_lb, new_ub);
+    }
+  else
+    set_varying (type ());
+}
+
+void
+prange::verify_range () const
+{
+  gcc_checking_assert (m_discriminator == VR_PRANGE);
+
+  if (m_kind == VR_UNDEFINED)
+    return;
+
+  gcc_checking_assert (supports_p (type ()));
+
+  if (m_kind == VR_VARYING)
+    {
+      gcc_checking_assert (varying_compatible_p ());
+      return;
+    }
+  gcc_checking_assert (!varying_compatible_p ());
+  gcc_checking_assert (m_kind == VR_RANGE);
+}
+
+void
+prange::update_bitmask (const irange_bitmask &bm)
+{
+  gcc_checking_assert (!undefined_p ());
+
+  // If all the bits are known, this is a singleton.
+  if (bm.mask () == 0)
+    {
+      set (type (), m_bitmask.value (), m_bitmask.value ());
+      return;
+    }
+
+  // Drop VARYINGs with known bits to a plain range.
+  if (m_kind == VR_VARYING && !bm.unknown_p ())
+    m_kind = VR_RANGE;
+
+  m_bitmask = bm;
+  if (varying_compatible_p ())
+    m_kind = VR_VARYING;
+
+  if (flag_checking)
+    verify_range ();
+}
+
+
+// Frange implementation.
+
 void
 frange::accept (const vrange_visitor &v) const
 {
@@ -2542,11 +2834,12 @@  range_tests_misc ()
   // Make sure NULL and non-NULL of pointer types work, and that
   // inverses of them are consistent.
   tree voidp = build_pointer_type (void_type_node);
-  r0.set_zero (voidp);
-  r1 = r0;
-  r0.invert ();
-  r0.invert ();
-  ASSERT_TRUE (r0 == r1);
+  prange p0;
+  p0.set_zero (voidp);
+  prange p1 = p0;
+  p0.invert ();
+  p0.invert ();
+  ASSERT_TRUE (p0 == p1);
 
   // [10,20] U [15, 30] => [10, 30].
   r0 = range_int (10, 20);
diff --git a/gcc/value-range.h b/gcc/value-range.h
index f52d5165707..6fe31d67582 100644
--- a/gcc/value-range.h
+++ b/gcc/value-range.h
@@ -47,6 +47,8 @@  enum value_range_discriminator
 {
   // Range holds an integer or pointer.
   VR_IRANGE,
+  // Pointer range.
+  VR_PRANGE,
   // Floating point range.
   VR_FRANGE,
   // Range holds an unsupported type.
@@ -380,32 +382,49 @@  private:
 
 class prange : public vrange
 {
+  friend class prange_storage;
+  friend class vrange_printer;
 public:
-  static bool supports_p (const_tree) { return false; }
-  virtual bool supports_type_p (const_tree) const final override { return false; }
-  virtual void accept (const vrange_visitor &) const final override {}
-  virtual void set_undefined () final override {}
-  virtual void set_varying (tree) final override {}
-  virtual void set_nonzero (tree) final override {}
-  virtual void set_zero (tree) final override;
-  virtual void set_nonnegative (tree) final override {}
-  virtual bool contains_p (tree) const final override { return false; }
-  virtual bool fits_p (const vrange &) const final override { return false; }
-  virtual bool singleton_p (tree * = NULL) const final override { return false; }
-  virtual bool zero_p () const final override { return false; }
-  virtual bool nonzero_p () const final override { return false; }
-  virtual void set (tree, tree, value_range_kind = VR_RANGE) final override {}
-  virtual tree type () const final override { return NULL; }
-  virtual bool union_ (const vrange &) final override { return false; }
-  virtual bool intersect (const vrange &) final override { return false; }
-  virtual tree lbound () const final override { return NULL; }
-  virtual tree ubound () const final override { return NULL; }
-
+  prange ();
+  prange (const prange &);
+  prange (tree type);
+  prange (tree type, const wide_int &, const wide_int &,
+	  value_range_kind = VR_RANGE);
+  static bool supports_p (const_tree type);
+  virtual bool supports_type_p (const_tree type) const final override;
+  virtual void accept (const vrange_visitor &v) const final override;
+  virtual void set_undefined () final override;
+  virtual void set_varying (tree type) final override;
+  virtual void set_nonzero (tree type) final override;
+  virtual void set_zero (tree type) final override;
+  virtual void set_nonnegative (tree type) final override;
+  virtual bool contains_p (tree cst) const final override;
+  virtual bool fits_p (const vrange &v) const final override;
+  virtual bool singleton_p (tree *result = NULL) const final override;
+  virtual bool zero_p () const final override;
+  virtual bool nonzero_p () const final override;
+  virtual void set (tree, tree, value_range_kind = VR_RANGE) final override;
+  virtual tree type () const final override;
+  virtual bool union_ (const vrange &v) final override;
+  virtual bool intersect (const vrange &v) final override;
+  virtual tree lbound () const final override;
+  virtual tree ubound () const final override;
+
+  prange& operator= (const prange &);
+  bool operator== (const prange &) const;
+  void set (tree type, const wide_int &, const wide_int &,
+	    value_range_kind = VR_RANGE);
+  void invert ();
+  bool contains_p (const wide_int &) const;
   wide_int lower_bound () const;
   wide_int upper_bound () const;
+  void verify_range () const;
   irange_bitmask get_bitmask () const final override;
-  void update_bitmask (const irange_bitmask &) final override {}
-private:
+  void update_bitmask (const irange_bitmask &) final override;
+protected:
+  bool varying_compatible_p () const;
+
+  tree m_type;
   wide_int m_min;
   wide_int m_max;
   irange_bitmask m_bitmask;
@@ -656,6 +675,13 @@  is_a <irange> (vrange &v)
   return v.m_discriminator == VR_IRANGE;
 }
 
+template <>
+inline bool
+is_a <prange> (vrange &v)
+{
+  return v.m_discriminator == VR_PRANGE;
+}
+
 template <>
 inline bool
 is_a <frange> (vrange &v)
@@ -708,6 +734,7 @@  class vrange_visitor
 {
 public:
   virtual void visit (const irange &) const { }
+  virtual void visit (const prange &) const { }
   virtual void visit (const frange &) const { }
   virtual void visit (const unsupported_range &) const { }
 };
@@ -775,6 +802,7 @@  private:
   vrange *m_vrange;
   // The buffer must be at least the size of the largest range.
   static_assert (sizeof (int_range_max) > sizeof (frange), "");
+  static_assert (sizeof (int_range_max) > sizeof (prange), "");
   char m_buffer[sizeof (int_range_max)];
 };
 
@@ -850,6 +878,8 @@  Value_Range::init (tree type)
 
   if (irange::supports_p (type))
     m_vrange = new (&m_buffer) int_range_max ();
+  else if (prange::supports_p (type))
+    m_vrange = new (&m_buffer) prange ();
   else if (frange::supports_p (type))
     m_vrange = new (&m_buffer) frange ();
   else
@@ -863,6 +893,8 @@  Value_Range::init (const vrange &r)
 {
   if (is_a <irange> (r))
     m_vrange = new (&m_buffer) int_range_max (as_a <irange> (r));
+  else if (is_a <prange> (r))
+    m_vrange = new (&m_buffer) prange (as_a <prange> (r));
   else if (is_a <frange> (r))
     m_vrange = new (&m_buffer) frange (as_a <frange> (r));
   else
@@ -920,7 +952,9 @@  Value_Range::operator const vrange &() const
 inline bool
 Value_Range::supports_type_p (const_tree type)
 {
-  return irange::supports_p (type) || frange::supports_p (type);
+  return irange::supports_p (type)
+    || prange::supports_p (type)
+    || frange::supports_p (type);
 }
 
 extern value_range_kind get_legacy_range (const vrange &, tree &min, tree &max);
@@ -1220,32 +1254,151 @@  irange_val_max (const_tree type)
   return wi::max_value (TYPE_PRECISION (type), TYPE_SIGN (type));
 }
 
+inline
+prange::prange ()
+  : vrange (VR_PRANGE)
+{
+  set_undefined ();
+}
+
+inline
+prange::prange (const prange &r)
+  : vrange (VR_PRANGE)
+{
+  *this = r;
+}
+
+inline
+prange::prange (tree type)
+  : vrange (VR_PRANGE)
+{
+  set_varying (type);
+}
+
+inline
+prange::prange (tree type, const wide_int &lb, const wide_int &ub,
+		value_range_kind kind)
+  : vrange (VR_PRANGE)
+{
+  set (type, lb, ub, kind);
+}
+
+inline bool
+prange::supports_p (const_tree type)
+{
+  return POINTER_TYPE_P (type);
+}
+
+inline bool
+prange::supports_type_p (const_tree type) const
+{
+  return POINTER_TYPE_P (type);
+}
+
+inline void
+prange::set_undefined ()
+{
+  m_kind = VR_UNDEFINED;
+}
+
+inline void
+prange::set_varying (tree type)
+{
+  m_kind = VR_VARYING;
+  m_type = type;
+  m_min = wi::zero (TYPE_PRECISION (type));
+  m_max = wi::max_value (TYPE_PRECISION (type), UNSIGNED);
+  m_bitmask.set_unknown (TYPE_PRECISION (type));
+
+  if (flag_checking)
+    verify_range ();
+}
+
+inline void
+prange::set_nonzero (tree type)
+{
+  m_kind = VR_RANGE;
+  m_type = type;
+  m_min = wi::one (TYPE_PRECISION (type));
+  m_max = wi::max_value (TYPE_PRECISION (type), UNSIGNED);
+  m_bitmask.set_unknown (TYPE_PRECISION (type));
+
+  if (flag_checking)
+    verify_range ();
+}
+
 inline void
 prange::set_zero (tree type)
 {
+  m_kind = VR_RANGE;
+  m_type = type;
   wide_int zero = wi::zero (TYPE_PRECISION (type));
   m_min = m_max = zero;
   m_bitmask = irange_bitmask (zero, zero);
+
+  if (flag_checking)
+    verify_range ();
+}
+
+inline bool
+prange::contains_p (tree cst) const
+{
+  return contains_p (wi::to_wide (cst));
+}
+
+inline bool
+prange::zero_p () const
+{
+  return m_kind == VR_RANGE && m_min == 0 && m_max == 0;
+}
+
+inline bool
+prange::nonzero_p () const
+{
+  return m_kind == VR_RANGE && m_min == 1 && m_max == -1;
+}
+
+inline tree
+prange::type () const
+{
+  gcc_checking_assert (!undefined_p ());
+  return m_type;
 }
 
 inline wide_int
 prange::lower_bound () const
 {
+  gcc_checking_assert (!undefined_p ());
   return m_min;
 }
 
 inline wide_int
 prange::upper_bound () const
 {
+  gcc_checking_assert (!undefined_p ());
   return m_max;
 }
 
+inline bool
+prange::varying_compatible_p () const
+{
+  return (!undefined_p ()
+	  && m_min == 0 && m_max == -1 && get_bitmask ().unknown_p ());
+}
+
 inline irange_bitmask
 prange::get_bitmask () const
 {
   return m_bitmask;
 }
 
+inline bool
+prange::fits_p (const vrange &) const
+{
+  return true;
+}
+
+
 inline
 frange::frange ()
   : vrange (VR_FRANGE)