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.
