]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
frange class to represent floating point ranges
authorAldy Hernandez <aldyh@redhat.com>
Sun, 24 Jul 2022 17:42:11 +0000 (19:42 +0200)
committerAldy Hernandez <aldyh@redhat.com>
Mon, 25 Jul 2022 06:45:21 +0000 (08:45 +0200)
This implements a basic frange class to represent floating point
ranges.  Although it is meant to be a base for further development, it
is enough to handle relations and propagate NAN and other properties.

For ranger clients to become floating point aware, we still need the
range-op entries, which I will submit later this week.  Since those
entries require specialized FP knowledge, I will ask for a review from
the FP experts before committing.

Once range-op entries come live, all ranger clients that have been
converted to the type agnostic vrange API will become FP aware: evrp,
DOM, the threaders, loop-ch, etc.  (Still missing is loop unswitching,
as a lot of the int_range* temporaries should be Value_Range.  I don't
have enough cycles to convert loop unswitching, but could gladly give
guidance.  It should be straightforward for those familiar with the
code ;-)).

Samples things we handle:

* We can set the FP properties (!NAN, !INF, etc) at assignment from
  constants (and propagate them throughout the CFG):

  float z = 0.0;
  if (__builtin_isnan (z))
    link_error ();

* The relation oracle works in tandem with the FP ranges:

      if (x > y)
       ;
      else if (!__builtin_isnan (x) && !__builtin_isnan (y))
       {
         // If x and y are not NAN, the x <= y relationship holds, and the
         // following conditional can be folded away.
         if (x <= y)
           bar ();
       }

* We know the true side of all ordered conditionals (except !=)
  implies !NAN:

  if (x > y)
    {
      if (__builtin_isnan (x) || __builtin_isnan (y))
        link_error ();
    }

Range-ops also works correctly with -ffinite-math-only, and avoids
checking for NANs, etc.

I believe this is enough to get a fully fleshed out floating point
support for evrp and friends, but doing so is beyond my limited FP
knowledge.  For example, frange could be enhanced to track constant
endpoints, and we could track other FP properties aside from NAN.
Further discussion is gladly welcome.

Tested on x86-64 Linux.

gcc/ChangeLog:

* value-range-pretty-print.cc (vrange_printer::visit): New.
(vrange_printer::print_frange_prop): New.
* value-range-pretty-print.h (class vrange_printer): Add visit and
print_frange_prop.
* value-range-storage.h (vrange_allocator::alloc_vrange): Handle frange.
(vrange_allocator::alloc_frange): New.
* value-range.cc (vrange::operator=): Handle frange.
(vrange::operator==): Same.
(frange::accept): New.
(frange::set): New.
(frange::normalize_kind): New.
(frange::union_): New.
(frange::intersect): New.
(frange::operator=): New.
(frange::operator==): New.
(frange::supports_type_p): New.
(frange::verify_range): New.
* value-range.h (enum value_range_discriminator): Handle frange.
(class fp_prop): New.
(FP_PROP_ACCESSOR): New.
(class frange_props): New.
(FRANGE_PROP_ACCESSOR): New.
(class frange): New.
(Value_Range::init): Handle frange.
(Value_Range::operator=): Same.
(Value_Range::supports_type_p): Same.
(frange_props::operator==): New.
(frange_props::union_): New.
(frange_props::intersect): New
(frange::frange): New.
(frange::type): New.
(frange::set_varying): New.
(frange::set_undefined): New.

gcc/value-range-pretty-print.cc
gcc/value-range-pretty-print.h
gcc/value-range-storage.h
gcc/value-range.cc
gcc/value-range.h

index b795e92d8fb65c91c0efa1888efc172bb09aceab..485612fe67c2bea7c2def83a85d31f964e08431b 100644 (file)
@@ -109,3 +109,44 @@ vrange_printer::print_irange_bitmasks (const irange &r) const
   print_hex (nz, buf);
   pp_string (pp, buf);
 }
+
+// Print an frange.
+
+void
+vrange_printer::visit (const frange &r) const
+{
+  pp_string (pp, "[frange] ");
+  if (r.undefined_p ())
+    {
+      pp_string (pp, "UNDEFINED");
+      return;
+    }
+  dump_generic_node (pp, r.type (), 0, TDF_NONE, false);
+  pp_string (pp, " ");
+  if (r.varying_p ())
+    {
+      pp_string (pp, "VARYING");
+      return;
+    }
+  print_frange_prop ("NAN", r.get_nan ());
+  print_frange_prop ("INF", r.get_inf ());
+  print_frange_prop ("NINF", r.get_ninf ());
+}
+
+// Print the FP properties in an frange.
+
+void
+vrange_printer::print_frange_prop (const char *str, const fp_prop &prop) const
+{
+  if (prop.varying_p ())
+    return;
+
+  if (prop.yes_p ())
+    pp_string (pp, str);
+  else if (prop.no_p ())
+    {
+      pp_character (pp, '!');
+      pp_string (pp, str);
+    }
+  pp_character (pp, ' ');
+}
index 6d2fb74cc7abfc5c39fcc32db22799d1b4b09528..c1c7c4244ccbb015b73134a14c461e3168a47719 100644 (file)
@@ -27,9 +27,11 @@ public:
   vrange_printer (pretty_printer *pp_) : pp (pp_) { }
   void visit (const unsupported_range &) const override;
   void visit (const irange &) const override;
+  void visit (const frange &) const override;
 private:
   void print_irange_bound (tree bound) const;
   void print_irange_bitmasks (const irange &) const;
+  void print_frange_prop (const char *str, const fp_prop &) const;
 
   pretty_printer *pp;
 };
index 7e005e4db5647bd9d9308a7f34ad1bfe2910b8ff..5a3336b673bc2c56da060a305e5875a60d11905d 100644 (file)
@@ -39,6 +39,7 @@ public:
   template <typename T> T *clone (const T &src);
 private:
   irange *alloc_irange (unsigned pairs);
+  frange *alloc_frange ();
   void operator= (const vrange_allocator &) = delete;
 };
 
@@ -142,7 +143,9 @@ vrange_allocator::alloc_vrange (tree type)
 {
   if (irange::supports_p (type))
     return alloc_irange (2);
-
+  if (frange::supports_p (type))
+    return alloc_frange ();
+  return NULL;
   gcc_unreachable ();
 }
 
@@ -164,6 +167,13 @@ vrange_allocator::alloc_irange (unsigned num_pairs)
   return new (r) irange (mem, num_pairs);
 }
 
+inline frange *
+vrange_allocator::alloc_frange ()
+{
+  void *r = alloc (sizeof (frange));
+  return new (r) frange ();
+}
+
 // Return a clone of an irange.
 
 template <>
@@ -175,6 +185,17 @@ vrange_allocator::clone <irange> (const irange &src)
   return r;
 }
 
+// Return a clone of an frange.
+
+template <>
+inline frange *
+vrange_allocator::clone <frange> (const frange &src)
+{
+  frange *r = alloc_frange ();
+  *r = src;
+  return r;
+}
+
 // Return a clone of a vrange.
 
 template <>
@@ -183,7 +204,9 @@ vrange_allocator::clone <vrange> (const vrange &src)
 {
   if (is_a <irange> (src))
     return clone <irange> (as_a <irange> (src));
-
+  if (is_a <frange> (src))
+    return clone <frange> (as_a <frange> (src));
+  return NULL;
   gcc_unreachable ();
 }
 
index 525e19240578c822a6a65680247831614904781f..e49b06d10380818a51fe12bc91eb1f08916d4e03 100644 (file)
@@ -195,12 +195,12 @@ vrange &
 vrange::operator= (const vrange &src)
 {
   if (is_a <irange> (src))
-    {
-      as_a <irange> (*this) = as_a <irange> (src);
-      return *this;
-    }
+    as_a <irange> (*this) = as_a <irange> (src);
+  else if (is_a <frange> (src))
+    as_a <frange> (*this) = as_a <frange> (src);
   else
     gcc_unreachable ();
+  return *this;
 }
 
 // Equality operator for generic ranges.
@@ -210,6 +210,8 @@ vrange::operator== (const vrange &src) const
 {
   if (is_a <irange> (src))
     return as_a <irange> (*this) == as_a <irange> (src);
+  if (is_a <frange> (src))
+    return as_a <frange> (*this) == as_a <frange> (src);
   gcc_unreachable ();
 }
 
@@ -252,6 +254,191 @@ unsupported_range::unsupported_range ()
   set_undefined ();
 }
 
+void
+frange::accept (const vrange_visitor &v) const
+{
+  v.visit (*this);
+}
+
+// Setter for franges.  Currently only singletons are supported.
+
+void
+frange::set (tree min, tree max, value_range_kind kind)
+{
+  gcc_checking_assert (kind == VR_RANGE);
+  gcc_checking_assert (operand_equal_p (min, max));
+  gcc_checking_assert (TREE_CODE (min) == REAL_CST);
+
+  m_kind = kind;
+  m_type = TREE_TYPE (min);
+
+  REAL_VALUE_TYPE *const cst = TREE_REAL_CST_PTR (min);
+  if (real_isnan (cst))
+    m_props.nan_set_yes ();
+  else
+    m_props.nan_set_no ();
+
+  if (real_isinf (cst))
+    {
+      if (real_isneg (cst))
+       {
+         m_props.inf_set_no ();
+         m_props.ninf_set_yes ();
+       }
+      else
+       {
+         m_props.inf_set_yes ();
+         m_props.ninf_set_no ();
+       }
+    }
+  else
+    {
+      m_props.inf_set_no ();
+      m_props.ninf_set_no ();
+    }
+
+  if (flag_checking)
+    verify_range ();
+}
+
+// Normalize range to VARYING or UNDEFINED, or vice versa.
+//
+// A range with no known properties can be dropped to VARYING.
+// Similarly, a VARYING with any properties should be dropped to a
+// VR_RANGE.  Normalizing ranges upon changing them ensures there is
+// only one representation for a given range.
+
+void
+frange::normalize_kind ()
+{
+  if (m_kind == VR_RANGE)
+    {
+      // No FP properties set means varying.
+      if (m_props.nan_varying_p ()
+         && m_props.inf_varying_p ()
+         && m_props.ninf_varying_p ())
+       {
+         set_varying (m_type);
+         return;
+       }
+      // Undefined is viral.
+      if (m_props.nan_undefined_p ()
+         || m_props.inf_undefined_p ()
+         || m_props.ninf_undefined_p ())
+       {
+         set_undefined ();
+         return;
+       }
+    }
+  else if (m_kind == VR_VARYING)
+    {
+      // If a VARYING has any FP properties, it's no longer VARYING.
+      if (!m_props.nan_varying_p ()
+         || !m_props.inf_varying_p ()
+         || !m_props.ninf_varying_p ())
+       m_kind = VR_RANGE;
+    }
+}
+
+bool
+frange::union_ (const vrange &v)
+{
+  const frange &r = as_a <frange> (v);
+
+  if (r.undefined_p () || varying_p ())
+    return false;
+  if (undefined_p () || r.varying_p ())
+    {
+      *this = r;
+      return true;
+    }
+
+  bool ret = m_props.union_ (r.m_props);
+  normalize_kind ();
+
+  if (flag_checking)
+    verify_range ();
+  return ret;
+}
+
+bool
+frange::intersect (const vrange &v)
+{
+  const frange &r = as_a <frange> (v);
+
+  if (undefined_p () || r.varying_p ())
+    return false;
+  if (r.undefined_p ())
+    {
+      set_undefined ();
+      return true;
+    }
+  if (varying_p ())
+    {
+      *this = r;
+      return true;
+    }
+
+  bool ret = m_props.intersect (r.m_props);
+  normalize_kind ();
+
+  if (flag_checking)
+    verify_range ();
+  return ret;
+}
+
+frange &
+frange::operator= (const frange &src)
+{
+  m_kind = src.m_kind;
+  m_type = src.m_type;
+  m_props = src.m_props;
+
+  if (flag_checking)
+    verify_range ();
+  return *this;
+}
+
+bool
+frange::operator== (const frange &src) const
+{
+  if (m_kind == src.m_kind)
+    {
+      if (undefined_p ())
+       return true;
+
+      if (varying_p ())
+       return types_compatible_p (m_type, src.m_type);
+
+      return m_props == src.m_props;
+    }
+  return false;
+}
+
+bool
+frange::supports_type_p (tree type) const
+{
+  return supports_p (type);
+}
+
+void
+frange::verify_range ()
+{
+  if (undefined_p ())
+    {
+      gcc_checking_assert (m_props.undefined_p ());
+      return;
+    }
+  else if (varying_p ())
+    {
+      gcc_checking_assert (m_props.varying_p ());
+      return;
+    }
+
+  gcc_checking_assert (m_kind == VR_RANGE);
+  gcc_checking_assert (!m_props.varying_p () && !m_props.undefined_p ());
+}
+
 // Here we copy between any two irange's.  The ranges can be legacy or
 // multi-ranges, and copying between any combination works correctly.
 
index 4af92fd65d96f15a7bbe55591f9ce6d113ea2a82..e43fbe30f2743b0edb10a1b09d9ab9274b669a26 100644 (file)
@@ -45,6 +45,8 @@ enum value_range_discriminator
 {
   // Range holds an integer or pointer.
   VR_IRANGE,
+  // Floating point range.
+  VR_FRANGE,
   // Range holds an unsupported type.
   VR_UNKNOWN
 };
@@ -252,6 +254,117 @@ public:
   virtual void accept (const vrange_visitor &v) const override;
 };
 
+// Floating point property to represent possible values of a NAN, INF, etc.
+
+class fp_prop
+{
+public:
+  enum kind {
+    UNDEFINED  = 0x0,          // Prop is impossible.
+    YES                = 0x1,          // Prop is definitely set.
+    NO         = 0x2,          // Prop is definitely not set.
+    VARYING    = (YES | NO)    // Prop may hold.
+  };
+  fp_prop (kind f) : m_kind (f) { }
+  bool varying_p () const { return m_kind == VARYING; }
+  bool undefined_p () const { return m_kind == UNDEFINED; }
+  bool yes_p () const { return m_kind == YES; }
+  bool no_p () const { return m_kind == NO; }
+private:
+  unsigned char m_kind : 2;
+};
+
+// Accessors for individual FP properties.
+
+#define FP_PROP_ACCESSOR(NAME) \
+  void NAME##_set_varying () { u.bits.NAME = fp_prop::VARYING; }       \
+  void NAME##_set_yes () { u.bits.NAME = fp_prop::YES; }       \
+  void NAME##_set_no () { u.bits.NAME = fp_prop::NO; } \
+  bool NAME##_varying_p () const { return u.bits.NAME == fp_prop::VARYING; } \
+  bool NAME##_undefined_p () const { return u.bits.NAME == fp_prop::UNDEFINED; } \
+  bool NAME##_yes_p () const { return u.bits.NAME == fp_prop::YES; }   \
+  bool NAME##_no_p () const { return u.bits.NAME == fp_prop::NO; } \
+  fp_prop get_##NAME () const                             \
+  { return fp_prop ((fp_prop::kind) u.bits.NAME); } \
+  void set_##NAME (fp_prop::kind f) { u.bits.NAME = f; }
+
+// Aggregate of all the FP properties in an frange packed into one
+// structure to save space.  Using explicit fp_prop's in the frange,
+// would take one byte per property because of padding.  Instead, we
+// can save all properties into one byte.
+
+class frange_props
+{
+public:
+  frange_props () { set_varying (); }
+  void set_varying () { u.bytes = 0xff; }
+  void set_undefined () { u.bytes = 0; }
+  bool varying_p () { return u.bytes == 0xff; }
+  bool undefined_p () { return u.bytes == 0; }
+  bool union_ (const frange_props &other);
+  bool intersect (const frange_props &other);
+  bool operator== (const frange_props &other) const;
+  FP_PROP_ACCESSOR(nan)
+  FP_PROP_ACCESSOR(inf)
+  FP_PROP_ACCESSOR(ninf)
+private:
+  union {
+    struct {
+      unsigned char nan : 2;
+      unsigned char inf : 2;
+      unsigned char ninf : 2;
+    } bits;
+    unsigned char bytes;
+  } u;
+};
+
+// Accessors for getting/setting all FP properties at once.
+
+#define FRANGE_PROP_ACCESSOR(NAME)                             \
+  fp_prop get_##NAME () const { return m_props.get_##NAME (); }        \
+  void set_##NAME (fp_prop::kind f)                            \
+  {                                                            \
+    m_props.set_##NAME (f);                                    \
+    normalize_kind ();                                         \
+  }
+
+// A floating point range.
+
+class frange : public vrange
+{
+  friend class frange_storage_slot;
+public:
+  frange ();
+  frange (const frange &);
+  static bool supports_p (tree type)
+  {
+    // Disabled until floating point range-ops come live.
+    return 0 && SCALAR_FLOAT_TYPE_P (type);
+  }
+  virtual tree type () const override;
+  virtual void set (tree, tree, value_range_kind = VR_RANGE) override;
+  virtual void set_varying (tree type) override;
+  virtual void set_undefined () override;
+  virtual bool union_ (const vrange &) override;
+  virtual bool intersect (const vrange &) override;
+  virtual bool supports_type_p (tree type) const override;
+  virtual void accept (const vrange_visitor &v) const override;
+  frange& operator= (const frange &);
+  bool operator== (const frange &) const;
+  bool operator!= (const frange &r) const { return !(*this == r); }
+
+  // Each fp_prop can be accessed with get_PROP() and set_PROP().
+  FRANGE_PROP_ACCESSOR(nan)
+  FRANGE_PROP_ACCESSOR(inf)
+  FRANGE_PROP_ACCESSOR(ninf)
+private:
+  void verify_range ();
+  void normalize_kind ();
+
+  frange_props m_props;
+  tree m_type;
+};
+
 // is_a<> and as_a<> implementation for vrange.
 
 // Anything we haven't specialized is a hard fail.
@@ -297,10 +410,18 @@ is_a <irange> (vrange &v)
   return v.m_discriminator == VR_IRANGE;
 }
 
+template <>
+inline bool
+is_a <frange> (vrange &v)
+{
+  return v.m_discriminator == VR_FRANGE;
+}
+
 class vrange_visitor
 {
 public:
   virtual void visit (const irange &) const { }
+  virtual void visit (const frange &) const { }
   virtual void visit (const unsupported_range &) const { }
 };
 
@@ -360,6 +481,7 @@ private:
   unsupported_range m_unsupported;
   vrange *m_vrange;
   int_range_max m_irange;
+  frange m_frange;
 };
 
 inline
@@ -401,6 +523,8 @@ Value_Range::init (tree type)
 
   if (irange::supports_p (type))
     m_vrange = &m_irange;
+  else if (frange::supports_p (type))
+    m_vrange = &m_frange;
   else
     m_vrange = &m_unsupported;
 }
@@ -426,6 +550,11 @@ Value_Range::operator= (const vrange &r)
       m_irange = as_a <irange> (r);
       m_vrange = &m_irange;
     }
+  else if (is_a <frange> (r))
+    {
+      m_frange = as_a <frange> (r);
+      m_vrange = &m_frange;
+    }
   else
     gcc_unreachable ();
 
@@ -461,7 +590,7 @@ Value_Range::operator const vrange &() const
 inline bool
 Value_Range::supports_type_p (tree type)
 {
-  return irange::supports_p (type);
+  return irange::supports_p (type) || frange::supports_p (type);
 }
 
 // Returns true for an old-school value_range as described above.
@@ -881,6 +1010,69 @@ irange::normalize_kind ()
     }
 }
 
+
+// Supporting methods for frange.
+
+inline bool
+frange_props::operator== (const frange_props &other) const
+{
+  return u.bytes == other.u.bytes;
+}
+
+inline bool
+frange_props::union_ (const frange_props &other)
+{
+  unsigned char saved = u.bytes;
+  u.bytes |= other.u.bytes;
+  return u.bytes != saved;
+}
+
+inline bool
+frange_props::intersect (const frange_props &other)
+{
+  unsigned char saved = u.bytes;
+  u.bytes &= other.u.bytes;
+  return u.bytes != saved;
+}
+
+inline
+frange::frange ()
+{
+  m_discriminator = VR_FRANGE;
+  m_type = nullptr;
+  set_undefined ();
+}
+
+inline
+frange::frange (const frange &src)
+{
+  m_discriminator = VR_FRANGE;
+  *this = src;
+}
+
+inline tree
+frange::type () const
+{
+  return m_type;
+}
+
+inline void
+frange::set_varying (tree type)
+{
+  m_kind = VR_VARYING;
+  m_type = type;
+  m_props.set_varying ();
+}
+
+inline void
+frange::set_undefined ()
+{
+  m_kind = VR_UNDEFINED;
+  m_type = NULL;
+  m_props.set_undefined ();
+}
+
+
 // Return the maximum value for TYPE.
 
 inline tree