https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128407
Bug ID: 128407
Summary: [14/15/16/17 Regression] RTL PRE/hoist moves a
trapping FP truncate above a call that may exit
Product: gcc
Version: 17.0
Status: UNCONFIRMED
Keywords: wrong-code
Severity: normal
Priority: P3
Component: rtl-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
#include <stdio.h>
#include <stdlib.h>
static unsigned overflow (void) { return __builtin_ia32_stmxcsr () & 8; }
static void at_exit (void) { printf ("overflow=%u\n", overflow ()); }
__attribute__((noipa)) void maybe_exit (int c) { if (c) exit (0); }
volatile float sinkf;
__attribute__((noipa)) int f (double d, int n, int c)
{
int s = 0;
for (int i = 0; i < n; i++)
{
maybe_exit (c);
sinkf = (float) d;
s++;
}
return s;
}
volatile double vd = 1e300;
int main (void)
{
atexit (at_exit);
__builtin_ia32_ldmxcsr (__builtin_ia32_stmxcsr () & ~0x3f);
return f (vd, 10, 1);
}
```
```
$ gcc -O2 t.c && ./a.out
overflow=8
$ gcc -O2 -fno-gcse t.c && ./a.out
overflow=0
```
The double->float narrowing of 1e300 sets FE_OVERFLOW. In the source it runs
only after maybe_exit () returns, and that
call always exits, so the flag must stay clear. -O0, -O1, -Os and -O2 -fno-gcse
print overflow=0; -O2 and -O3 print
overflow=8. -fno-tree-pre -fno-code-hoisting does not help, so this is the RTL
pass. The rtl-pre dump shows
`PRE: redundant insn 17 (expression 0) in bb 4` for `(float_truncate:SF (reg
d))`, with a copy inserted on the loop
preheader edge, before the call.
-fsanitize=undefined,address,float-cast-overflow is clean, clang -O2 prints
overflow=0.
Same thing with RTL code hoisting at -Os (diamond):
```c
__attribute__((noipa)) void f (double d, int k, int c)
{
if (k)
{ sinki = 1; sinkf = (float) d; }
else
{ maybe_exit (c); sinkf = (float) d; }
}
/* main: f (vd, 0, 1); helpers as above, plus volatile int sinki; */
```
-Os prints overflow=8 (rtl-hoist: `PRE/HOIST: end of bb 2, insn 28, copying
expression 0`); -O2 and -Os -fno-gcse print 0.
Versions (x86_64, run on Compiler Explorer plus local builds), both testcases:
- overflow=0 (good): 7.5.0
- overflow=8 (bad): 8.5.0, 9.5.0, 10.5.0, 11.5.0, 12.5.0, 13.4.0, 14.2.0,
14.4.0, 15.3.0, 16.2.0, 16.2.1 (20261030),
17.0 (20261030 and 20261102)
Cause: gcse.cc prune_expressions only removes may_trap_p expressions from
ANTLOC/TRANSP at blocks with an abnormal
incoming edge, and compute_hash_table_work treats a call only as a clobber of
call-clobbered registers and memory. A
register-only FP expression therefore stays transparent through a block whose
call may not return, and LCM / hoisting
places it before the call. may_trap_p itself says FLOAT_TRUNCATE traps under
the default -ftrapping-math. Other code
movers already handle this: tree PRE (BB_MAY_NOTRETURN, PR42438/PR93491; except
the FP-compare hole in PR128398), RTL loop-invariant motion (always_reached is
reset after a non-const call), and the scheduler ("Avoid moving trapping
instructions across function calls that might
not always return", last_function_call_may_noreturn in sched-deps.cc).
Possible fix (not tested): in prune_expressions, also clear TRANSP (and ANTLOC
for hoisting) of every may_trap_p
expression in blocks containing a call that may not return (as sched-deps.cc
call_may_noreturn_p decides).
Related: PR106078 (RTL PRE moves a load past a trapping division with
-fnon-call-exceptions; its draft fix only changes
memory kills), PR128229 (FP-trapping unspecs hoisted by RTL loop-invariant
motion; different pass and cause),
PR128398 (tree PRE/code hoisting moves a trapping FP compare above a call that
may not return, via vn_nary_may_trap;
same symptom, different pass: here -fno-tree-pre -fno-code-hoisting does not
help, only -fno-gcse does).
Disclaimer: This test case was found with LLM-assisted targeted testing, and
the report was written with LLM assistance.