When bitmasks were added to value-range, they were also added to operator==.   we never added the ability to compare just the ranges and ignore the bitmask.  There have been a couple of times that would have been convenient, but never really had a serious need.

This patch adds equal_p () which also takes a flag indicating whether to include bitmasks in the comparison.  And the first consumer is the on-entry cache propagator.

We've had a number of cases over the past few months which generate oscillating patterns in rangers cache propogator.  I fixed most of them by ensuring all the subrange bounds conformed to the bitmask.. this prevented stray values that were not relevant from causing this sort of oscillation.  This case is slightly different.

There is no canonical representation of bitmasks.  Multiple bitmasks can represent the same thing.. in this particular case, we have the cache containing

    [irange] sizetype [12, 12][20, 20][28, 28] MASK 0x18 VALUE 0x4

and when we calculate  an ew on-entry range, the incoming edges are unioned together and value_range::union_ decided to produce:

   [irange] sizetype [12, 12][20, 20][28, 28] MASK 0x1c VALUE 0x0

This is a different range when the bitmask is included, and so the propagator replaces it, and propagated this value through the CFG...  after a couple of edges and calculations this new value causes a slightly different result, and although the range is really the same,  we just keep cycling back and forth.

The permanent fix is to have the on-entry propagator ignore masks when propagating values. That will stop these shenanigans going forward.  The obvious question is will we ever miss something?

The short answer is... possibly, but it seems highly unlikely. And if it did happen, it would never cause  an incorrect result.

This should prevent infinite loops in the propagator. It was originally written to always converge... but that predated bitmasks being added to irange and they add some variability which this now removes.

Bootstraps on x86_64-pc-linux-gnu  with no regressions .  OK for trunk?

Andrew

From 8db7400b89e571031e60207535a66788361ab68d Mon Sep 17 00:00:00 2001
From: Andrew MacLeod <[email protected]>
Date: Tue, 25 Nov 2025 14:06:32 -0500
Subject: [PATCH] Provide value_range::equal_p which ignores bitmaks.

Bitmasks do not have a canonical representation, and with multiple
masks matching the same range, the cache propagator can find itself
in an oscillating mask situation even though the range doesn't change.

	PR tree-optimization/122686
	gcc/
	* gimple-range-cache.cc (ranger_cache::propagate_cache): Do not
	include the bitmask when checking if the calculated value is different.
	* value-range.cc (vrange::equal_p):New.
	(prange::equal_p): New.
	(prange::operator==): Use equal_p.
	(frange::equal_p): New.
	(frange::operator==): Use equal_p.
	(irange::equal_p): New.
	(irange::operator==): Use equal_p.
	* value-range.h (vrange::equal_p): New prototype.
	(irange::equal_p): New prototype.
	(prange::equal_p): New prototype.
	(frange::equal_p): New prototype.
	(value_range::equal_p): New.
---
 gcc/gimple-range-cache.cc |  6 ++++--
 gcc/value-range.cc        | 43 ++++++++++++++++++++++++++++++++++++---
 gcc/value-range.h         | 11 ++++++++++
 3 files changed, 55 insertions(+), 5 deletions(-)

diff --git a/gcc/gimple-range-cache.cc b/gcc/gimple-range-cache.cc
index 08a953f5012..a32b93f0dc6 100644
--- a/gcc/gimple-range-cache.cc
+++ b/gcc/gimple-range-cache.cc
@@ -1376,8 +1376,10 @@ ranger_cache::propagate_cache (tree name)
 	    break;
 	}
 
-      // If the range on entry has changed, update it.
-      if (new_range != current_range)
+      // If the range on entry has changed, update it.  Do not include
+      // bitmasks as calculations across the CFG can produce
+      // oscillating values under some conditions.
+      if (!new_range.equal_p (current_range, false))
 	{
 	  bool ok_p = m_on_entry.set_bb_range (name, bb, new_range);
 	  // If the cache couldn't set the value, mark it as failed.
diff --git a/gcc/value-range.cc b/gcc/value-range.cc
index f93a7e5c53a..c164446b839 100644
--- a/gcc/value-range.cc
+++ b/gcc/value-range.cc
@@ -364,6 +364,20 @@ vrange::operator== (const vrange &src) const
   gcc_unreachable ();
 }
 
+// Equality operator optionally without bitmasks.
+
+bool
+vrange::equal_p (const vrange &src, bool include_mask_p) const
+{
+  if (is_a <irange> (src))
+    return as_a <irange> (*this).equal_p (as_a <irange> (src), include_mask_p);
+  if (is_a <prange> (src))
+    return as_a <prange> (*this).equal_p (as_a <prange> (src), include_mask_p);
+  if (is_a <frange> (src))
+    return as_a <frange> (*this).equal_p ( as_a <frange> (src), include_mask_p);
+  gcc_unreachable ();
+}
+
 // Wrapper for vrange_printer to dump a range to a file.
 
 void
@@ -703,7 +717,7 @@ prange::operator= (const prange &src)
 }
 
 bool
-prange::operator== (const prange &src) const
+prange::equal_p (const prange &src, bool include_mask_p) const
 {
   if (m_kind == src.m_kind)
     {
@@ -714,11 +728,17 @@ prange::operator== (const prange &src) const
 	return types_compatible_p (type (), src.type ());
 
       return (m_min == src.m_min && m_max == src.m_max
-	      && m_bitmask == src.m_bitmask);
+	      && (!include_mask_p || m_bitmask == src.m_bitmask));
     }
   return false;
 }
 
+bool
+prange::operator== (const prange &src) const
+{
+  return equal_p (src, true);
+}
+
 void
 prange::invert ()
 {
@@ -1195,6 +1215,13 @@ frange::operator== (const frange &src) const
   return false;
 }
 
+// frange does not have bitmasks, simply use operator==.
+bool
+frange::equal_p (const frange &src, bool) const
+{
+  return operator== (src);
+}
+
 // Return TRUE if range contains R.
 
 bool
@@ -1649,7 +1676,7 @@ irange::verify_range () const
 }
 
 bool
-irange::operator== (const irange &other) const
+irange::equal_p (const irange &other, bool include_mask_p) const
 {
   if (m_num_ranges != other.m_num_ranges)
     return false;
@@ -1670,6 +1697,10 @@ irange::operator== (const irange &other) const
 	return false;
     }
 
+  // If bitmasks are not included, reutrn true now.
+  if (!include_mask_p)
+    return true;
+
   irange_bitmask bm1 = get_bitmask ();
   irange_bitmask bm2 = other.get_bitmask ();
   widest_int tmp1 = widest_int::from (bm1.mask (), sign1);
@@ -1683,6 +1714,12 @@ irange::operator== (const irange &other) const
   return tmp1 == tmp2;
 }
 
+bool
+irange::operator== (const irange &other) const
+{
+  return equal_p (other, true);
+}
+
 /* If range is a singleton, place it in RESULT and return TRUE.  */
 
 bool
diff --git a/gcc/value-range.h b/gcc/value-range.h
index 6ae46e17959..b6f9b794fa1 100644
--- a/gcc/value-range.h
+++ b/gcc/value-range.h
@@ -110,6 +110,7 @@ public:
   vrange& operator= (const vrange &);
   bool operator== (const vrange &) const;
   bool operator!= (const vrange &r) const { return !(*this == r); }
+  bool equal_p (const vrange &, bool include_mask_p = true) const;
   void dump (FILE *) const;
   virtual void verify_range () const { }
 protected:
@@ -317,6 +318,7 @@ public:
   irange& operator= (const irange &);
   bool operator== (const irange &) const;
   bool operator!= (const irange &r) const { return !(*this == r); }
+  bool equal_p (const irange &, bool include_mask_p = true) const;
 
   // Misc methods.
   virtual bool fits_p (const vrange &r) const override;
@@ -417,6 +419,7 @@ public:
   virtual tree ubound () const final override;
 
   prange& operator= (const prange &);
+  bool equal_p (const prange &, bool include_mask_p = true) const;
   bool operator== (const prange &) const;
   void set (tree type, const wide_int &, const wide_int &,
 	    value_range_kind = VR_RANGE);
@@ -571,6 +574,7 @@ public:
   virtual void set_nonnegative (tree type) override;
   virtual bool fits_p (const vrange &) const override;
   frange& operator= (const frange &);
+  bool equal_p (const frange &, bool include_mask_p = true) const;
   bool operator== (const frange &) const;
   bool operator!= (const frange &r) const { return !(*this == r); }
   const REAL_VALUE_TYPE &lower_bound () const;
@@ -772,6 +776,7 @@ public:
   void set_type (tree type);
   vrange& operator= (const vrange &);
   value_range& operator= (const value_range &);
+  bool equal_p (const value_range &, bool include_mask_p = true) const;
   bool operator== (const value_range &r) const;
   bool operator!= (const value_range &r) const;
   operator vrange &();
@@ -935,6 +940,12 @@ value_range::operator= (const value_range &r)
   return *this;
 }
 
+inline bool
+value_range::equal_p (const value_range &r, bool include_mask_p) const
+{
+  return m_vrange->equal_p (*r.m_vrange, include_mask_p);
+}
+
 inline bool
 value_range::operator== (const value_range &r) const
 {
-- 
2.45.0

Reply via email to