PR tree-optimization/126942- Disallow path ranger from current_range_query.

Message ID fa4aa34c-29e6-4842-ac47-721e448fb796@redhat.com
State New
Headers
Series PR tree-optimization/126942- Disallow path ranger from current_range_query. |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_simplebootstrap_build--master-aarch64-bootstrap success Build passed
linaro-tcwg-bot/tcwg_simplebootstrap_build--master-arm-bootstrap success Build passed

Commit Message

Andrew MacLeod Sept. 1, 2026, 12:53 p.m. UTC
  I think this PR ends the debate about whether a path_range should become 
the current range_query.

The problem is in tree-ssa-loop-ch.cc. It uses path_range_query to 
evaluate a path, and during a range_of_stmt() call for a statement on 
that path, the path ranger becomes the current range query.   While 
servicing that query, a nested query involving a PHI causes SCEV 
analysis to be invoked. SCEV then performs loop analysis using the path 
ranger as the current query.  This use to be a query mismatch and we 
refused to invoke SCEV  because it wouldn't work right when picking up 
ranges from the path further down in the loop during general analysis.

So.  I slightly reworked the current ad-hoc "change the current query" 
mechanism to allow for a small amount of organized push/pop.  The 
current query always starts as a global_range_query.  
Enable/disable_ranger() is altered to push/pop a new ranger instance (as 
has fast_vrp), and gimple-range-fold() also temporarily changes to 
current range-query IFF the incoming query is different than the current 
one.

I added an active_query_compatible_p () method to a range_query, and 
have it return false for any range_query (ie path-ranger) which is not 
compatible with this.

I put all the "smarts"  into the push routine.  There is a series of 3 
PRs:  125854, 126814 and now 126942  which combined, demonstrate that 
the only safe course of action is to revert to the global query if there 
is a mismatch which can't be replaced...  And this is now what 
push_range_query () does.   If the current query is the same as the 
requested one, it does nothing.   If the incoming query to be used does 
not match the current query, AND is not compatible to become the current 
query, it instead pushes the global range query.

I submit this for approval because I added a small vector to "struct 
function" which managed the push/pop list, and I wanted to make sure 
that is OK. Its currently set to  5 pointer elements and an unsigned 
index counter.  It might be possible to reduce it to 3, but I figured 5 
was future proof.   Im not sure how sensitive the size of struct 
function is, so wanted to check. If anything ever traps the vec size 
check, we''re doing something wonky that is unexpected and needs to be 
examined.    Is a vec of 5 plus a word OK to add to struct function?

Bootstrapped on x86_64-pc-linux-gnu with no regressions.   OK?

Andrew
  

Comments

Richard Biener Sept. 1, 2026, 3:06 p.m. UTC | #1
> Am 01.09.2026 um 14:53 schrieb Andrew MacLeod <amacleod@redhat.com>:
> 
> I think this PR ends the debate about whether a path_range should become the current range_query.
> 
> The problem is in tree-ssa-loop-ch.cc. It uses path_range_query to evaluate a path, and during a range_of_stmt() call for a statement on that path, the path ranger becomes the current range query.   While servicing that query, a nested query involving a PHI causes SCEV analysis to be invoked. SCEV then performs loop analysis using the path ranger as the current query.  This use to be a query mismatch and we refused to invoke SCEV  because it wouldn't work right when picking up ranges from the path further down in the loop during general analysis.
> 
> So.  I slightly reworked the current ad-hoc "change the current query" mechanism to allow for a small amount of organized push/pop.  The current query always starts as a global_range_query.  Enable/disable_ranger() is altered to push/pop a new ranger instance (as has fast_vrp), and gimple-range-fold() also temporarily changes to current range-query IFF the incoming query is different than the current one.
> 
> I added an active_query_compatible_p () method to a range_query, and have it return false for any range_query (ie path-ranger) which is not compatible with this.
> 
> I put all the "smarts"  into the push routine.  There is a series of 3 PRs:  125854, 126814 and now 126942  which combined, demonstrate that the only safe course of action is to revert to the global query if there is a mismatch which can't be replaced...  And this is now what push_range_query () does.   If the current query is the same as the requested one, it does nothing.   If the incoming query to be used does not match the current query, AND is not compatible to become the current query, it instead pushes the global range query.
> 
> I submit this for approval because I added a small vector to "struct function" which managed the push/pop list, and I wanted to make sure that is OK. Its currently set to  5 pointer elements and an unsigned index counter.  It might be possible to reduce it to 3, but I figured 5 was future proof.   Im not sure how sensitive the size of struct function is, so wanted to check. If anything ever traps the vec size check, we''re doing something wonky that is unexpected and needs to be examined.    Is a vec of 5 plus a word OK to add to struct function?

It is quite sensitive.  Since we should never inherit the ranger stack state between switching the current function (we could assert its empty) can you make this a global stack instead please?

Richard 

> Bootstrapped on x86_64-pc-linux-gnu with no regressions.   OK?
> 
> Andrew
> 
> <0002-Disallow-path-ranger-from-current_range_query.patch>
  
Andrew MacLeod Sept. 4, 2026, 1:48 p.m. UTC | #2
On 9/1/26 11:06 AM, Richard Biener wrote:
>
>> Am 01.09.2026 um 14:53 schrieb Andrew MacLeod <amacleod@redhat.com>:
>>
>> I think this PR ends the debate about whether a path_range should become the current range_query.
>>
>> The problem is in tree-ssa-loop-ch.cc. It uses path_range_query to evaluate a path, and during a range_of_stmt() call for a statement on that path, the path ranger becomes the current range query.   While servicing that query, a nested query involving a PHI causes SCEV analysis to be invoked. SCEV then performs loop analysis using the path ranger as the current query.  This use to be a query mismatch and we refused to invoke SCEV  because it wouldn't work right when picking up ranges from the path further down in the loop during general analysis.
>>
>> So.  I slightly reworked the current ad-hoc "change the current query" mechanism to allow for a small amount of organized push/pop.  The current query always starts as a global_range_query.  Enable/disable_ranger() is altered to push/pop a new ranger instance (as has fast_vrp), and gimple-range-fold() also temporarily changes to current range-query IFF the incoming query is different than the current one.
>>
>> I added an active_query_compatible_p () method to a range_query, and have it return false for any range_query (ie path-ranger) which is not compatible with this.
>>
>> I put all the "smarts"  into the push routine.  There is a series of 3 PRs:  125854, 126814 and now 126942  which combined, demonstrate that the only safe course of action is to revert to the global query if there is a mismatch which can't be replaced...  And this is now what push_range_query () does.   If the current query is the same as the requested one, it does nothing.   If the incoming query to be used does not match the current query, AND is not compatible to become the current query, it instead pushes the global range query.
>>
>> I submit this for approval because I added a small vector to "struct function" which managed the push/pop list, and I wanted to make sure that is OK. Its currently set to  5 pointer elements and an unsigned index counter.  It might be possible to reduce it to 3, but I figured 5 was future proof.   Im not sure how sensitive the size of struct function is, so wanted to check. If anything ever traps the vec size check, we''re doing something wonky that is unexpected and needs to be examined.    Is a vec of 5 plus a word OK to add to struct function?
> It is quite sensitive.  Since we should never inherit the ranger stack state between switching the current function (we could assert its empty) can you make this a global stack instead please?
>
>
I feared that.  Im not a big fan of the global stack either since the 
current query  keys off a function pointer.. so instead I just made the 
caller deal with it.  this isn't something a client pass really needs to 
deal with anyway, enable/disable_ranger takes care of that.

Bootstrapped on x86_64-pc-linux-gnu with no regressions.  pushed.

Andrew
  

Patch

From c3f9e9bc841b969e18a113bf2a60d9bdba7c4ebc Mon Sep 17 00:00:00 2001
From: Andrew MacLeod <amacleod@redhat.com>
Date: Mon, 31 Aug 2026 14:25:21 -0400
Subject: [PATCH 2/2] Disallow path ranger from current_range_query.

Provide a generic way to push/pop new range_queries, and disable
path_ranger from being pushed..

	PR tree-optimization/126942.c
	gcc/
	* function.h (X_RANGE_QUERY_LIMIT): New.
	(x_range_query_stack): New.
	(x_range_query_index): New.
	* gimple-range-fold.cc (fold_using_range::fold_stmt): Use new push
	and pop interface.
	* gimple-range-path.h (path_ranger::active_query_compatible_p): New.
	* gimple-range.cc (enable_ranger): Use new interface.
	(disable_ranger): Likewise.
	* tree-vrp.cc (execute_fast_vrp): Likewise.
	* value-query.cc (push_range_query): New.
	(pop_range_query): New.
	* value-query.h (range_query::active_query_compatible_p): New.
	(push_range_query): Declare.
	(pop_range_query): Declare.

	gcc/testsuite/
	* gcc.dg/pr126942.c: New.
---
 gcc/function.h                  |  7 ++++++
 gcc/gimple-range-fold.cc        | 17 ++++++++------
 gcc/gimple-range-path.h         |  2 ++
 gcc/gimple-range.cc             | 12 +++++-----
 gcc/testsuite/gcc.dg/pr126942.c | 35 +++++++++++++++++++++++++++++
 gcc/tree-vrp.cc                 | 13 ++++++-----
 gcc/value-query.cc              | 40 +++++++++++++++++++++++++++++++++
 gcc/value-query.h               |  7 ++++++
 8 files changed, 115 insertions(+), 18 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/pr126942.c

diff --git a/gcc/function.h b/gcc/function.h
index 765c71309fa..1701eae67a9 100644
--- a/gcc/function.h
+++ b/gcc/function.h
@@ -318,6 +318,13 @@  struct GTY(()) function {
      should be queried by calling get_range_query().  */
   range_query * GTY ((skip)) x_range_query;
 
+  /* push_range_query and pop_range_query can temporarily change the
+     current query.  This stack allows for simple restoration.
+     This stack should never be very large.*/
+#define X_RANGE_QUERY_LIMIT	5
+  range_query * GTY ((skip)) x_range_query_stack[X_RANGE_QUERY_LIMIT];
+  unsigned x_range_query_index;
+
   /* Last statement uid.  */
   int last_stmt_uid;
 
diff --git a/gcc/gimple-range-fold.cc b/gcc/gimple-range-fold.cc
index a64529603f8..ad4180023e9 100644
--- a/gcc/gimple-range-fold.cc
+++ b/gcc/gimple-range-fold.cc
@@ -694,9 +694,9 @@  fold_using_range::fold_stmt (vrange &r, gimple *s, fur_source &src, tree name)
   // If the specified query is different, make it the current one.
   // PR 125854 - The fold machinery may make a query call.
   // PR 126814 - tree_expr_nonnegative_p may make a call.
-  range_query *save = cfun->x_range_query;
-  if (src.query () != get_range_query (cfun))
-    cfun->x_range_query = src.query ();
+  // PR 126942 - path_ranger queries should never be the current query.
+  //             push_range_query will revert to a global query for this.
+  bool tmp_query_p = push_range_query (cfun, src.query ());
 
   gimple_range_op_handler handler (s);
   if (gimple_code (s) == GIMPLE_ASSIGN
@@ -731,7 +731,8 @@  fold_using_range::fold_stmt (vrange &r, gimple *s, fur_source &src, tree name)
   if (!res)
     {
       // Restore the original query.
-      cfun->x_range_query = save;
+      if (tmp_query_p)
+	pop_range_query (cfun);
       // If no name specified or range is unsupported, bail.
       if (!name || !gimple_range_ssa_p (name))
 	return false;
@@ -743,7 +744,8 @@  fold_using_range::fold_stmt (vrange &r, gimple *s, fur_source &src, tree name)
   if (r.undefined_p ())
     {
       // Restore the original query.
-      cfun->x_range_query = save;
+      if (tmp_query_p)
+	pop_range_query (cfun);
       return true;
     }
 
@@ -771,7 +773,7 @@  fold_using_range::fold_stmt (vrange &r, gimple *s, fur_source &src, tree name)
 	  else
 	    {
 	      // If we couldn't find anything, try fold.
-	      x_fold_context = { s, src.query () };
+	      x_fold_context = { s, get_range_query (cfun) };
 	      rhs = gimple_fold_stmt_to_constant_1 (s, pta_valueize,
 						    pta_valueize);
 	      if (rhs && TREE_CODE (rhs) == ADDR_EXPR)
@@ -780,7 +782,8 @@  fold_using_range::fold_stmt (vrange &r, gimple *s, fur_source &src, tree name)
 	}
     }
   // Restore the original query.
-  cfun->x_range_query = save;
+  if (tmp_query_p)
+    pop_range_query (cfun);
   return true;
 }
 
diff --git a/gcc/gimple-range-path.h b/gcc/gimple-range-path.h
index 4c162ef11f6..b6a37638a33 100644
--- a/gcc/gimple-range-path.h
+++ b/gcc/gimple-range-path.h
@@ -42,6 +42,8 @@  public:
   bool range_of_expr (vrange &r, tree name, gimple * = NULL) override;
   bool range_of_stmt (vrange &r, gimple *, tree name = NULL) override;
   bool unreachable_path_p ();
+  // Path ranger should not be an active query.
+  virtual bool active_query_compatible_p () { return false; }
   void dump (FILE *) override;
   void debug ();
 
diff --git a/gcc/gimple-range.cc b/gcc/gimple-range.cc
index b221cfc5616..8fca11555e9 100644
--- a/gcc/gimple-range.cc
+++ b/gcc/gimple-range.cc
@@ -748,10 +748,11 @@  enable_ranger (struct function *fun, bool use_imm_uses)
 {
   gimple_ranger *r;
 
-  gcc_checking_assert (!fun->x_range_query);
   r = new gimple_ranger (use_imm_uses);
-  fun->x_range_query = r;
+  gcc_assert (push_range_query (fun, r));
 
+  // Ranger should be the first and only instance.
+  gcc_checking_assert (fun->x_range_query_index == 1);
   return r;
 }
 
@@ -761,9 +762,10 @@  enable_ranger (struct function *fun, bool use_imm_uses)
 void
 disable_ranger (struct function *fun)
 {
-  gcc_checking_assert (fun->x_range_query);
-  delete fun->x_range_query;
-  fun->x_range_query = NULL;
+  range_query *q = pop_range_query (fun);
+  // There should be no other range queries active.
+  gcc_checking_assert (fun->x_range_query_index == 0);
+  delete q;
 }
 
 // ---------------------------------------------------------------------------
diff --git a/gcc/testsuite/gcc.dg/pr126942.c b/gcc/testsuite/gcc.dg/pr126942.c
new file mode 100644
index 00000000000..c2100c6a895
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/pr126942.c
@@ -0,0 +1,35 @@ 
+/* { dg-do run } */
+/* { dg-options "-O1" } */
+
+signed char a;
+int b, c;
+short d[1];
+
+short
+foo (short *f, short g)
+{
+  int i = 0, j, l;
+  long n = 1;
+  for (int m = 0; m < a; m++)
+    for (; i <= m; i++)
+      n = i;
+  for (unsigned k = 0; k < g; k++)
+    {
+      l = 0;
+      do
+	if (f)
+	  j = l + a;
+      while (++l <= k);
+    }
+  n ^= j;
+  while (a)
+    n ^= b;
+  return n;
+}
+
+int
+main ()
+{
+  if (foo (d, 2) != 0)
+    __builtin_abort ();
+}
diff --git a/gcc/tree-vrp.cc b/gcc/tree-vrp.cc
index dc8256dc33d..8ef6ae7ee13 100644
--- a/gcc/tree-vrp.cc
+++ b/gcc/tree-vrp.cc
@@ -1241,20 +1241,21 @@  execute_fast_vrp (struct function *fun, bool final_p)
 {
   calculate_dominance_info (CDI_DOMINATORS);
   dom_ranger dr;
+  // Create a relation oracle without transitives.  It will automatically
+  // be destroyed when the destructor for 'dr' runs.
+  dr.create_relation_oracle (false);
   fvrp_folder folder (&dr, final_p);
 
-  gcc_checking_assert (!fun->x_range_query);
   set_all_edges_as_executable (fun);
-  fun->x_range_query = &dr;
-  // Create a relation oracle without transitives.
-  get_range_query (fun)->create_relation_oracle (false);
+  // Make DR the current range_query.
+  gcc_assert (push_range_query (fun, &dr));
 
   folder.substitute_and_fold ();
   if (folder.m_unreachable)
     folder.m_unreachable->remove ();
 
-  get_range_query (fun)->destroy_relation_oracle ();
-  fun->x_range_query = NULL;
+  range_query *q = pop_range_query (fun);
+  gcc_checking_assert (q == &dr);
   return 0;
 }
 
diff --git a/gcc/value-query.cc b/gcc/value-query.cc
index 831dba9a39a..5878c3e67dd 100644
--- a/gcc/value-query.cc
+++ b/gcc/value-query.cc
@@ -35,6 +35,46 @@  along with GCC; see the file COPYING3.  If not see
 #include "value-range-storage.h"
 #include "target.h"
 
+// Attempt to make Q the current range query for FUN.  If it is already the
+// current range query, return false.
+// If the specified query is not compatible with being the current query,
+// instead push the global query for safety.  This is most common when
+// the requested query is a path ranger, and it can be unstable to make
+// arbitrary queries which may be in the middle or after a path.
+
+bool
+push_range_query (struct function *fun, range_query *q)
+{
+  // Set the global query if it hasn't been set.
+  if (!fun->x_range_query)
+    fun->x_range_query = get_global_range_query ();
+  // Ensure we have't pushed too many, otherwise this is indicitive of
+  // a problem,
+  gcc_checking_assert (fun->x_range_query_index < X_RANGE_QUERY_LIMIT);
+
+  // If this is not a compatible range query, revert to the global query.
+  if (!q->active_query_compatible_p ())
+    q = get_global_range_query ();
+
+  if (q == fun->x_range_query)
+    return false;
+  fun->x_range_query_stack[(fun->x_range_query_index)++] = fun->x_range_query;
+  fun->x_range_query = q;
+  return true;
+}
+
+// Restore the previous range query, and return the current one.
+
+range_query *
+pop_range_query (struct function *fun)
+{
+  range_query *q = fun->x_range_query;
+  gcc_checking_assert (fun->x_range_query_index > 0);
+  fun->x_range_query = fun->x_range_query_stack[--(fun->x_range_query_index)];
+  gcc_checking_assert (fun->x_range_query);
+  return q;
+}
+
 // range_query default methods.
 
 bool
diff --git a/gcc/value-query.h b/gcc/value-query.h
index 9932d7d493b..7e1c7e7453d 100644
--- a/gcc/value-query.h
+++ b/gcc/value-query.h
@@ -95,6 +95,8 @@  public:
   void create_gori (int not_executable_flag = 0, int sw_max_edges = INT_MAX);
   void destroy_gori ();
 
+  // Return TRUE if this path query can be a current_range_query.
+  virtual bool active_query_compatible_p () { return true; }
   virtual void dump (FILE *);
 
 protected:
@@ -139,6 +141,11 @@  get_range_query (const struct function *fun)
   return (fun && fun->x_range_query) ? fun->x_range_query : &global_ranges;
 }
 
+// Provide a way to set and a new current range query and then restore the
+// previous range query.  Return FALSE if the current query didn't change.
+bool push_range_query (struct function *fun, range_query *q);
+range_query *pop_range_query (struct function *fun);
+
 // Query the global range of NAME in function F.  Default to cfun.
 extern void gimple_range_global (vrange &v, tree name,
 				 struct function *f = cfun);
-- 
2.55.0