https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128427
Bug ID: 128427
Summary: [14/15/16/17 Regression] crossjumping head merge moves
a trapping division above a call that may not return
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 <stdlib.h>
#include <stdio.h>
volatile int sink2, sink, ve, vd;
__attribute__((noipa)) void maybe_exit (void) { if (ve) { puts ("exit-ok");
exit (0); } }
__attribute__((noipa)) void g (void) { sink++; }
__attribute__((noipa)) int f (int y, int c)
{
int r;
if (c) goto A;
maybe_exit ();
if (vd) {
r = 100 / y;
g ();
sink2 += r; return r - 1;
}
A:
r = 100 / y;
g ();
return r * 3;
}
int main (void)
{
printf ("%d\n", f (2, 1));
ve = 1; vd = 1;
f (0, 0);
return 0;
}
```
```
$ gcc -Os t.c && ./a.out
Floating point exception
$ gcc -Os -fno-crossjumping t.c && ./a.out
150
exit-ok
```
Godbolt (trunk, 16.2.0, 11.5.0 at -Os): https://godbolt.org/z/6Y7h666ff
f (0, 0) calls maybe_exit (), which exits, so 100 / y with y == 0 never runs in
the abstract machine. Expected output is "150" then "exit-ok". At -Os the
program dies with SIGFPE on 12.5.0, 13.4.0, 14.4.0, 15.3.0, 16.2.0 and trunk
(17.0.0 20261103); 11.5.0, 10.5.0, 9.5.0, 8.5.0 and 7.5.0 are fine. At -O2 it
fails on 12.5.0 through 14.4.0 and passes on 15.3.0 and trunk only because
register allocation gives a different shape. -fno-crossjumping fixes every
version, and -fsanitize=undefined reports nothing.
In the jump2 cleanup, try_head_merge_bb (cfgcleanup.cc:2315) finds the common
head "eax = 100; eax = eax / edi" of the two successors and, in the
final_dest_bb case, moves it into the first block, above the block that
contains the call to maybe_exit (). The asm of f starts with "movl $100, %eax;
cltd; idivl %edi". The safety check is can_move_insns_across
(df-problems.cc:4155), called with other_branch_live == NULL
(cfgcleanup.cc:2482 and :2509). A may_trap_or_fault_p insn is only refused
(df-problems.cc:4253) if the ACROSS range itself contains a trapping insn, if
other_branch_live is set, or if the insn is volatile; a call in the ACROSS
range only sets the memory reference flags, so a trapping insn can move above a
call that may not return. The scheduler avoids the same thing with
call_may_noreturn_p / last_function_call_may_noreturn. A possible one-line fix
(not tested) is to set trapping_insns_in_across for every non-const/pure (or
looping) call in the ACROSS loop of can_move_insns_across, i.e.
"trapping_insns_in_across |= CALL_P (insn) && !RTL_CONST_OR_PURE_CALL_P
(insn);".
Related: PR128407 (RTL PRE/hoist in gcse.cc moves a trapping FP truncate above
a call that may exit) and PR128398 (tree PRE/code hoisting moves a trapping FP
comparison above a call that may not return) are the same kind of problem in
other passes; their fixes do not touch cfgcleanup/df-problems.
Disclaimer: This test case was found with LLM-assisted targeted testing, and
the report was written with LLM assistance.