https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128424
Bug ID: 128424
Summary: [14/15/16/17 Regression] loop unswitching evaluates a
trapping FP comparison that is never executed
Product: gcc
Version: 17.0
Status: UNCONFIRMED
Keywords: wrong-code
Severity: normal
Priority: P3
Component: tree-optimization
Assignee: unassigned at gcc dot gnu.org
Reporter: lzy20000419 at outlook dot com
Target Milestone: ---
Host: x86_64-pc-linux-gnu
Target: x86_64-pc-linux-gnu
Build: x86_64-pc-linux-gnu
```c
/* Loop unswitching evaluates a trapping float compare that the source never
executes.
The compare x < 1.0 is reached only when a[i] != 0, and a[] is all zero.
x = NaN, so the ordered compare would raise FE_INVALID. Expected:
invalid=0. */
#include <stdio.h>
volatile int sink;
int a[16];
__attribute__((noipa)) void f (double x, int n)
{
for (int i = 0; i < n; i++)
if (a[i])
{
if (x < 1.0)
sink = 1;
else
sink = 2;
}
}
volatile double vx = __builtin_nan ("");
int main (void)
{
__builtin_ia32_ldmxcsr (__builtin_ia32_stmxcsr () & ~0x3f);
f (vx, 16);
printf ("invalid=%u\n", __builtin_ia32_stmxcsr () & 1);
return 0;
}
```
```
$ gcc -O3 t.c && ./a.out
invalid=1
$ gcc -O3 -fno-unswitch-loops t.c && ./a.out
invalid=0
$ gcc -O2 t.c && ./a.out
invalid=0
```
Godbolt (trunk, 16.2.0, 8.5.0, 7.5.0 at -O3): https://godbolt.org/z/qe5nEdv1P
The comparison x < 1.0 is only reached when a[i] != 0, and a[] is all zero, so
in the abstract machine it never runs and FE_INVALID must stay clear (default
-ftrapping-math, no fast-math options). Loop unswitching evaluates the
comparison unconditionally in front of the loop; -fdump-tree-unswitch-details
shows "unswitching loop 1 on 'if' with condition: x_7(D) < 1.0e+0". With the
invalid exception unmasked in MXCSR (same test, ldmxcsr mask also clearing bit
0x80) the -O3 program dies with SIGFPE on trunk and 16.2.0. The same happens
for a loop that runs zero times when the unswitch test ends up before the loop
entry check, e.g. "for (i = 0; i < n; i++) if (x < 1.0) sink = 1; else sink =
i;" with n = 0 at -O3 -fno-tree-ch.
Rechecked on trunk 17.0.0 20261103. Wrong on 8.5.0, 9.5.0, 10.5.0, 11.5.0,
12.5.0, 13.4.0, 14.4.0, 15.3.0, 16.2.0 and trunk; 7.5.0 and 6.5.0 print
invalid=0 (tested with noinline,noclone because 7.5.0 has no noipa).
-fsanitize=undefined,address reports nothing.
find_unswitching_predicates_for_bb (tree-ssa-loop-unswitch.cc:486) accepts any
gcond with loop-invariant operands. It rejects operands that may be undefined
(is_maybe_undefined, :468, which notes that the loop header is the only block
known to always execute), but there is no matching check that the condition
itself cannot trap. A possible one-line fix (not tested) is to skip the
predicate when "gimple_could_trap_p (stmt) && bb != loop->header" (or, more
precisely, when the block is not always executed).
Related: PR128398 (tree PRE/code hoisting moves a trapping FP comparison above
a call that may not return) and PR128407 (RTL PRE/hoist moves a trapping FP
truncate above a call that may exit) are the same kind of problem, a trapping
FP operation moved onto a path where the source does not execute it, but in
different passes with different missing checks; a fix for either does not cover
unswitching.
Disclaimer: This test case was generated by a fuzzer, and the report was
written with LLM assistance.