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.
  • [Bug rtl-optimization/128427] ... lzy20000419 at outlook dot com via Gcc-bugs

Reply via email to