[PATCH v1 1/1] tree-optimization/114502 - final value replacement for idempotent negation
Rachit Mehta via Sourceware Forge <[email protected]> Thu, 06 Aug 2026 11:05:31 +0000
| Newsgroups | gmane.comp.gcc.patches |
|---|---|
| Message-ID | <[email protected]> |
From: Rachit Mehta <[email protected]> SCEV cannot represent a period-2 recurrence, so recognize loops of the form 'c = -c' with a constant trip count in the final-value replacement and replace the exit value by c or -c depending on the parity of the trip count. Bail out when signed overflow must trap (-ftrapv) or be sanitized, mirroring the existing bitop special-cases. PR tree-optimization/114502 gcc/ChangeLog: * tree-scalar-evolution.cc (analyze_and_compute_negate_effect): New. (final_value_replacement_loop): Call it. gcc/testsuite/ChangeLog: * gcc.dg/tree-ssa/scev-negate-1.c: New test. * gcc.dg/tree-ssa/scev-negate-2.c: New test. * gcc.dg/tree-ssa/scev-negate-3.c: New test. Signed-off-by: Rachit Mehta <[email protected]> --- gcc/testsuite/gcc.dg/tree-ssa/scev-negate-1.c | 47 ++++++++ gcc/testsuite/gcc.dg/tree-ssa/scev-negate-2.c | 65 +++++++++++ gcc/testsuite/gcc.dg/tree-ssa/scev-negate-3.c | 82 ++++++++++++++ gcc/tree-scalar-evolution.cc | 103 +++++++++++++++++- 4 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/scev-negate-1.c create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/scev-negate-2.c create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/scev-negate-3.c diff --git a/gcc/testsuite/gcc.dg/tree-ssa/scev-negate-1.c b/gcc/testsuite/gcc.dg/tree-ssa/scev-negate-1.c new file mode 100644 index 0000000000000..c3ef0704532be --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/scev-negate-1.c @@ -0,0 +1,47 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fdump-tree-sccp-details" } */ + +/* Test idempotent NEGATE_EXPR optimization with constant trip counts. + Even iteration count leaves value unchanged; odd count negates it. + Trip counts are kept large enough to avoid complete loop unrolling, + so the recurrence reaches the SCCP final-value replacement. */ + +unsigned +neg_even (unsigned c) +{ + for (int i = 0; i < 1024; i++) + c *= -1; + return c; +} + +unsigned +neg_odd (unsigned c) +{ + for (int i = 0; i < 1025; i++) + c *= -1; + return c; +} + +unsigned +neg_while (unsigned c) +{ + int i = 0; + while (i < 100) + { + c = -c; + i++; + } + return c; +} + +unsigned +neg_odd2 (unsigned c) +{ + for (int i = 0; i < 101; i++) + c *= -1; + return c; +} + +/* { dg-final { scan-tree-dump-times "Idempotent NEGATE_EXPR" 4 "sccp" } } */ +/* { dg-final { scan-tree-dump-times "with expr: c_\[0-9\]+\\(D\\)" 2 "sccp" } } */ +/* { dg-final { scan-tree-dump-times "with expr: -c_\[0-9\]+\\(D\\)" 2 "sccp" } } */ diff --git a/gcc/testsuite/gcc.dg/tree-ssa/scev-negate-2.c b/gcc/testsuite/gcc.dg/tree-ssa/scev-negate-2.c new file mode 100644 index 0000000000000..64388c165f67e --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/scev-negate-2.c @@ -0,0 +1,65 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fdump-tree-sccp-details" } */ + +/* Edge cases with constant trip counts. */ + +unsigned +neg_conditional (unsigned c, int cond) +{ + if (cond) + for (int i = 0; i < 100; i++) + c *= -1; + return c; +} + +unsigned +neg_nested (unsigned c) +{ + for (int j = 0; j < 100; j++) + for (int i = 0; i < 100; i++) + c *= -1; + return c; +} + +unsigned +neg_do_while (unsigned c) +{ + int i = 0; + do + { + c = -c; + i++; + } + while (i < 100); + return c; +} + +unsigned +neg_odd_extra (unsigned c) +{ + for (int i = 0; i < 101; i++) + c *= -1; + return c; +} + +unsigned +neg_large_even (unsigned c) +{ + for (int i = 0; i < 1000000; i++) + c *= -1; + return c; +} + +unsigned +neg_large_odd (unsigned c) +{ + for (int i = 0; i < 999999; i++) + c *= -1; + return c; +} + +/* { dg-final { scan-tree-dump-times "Idempotent NEGATE_EXPR" 6 "sccp" } } */ +/* neg_nested's inner loop is replaced by an intermediate value, not a + parameter default-def, so only 3 even cases match the "(D)" pattern. */ +/* { dg-final { scan-tree-dump-times "with expr: c_\[0-9\]+\\(D\\)" 3 "sccp" } } */ +/* { dg-final { scan-tree-dump-times "with expr: -c_\[0-9\]+\\(D\\)" 2 "sccp" } } */ diff --git a/gcc/testsuite/gcc.dg/tree-ssa/scev-negate-3.c b/gcc/testsuite/gcc.dg/tree-ssa/scev-negate-3.c new file mode 100644 index 0000000000000..1694d9d69c240 --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/scev-negate-3.c @@ -0,0 +1,82 @@ +/* { dg-do run } */ +/* { dg-options "-O2 -fwrapv -fdump-tree-sccp-details" } */ + +#include <limits.h> + +/* noipa keeps the loop bodies intact and forces real calls. */ + +unsigned __attribute__((noipa)) +neg_runtime (unsigned c, int n) +{ + for (int i = 0; i < n; i++) + c *= -1; + return c; +} + +int __attribute__((noipa)) +neg_signed_even (int c) +{ + for (int i = 0; i < 100; i++) + c = -c; + return c; +} + +int __attribute__((noipa)) +neg_signed_odd (int c) +{ + for (int i = 0; i < 101; i++) + c = -c; + return c; +} + +long __attribute__((noipa)) +neg_long_even (long c) +{ + for (int i = 0; i < 1000; i++) + c = -c; + return c; +} + +int +main (void) +{ + /* Zero-iteration path returns the initial value. */ + if (neg_runtime (5u, 0) != 5u) + __builtin_abort (); + + /* Even parity -> unchanged. */ + if (neg_runtime (7u, 1024) != 7u) + __builtin_abort (); + if (neg_runtime (7u, 2) != 7u) + __builtin_abort (); + + /* Odd parity -> negated (unsigned wraps: -7u). */ + if (neg_runtime (7u, 1025) != (unsigned) -7) + __builtin_abort (); + if (neg_runtime (7u, 1) != (unsigned) -7) + __builtin_abort (); + + /* Signed even count, incl. INT_MIN: even parity introduces no negation. */ + if (neg_signed_even (INT_MIN) != INT_MIN) + __builtin_abort (); + if (neg_signed_even (-9) != -9) + __builtin_abort (); + + /* Signed odd count: value negated. */ + if (neg_signed_odd (5) != -5) + __builtin_abort (); + if (neg_signed_odd (-3) != 3) + __builtin_abort (); + + /* Long type even count: value unchanged. */ + if (neg_long_even (123L) != 123L) + __builtin_abort (); + if (neg_long_even (-456L) != -456L) + __builtin_abort (); + + return 0; +} + +/* { dg-final { scan-tree-dump-times "Idempotent NEGATE_EXPR" 3 "sccp" } } */ +/* { dg-final { scan-tree-dump-times "with expr: c_\[0-9\]+\\(D\\)" 2 "sccp" } } */ +/* { dg-final { scan-tree-dump-times "with expr: -c_\[0-9\]+\\(D\\)" 1 "sccp" } } */ diff --git a/gcc/tree-scalar-evolution.cc b/gcc/tree-scalar-evolution.cc index cd99cbe86ce5a..43125926a22e6 100644 --- a/gcc/tree-scalar-evolution.cc +++ b/gcc/tree-scalar-evolution.cc @@ -3862,6 +3862,100 @@ 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 unary negation loop pattern. + + Detects loops of the form + for (i = 0; i < n; i++) + c = -c; + such as those produced by writing c *= -1. + + The loop body negates C exactly N times, so the final value depends only + on the parity of the (constant) trip count N: + N even -> c_init (unchanged) + N odd -> -c_init (negated) + + NITER is the number of latch executions (N == NITER + 1) and must be + constant so the parity is known at compile time. + + Return the replacement expression, or NULL_TREE when the pattern does + not apply. */ + +static tree +analyze_and_compute_negate_effect (class loop *loop, tree phidef, tree niter) +{ + 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 + || SSA_NAME_OCCURS_IN_ABNORMAL_PHI (phidef)) + return NULL_TREE; + + /* PHIDEF must be defined by a NEGATE_EXPR inside the loop. */ + def = SSA_NAME_DEF_STMT (phidef); + if (!is_gimple_assign (def) + || gimple_assign_rhs_code (def) != NEGATE_EXPR + || !flow_bb_inside_loop_p (loop, gimple_bb (def))) + return NULL_TREE; + + op_arg = gimple_assign_rhs1 (def); + + /* The operand must be a 2-arg PHI 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; + + /* 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)); + + /* Need a constant trip count to know the parity. */ + if (!tree_fits_uhwi_p (niter)) + return NULL_TREE; + + tree type = TREE_TYPE (phidef); + unsigned HOST_WIDE_INT niter_num = tree_to_uhwi (niter); + + /* Only integral recurrences are handled here. The caller already + restricts final value replacement to pointer and integral types, and + NEGATE_EXPR is not generated for pointers, so this is normally an + integral type; make the requirement explicit. */ + if (!INTEGRAL_TYPE_P (type)) + return NULL_TREE; + + /* -INT_MIN overflows. */ + if (TYPE_OVERFLOW_TRAPS (type) || TYPE_OVERFLOW_SANITIZED (type)) + return NULL_TREE; + + /* NITER is the number of latch executions, so the loop body (the + negation) runs NITER + 1 times. The final value depends on the + parity of NITER + 1: + NITER odd -> NITER + 1 even -> value unchanged + NITER even -> NITER + 1 odd -> value negated. */ + + /* Even number of negations: value is unchanged. */ + if (niter_num & 1) + { + if (dump_file && (dump_flags & TDF_DETAILS)) + fprintf (dump_file, + " Idempotent NEGATE_EXPR: even count, value unchanged\n"); + return init; + } + + if (dump_file && (dump_flags & TDF_DETAILS)) + fprintf (dump_file, + " Idempotent NEGATE_EXPR: odd count, value negated\n"); + + return fold_build1 (NEGATE_EXPR, type, init); +} + /* Try to compute the final value of PHIDEF when PHIDEF is the result of a loop-header PHI. @@ -3979,7 +4073,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, negate_def, phi_latch_final_value; unsigned HOST_WIDE_INT niter_num; gphi *header_phi = TREE_CODE (phidef) == SSA_NAME @@ -4001,6 +4095,13 @@ final_value_replacement_loop (class loop *loop) phidef, niter))) def = bitinv_def; + else if (integer_zerop (niter_desc.may_be_zero) + && (negate_def + = analyze_and_compute_negate_effect (loop, + phidef, + niter))) + def = negate_def; + /* Handle bitwise induction expression. .i.e. -- 2.54.0