This was a fun one!   An actual bug, and it took a while to sort out.  After chasing down some red herrings, this turns out to be an issue of interaction between the range and value masks and intervening calculations.

The original patch from 11/2023 adjusts intersection so that it can enhance subranges based on the value mask.  ie in this testcase

[irange] int [-INF, 2147483644] MASK 0xfffffffc VALUE 0x1

 If adjust_range() were called on this, it would eliminate the trailing mask/value bit ranges that are invalid and turn it into :

[-INF, -3][1, 1][4, 2147483626] MASK 0xfffffffc VALUE 0x1

reflecting the lower bits into the range.   The problem develops because we only apply adjust_range ()  during intersection in an attempt to avoid expensive work when it isnt needed.

Unfortunately, that is what triggers this infinite loop.  Rangers cache propagates changes, and the algorithm is designed to always improve the range.  In this case, the first iteration through, _11 receives the above value, [irange] int [-INF, 2147483644] MASK 0xfffffffc VALUE 0x1  which via the mask, excludes 0, 2 and 3.

The ensuing calculations in block 7 do not trigger a successful intersection operation, and thus the range pairs are never expanded to eliminate the lower ranges, and it triggers another change in values which leads to the next iteration being less precise, but not obviously so. [irange] int [-INF, 2147483644] MASK 0xfffffffd VALUE 0x0 is a result of the calculation.   As ranges as suppose to always get better with this algorithm, we simply compare for difference.. and this range is different, and thus we replace it. It only excludes 2 and 3.

Next iteration through the less precise range DOES trigger an intersection operation in block 7, and when that is expanded to [irange] int [-INF, 1][4, 2147483644] MASK 0xfffffffd VALUE 0x0 using that we can again create the more precise range for _11 that started the cycle. and we go on and on and on.

If we fix this so that we always expand subranges to reflect the lower bits in a bitmask, the initial value starts with

[irange] int [-INF, -3][1, 1][4, 2147483644] MASK 0xfffffffc VALUE 0x1

And everything falls into place as it should.  The fix is to be consistent about expanding those lower subranges.

I also added a couple of minor performance tweaks to avoid unnecessary work, along with removing adjust_range () directly into set_range_from_bitmask () .

I started at a 0.2% overall compilation increase (1.8% in VRP). In the end, this patch is down to 0.6% in VRP, and only 0.08% overall, so manageable for all the extra work.

It also causes a few ripples in the testsuite so 3 test cases also needed adjustment:

 * gcc.dg/pr83072-2.c :  With the addition of the expanded ranges, CCP use to export a global:     Global Exported: c_3 = [irange] int [-INF, +INF] MASK 0xfffffffe VALUE 0x1
and now
   Global Exported: c_3 = [irange] int [-INF, -1][1, +INF] MASK 0xfffffffe VALUE 0x1 Which in turn enables forwprop to collapse part of the testcase much earlier.     So I turned off forwprop for the testcase

* gcc.dg/tree-ssa/phi-opt-value-5.c  : WIth the expanded ranges, CCP2 pass use to export:    Global Exported: d_3 = [irange] int [-INF, +INF] MASK 0xfffffffe VALUE 0x1
and now
   Global Exported: d_3 = [irange] int [-INF, -1][1, +INF] MASK 0xfffffffe VALUE 0x1 which in turn makes the following comment obsolete as the optimization does happen earlier.:
/* fdiv1 requires until later than phiopt2 to be able to detect that
   d is non-zero. to be able to remove the conditional.  */
Adjusted the testcase to expect everything to be taken care of by phi-opt2 pass.

 * gcc.dg/tree-ssa/vrp122.c : Previously, CCP exported:
   Global Exported: g_4 = [irange] unsigned int [0, +INF] MASK 0xfffffff0 VALUE 0x0
and then EVRP refined that and stored it, then the testcase tested for:
   Global Exported: g_4 = [irange] unsigned int [0, 0][16, +INF] MASK 0xfffffff0 VALUE 0x0 Now, CCP itself exported the expanded range, so there is nothing for VRP to do.
adjusted the testcase to look for the expanded range in CCP.

Now we never get into this situation where the bitmask is explicitly applied in some places and not others.

Bootstraps on x86_64-pc-linux-gnu with no regressions. Finally.   Is this OK for trunk, or should I hold off a little bit?

Andrew
From 36e4b77565a1965d5bca15d196f32d5758393063 Mon Sep 17 00:00:00 2001
From: Andrew MacLeod <amacl...@redhat.com>
Date: Mon, 14 Apr 2025 16:25:15 -0400
Subject: [PATCH 3/3] Always reflect lower bits from mask in subranges.

During intersection, we expand the subranges to exclude the lower values
from a bitmask with trailing zeros.  This leads to inconsistant evaluations
and in this case of this PR, that lead to an infinite cycle.

Always expand the lower subranges in set_range_from_bitmask instead.

	PR tree-optimization/119712
	gcc/
	* value-range.cc (range_bitmask::adjust_range): Delete.
	(irange::set_range_from_bitmask): Integrate adjust_range.
	(irange::update_bitmask): Do nothing if bitmask doesnt change.
	(irange:intersect_bitmask): Do not call adjust_range. Exit if there
	is no second bitmask.
	* value-range.h (adjust_range): Remove prototype.

	gcc/testsuite/
	* gcc.dg/pr119712.c: New.
	* gcc.dg/pr83072-2.c: Adjust.
	* gcc.dg/tree-ssa/phi-opt-value-5.c: Adjust.
	* gcc.dg/tree-ssa/vrp122.c: Adjust
---
 gcc/testsuite/gcc.dg/pr119712.c               | 27 ++++++++
 gcc/testsuite/gcc.dg/pr83072-2.c              |  2 +-
 .../gcc.dg/tree-ssa/phi-opt-value-5.c         |  2 +-
 gcc/testsuite/gcc.dg/tree-ssa/vrp122.c        |  4 +-
 gcc/value-range.cc                            | 64 +++++++++----------
 gcc/value-range.h                             |  1 -
 6 files changed, 61 insertions(+), 39 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/pr119712.c

diff --git a/gcc/testsuite/gcc.dg/pr119712.c b/gcc/testsuite/gcc.dg/pr119712.c
new file mode 100644
index 00000000000..e845dd9ce55
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/pr119712.c
@@ -0,0 +1,27 @@
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+int a, b, c, d, e, f;
+int main() {
+          f--;
+          goto q;
+j:
+          if (-1642776935 * c + 7 >= 0)
+            goto l;
+m:
+          if (4 * a - c - 21 >= 0)
+            goto i;
+          return 0;
+i:
+          if (d)
+            goto l;
+q:
+          c = 4 * c - 3;
+          if (c - f)
+             goto m;
+          goto j;
+l:
+          e = b + 1958960196 * c - 1016458303;
+          if (20 * e + 1 >= 0)
+              return 0;
+          goto j;
+}
diff --git a/gcc/testsuite/gcc.dg/pr83072-2.c b/gcc/testsuite/gcc.dg/pr83072-2.c
index dff6b50b717..485e8041381 100644
--- a/gcc/testsuite/gcc.dg/pr83072-2.c
+++ b/gcc/testsuite/gcc.dg/pr83072-2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fdump-tree-evrp-details" } */
+/* { dg-options "-O2 -fdump-tree-evrp-details -fno-tree-forwprop" } */
 
 int f1(int a, int b, int c){
   if(c==0)__builtin_unreachable();
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/phi-opt-value-5.c b/gcc/testsuite/gcc.dg/tree-ssa/phi-opt-value-5.c
index 12ba475b24e..cb781774294 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/phi-opt-value-5.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/phi-opt-value-5.c
@@ -33,7 +33,7 @@ int fdiv1(int a, int b)
 
 /* fdiv1 requires until later than phiopt2 to be able to detect that
    d is non-zero. to be able to remove the conditional.  */
-/* { dg-final { scan-tree-dump-times "goto" 2 "phiopt2" } } */
+/* { dg-final { scan-tree-dump-not "goto" "phiopt2" } } */
 /* { dg-final { scan-tree-dump-not "goto" "phiopt3" } } */
 /* { dg-final { scan-tree-dump-not "goto" "optimized" } } */
 
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp122.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp122.c
index 5a4ca850bee..def2b892bd6 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/vrp122.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp122.c
@@ -1,5 +1,5 @@
 // { dg-do compile }
-// { dg-options "-O2 -fdump-tree-evrp-details" }
+// { dg-options "-O2 -fdump-tree-ccp1-details" }
 
 void gg(void);
 int f(unsigned t)
@@ -16,4 +16,4 @@ int f(unsigned t)
   return 0;
 }
 
-// { dg-final { scan-tree-dump "Global Exported: g_.* MASK 0x1 VALUE 0x0" "evrp" } }
+// { dg-final { scan-tree-dump "Global Exported: g_.*MASK.*0 VALUE 0x0" "ccp1" } }
diff --git a/gcc/value-range.cc b/gcc/value-range.cc
index 5136674b361..a770b41b474 100644
--- a/gcc/value-range.cc
+++ b/gcc/value-range.cc
@@ -2251,37 +2251,9 @@ irange::invert ()
     verify_range ();
 }
 
-// Remove trailing ranges that this bitmask indicates can't exist.
-
-void
-irange_bitmask::adjust_range (irange &r) const
-{
-  if (unknown_p () || r.undefined_p ())
-    return;
-
-  int_range_max range;
-  tree type = r.type ();
-  int prec = TYPE_PRECISION (type);
-  // If there are trailing zeros, create a range representing those bits.
-  gcc_checking_assert (m_mask != 0);
-  int z = wi::ctz (m_mask);
-  if (z)
-    {
-      wide_int ub = (wi::one (prec) << z) - 1;
-      range = int_range<5> (type, wi::zero (prec), ub);
-      // Then remove the specific value these bits contain from the range.
-      wide_int value = m_value & ub;
-      range.intersect (int_range<2> (type, value, value, VR_ANTI_RANGE));
-      // Inverting produces a list of ranges which can be valid.
-      range.invert ();
-      // And finally select R from only those valid values.
-      r.intersect (range);
-      return;
-    }
-}
-
-// If the mask can be trivially converted to a range, do so and
-// return TRUE.
+// If the mask can be trivially converted to a range, do so.
+// Otherwise attempt to remove the lower bits from the range.
+// Return true if the range changed in any way.
 
 bool
 irange::set_range_from_bitmask ()
@@ -2326,7 +2298,28 @@ irange::set_range_from_bitmask ()
       set_zero (type ());
       return true;
     }
-  return false;
+
+  // If the mask doesn't have any trailing zero, return.
+  int z = wi::ctz (m_bitmask.mask ());
+  if (!z)
+    return false;
+
+  // Remove trailing ranges that this bitmask indicates can't exist.
+  int_range_max mask_range;
+  int prec = TYPE_PRECISION (type ());
+  wide_int ub = (wi::one (prec) << z) - 1;
+  mask_range = int_range<2> (type (), wi::zero (prec), ub);
+
+  // Then remove the specific value these bits contain from the range.
+  wide_int value = m_bitmask.value () & ub;
+  mask_range.intersect (int_range<2> (type (), value, value, VR_ANTI_RANGE));
+
+  // Inverting produces a list of ranges which can be valid.
+  mask_range.invert ();
+
+  // And finally select R from only those valid values.
+  intersect (mask_range);
+  return true;
 }
 
 void
@@ -2334,6 +2327,10 @@ irange::update_bitmask (const irange_bitmask &bm)
 {
   gcc_checking_assert (!undefined_p ());
 
+  // If masks are the same, there is no change.
+  if (m_bitmask == bm)
+    return;
+
   // Drop VARYINGs with known bits to a plain range.
   if (m_kind == VR_VARYING && !bm.unknown_p ())
     m_kind = VR_RANGE;
@@ -2408,7 +2405,7 @@ irange::intersect_bitmask (const irange &r)
 {
   gcc_checking_assert (!undefined_p () && !r.undefined_p ());
 
-  if (m_bitmask == r.m_bitmask)
+  if (r.m_bitmask.unknown_p () || m_bitmask == r.m_bitmask)
     return false;
 
   irange_bitmask bm = get_bitmask ();
@@ -2427,7 +2424,6 @@ irange::intersect_bitmask (const irange &r)
 
   if (!set_range_from_bitmask ())
     normalize_kind ();
-  m_bitmask.adjust_range (*this);
   if (flag_checking)
     verify_range ();
   return true;
diff --git a/gcc/value-range.h b/gcc/value-range.h
index b50cb896340..f6942989a6f 100644
--- a/gcc/value-range.h
+++ b/gcc/value-range.h
@@ -149,7 +149,6 @@ public:
   void dump (FILE *) const;
 
   bool member_p (const wide_int &val) const;
-  void adjust_range (irange &r) const;
 
   // Convenience functions for nonzero bitmask compatibility.
   wide_int get_nonzero_bits () const;
-- 
2.45.0

Reply via email to