[PATCH 1/2] tree-scalar-evolution: Handle idempotent recurrences [PR124460]

liuhongt <[email protected]> Tue, 4 Aug 2026 19:45:29 -0700
Newsgroups gmane.comp.gcc.patches
Message-ID <[email protected]>
SCEV does not represent recurrences such as repeated MIN_EXPR or
BIT_AND_EXPR updates.  Recognize MIN_EXPR, MAX_EXPR, BIT_AND_EXPR,
BIT_IOR_EXPR and ABS_EXPR updates with a loop-invariant second operand.
After any positive number of iterations their value is the operation
applied once to the initial value.

gcc/ChangeLog:

	PR middle-end/124460
	* tree-scalar-evolution.cc (loop_phi_for_update): New function.
	(build_loop_update): Likewise.
	(compute_idempotent_loop_value): Likewise.
	(final_value_replacement_loop): Use it.

gcc/testsuite/ChangeLog:

	PR middle-end/124460
	* gcc.dg/tree-ssa/pr124460-1.c: New test.
	* gcc.dg/tree-ssa/pr124460-2.c: Likewise.
	* gcc.dg/tree-ssa/pr124460-3.c: Likewise.
---
 gcc/testsuite/gcc.dg/tree-ssa/pr124460-1.c | 40 ++++++++++++
 gcc/testsuite/gcc.dg/tree-ssa/pr124460-2.c | 41 ++++++++++++
 gcc/testsuite/gcc.dg/tree-ssa/pr124460-3.c | 22 +++++++
 gcc/tree-scalar-evolution.cc               | 76 +++++++++++++++++++++-
 4 files changed, 176 insertions(+), 3 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/pr124460-1.c
 create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/pr124460-2.c
 create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/pr124460-3.c

diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr124460-1.c b/gcc/testsuite/gcc.dg/tree-ssa/pr124460-1.c
new file mode 100644
index 00000000000..24d0075ae86
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/pr124460-1.c
@@ -0,0 +1,40 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-sccp-details" } */
+
+int
+test_min_zero (int f, int n)
+{
+  for (int i = 0; i < n; ++i)
+    f = f < 0 ? f : 0;
+  return f;
+}
+
+int
+test_max_100 (int x, int n)
+{
+  for (int i = 0; i < n; ++i)
+    x = x > 100 ? x : 100;
+  return x;
+}
+
+int
+test_max_50 (int x, int n)
+{
+  for (int i = 0; i < n; ++i)
+    x = x < 50 ? 50 : x;
+  return x;
+}
+
+int
+test_min_neg10 (int x, int n)
+{
+  for (int i = 0; i < n; ++i)
+    x = x > -10 ? -10 : x;
+  return x;
+}
+
+/* { dg-final { scan-tree-dump-times {final value replacement} 4 "sccp" } } */
+/* { dg-final { scan-tree-dump {with expr: MIN_EXPR <f_[0-9]+\(D\), 0>} "sccp" } } */
+/* { dg-final { scan-tree-dump {with expr: MAX_EXPR <x_[0-9]+\(D\), 100>} "sccp" } } */
+/* { dg-final { scan-tree-dump {with expr: MAX_EXPR <x_[0-9]+\(D\), 50>} "sccp" } } */
+/* { dg-final { scan-tree-dump {with expr: MIN_EXPR <x_[0-9]+\(D\), -10>} "sccp" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr124460-2.c b/gcc/testsuite/gcc.dg/tree-ssa/pr124460-2.c
new file mode 100644
index 00000000000..263b2e4c788
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/pr124460-2.c
@@ -0,0 +1,41 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-sccp-details" } */
+
+int
+test_and_mask (int x, int n)
+{
+  for (int i = 0; i < n; ++i)
+    x &= 0xff;
+  return x;
+}
+
+int
+test_or_flags (int x, int n)
+{
+  for (int i = 0; i < n; ++i)
+    x |= 3;
+  return x;
+}
+
+unsigned long
+test_and_mask64 (unsigned long x, int n)
+{
+  for (int i = 0; i < n; ++i)
+    x &= 0xffffffff00000000UL;
+  return x;
+}
+
+unsigned
+test_or_bit (unsigned x, int n)
+{
+  for (int i = 0; i < n; ++i)
+    x |= 1U << 15;
+  return x;
+}
+
+/* { dg-final { scan-tree-dump-times {final value replacement} 4 "sccp" { target lp64 } } } */
+/* { dg-final { scan-tree-dump-times {final value replacement} 3 "sccp" { target ilp32 } } } */
+/* { dg-final { scan-tree-dump {with expr: x_[0-9]+\(D\) & 255} "sccp" } } */
+/* { dg-final { scan-tree-dump {with expr: x_[0-9]+\(D\) \| 3} "sccp" } } */
+/* { dg-final { scan-tree-dump {with expr: x_[0-9]+\(D\) & 18446744069414584320} "sccp" { target lp64 } } } */
+/* { dg-final { scan-tree-dump {with expr: x_[0-9]+\(D\) \| 32768} "sccp" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr124460-3.c b/gcc/testsuite/gcc.dg/tree-ssa/pr124460-3.c
new file mode 100644
index 00000000000..692570eb047
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/pr124460-3.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-sccp-details" } */
+
+int
+test_abs (int f, int n)
+{
+  for (int i = 0; i < n; ++i)
+    f = f < 0 ? -f : f;
+  return f;
+}
+
+int
+test_abs_alt (int x, int n)
+{
+  for (int i = 0; i < n; ++i)
+    x = x >= 0 ? x : -x;
+  return x;
+}
+
+/* { dg-final { scan-tree-dump-times {final value replacement} 2 "sccp" } } */
+/* { dg-final { scan-tree-dump {with expr: ABS_EXPR <f_[0-9]+\(D\)>} "sccp" } } */
+/* { dg-final { scan-tree-dump {with expr: ABS_EXPR <x_[0-9]+\(D\)>} "sccp" } } */
diff --git a/gcc/tree-scalar-evolution.cc b/gcc/tree-scalar-evolution.cc
index cd99cbe86ce..11fa70a162f 100644
--- a/gcc/tree-scalar-evolution.cc
+++ b/gcc/tree-scalar-evolution.cc
@@ -3800,6 +3800,71 @@ enum bit_op_kind
   return fold_build2 (code1, type, inv, wide_int_to_tree (type, bits));
 }
 
+/* Return the header PHI for an update whose latch value is PHIDEF.  */
+static gphi *
+loop_phi_for_update (class loop *loop, tree phidef, tree op)
+{
+  if (TREE_CODE (op) != SSA_NAME)
+    return NULL;
+
+  gphi *phi = dyn_cast <gphi *> (SSA_NAME_DEF_STMT (op));
+  if (phi
+      && gimple_bb (phi) == loop->header
+      && gimple_phi_num_args (phi) == 2
+      && PHI_ARG_DEF_FROM_EDGE (phi, loop_latch_edge (loop)) == phidef)
+    return phi;
+
+  return NULL;
+}
+
+/* Rebuild CODE with the initial value of the recurrence that produces
+   PHIDEF.  OP1 is null for unary operations.  */
+static tree
+build_loop_update (class loop *loop, tree phidef, enum tree_code code,
+		   tree op0, tree op1, tree *init)
+{
+  gphi *phi = loop_phi_for_update (loop, phidef, op0);
+  tree invariant = op1;
+
+  if (!phi && op1)
+    {
+      phi = loop_phi_for_update (loop, phidef, op1);
+      invariant = op0;
+    }
+
+  if (!phi
+      || (invariant && !expr_invariant_in_loop_p (loop, invariant)))
+    return NULL_TREE;
+
+  tree initial = PHI_ARG_DEF_FROM_EDGE (phi, loop_preheader_edge (loop));
+  if (init)
+    *init = initial;
+  if (!invariant)
+    return fold_build1 (code, TREE_TYPE (phidef), initial);
+
+  return fold_build2 (code, TREE_TYPE (phidef), initial, invariant);
+}
+
+/* Return the effect of a repeated idempotent update, if recognized.  */
+static tree
+compute_idempotent_loop_value (class loop *loop, tree phidef)
+{
+  gimple *def = SSA_NAME_DEF_STMT (phidef);
+  if (!is_gimple_assign (def))
+    return NULL_TREE;
+
+  enum tree_code code = gimple_assign_rhs_code (def);
+  if (code != MIN_EXPR && code != MAX_EXPR
+      && code != BIT_AND_EXPR && code != BIT_IOR_EXPR
+      && code != ABS_EXPR)
+    return NULL_TREE;
+
+  tree op0 = gimple_assign_rhs1 (def);
+  tree op1 = (code == ABS_EXPR) ? NULL_TREE : gimple_assign_rhs2 (def);
+
+  return build_loop_update (loop, phidef, code, op0, op1, NULL);
+}
+
 /* Match.pd function to match bitop with invariant expression
   .i.e.
   tmp_7 = _0 & _1; */
@@ -3979,7 +4044,7 @@ final_value_replacement_loop (class loop *loop)
       def = analyze_scalar_evolution_in_loop (ex_loop, loop, def,
 					      &folded_casts);
 
-      tree bitinv_def, bit_def, phi_latch_final_value;
+      tree loop_value, bit_def, phi_latch_final_value;
       unsigned HOST_WIDE_INT niter_num;
 
       gphi *header_phi = TREE_CODE (phidef) == SSA_NAME
@@ -3996,10 +4061,15 @@ final_value_replacement_loop (class loop *loop)
 	  tmp &= bit2;
 	if bit2 is an invariant in loop which could simple to
 	tmp &= bit2.  */
-      else if ((bitinv_def
+      else if ((loop_value
 		= analyze_and_compute_bitop_with_inv_effect (loop,
 							     phidef, niter)))
-	def = bitinv_def;
+	def = loop_value;
+
+      /* Handle simple recurrences not represented by SCEV.  */
+      else if ((loop_value
+		= compute_idempotent_loop_value (loop, phidef)))
+	def = loop_value;
 
       /* Handle bitwise induction expression.
 
-- 
2.34.1