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