[PATCH 2/2] tree-scalar-evolution: Handle toggle recurrences

liuhongt <[email protected]> Tue, 4 Aug 2026 19:45:30 -0700
Newsgroups gmane.comp.gcc.patches
Message-ID <[email protected]>
Recognize NEGATE_EXPR, BIT_NOT_EXPR and BIT_XOR_EXPR recurrences.
Fold constant iteration counts and use a conditional for symbolic counts.

Require may_be_zero to be false, since otherwise niter does not
necessarily give the latch count.  Keep trapping signed negations
unchanged.  This replaces the old invariant bit-operation helper;
idempotent AND and IOR updates are handled by the preceding patch.

gcc/ChangeLog:

	PR middle-end/124460
	PR middle-end/114502
	PR middle-end/112104
	PR middle-end/98909
	* tree-scalar-evolution.cc
	(analyze_and_compute_bitop_with_inv_effect): Remove.
	(compute_toggle_loop_value): New function.
	(final_value_replacement_loop): Use it.

gcc/testsuite/ChangeLog:

	PR middle-end/124460
	PR middle-end/114502
	PR middle-end/112104
	PR middle-end/98909
	* gcc.dg/tree-ssa/pr124460-4.c: New test.
	* gcc.dg/tree-ssa/pr124460-5.c: Likewise.
	* gcc.target/i386/pr105735-1.c: Adjust scan count.
	* gcc.target/i386/pr105735-3.c: Likewise.
	* gcc.target/i386/pr92080-12.c: Keep the XOR operand variant.
---
 gcc/testsuite/gcc.dg/tree-ssa/pr124460-4.c | 96 ++++++++++++++++++++++
 gcc/testsuite/gcc.dg/tree-ssa/pr124460-5.c | 40 +++++++++
 gcc/testsuite/gcc.target/i386/pr105735-1.c |  2 +-
 gcc/testsuite/gcc.target/i386/pr105735-3.c |  2 +-
 gcc/testsuite/gcc.target/i386/pr92080-12.c | 12 +--
 gcc/tree-scalar-evolution.cc               | 91 +++++++-------------
 6 files changed, 174 insertions(+), 69 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/pr124460-4.c
 create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/pr124460-5.c

diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr124460-4.c b/gcc/testsuite/gcc.dg/tree-ssa/pr124460-4.c
new file mode 100644
index 00000000000..95cdd245a21
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/pr124460-4.c
@@ -0,0 +1,96 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-sccp-details" } */
+
+int
+test_neg_odd (int x)
+{
+  for (int i = 0; i < 103; ++i)
+    x = -x;
+  return x;
+}
+
+int
+test_neg_even (int x)
+{
+  for (int i = 0; i < 104; ++i)
+    x = -x;
+  return x;
+}
+
+unsigned
+test_not_odd (unsigned x)
+{
+  for (int i = 0; i < 105; ++i)
+    x = ~x;
+  return x;
+}
+
+unsigned
+test_not_even (unsigned x)
+{
+  for (int i = 0; i < 106; ++i)
+    x = ~x;
+  return x;
+}
+
+int
+test_xor_odd (int x)
+{
+  for (int i = 0; i < 107; ++i)
+    x ^= 0xff;
+  return x;
+}
+
+int
+test_xor_even (int x)
+{
+  for (int i = 0; i < 108; ++i)
+    x ^= 0xff;
+  return x;
+}
+
+int
+test_neg_variable (int x, int n)
+{
+  for (int i = 0; i < n; ++i)
+    x = -x;
+  return x;
+}
+
+unsigned
+test_not_variable (unsigned x, int n)
+{
+  for (int i = 0; i < n; ++i)
+    x = ~x;
+  return x;
+}
+
+int
+test_xor_variable (int x, int n)
+{
+  for (int i = 0; i < n; ++i)
+    x ^= 0xff;
+  return x;
+}
+
+/* NITER is not exact when the latch can be skipped.  */
+int
+test_neg_may_be_zero (int x, int i, int n)
+{
+  do
+    {
+      x = -x;
+      ++i;
+    }
+  while (i < n);
+  return x;
+}
+
+/* { dg-final { scan-tree-dump-times {final value replacement} 9 "sccp" } } */
+/* { dg-final { scan-tree-dump {with expr: -x_[0-9]+\(D\)} "sccp" } } */
+/* { dg-final { scan-tree-dump {with expr: ~x_[0-9]+\(D\)} "sccp" } } */
+/* { dg-final { scan-tree-dump {with expr: x_[0-9]+\(D\) \^ 255} "sccp" } } */
+/* { dg-final { scan-tree-dump-times {(?n)with expr: x_[0-9]+\(D\)$} 3 "sccp" } } */
+/* { dg-final { scan-tree-dump {with expr: .* == 0 \? -x_[0-9]+\(D\) : x_[0-9]+\(D\)} "sccp" } } */
+/* { dg-final { scan-tree-dump {with expr: .* == 0 \? ~x_[0-9]+\(D\) : x_[0-9]+\(D\)} "sccp" } } */
+/* { dg-final { scan-tree-dump {with expr: .* == 0 \? x_[0-9]+\(D\) \^ 255 : x_[0-9]+\(D\)} "sccp" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr124460-5.c b/gcc/testsuite/gcc.dg/tree-ssa/pr124460-5.c
new file mode 100644
index 00000000000..548b5b51623
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/pr124460-5.c
@@ -0,0 +1,40 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -ftrapv -fdump-tree-sccp-details" } */
+
+int
+test_neg_trapv (int n)
+{
+  for (int i = 0; i < 1024; ++i)
+    n = -n;
+  return n;
+}
+
+unsigned
+test_neg_unsigned (unsigned u)
+{
+  for (int i = 0; i < 1024; ++i)
+    u = -u;
+  return u;
+}
+
+int
+test_not_trapv (int b)
+{
+  for (int i = 0; i < 1024; ++i)
+    b = ~b;
+  return b;
+}
+
+int
+test_xor_trapv (int x)
+{
+  for (int i = 0; i < 1024; ++i)
+    x ^= 0xff;
+  return x;
+}
+
+/* { dg-final { scan-tree-dump-times {final value replacement} 3 "sccp" } } */
+/* { dg-final { scan-tree-dump-not {with expr: n_[0-9]+\(D\)} "sccp" } } */
+/* { dg-final { scan-tree-dump {with expr: u_[0-9]+\(D\)} "sccp" } } */
+/* { dg-final { scan-tree-dump {with expr: b_[0-9]+\(D\)} "sccp" } } */
+/* { dg-final { scan-tree-dump {with expr: x_[0-9]+\(D\)} "sccp" } } */
diff --git a/gcc/testsuite/gcc.target/i386/pr105735-1.c b/gcc/testsuite/gcc.target/i386/pr105735-1.c
index 69de6b2911a..4d9ca085127 100644
--- a/gcc/testsuite/gcc.target/i386/pr105735-1.c
+++ b/gcc/testsuite/gcc.target/i386/pr105735-1.c
@@ -1,6 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-O1 -fdump-tree-sccp-details" } */
-/* { dg-final { scan-tree-dump-times {final value replacement} 8 "sccp" } } */
+/* { dg-final { scan-tree-dump-times {final value replacement} 9 "sccp" } } */
 
 unsigned int
 __attribute__((noipa))
diff --git a/gcc/testsuite/gcc.target/i386/pr105735-3.c b/gcc/testsuite/gcc.target/i386/pr105735-3.c
index 9e268a1a997..a3280ba6860 100644
--- a/gcc/testsuite/gcc.target/i386/pr105735-3.c
+++ b/gcc/testsuite/gcc.target/i386/pr105735-3.c
@@ -1,6 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-O1 -fdump-tree-sccp-details" } */
-/* { dg-final { scan-tree-dump-times {final value replacement} 8 "sccp" } } */
+/* { dg-final { scan-tree-dump-times {final value replacement} 9 "sccp" } } */
 
 unsigned int
 __attribute__((noipa))
diff --git a/gcc/testsuite/gcc.target/i386/pr92080-12.c b/gcc/testsuite/gcc.target/i386/pr92080-12.c
index cb09eb2f0a8..97f092604c4 100644
--- a/gcc/testsuite/gcc.target/i386/pr92080-12.c
+++ b/gcc/testsuite/gcc.target/i386/pr92080-12.c
@@ -2,15 +2,17 @@
 /* { dg-additional-options "-O3 -mno-mmx -march=icelake-server" } */
 /* { dg-final { scan-assembler-times "vpbroadcastb" 1 } } */
 
+/* Keep the XOR operand loop variant so SCCP leaves vector code to CSE.  */
+
 signed char a;
 signed char f (int i, int j)
 {
   signed char c;
   while (i != 0)
-  {
-    a ^= j;
-    ++c;
-    ++i;
-  }
+    {
+      a ^= j + i;
+      ++c;
+      ++i;
+    }
   return c;
 }
diff --git a/gcc/tree-scalar-evolution.cc b/gcc/tree-scalar-evolution.cc
index 11fa70a162f..9200866d3a4 100644
--- a/gcc/tree-scalar-evolution.cc
+++ b/gcc/tree-scalar-evolution.cc
@@ -3865,66 +3865,40 @@ compute_idempotent_loop_value (class loop *loop, tree phidef)
   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; */
-extern bool gimple_bitop_with_inv_p (tree, tree *, tree (*)(tree));
-
-/* Return the inductive expression of bitop with invariant if possible,
-   otherwise returns DEF.  */
+/* Return the effect of a repeated toggle update, if recognized.  */
 static tree
-analyze_and_compute_bitop_with_inv_effect (class loop* loop, tree phidef,
-					   tree niter)
+compute_toggle_loop_value (class loop *loop, tree phidef, tree niter)
 {
-  tree match_op[2],inv;
-  tree type = TREE_TYPE (phidef);
-  gphi* header_phi = NULL;
-  enum tree_code code;
-  /* match thing like op0 (match[0]), op1 (match[1]), phidef (PHIDEF)
-
-    op1 =  PHI <phidef, inv>
-    phidef = op0 & op1
-    if op0 is an invariant, it could change to
-    phidef = op0 & inv.  */
-  gimple *def;
-  def = SSA_NAME_DEF_STMT (phidef);
-  if (!(is_gimple_assign (def)
-      && ((code = gimple_assign_rhs_code (def)), true)
-      && (code == BIT_AND_EXPR || code == BIT_IOR_EXPR
-	  || code == BIT_XOR_EXPR)))
+  gimple *def = SSA_NAME_DEF_STMT (phidef);
+  if (!is_gimple_assign (def))
     return NULL_TREE;
 
-  match_op[0] = gimple_assign_rhs1 (def);
-  match_op[1] = gimple_assign_rhs2 (def);
-
-  if (expr_invariant_in_loop_p (loop, match_op[1]))
-    std::swap (match_op[0], match_op[1]);
-
-  if (TREE_CODE (match_op[1]) != SSA_NAME
-      || !expr_invariant_in_loop_p (loop, match_op[0])
-      || !(header_phi = dyn_cast <gphi *> (SSA_NAME_DEF_STMT (match_op[1])))
-      || gimple_bb (header_phi) != loop->header
-      || gimple_phi_num_args (header_phi) != 2)
+  enum tree_code code = gimple_assign_rhs_code (def);
+  if (code != NEGATE_EXPR && code != BIT_NOT_EXPR && code != BIT_XOR_EXPR)
     return NULL_TREE;
 
-  if (PHI_ARG_DEF_FROM_EDGE (header_phi, loop_latch_edge (loop)) != phidef)
+  tree type = TREE_TYPE (phidef);
+  if (code == NEGATE_EXPR && TYPE_OVERFLOW_TRAPS (type))
     return NULL_TREE;
 
-  enum tree_code code1
-    = gimple_assign_rhs_code (def);
+  tree op0 = gimple_assign_rhs1 (def);
+  tree op1 = (code == BIT_XOR_EXPR) ? gimple_assign_rhs2 (def) : NULL_TREE;
 
-  if (code1 == BIT_XOR_EXPR)
-    {
-       if (!tree_fits_uhwi_p (niter))
-	return NULL_TREE;
-       unsigned HOST_WIDE_INT niter_num;
-       niter_num = tree_to_uhwi (niter);
-       if (niter_num % 2 != 0)
-	match_op[0] =  build_zero_cst (type);
-    }
+  tree initial;
+  tree toggled = build_loop_update (loop, phidef, code, op0, op1, &initial);
+  if (!toggled)
+    return NULL_TREE;
 
-  inv = PHI_ARG_DEF_FROM_EDGE (header_phi, loop_preheader_edge (loop));
-  return fold_build2 (code1, type, inv, match_op[0]);
+  /* NITER counts latch executions, so the body runs NITER + 1 times.  */
+  if (tree_fits_uhwi_p (niter))
+    return (tree_to_uhwi (niter) & 1) ? initial : toggled;
+
+  tree niter_type = TREE_TYPE (niter);
+  tree even = fold_build2 (EQ_EXPR, boolean_type_node,
+			   fold_build2 (BIT_AND_EXPR, niter_type, niter,
+					build_one_cst (niter_type)),
+			   build_zero_cst (niter_type));
+  return fold_build3 (COND_EXPR, type, even, toggled, initial);
 }
 
 /* Try to compute the final value of PHIDEF when PHIDEF is the result of a
@@ -4054,23 +4028,16 @@ final_value_replacement_loop (class loop *loop)
       if (def != chrec_dont_know)
 	def = compute_overall_effect_of_inner_loop (ex_loop, def);
 
-      /* Handle bitop with invariant induction expression.
-
-	.i.e
-	for (int i =0 ;i < 32; i++)
-	  tmp &= bit2;
-	if bit2 is an invariant in loop which could simple to
-	tmp &= bit2.  */
-      else if ((loop_value
-		= analyze_and_compute_bitop_with_inv_effect (loop,
-							     phidef, niter)))
-	def = loop_value;
-
       /* Handle simple recurrences not represented by SCEV.  */
       else if ((loop_value
 		= compute_idempotent_loop_value (loop, phidef)))
 	def = loop_value;
 
+      else if (integer_zerop (niter_desc.may_be_zero)
+	       && (loop_value
+		   = compute_toggle_loop_value (loop, phidef, niter)))
+	def = loop_value;
+
       /* Handle bitwise induction expression.
 
 	 .i.e.
-- 
2.34.1