This patch recognizes and optimizes loops that repeatedly apply abs()
to the same variable, exploiting the idempotence property of ABS_EXPR:
abs(abs(x)) == abs(x).  After the first iteration, the value no longer
changes, so the loop's final value can be replaced with abs(init).

The optimization is guarded by may_be_zero to ensure the loop executes
at least once, preventing speculation of abs() on zero-iteration paths.
It is restricted to integral types and includes overflow guards for
-ftrapv and -fsanitize=signed-integer-overflow.

gcc/ChangeLog:

        * tree-scalar-evolution.cc (analyze_and_compute_abs_effect): New
        helper function to recognize and optimize idempotent ABS_EXPR
        loop patterns.
        (final_value_replacement_loop): Call analyze_and_compute_abs_effect
        when may_be_zero is known false.

gcc/testsuite/ChangeLog:

        * gcc.dg/tree-ssa/scev-abs-1.c: New test for basic abs() loops.
        * gcc.dg/tree-ssa/scev-abs-2.c: New test for edge cases.
        * gcc.dg/tree-ssa/scev-abs-3.c: New runtime test.
        * gcc.dg/tree-ssa/scev-abs-4.c: New negative test for may_be_zero.
---
 gcc/testsuite/gcc.dg/tree-ssa/scev-abs-1.c | 29 ++++++++
 gcc/testsuite/gcc.dg/tree-ssa/scev-abs-2.c | 53 ++++++++++++++
 gcc/testsuite/gcc.dg/tree-ssa/scev-abs-3.c | 56 +++++++++++++++
 gcc/testsuite/gcc.dg/tree-ssa/scev-abs-4.c | 20 ++++++
 gcc/tree-scalar-evolution.cc               | 81 +++++++++++++++++++++-
 5 files changed, 238 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/scev-abs-1.c
 create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/scev-abs-2.c
 create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/scev-abs-3.c
 create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/scev-abs-4.c

diff --git a/gcc/testsuite/gcc.dg/tree-ssa/scev-abs-1.c 
b/gcc/testsuite/gcc.dg/tree-ssa/scev-abs-1.c
new file mode 100644
index 00000000000..d92d3cc89f5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/scev-abs-1.c
@@ -0,0 +1,29 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-sccp-details" } */
+
+/* Test abs () idempotent loop optimization on loops proven to run >= 1 time.
+   abs (abs (x)) == abs (x), so the loop collapses to r = abs (r_initial).
+   Constant trip counts keep may_be_zero == 0 so the guarded transform fires.  
*/
+
+int
+test_abs_basic (int r)
+{
+  for (int i = 0; i < 100; i++)
+    r = __builtin_abs (r);
+  return r;
+}
+
+int
+test_abs_while (int r)
+{
+  int i = 0;
+  while (i < 100)
+    {
+      r = __builtin_abs (r);
+      i++;
+    }
+  return r;
+}
+
+/* The transform fires once per function (2 functions).  */
+/* { dg-final { scan-tree-dump-times "Optimizing idempotent ABS_EXPR 
operation" 2 "sccp" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/scev-abs-2.c 
b/gcc/testsuite/gcc.dg/tree-ssa/scev-abs-2.c
new file mode 100644
index 00000000000..508ef0b30ce
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/scev-abs-2.c
@@ -0,0 +1,53 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-sccp-details" } */
+
+/* Edge cases for abs () idempotent loop optimization, using constant trip
+   counts so each loop is provably entered (may_be_zero == 0).  */
+
+int
+test_abs_conditional (int r, int cond)
+{
+  if (cond)
+    {
+      for (int i = 0; i < 100; i++)
+       r = __builtin_abs (r);
+    }
+  return r;
+}
+
+int
+test_abs_nested (int r)
+{
+  for (int j = 0; j < 100; j++)
+    for (int i = 0; i < 100; i++)
+      r = __builtin_abs (r);
+  return r;
+}
+
+int
+test_abs_multiple (int r1, int r2)
+{
+  for (int i = 0; i < 100; i++)
+    {
+      r1 = __builtin_abs (r1);
+      r2 = __builtin_abs (r2);
+    }
+  return r1 + r2;
+}
+
+int
+test_abs_do_while (int r)
+{
+  int i = 0;
+  do
+    {
+      r = __builtin_abs (r);
+      i++;
+    }
+  while (i < 100);
+  return r;
+}
+
+/* Optimizations: conditional (1) + nested inner loop (1) + multiple (2)
+   + do-while (1, provably >= 1 iteration with a constant bound) = 5.  */
+/* { dg-final { scan-tree-dump-times "Optimizing idempotent ABS_EXPR 
operation" 5 "sccp" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/scev-abs-3.c 
b/gcc/testsuite/gcc.dg/tree-ssa/scev-abs-3.c
new file mode 100644
index 00000000000..b56fd543828
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/scev-abs-3.c
@@ -0,0 +1,56 @@
+/* { dg-do run } */
+/* { dg-options "-O2" } */
+
+#include <limits.h>
+
+/* noipa keeps the body intact and forces real calls, so the loop transform
+   is exercised inside the function rather than folded at the call site.  */
+
+/* Parametric trip count: exercises the zero-iteration path.  */
+int __attribute__((noipa))
+test_abs_runtime (int r, int n)
+{
+  for (int i = 0; i < n; i++)
+    r = __builtin_abs (r);
+  return r;
+}
+
+/* Constant positive trip count: checks the optimized closed form gives
+   correct runtime results.  */
+int __attribute__((noipa))
+abs_fixed (int r)
+{
+  for (int i = 0; i < 100; i++)
+    r = __builtin_abs (r);
+  return r;
+}
+
+int
+main (void)
+{
+  /* Zero-iteration path must return the initial value unchanged.  */
+  if (test_abs_runtime (-5, 0) != -5)
+    __builtin_abort ();
+  if (test_abs_runtime (INT_MIN, 0) != INT_MIN)
+    __builtin_abort ();
+  if (test_abs_runtime (42, 0) != 42)
+    __builtin_abort ();
+
+  /* Positive-iteration paths (parametric).  */
+  if (test_abs_runtime (-42, 1) != 42)
+    __builtin_abort ();
+  if (test_abs_runtime (-42, 100) != 42)
+    __builtin_abort ();
+  if (test_abs_runtime (-1, 5) != 1)
+    __builtin_abort ();
+  if (test_abs_runtime (0, 100) != 0)
+    __builtin_abort ();
+
+  /* Fixed positive-trip path (optimized closed form).  */
+  if (abs_fixed (-9) != 9)
+    __builtin_abort ();
+  if (abs_fixed (9) != 9)
+    __builtin_abort ();
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/scev-abs-4.c 
b/gcc/testsuite/gcc.dg/tree-ssa/scev-abs-4.c
new file mode 100644
index 00000000000..3dcc6d55400
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/scev-abs-4.c
@@ -0,0 +1,20 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fno-tree-ch -fdump-tree-sccp-details" } */
+
+/* The trip count may be zero (n <= 0) and nothing proves otherwise, so the
+   loop may run zero times.  Replacing the final value with abs (init) would
+   speculate abs () on the zero-iteration path (e.g. n == 0 must return the
+   initial value unchanged), so the transform must be declined here.
+
+   -fno-tree-ch keeps the loop from being guarded/rotated before sccp, so its
+   may_be_zero stays non-trivial and the guard is exercised.  */
+
+int
+abs_may_be_zero (int r, int n)
+{
+  for (int i = 0; i < n; i++)
+    r = __builtin_abs (r);
+  return r;
+}
+
+/* { dg-final { scan-tree-dump-not "Optimizing idempotent ABS_EXPR operation" 
"sccp" } } */
diff --git a/gcc/tree-scalar-evolution.cc b/gcc/tree-scalar-evolution.cc
index 279ff46474e..ad1d0bfdf78 100644
--- a/gcc/tree-scalar-evolution.cc
+++ b/gcc/tree-scalar-evolution.cc
@@ -3854,6 +3854,79 @@ analyze_and_compute_bitop_with_inv_effect (class loop* 
loop, tree phidef,
   return fold_build2 (code1, type, inv, match_op[0]);
 }
 
+/* Recognize and analyze the idempotent ABS_EXPR loop pattern.
+
+   Detects loops of the form:
+     for (i = 0; i < n; i++)
+       r = abs (r);
+
+   which exploit the idempotence of ABS_EXPR (abs (abs (x)) == abs (x)):
+   after the first iteration the value no longer changes.
+
+   The caller must guarantee the loop executes at least once (niter's
+   may_be_zero is known false); given that, the exact exit value is simply
+   abs (r_init), and abs is never speculated onto a zero-iteration path.
+
+   Return the replacement expression, or NULL_TREE when the pattern does
+   not apply.  */
+
+static tree
+analyze_and_compute_abs_effect (class loop *loop, tree phidef)
+{
+  tree op_arg, init;
+  gphi *header_phi = NULL;
+  gimple *def;
+
+  /* PHIDEF must be a plain SSA name; reject virtual operands and names
+     that occur in abnormal PHIs  */
+  if (TREE_CODE (phidef) != SSA_NAME
+      || virtual_operand_p (phidef)
+      || SSA_NAME_OCCURS_IN_ABNORMAL_PHI (phidef))
+    return NULL_TREE;
+
+  /* Only integer abs is handled  */
+  tree type = TREE_TYPE (phidef);
+  if (!INTEGRAL_TYPE_P (type))
+    return NULL_TREE;
+
+  /* abs (INT_MIN) overflows.  Do not introduce an abs that could trap
+     (-ftrapv) or be reported by the signed-overflow sanitizer on a value
+     the transformed program would compute unconditionally.  */
+  if (TYPE_OVERFLOW_TRAPS (type) || TYPE_OVERFLOW_SANITIZED (type))
+    return NULL_TREE;
+
+  /* PHIDEF must be defined by an ABS_EXPR inside the loop.  */
+  def = SSA_NAME_DEF_STMT (phidef);
+  if (!is_gimple_assign (def)
+      || gimple_assign_rhs_code (def) != ABS_EXPR
+      || !flow_bb_inside_loop_p (loop, gimple_bb (def)))
+    return NULL_TREE;
+
+  op_arg = gimple_assign_rhs1 (def);
+
+  /* The operand should be defined by a PHI node in the loop header.  */
+  if (TREE_CODE (op_arg) != SSA_NAME
+      || SSA_NAME_OCCURS_IN_ABNORMAL_PHI (op_arg)
+      || !(header_phi = dyn_cast <gphi *> (SSA_NAME_DEF_STMT (op_arg)))
+      || gimple_bb (header_phi) != loop->header
+      || gimple_phi_num_args (header_phi) != 2)
+    return NULL_TREE;
+
+  /* Verify that the PHI's latch argument is phidef (creating the loop).
+     Pattern: op_arg = PHI <phidef (latch), init (preheader)>  */
+  if (PHI_ARG_DEF_FROM_EDGE (header_phi, loop_latch_edge (loop)) != phidef)
+    return NULL_TREE;
+
+  init = PHI_ARG_DEF_FROM_EDGE (header_phi, loop_preheader_edge (loop));
+
+  if (dump_file && (dump_flags & TDF_DETAILS))
+    fprintf (dump_file, "  Optimizing idempotent ABS_EXPR operation\n");
+
+  /* Loop runs >= 1 time (guaranteed by caller), so the exit value is
+     exactly abs (init).  */
+  return fold_build1 (ABS_EXPR, type, init);
+}
+
 /* Try to compute the final value of PHIDEF when PHIDEF is the result of a
    loop-header PHI.
 
@@ -3971,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 bitinv_def, bit_def, abs_def, phi_latch_final_value;
       unsigned HOST_WIDE_INT niter_num;
 
       gphi *header_phi = TREE_CODE (phidef) == SSA_NAME
@@ -3993,6 +4066,12 @@ final_value_replacement_loop (class loop *loop)
                                                             phidef, niter)))
        def = bitinv_def;
 
+      /* Handle idempotent abs () recurrence.  */
+      else if (integer_zerop (niter_desc.may_be_zero)
+               && (abs_def = analyze_and_compute_abs_effect (loop,
+                                                             phidef)))
+       def = abs_def;
+
       /* Handle bitwise induction expression.
 
         .i.e.
-- 
2.34.1

Reply via email to