https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127406
Bug ID: 127406
Summary: Wrong code: `(X + M*N) / N` fold introduces a trapping
`INT_MIN / -1` division
Product: gcc
Version: 17.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: tree-optimization
Assignee: unassigned at gcc dot gnu.org
Reporter: zyx2024 at sjtu dot edu.cn
Target Milestone: ---
Created attachment 65596
--> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65596&action=edit
The triggering test case
GCC folds `(X + M*N) / N` to `X / N + M`. The source expression below is
fully defined, but the replacement evaluates `INT_MIN / -1`. On x86_64 the
generated `idivl` raises SIGFPE.
Testcase (`div-intmin.c`, attached):
```c
static int __attribute__((noipa)) f(int x, int m, int n)
{
if (x > -6 || m < -3 || m > -2 || n < -2 || n > -1)
return 0;
return (x + m * n) / n;
}
int main(void)
{
return f(-2147483647 - 1, -2, -1) != 2147483646;
}
```
The testcase is standalone and includes no headers.
Compiler under test: GCC Git commit
`a426e0a8c5ee9cdcf842ea44dc396069e890a4ca`.
`gcc -v` output (local filesystem paths redacted):
```
Reading specs from <gcc-build>/gcc/specs
COLLECT_GCC=<gcc-build>/gcc/xgcc
COLLECT_LTO_WRAPPER=<gcc-build>/gcc/lto-wrapper
Target: x86_64-pc-linux-gnu
Configured with: <gcc-source>/configure --prefix=<install-prefix>
--disable-bootstrap --disable-multilib --enable-languages=c
--enable-checking=release --disable-werror CFLAGS=-O2 CXXFLAGS=-O2
Thread model: posix
Supported LTO compression algorithms: zlib
gcc version 17.0.0 20260914 (experimental) (GCC)
```
Reproducer command (local build path redacted):
```
$ BUILD=<gcc-build>
$ $BUILD/gcc/xgcc -B$BUILD/gcc -B$BUILD/x86_64-pc-linux-gnu/libgcc
-B$BUILD/x86_64-pc-linux-gnu/libatomic/.libs -std=c11 -Wall -Wextra -O2
div-intmin.c -o test
$ ./test
Floating point exception (core dumped)
$ echo $?
136
```
There are no compiler diagnostics. The testcase returns 0 at `-O0` and
`-O1`; it raises SIGFPE at `-O2` and `-O3`.
`main` calls `f` with `x = INT_MIN`, `m = -2`, and `n = -1`. The source
computes:
```
m * n = 2
x + m * n = INT_MIN + 2 = -2147483646
(x + m * n)/n = 2147483646
```
All source intermediates are representable as `int`, and `n` is nonzero.
The `evrp` dump shows the invalid replacement:
```
_14 = x_10(D) / n_12(D);
_13 = m_11(D) + _14;
```
The `match.pd` rule around lines 1163-1181 checks the multiplication and the
original addition for overflow, but does not check whether the newly introduced
`X/N` has a representable quotient. The sibling
`(X - M*N) / N -> X / N - M` rule has the same issue; `div-minus.c` is attached
as a second testcase.
The failure remains with
`-fno-strict-aliasing -fwrapv -fno-aggressive-loop-optimizations`. With
`-fsanitize=address,undefined`, the testcase is clean at `-O0`; at `-O2`, ASan
catches the same optimizer-generated FPE without a UBSan source-operation
diagnostic. GCC 11.4.0, Clang 14.0.0, and CompCert 3.18 return 0 at `-O2`.
GCC 11 and Clang also return 0 with `-fsanitize=address,undefined`.