https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127405
--- Comment #3 from Richard Biener <rguenth at gcc dot gnu.org> ---
Root Cause
The loop unrolling framework in tree-ssa-loop-manip.cc handles NE_EXPR loop
conditions by converting them to directional inequalities (LT_EXPR or
GT_EXPR)
to prevent overshooting under unrolled loop body decrements/increments.
However, when an unsigned loop decrements towards a boundary (like index !=
static_cast<size_t>(-1) where the boundary value -1 wraps to max), the
unroller:
1. Converted NE_EXPR to GT_EXPR (due to negative step).
2. Generated an unrolled loop comparison check of ctr_after > 0 (where 0 is
the exit bound calculated relative to the max wrap-around boundary).
3. Under -O3, during Predictive Commoning with an unroll factor of 2, the
counter was decremented by 2 on each iteration.
4. When ctr was 1, ctr_after = 1 - 2 underflowed to max, which is still
strictly > 0 in unsigned arithmetic.
5. Consequently, the exit check ctr_after > 0 evaluated to true, bypassing
loop termination entirely and leading to an infinite loop (which ran
until
memory underflow caused a segmentation fault or generated incorrect
values).
reduced testcase (does not fail on trunk either):
/* { dg-do run } */
/* { dg-options "-O3" } */
extern void abort (void);
void __attribute__((noipa)) test(unsigned long n, double *a, double *b) {
for (unsigned long i = n - 2; i != (unsigned long)-1; --i) {
a[i] = b[i] - a[i+1] - a[i+2];
}
}
int main() {
/* Allocate larger size of 6 to prevent any out-of-bounds accesses. */
double a[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
double b[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
/* n = 4, so loop starting index is 2.
Expected trace of correct execution:
i = 2: a[2] = b[2] - a[3] - a[4] = 3.0 - 0.0 - 0.0 = 3.0
i = 1: a[1] = b[1] - a[2] - a[3] = 2.0 - 3.0 - 0.0 = -1.0
i = 0: a[0] = b[0] - a[1] - a[2] = 1.0 - (-1.0) - 3.0 = -1.0
*/
test(4, a, b);
if (a[2] != 3.0 || a[1] != -1.0 || a[0] != -1.0) {
abort ();
}
return 0;
}