https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113703

--- Comment #10 from GCC Commits <cvs-commit at gcc dot gnu.org> ---
The master branch has been updated by Richard Biener <[email protected]>:

https://gcc.gnu.org/g:afa2c7515d19b01a63bf0bca9a77ea9d55e556a1

commit r17-4249-gafa2c7515d19b01a63bf0bca9a77ea9d55e556a1
Author: Richard Biener <[email protected]>
Date:   Wed Sep 2 13:48:33 2026 +0200

    ivopts: fix the bound when eliminating an IV with a LT comparison

    iv_elimination_compare_lt turns a loop with the exit test i < b, entered
    with i = a and a pointer p = base + a stepping in lockstep with i, into a
    loop with the exit test p < p_0 - a + b, so as to eliminate the counter.

    This is unsound in two ways when the loop may roll zero times, i.e. when
    the number of iterations is a + 1 > b ? 0 : b - a - 1.

    The first one is that the bound is not recomputed by the function, but
    taken from cand_value_at, which evaluates the candidate at the number of
    iterations, i.e. computes p_0 + (b - a - 1 + 1) with b - a computed in the
    type of the number of iterations.  If that type is narrower than the type
    of the offsets, converting the difference is not value preserving when the
    loop rolls zero times, since b - a is negative there.  For example, with

      void f (char *p, unsigned int i, unsigned int n)
      { p += i; do { *p = 1; p += 1; i++; } while (i < n); }

    called as f (p, 2, 0), the bound is computed as p_0 + (sizetype) 0xfffffffd
    and the loop walks 4G bytes forward instead of exiting at the first test.

    The second one is that the comparison of the pointers is performed modulo
    the size of the address space, so replacing i < b by p_0 - a + i < p_0 - a
    + b requires the addition of p_0 - a to be order preserving on the values
    of i and b.  That does not hold when the offsets are as wide as a pointer
    and either of them is large, e.g. for

      void f (char *p, uintptr_t i, uintptr_t n)
      { p += i; do { *p = 1; p += 1; i++; } while (i < n); }

    called as f (a + 2, -2, 8): the bound p_0 - a + b wraps around to the very
    end of the address space, the test holds and the loop again runs away.

    So recompute the bound as base + step * b - step * a, converting a and b to
    the offset type separately, and require the may_be_zero comparison to be an
    unsigned one and both scaled offsets to be provably non-negative and free
of
    overflow.  Since the recomputed bound is the value of the candidate after
    the last iteration, also require the exit test to come after the increment
    of the candidate.  Finally, move the expression_expensive_p check on the
    bound after the call so that it applies to the recomputed bound.

    Assisted-by: Claude Opus 5 <[email protected]>

            PR tree-optimization/113703
            * tree-ssa-loop-ivopts.cc: Include gimple-range.h.
            (nonneg_scaled_offset_p): New function.
            (iv_elimination_compare_lt): Add USE and BOUND_P parameters.  Bail
            out if the exit test does not come after the increment of the
            candidate, if the comparison in the number of iterations is signed
            or if the offsets are not known to be non-negative and to not
            overflow.  Recompute the bound and store it in BOUND_P.
            (may_eliminate_iv): Adjust the call to iv_elimination_compare_lt
            and move the expensiveness check on the bound after it.

            * gcc.dg/tree-ssa/ivopts-lt.c: Use unsigned int counters,
            restrict to lp64.
            * gcc.dg/tree-ssa/ivopts-lt-2.c: Restrict to lp64.
            * gcc.dg/torture/pr113703-1.c: New test.
            * gcc.dg/torture/pr113703-2.c: New test.

Reply via email to