https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128398

            Bug ID: 128398
           Summary: [14/15/16/17 Regression] code hoisting moves a
                    trapping FP comparison above a call that may not
                    return
           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: ---

```c
#define _GNU_SOURCE
#include <fenv.h>
#include <stdlib.h>

__attribute__((noipa)) void maybe_exit (int c) { if (c) exit (0); }

__attribute__((noipa)) int f (double a, double b, int c)
{
  int r;
  if (c > 5)
    {
      maybe_exit (c);
      r = a < b;
    }
  else
    r = (a < b) * c + 1;
  return r;
}

int main (void)
{
  feenableexcept (FE_INVALID);
  f (__builtin_nan (""), 1.0, 7);   /* exits inside maybe_exit, before any
comparison */
  return 1;
}
```

```
$ gcc -O2 t.c -lm && ./a.out
Floating point exception
$ gcc -O2 -fno-code-hoisting t.c -lm && ./a.out; echo $?
0
```

-O0, -O1, -Og and clang are fine (exit status 0). -O2, -O3 and -Os fail;
-fno-tree-pre does not help, -fno-code-hoisting does.
The program has no undefined behaviour (-fsanitize=undefined,address is clean).
An ordered comparison `<` with a NaN
operand raises FE_INVALID (comisd), and -ftrapping-math is on by default, so
the compiler must not execute it on a
path where the source does not. Without feenableexcept the same problem shows
up as a spurious FE_INVALID flag
(an atexit handler sees fetestexcept (FE_INVALID) != 0 at -O2).

The PRE dump shows the comparison inserted in the condition block, before the
call:

```
<bb 2>:  _14 = a_8(D) < b_9(D);  _15 = (int) _14;  if (c_7(D) > 5) ...
<bb 3>:  maybe_exit (c_7(D));
```

Versions (x86_64, -O2 -lm, run on Compiler Explorer plus local builds):
- exit 0 (good): 7.5.0, 8.5.0, 9.5.0, 10.5.0, 11.5.0
- SIGFPE (bad): 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)
- -O2 -fno-code-hoisting: exit 0 on 14.2.0, 16.2.1 and trunk.

Compiler Explorer (11.4 vs trunk, -O2 -lm): https://godbolt.org/z/KfcWdGvEj

Cause: PRE removes possibly trapping expressions from ANTIC in blocks that may
not return (BB_MAY_NOTRETURN in
tree-ssa-pre.cc prune_clobbered_mems and in compute_avail for EXP_GEN), using
vn_nary_may_trap (tree-ssa-sccvn.cc).
That function takes fp_operation from nary->type, which for a comparison is the
boolean result type, so
FLOAT_TYPE_P is false and a float comparison is never seen as trapping. The
function says it is "a copy of
stmt_could_throw_1_p adjusted to the SCCVN IL", but gimple_could_trap_p_1 ("For
comparisons we need to check rhs
operand types instead of lhs type") and stmt_could_throw_1_p already use the
type of the first operand.

Controls (same shape): replacing the comparison with `(a - b) > 0.5` (a = b =
inf) or with an integer division by zero
is not hoisted, because those operations are seen as trapping; only the
comparison slips through.

Possible fix (not tested): in vn_nary_may_trap, use
`type = (TREE_CODE_CLASS (nary->opcode) == tcc_comparison ? TREE_TYPE
(nary->op[0]) : nary->type);`

Reply via email to