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

            Bug ID: 126576
           Summary: isinf() overflow check on a chained floating-point
                    multiply is deleted at -O1+ without -ffinite-math-only
           Product: gcc
           Version: 14.2.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c
          Assignee: unassigned at gcc dot gnu.org
          Reporter: michaelmalis2 at gmail dot com
  Target Milestone: ---

Created attachment 65202
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65202&action=edit
P

## Description

Given two multiplies where the second consumes the first's result, and each is
guarded by `isinf(result) && !isinf(operand)`, gcc deletes the *second* guard.
The first guard is kept. No `-ffast-math` / `-ffinite-math-only` is in effect,
so `finite * finite -> inf` (i.e. overflow) is a case the compiler must model.

The deletion appears to use a fact derived from the first guard: on its
non-error fall-through path the intermediate product is known finite, and gcc
then concludes the second guard's condition is unsatisfiable — which would only
hold if a product of two finite values could not be infinite.

This is a regression: gcc 12 is correct, gcc 13 and later are not.

## Reproducer

The source is self-contained (no headers), so the preprocessed file is
identical
to it. `-save-temps` output attached as `g.i`.

`g.c`:

```c
extern void oflow(void);
double g(double x) {
        double r1 = x * x;
        if (__builtin_isinf(r1) && !__builtin_isinf(x)) oflow();
        double r2 = r1 * 3.0;
        if (__builtin_isinf(r2) && !__builtin_isinf(r1)) oflow();
        return r2;
}
```

`main.c`:

```c
#include <stdio.h>
#include <stdlib.h>
extern double g(double);
void oflow(void) { printf("oflow() called\n"); exit(1); }
int main(int c, char **v) { printf("g = %g, no call\n", g(atof(v[1]))); return
0; }
```

### Command line and output

```
$ gcc -v -save-temps -O2 g.c main.c -o t
$ ./t 1e154
g = inf, no call            <-- WRONG: oflow() must be called

$ gcc -O0 g.c main.c -o t0 && ./t0 1e154
oflow() called              <-- expected behaviour

$ ./t 1e200
oflow() called              <-- control: the FIRST guard is still emitted
```

With `x = 1e154`: `x*x = 1e308`, which is finite (below `DBL_MAX =
1.7976931348623157e308`); `1e308 * 3.0` overflows to `+inf` with both operands
finite, so the second `oflow()` must be reached.

With `x = 1e200`: `x*x` itself overflows, the first guard fires — showing the
pattern is not wholly removed, only its second instance.

### Generated code

At `-O2` the second multiply is followed directly by the underflow-free return
path; there is no comparison of `r2` against `DBL_MAX` and no call:

`gcc -O2 -c g.c && objdump -d g.o`:

```
   4:   mov     x0, #0x7fefffffffffffff   ; DBL_MAX
   8:   fmov    d30, x0                   ; gcc lowers isinf(v) to |v| >
DBL_MAX
  14:   fmul    d15, d0, d0               ; r1 = x*x
  18:   fcmp    d15, d30                  ; isinf(r1)?
  1c:   b.le    2c                        ; finite -> skip guard 1
  20:   fabs    d31, d0                   ; |x|   <- the !isinf(x) test,
PRESENT
  24:   fcmp    d31, d30
  28:   b.le    40                        ; -> bl oflow
  2c:   fmov    d0, #3.0
  30:   fmul    d0, d15, d0               ; r2 = r1 * 3.0
  34:   ldr     d15, [sp, #16]
  38:   ldp     x29, x30, [sp], #32
  3c:   ret                               ; <-- guard 2 absent: no fcmp of r2,
no call
  40:   bl      0 <oflow>
```

Guard 1 (`14`-`28`) is emitted in full, including the `!isinf(x)` operand test.
Guard 2 is absent: after the second `fmul` at `30` there is no comparison of
`r2` against `DBL_MAX` and no path to `oflow`. Control reaches `2c` only via
`b.le` at `1c`, i.e. only when `r1` is finite, and `3.0` is a finite constant —
so guard 2's condition is satisfiable there and must be tested.

## Affected versions

Verified by runtime witness (does `oflow()` get called for `x = 1e154`?):

| gcc | -O0 | -O1 | -O2 | -O3 |
|------|-----|-----|-----|-----|
| 12.5.0 | ok | ok | ok | ok |
| 13.4.0 | ok | **wrong** | **wrong** | **wrong** |
| 14.2.0 | ok | **wrong** | **wrong** | **wrong** |
| 14.3.0 | ok | **wrong** | **wrong** | **wrong** |
| 15.3.0 | ok | **wrong** | **wrong** | **wrong** |

All on aarch64-linux-gnu (official `gcc:<tag>` container images). Apple clang
17
on arm64 is unaffected at every level. Not yet bisected to a specific commit;
the 12 -> 13 window is the range.

## Ruling out user error

Per https://gcc.gnu.org/bugs/ :

- `gcc -O2 -Wall -Wextra -c g.c` — no diagnostics.
- `-fsanitize=undefined,address` reports **no** undefined behaviour; the wrong
  result still occurs at `-O2` with both sanitizers enabled.
- Still reproduces with `-fno-strict-aliasing -fwrapv
  -fno-aggressive-loop-optimizations`.
- No `-ffast-math`, `-ffinite-math-only`, `-Ofast`, or
`-funsafe-math-optimizations`.
  (For completeness: with `-ffinite-math-only` the deletion is of course
expected
  and correct.)
- `-ffp-contract=on` / `=fast` / `=off` make no difference.

## gcc -v

```
Configured with: /usr/src/gcc/configure --build=aarch64-linux-gnu \
  --disable-multilib --enable-languages=c,c++,fortran,go
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 14.2.0 (GCC)
```

## Real-world impact

This is PostgreSQL's overflow-detection idiom, verbatim
(`src/include/utils/float.h:207`, `float8_mul`), reached through
`circle_ar() = float8_mul(float8_mul(r, r), M_PI)`
(`src/backend/utils/adt/geo_ops.c:5159`). In stock PostgreSQL 18.3 builds at
the
default `-O2`:

```sql
SELECT area(circle '<(0,0),1e154>');   -- returns Infinity
                                       -- must raise 22003 "value out of range:
overflow"
SELECT area(circle '<(0,0),1e200>');   -- correctly raises 22003
```

Reproduced from an unmodified PostgreSQL 18.3 source tree with gcc 14.2, so it
is not specific to a distribution's patches. A separate report has been filed
with the PostgreSQL project.

## Attachments

- `g.i` (preprocessed, `-save-temps`)
- `main.c`
- `g.s` (`-O2` assembly, for reference)

Reply via email to