BUG #19593: area(circle) silently returns Infinity instead of raising "value out of range: overflow"
Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.
The following bug has been logged on the website:
Bug reference: 19593
Logged by: Michael Malis
Email address: malis@pgrust.com
PostgreSQL version: 18.3
Operating system: Debian (official Docker image), aarch64
Description:
I'm not sure what you'll want to do with this one, but I figured I would at
least report it. The cause seems to be a bug in gcc.
area(circle) returns Infinity where it must raise ERROR 22003 "value out of
range: overflow". The overflow check in float8_mul() is present in the
source
but is absent from the generated code, because gcc 13 and later delete it at
-O1 and above.
The same binary raises the error correctly for the equivalent SQL-level
expression, and for a circle whose radius overflows one step earlier, so
this
is not "PostgreSQL does not check circle areas".
-- WRONG: no error, returns Infinity
SELECT area(circle '<(0,0),1e154>');
area
------------------------
Infinity
-- CORRECT (control): the *inner* multiply overflows, so the surviving
-- check fires
SELECT area(circle '<(0,0),1e200>');
ERROR: value out of range: overflow
-- CORRECT (control): the same arithmetic, expressed in SQL
SELECT 1e154::float8 * 1e154::float8 * pi();
ERROR: value out of range: overflow
-- sane value, for reference
SELECT area(circle '<(0,0),1e10>');
area
-------------------------
3.1415926535897933e+20
1e154 * 1e154 = 1e308, which is finite (below DBL_MAX); multiplying that by
pi
overflows. Behaviour is identical whether the expression is constant-folded
at
plan time or evaluated at runtime:
SELECT area(c) FROM (VALUES (circle '<(0,0),1e154>')) t(c); --
Infinity
NaN and Infinity radii behave correctly (NaN -> NaN, Infinity -> Infinity).
WHERE IT COMES FROM
src/backend/utils/adt/geo_ops.c:5159
static float8
circle_ar(CIRCLE *circle)
{
return float8_mul(float8_mul(circle->radius, circle->radius), M_PI);
}
src/include/utils/float.h:207
static inline float8
float8_mul(const float8 val1, const float8 val2)
{
float8 result;
result = val1 * val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
float_overflow_error();
...
Two float8_mul calls are inlined into one function. gcc keeps the first
copy's
overflow check and deletes the second's.
Disassembly of the shipped binary (circle_area; symbols are present in
.dynsym).
gcc lowers isinf(x) to |x| > DBL_MAX, with d30 = 0x7fefffffffffffff:
; inner multiply -- check intact
5540dc fmul d31, d29, d29 ; r*r
5540e0 fcmp d31, d30
5540e4 b.le 5540fc
5540e8 fabs d29, d29 ; |r| <- the !isinf(val1) test
5540ec fcmp d29, d30
5540f0 b.le 554154
554154 bl float_overflow_error ; raises
; outer multiply -- operand test gone
554104 adrp x0, 76c000
554108 ldr d29, [x0, #568] ; M_PI
55410c fmul d31, d31, d29 ; (r*r) * M_PI
554110 fcmp d31, d30
554114 b.le 554120
554118 mov x0, #0x7ff0000000000000 ; returns +Infinity
55411c b 55412c
Control reaches the outer multiply only via the b.le at 5540e4, i.e. only
when
r*r <= DBL_MAX, and M_PI is a finite constant. So
"!isinf(val1) && !isinf(val2)" is true on that path and
float_overflow_error()
must be called.
Building from an unmodified 18.3 tree (git tag stamp 62d6c7d) with gcc 14.2
and
the same CFLAGS reproduces it, so this is not specific to Debian's
packaging:
./configure --without-readline --without-zlib --without-icu \
CFLAGS="-g -O2 -fno-strict-aliasing -fwrapv
-fexcess-precision=standard"
make -C src/backend submake-generated-headers
make -C src/backend/utils/adt geo_ops.o
objdump -d geo_ops.o
circle_area then contains 3 fmul but only 1 call to float_overflow_error.
From
pristine source the outer check is removed entirely: there is no DBL_MAX
comparison after the second fmul at all, only the underflow test.
Hi, Michael!
Agreed — PostgreSQL's check is fine, this is a gcc wrong-code bug.
circle_ar() is just two inlined float8_mul() calls:
```
return float8_mul(float8_mul(circle->radius, circle->radius), M_PI);
```
For radius 1e154, r*r is still finite (~1e308), but (r*r)*pi overflows.
float8_mul() is supposed to catch that with:
```
result = val1 * val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
float_overflow_error();
```
That check is present in the source and survives at -O0, with clang,
and for the equivalent SQL expression
1e154::float8 * 1e154::float8 * pi() (as in the report). With gcc -O1
and above it is deleted for the outer multiply.
What gcc does is jump threading (-fthread-jumps; enabled by default at
-O1 and above). After the inner check it knows r*r is finite, M_PI is
a finite constant, and it incorrectly treats "finite * finite yields
Inf" as impossible. The outer float_overflow_error() call disappears;
the function just returns Infinity. -O2 -fno-thread-jumps restores the
correct behaviour. This is with a normal -O2 build; we do not use
-ffinite-math-only / -ffast-math, so gcc is not entitled to assume that
finite*finite cannot produce Infinity.
Reduced C reproducer (same shape as circle_ar / float8_mul). On the
correct path it prints the same message as PostgreSQL:
```
ERROR: value out of range: overflow
```
On the buggy path it prints Infinity (and exits 1):
```
Host: Linux x86_64
gcc: gcc (Ubuntu 15.2.0-16ubuntu1) 15.2.0
clang: Ubuntu clang version 21.1.8
compiler / flags result
------------------------------- ------------------------------------
gcc -O0 OK (ERROR: value out of range...)
gcc -O1 BUG (Infinity)
gcc -O2 BUG (Infinity)
gcc -O2 -fno-thread-jumps OK (ERROR: value out of range...)
clang -O0 OK (ERROR: value out of range...)
clang -O1 OK (ERROR: value out of range...)
clang -O2 OK (ERROR: value out of range...)
```
So this is not "PostgreSQL does not check circle areas". The check is
there, gcc optimizes it away, clang does not. The same pattern can
affect other inlined float4/8 helpers when both operands are proven
finite.
One possible workaround on our side would be to store the operation
result in a volatile temporary in those helpers, so the isinf() check
must inspect the computed value. But it seems to me that we should
address the root cause first: file this with gcc bugzilla and decide
what, if anything, to do in PostgreSQL based on their response.
сб, 1 авг. 2026 г. в 18:02, PG Bug reporting form <noreply@postgresql.org>:
The following bug has been logged on the website:
Bug reference: 19593
Logged by: Michael Malis
Email address: malis@pgrust.com
PostgreSQL version: 18.3
Operating system: Debian (official Docker image), aarch64
Description:I'm not sure what you'll want to do with this one, but I figured I would at
least report it. The cause seems to be a bug in gcc.area(circle) returns Infinity where it must raise ERROR 22003 "value out of
range: overflow". The overflow check in float8_mul() is present in the
source
but is absent from the generated code, because gcc 13 and later delete it
at
-O1 and above.The same binary raises the error correctly for the equivalent SQL-level
expression, and for a circle whose radius overflows one step earlier, so
this
is not "PostgreSQL does not check circle areas".-- WRONG: no error, returns Infinity
SELECT area(circle '<(0,0),1e154>');
area
------------------------
Infinity-- CORRECT (control): the *inner* multiply overflows, so the surviving
-- check fires
SELECT area(circle '<(0,0),1e200>');
ERROR: value out of range: overflow-- CORRECT (control): the same arithmetic, expressed in SQL
SELECT 1e154::float8 * 1e154::float8 * pi();
ERROR: value out of range: overflow-- sane value, for reference
SELECT area(circle '<(0,0),1e10>');
area
-------------------------
3.1415926535897933e+201e154 * 1e154 = 1e308, which is finite (below DBL_MAX); multiplying that by
pi
overflows. Behaviour is identical whether the expression is
constant-folded
at
plan time or evaluated at runtime:SELECT area(c) FROM (VALUES (circle '<(0,0),1e154>')) t(c); --
InfinityNaN and Infinity radii behave correctly (NaN -> NaN, Infinity -> Infinity).
WHERE IT COMES FROM
src/backend/utils/adt/geo_ops.c:5159
static float8
circle_ar(CIRCLE *circle)
{
return float8_mul(float8_mul(circle->radius, circle->radius),
M_PI);
}src/include/utils/float.h:207
static inline float8
float8_mul(const float8 val1, const float8 val2)
{
float8 result;result = val1 * val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
float_overflow_error();
...Two float8_mul calls are inlined into one function. gcc keeps the first
copy's
overflow check and deletes the second's.Disassembly of the shipped binary (circle_area; symbols are present in
.dynsym).
gcc lowers isinf(x) to |x| > DBL_MAX, with d30 = 0x7fefffffffffffff:; inner multiply -- check intact
5540dc fmul d31, d29, d29 ; r*r
5540e0 fcmp d31, d30
5540e4 b.le 5540fc
5540e8 fabs d29, d29 ; |r| <- the !isinf(val1) test
5540ec fcmp d29, d30
5540f0 b.le 554154
554154 bl float_overflow_error ; raises; outer multiply -- operand test gone
554104 adrp x0, 76c000
554108 ldr d29, [x0, #568] ; M_PI
55410c fmul d31, d31, d29 ; (r*r) * M_PI
554110 fcmp d31, d30
554114 b.le 554120
554118 mov x0, #0x7ff0000000000000 ; returns +Infinity
55411c b 55412cControl reaches the outer multiply only via the b.le at 5540e4, i.e. only
when
r*r <= DBL_MAX, and M_PI is a finite constant. So
"!isinf(val1) && !isinf(val2)" is true on that path and
float_overflow_error()
must be called.Building from an unmodified 18.3 tree (git tag stamp 62d6c7d) with gcc 14.2
and
the same CFLAGS reproduces it, so this is not specific to Debian's
packaging:./configure --without-readline --without-zlib --without-icu \
CFLAGS="-g -O2 -fno-strict-aliasing -fwrapv
-fexcess-precision=standard"
make -C src/backend submake-generated-headers
make -C src/backend/utils/adt geo_ops.o
objdump -d geo_ops.ocircle_area then contains 3 fmul but only 1 call to float_overflow_error.
From
pristine source the outer check is removed entirely: there is no DBL_MAX
comparison after the second fmul at all, only the underflow test.
--
Regards,
Rachitskiy Andrey
Hi Michael, all,
I've opened PR in Bugzilla GCC for the issue you reported:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126631
I'll follow up there with any progress.
сб, 1 авг. 2026 г. в 20:16, Andrey Rachitskiy <pl0h0yp1@gmail.com>:
Hi, Michael!
Agreed — PostgreSQL's check is fine, this is a gcc wrong-code bug.
circle_ar() is just two inlined float8_mul() calls:
```
return float8_mul(float8_mul(circle->radius, circle->radius), M_PI);
```
For radius 1e154, r*r is still finite (~1e308), but (r*r)*pi overflows.
float8_mul() is supposed to catch that with:
```
result = val1 * val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
float_overflow_error();
```
That check is present in the source and survives at -O0, with clang,
and for the equivalent SQL expression
1e154::float8 * 1e154::float8 * pi() (as in the report). With gcc -O1
and above it is deleted for the outer multiply.What gcc does is jump threading (-fthread-jumps; enabled by default at
-O1 and above). After the inner check it knows r*r is finite, M_PI is
a finite constant, and it incorrectly treats "finite * finite yields
Inf" as impossible. The outer float_overflow_error() call disappears;
the function just returns Infinity. -O2 -fno-thread-jumps restores the
correct behaviour. This is with a normal -O2 build; we do not use
-ffinite-math-only / -ffast-math, so gcc is not entitled to assume that
finite*finite cannot produce Infinity.Reduced C reproducer (same shape as circle_ar / float8_mul). On the
correct path it prints the same message as PostgreSQL:
```
ERROR: value out of range: overflow
```
On the buggy path it prints Infinity (and exits 1):
```
Host: Linux x86_64
gcc: gcc (Ubuntu 15.2.0-16ubuntu1) 15.2.0
clang: Ubuntu clang version 21.1.8compiler / flags result
------------------------------- ------------------------------------
gcc -O0 OK (ERROR: value out of range...)
gcc -O1 BUG (Infinity)
gcc -O2 BUG (Infinity)
gcc -O2 -fno-thread-jumps OK (ERROR: value out of range...)
clang -O0 OK (ERROR: value out of range...)
clang -O1 OK (ERROR: value out of range...)
clang -O2 OK (ERROR: value out of range...)
```
So this is not "PostgreSQL does not check circle areas". The check is
there, gcc optimizes it away, clang does not. The same pattern can
affect other inlined float4/8 helpers when both operands are proven
finite.One possible workaround on our side would be to store the operation
result in a volatile temporary in those helpers, so the isinf() check
must inspect the computed value. But it seems to me that we should
address the root cause first: file this with gcc bugzilla and decide
what, if anything, to do in PostgreSQL based on their response.сб, 1 авг. 2026 г. в 18:02, PG Bug reporting form <noreply@postgresql.org
:
The following bug has been logged on the website:
Bug reference: 19593
Logged by: Michael Malis
Email address: malis@pgrust.com
PostgreSQL version: 18.3
Operating system: Debian (official Docker image), aarch64
Description:I'm not sure what you'll want to do with this one, but I figured I would
at
least report it. The cause seems to be a bug in gcc.area(circle) returns Infinity where it must raise ERROR 22003 "value out
of
range: overflow". The overflow check in float8_mul() is present in the
source
but is absent from the generated code, because gcc 13 and later delete it
at
-O1 and above.The same binary raises the error correctly for the equivalent SQL-level
expression, and for a circle whose radius overflows one step earlier, so
this
is not "PostgreSQL does not check circle areas".-- WRONG: no error, returns Infinity
SELECT area(circle '<(0,0),1e154>');
area
------------------------
Infinity-- CORRECT (control): the *inner* multiply overflows, so the surviving
-- check fires
SELECT area(circle '<(0,0),1e200>');
ERROR: value out of range: overflow-- CORRECT (control): the same arithmetic, expressed in SQL
SELECT 1e154::float8 * 1e154::float8 * pi();
ERROR: value out of range: overflow-- sane value, for reference
SELECT area(circle '<(0,0),1e10>');
area
-------------------------
3.1415926535897933e+201e154 * 1e154 = 1e308, which is finite (below DBL_MAX); multiplying that
by
pi
overflows. Behaviour is identical whether the expression is
constant-folded
at
plan time or evaluated at runtime:SELECT area(c) FROM (VALUES (circle '<(0,0),1e154>')) t(c); --
InfinityNaN and Infinity radii behave correctly (NaN -> NaN, Infinity ->
Infinity).WHERE IT COMES FROM
src/backend/utils/adt/geo_ops.c:5159
static float8
circle_ar(CIRCLE *circle)
{
return float8_mul(float8_mul(circle->radius, circle->radius),
M_PI);
}src/include/utils/float.h:207
static inline float8
float8_mul(const float8 val1, const float8 val2)
{
float8 result;result = val1 * val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
float_overflow_error();
...Two float8_mul calls are inlined into one function. gcc keeps the first
copy's
overflow check and deletes the second's.Disassembly of the shipped binary (circle_area; symbols are present in
.dynsym).
gcc lowers isinf(x) to |x| > DBL_MAX, with d30 = 0x7fefffffffffffff:; inner multiply -- check intact
5540dc fmul d31, d29, d29 ; r*r
5540e0 fcmp d31, d30
5540e4 b.le 5540fc
5540e8 fabs d29, d29 ; |r| <- the !isinf(val1) test
5540ec fcmp d29, d30
5540f0 b.le 554154
554154 bl float_overflow_error ; raises; outer multiply -- operand test gone
554104 adrp x0, 76c000
554108 ldr d29, [x0, #568] ; M_PI
55410c fmul d31, d31, d29 ; (r*r) * M_PI
554110 fcmp d31, d30
554114 b.le 554120
554118 mov x0, #0x7ff0000000000000 ; returns +Infinity
55411c b 55412cControl reaches the outer multiply only via the b.le at 5540e4, i.e. only
when
r*r <= DBL_MAX, and M_PI is a finite constant. So
"!isinf(val1) && !isinf(val2)" is true on that path and
float_overflow_error()
must be called.Building from an unmodified 18.3 tree (git tag stamp 62d6c7d) with gcc
14.2
and
the same CFLAGS reproduces it, so this is not specific to Debian's
packaging:./configure --without-readline --without-zlib --without-icu \
CFLAGS="-g -O2 -fno-strict-aliasing -fwrapv
-fexcess-precision=standard"
make -C src/backend submake-generated-headers
make -C src/backend/utils/adt geo_ops.o
objdump -d geo_ops.ocircle_area then contains 3 fmul but only 1 call to float_overflow_error.
From
pristine source the outer check is removed entirely: there is no DBL_MAX
comparison after the second fmul at all, only the underflow test.--
Regards,
Rachitskiy Andrey
--
Regards,
Rachitskiy Andrey
I researched related past bugs and found this is already fixed in
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126464 (reverse Inf handling
in float_widen_lhs_range / range-op-float.cc).
Jakub Jelinek says: Fixed also for 15.4+, as well as backported to 14.5 and
13.5.
вт, 4 авг. 2026 г. в 13:05, Andrey Rachitskiy <pl0h0yp1@gmail.com>:
Show quoted text
Hi Michael, all,
I've opened PR in Bugzilla GCC for the issue you reported:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126631
I'll follow up there with any progress.
сб, 1 авг. 2026 г. в 20:16, Andrey Rachitskiy <pl0h0yp1@gmail.com>:
Hi, Michael!
Agreed — PostgreSQL's check is fine, this is a gcc wrong-code bug.
circle_ar() is just two inlined float8_mul() calls:
```
return float8_mul(float8_mul(circle->radius, circle->radius), M_PI);
```
For radius 1e154, r*r is still finite (~1e308), but (r*r)*pi overflows.
float8_mul() is supposed to catch that with:
```
result = val1 * val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
float_overflow_error();
```
That check is present in the source and survives at -O0, with clang,
and for the equivalent SQL expression
1e154::float8 * 1e154::float8 * pi() (as in the report). With gcc -O1
and above it is deleted for the outer multiply.What gcc does is jump threading (-fthread-jumps; enabled by default at
-O1 and above). After the inner check it knows r*r is finite, M_PI is
a finite constant, and it incorrectly treats "finite * finite yields
Inf" as impossible. The outer float_overflow_error() call disappears;
the function just returns Infinity. -O2 -fno-thread-jumps restores the
correct behaviour. This is with a normal -O2 build; we do not use
-ffinite-math-only / -ffast-math, so gcc is not entitled to assume that
finite*finite cannot produce Infinity.Reduced C reproducer (same shape as circle_ar / float8_mul). On the
correct path it prints the same message as PostgreSQL:
```
ERROR: value out of range: overflow
```
On the buggy path it prints Infinity (and exits 1):
```
Host: Linux x86_64
gcc: gcc (Ubuntu 15.2.0-16ubuntu1) 15.2.0
clang: Ubuntu clang version 21.1.8compiler / flags result
------------------------------- ------------------------------------
gcc -O0 OK (ERROR: value out of range...)
gcc -O1 BUG (Infinity)
gcc -O2 BUG (Infinity)
gcc -O2 -fno-thread-jumps OK (ERROR: value out of range...)
clang -O0 OK (ERROR: value out of range...)
clang -O1 OK (ERROR: value out of range...)
clang -O2 OK (ERROR: value out of range...)
```
So this is not "PostgreSQL does not check circle areas". The check is
there, gcc optimizes it away, clang does not. The same pattern can
affect other inlined float4/8 helpers when both operands are proven
finite.One possible workaround on our side would be to store the operation
result in a volatile temporary in those helpers, so the isinf() check
must inspect the computed value. But it seems to me that we should
address the root cause first: file this with gcc bugzilla and decide
what, if anything, to do in PostgreSQL based on their response.сб, 1 авг. 2026 г. в 18:02, PG Bug reporting form <noreply@postgresql.org
:
The following bug has been logged on the website:
Bug reference: 19593
Logged by: Michael Malis
Email address: malis@pgrust.com
PostgreSQL version: 18.3
Operating system: Debian (official Docker image), aarch64
Description:I'm not sure what you'll want to do with this one, but I figured I would
at
least report it. The cause seems to be a bug in gcc.area(circle) returns Infinity where it must raise ERROR 22003 "value out
of
range: overflow". The overflow check in float8_mul() is present in the
source
but is absent from the generated code, because gcc 13 and later delete
it at
-O1 and above.The same binary raises the error correctly for the equivalent SQL-level
expression, and for a circle whose radius overflows one step earlier, so
this
is not "PostgreSQL does not check circle areas".-- WRONG: no error, returns Infinity
SELECT area(circle '<(0,0),1e154>');
area
------------------------
Infinity-- CORRECT (control): the *inner* multiply overflows, so the
surviving
-- check fires
SELECT area(circle '<(0,0),1e200>');
ERROR: value out of range: overflow-- CORRECT (control): the same arithmetic, expressed in SQL
SELECT 1e154::float8 * 1e154::float8 * pi();
ERROR: value out of range: overflow-- sane value, for reference
SELECT area(circle '<(0,0),1e10>');
area
-------------------------
3.1415926535897933e+201e154 * 1e154 = 1e308, which is finite (below DBL_MAX); multiplying that
by
pi
overflows. Behaviour is identical whether the expression is
constant-folded
at
plan time or evaluated at runtime:SELECT area(c) FROM (VALUES (circle '<(0,0),1e154>')) t(c); --
InfinityNaN and Infinity radii behave correctly (NaN -> NaN, Infinity ->
Infinity).WHERE IT COMES FROM
src/backend/utils/adt/geo_ops.c:5159
static float8
circle_ar(CIRCLE *circle)
{
return float8_mul(float8_mul(circle->radius, circle->radius),
M_PI);
}src/include/utils/float.h:207
static inline float8
float8_mul(const float8 val1, const float8 val2)
{
float8 result;result = val1 * val2;
if (unlikely(isinf(result)) && !isinf(val1) && !isinf(val2))
float_overflow_error();
...Two float8_mul calls are inlined into one function. gcc keeps the first
copy's
overflow check and deletes the second's.Disassembly of the shipped binary (circle_area; symbols are present in
.dynsym).
gcc lowers isinf(x) to |x| > DBL_MAX, with d30 = 0x7fefffffffffffff:; inner multiply -- check intact
5540dc fmul d31, d29, d29 ; r*r
5540e0 fcmp d31, d30
5540e4 b.le 5540fc
5540e8 fabs d29, d29 ; |r| <- the !isinf(val1) test
5540ec fcmp d29, d30
5540f0 b.le 554154
554154 bl float_overflow_error ; raises; outer multiply -- operand test gone
554104 adrp x0, 76c000
554108 ldr d29, [x0, #568] ; M_PI
55410c fmul d31, d31, d29 ; (r*r) * M_PI
554110 fcmp d31, d30
554114 b.le 554120
554118 mov x0, #0x7ff0000000000000 ; returns +Infinity
55411c b 55412cControl reaches the outer multiply only via the b.le at 5540e4, i.e. only
when
r*r <= DBL_MAX, and M_PI is a finite constant. So
"!isinf(val1) && !isinf(val2)" is true on that path and
float_overflow_error()
must be called.Building from an unmodified 18.3 tree (git tag stamp 62d6c7d) with gcc
14.2
and
the same CFLAGS reproduces it, so this is not specific to Debian's
packaging:./configure --without-readline --without-zlib --without-icu \
CFLAGS="-g -O2 -fno-strict-aliasing -fwrapv
-fexcess-precision=standard"
make -C src/backend submake-generated-headers
make -C src/backend/utils/adt geo_ops.o
objdump -d geo_ops.ocircle_area then contains 3 fmul but only 1 call to float_overflow_error.
From
pristine source the outer check is removed entirely: there is no DBL_MAX
comparison after the second fmul at all, only the underflow test.--
Regards,
Rachitskiy Andrey--
Regards,
Rachitskiy Andrey
On Tue, 4 Aug 2026 at 22:41, Andrey Rachitskiy <pl0h0yp1@gmail.com> wrote:
I researched related past bugs and found this is already fixed in https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126464 (reverse Inf handling in float_widen_lhs_range / range-op-float.cc).
Jakub Jelinek says: Fixed also for 15.4+, as well as backported to 14.5 and 13.5.
Thanks for doing that work. I see that master isn't affected by this
particular issue. The changes made in 45cdaf366 must have shuffled the
code around enough that the bug isn't getting triggered.
As for what to do in the meantime... I can't think of anything that's
not painful in some way or another.
A few options which might be worth at least writing down:
1. Add a config precheck using the code you posted to the GCC bugzilla
as a configure test and if the bug appears, add -fno-thread-jumps to
CFLAGS.
2. Add a volatile qualifier to the result variable in float_mul().
3. Add a regression test for "SELECT area(circle '<(0,0),1e154>');"
and leave a comment saying the compiler is broken.
All of these seem quite terrible...
#1 ends up reducing pgbench -S TPS by half. (tps = 1058074 down to tps
= 542227 with -c 100 -j 100).
#2 would fix this one instance with probably minimal performance loss,
but there are quite a few other similar checks that would all need to
be edited. Also, at what point would we ever remove these?
Effectively, by removing them, that risks reintroducing the bug(s).
#3 is very likely not an option at the moment as the buildfarm would
hate it, but it might be an option at some point in the future, once
some time has gone by.
We could perhaps do #2 then remove it and replace with #3 in some
number of months or years.
Another thing that might be worth looking into is exactly which part
of 45cdaf366 resulted in this inadvertently getting fixed. Maybe
there's a realistic subset of that we can do to change the code enough
to not trigger the bug.
David
Hi, David!
You asked which part of 45cdaf366 stopped the miscompile on master.
The relevant change is the float8_mul error path. Calling the noreturn
float_overflow_error() is enough for gcc 13+ jump threading to drop the
outer isinf() check in circle_ar() after proving both operands finite.
Returning through a non-noreturn helper (float_overflow_error_ext) keeps
that check alive. The geo_ops soft-error churn from 45cdaf366 is not
needed.
Attached is one patch that applies to REL_14_STABLE through REL_18_STABLE.
It backports that float8_mul subset and adds a geometry regress for:
SELECT area(circle '<(0,0),1e154>');
Verified with gcc 15: unpatched REL_14 returns Infinity, patched raises
"value out of range: overflow". Same for REL_18.
REL_19 and master already have the helpers via 45cdaf366. A regress-only
follow-up for those can be sent separately if wanted.
вт, 4 авг. 2026 г. в 16:42, David Rowley <dgrowleyml@gmail.com>:
On Tue, 4 Aug 2026 at 22:41, Andrey Rachitskiy <pl0h0yp1@gmail.com> wrote:
I researched related past bugs and found this is already fixed in
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126464 (reverse Inf handling
in float_widen_lhs_range / range-op-float.cc).Jakub Jelinek says: Fixed also for 15.4+, as well as backported to 14.5
and 13.5.
Thanks for doing that work. I see that master isn't affected by this
particular issue. The changes made in 45cdaf366 must have shuffled the
code around enough that the bug isn't getting triggered.As for what to do in the meantime... I can't think of anything that's
not painful in some way or another.A few options which might be worth at least writing down:
1. Add a config precheck using the code you posted to the GCC bugzilla
as a configure test and if the bug appears, add -fno-thread-jumps to
CFLAGS.
2. Add a volatile qualifier to the result variable in float_mul().
3. Add a regression test for "SELECT area(circle '<(0,0),1e154>');"
and leave a comment saying the compiler is broken.All of these seem quite terrible...
#1 ends up reducing pgbench -S TPS by half. (tps = 1058074 down to tps
= 542227 with -c 100 -j 100).
#2 would fix this one instance with probably minimal performance loss,
but there are quite a few other similar checks that would all need to
be edited. Also, at what point would we ever remove these?
Effectively, by removing them, that risks reintroducing the bug(s).
#3 is very likely not an option at the moment as the buildfarm would
hate it, but it might be an option at some point in the future, once
some time has gone by.We could perhaps do #2 then remove it and replace with #3 in some
number of months or years.Another thing that might be worth looking into is exactly which part
of 45cdaf366 resulted in this inadvertently getting fixed. Maybe
there's a realistic subset of that we can do to change the code enough
to not trigger the bug.David
--
Regards,
Rachitskiy Andrey
Attachments:
0001-Keep-float8-mul-overflow-checks-alive-under-gcc-13-UNIVERSAL.patchtext/x-patch; charset=US-ASCII; name=0001-Keep-float8-mul-overflow-checks-alive-under-gcc-13-UNIVERSAL.patchDownload+31-3
Attached is one patch that applies to REL_14_STABLE through REL_18_STABLE.
Please use v2.
I in v1 copied the float_*_error_ext(struct Node *escontext) signature from
master (45cdaf366), where escontext is used for soft-error reporting via
ereturn. On REL_14 through REL_18 that path does not exist, so the
argument was unused. That was needless API mirroring.
v2 drops escontext. The helpers are plain float8-returning wrappers
around the existing noreturn float_*_error() calls. The return 0.0 is
never reached. It is only there so the compiler treats the call as an
ordinary returning call. That CFG change is what keeps gcc 13+ jump
threading from deleting the outer isinf() check in float8_mul().
A short comment in float.c spells that out.
вт, 4 авг. 2026 г. в 21:38, Andrey Rachitskiy <pl0h0yp1@gmail.com>:
Hi, David!
You asked which part of 45cdaf366 stopped the miscompile on master.
The relevant change is the float8_mul error path. Calling the noreturn
float_overflow_error() is enough for gcc 13+ jump threading to drop the
outer isinf() check in circle_ar() after proving both operands finite.
Returning through a non-noreturn helper (float_overflow_error_ext) keeps
that check alive. The geo_ops soft-error churn from 45cdaf366 is not
needed.Attached is one patch that applies to REL_14_STABLE through REL_18_STABLE.
It backports that float8_mul subset and adds a geometry regress for:SELECT area(circle '<(0,0),1e154>');
Verified with gcc 15: unpatched REL_14 returns Infinity, patched raises
"value out of range: overflow". Same for REL_18.REL_19 and master already have the helpers via 45cdaf366. A regress-only
follow-up for those can be sent separately if wanted.вт, 4 авг. 2026 г. в 16:42, David Rowley <dgrowleyml@gmail.com>:
On Tue, 4 Aug 2026 at 22:41, Andrey Rachitskiy <pl0h0yp1@gmail.com>
wrote:I researched related past bugs and found this is already fixed in
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126464 (reverse Inf
handling in float_widen_lhs_range / range-op-float.cc).Jakub Jelinek says: Fixed also for 15.4+, as well as backported to 14.5
and 13.5.
Thanks for doing that work. I see that master isn't affected by this
particular issue. The changes made in 45cdaf366 must have shuffled the
code around enough that the bug isn't getting triggered.As for what to do in the meantime... I can't think of anything that's
not painful in some way or another.A few options which might be worth at least writing down:
1. Add a config precheck using the code you posted to the GCC bugzilla
as a configure test and if the bug appears, add -fno-thread-jumps to
CFLAGS.
2. Add a volatile qualifier to the result variable in float_mul().
3. Add a regression test for "SELECT area(circle '<(0,0),1e154>');"
and leave a comment saying the compiler is broken.All of these seem quite terrible...
#1 ends up reducing pgbench -S TPS by half. (tps = 1058074 down to tps
= 542227 with -c 100 -j 100).
#2 would fix this one instance with probably minimal performance loss,
but there are quite a few other similar checks that would all need to
be edited. Also, at what point would we ever remove these?
Effectively, by removing them, that risks reintroducing the bug(s).
#3 is very likely not an option at the moment as the buildfarm would
hate it, but it might be an option at some point in the future, once
some time has gone by.We could perhaps do #2 then remove it and replace with #3 in some
number of months or years.Another thing that might be worth looking into is exactly which part
of 45cdaf366 resulted in this inadvertently getting fixed. Maybe
there's a realistic subset of that we can do to change the code enough
to not trigger the bug.David
--
Regards,
Rachitskiy Andrey
--
Regards,
Rachitskiy Andrey
Attachments:
v2-0001-Keep-float8-mul-overflow-checks-alive-under-gcc-13-UNIVERSAL.patchtext/x-patch; charset=US-ASCII; name=v2-0001-Keep-float8-mul-overflow-checks-alive-under-gcc-13-UNIVERSAL.patchDownload+38-3
On Wed, 5 Aug 2026 at 05:11, Andrey Rachitskiy <pl0h0yp1@gmail.com> wrote:
v2 drops escontext. The helpers are plain float8-returning wrappers
around the existing noreturn float_*_error() calls. The return 0.0 is
never reached. It is only there so the compiler treats the call as an
ordinary returning call. That CFG change is what keeps gcc 13+ jump
threading from deleting the outer isinf() check in float8_mul().
IMO, this seems like a reasonably clean way to resolve the issue. I
think it's quite good that we don't need to consider when to remove
this once the GCC fix is patched out of existence. Since master has a
good reason for using this pattern, that's going to stay, and we can
let this fix die as the back branches age out.
The outstanding question I have is, out of all the isinf(x) &&
!isinf(y) checks, is float8_mul() the only one that suffers from this?
Is it worth writing a set of regression tests that verify that's the
case, so we can have some confidence that we've not missed something
here?
David
The outstanding question I have is, out of all the isinf(x) &&
!isinf(y) checks, is float8_mul() the only one that suffers from this?
Is it worth writing a set of regression tests that verify that's the
case, so we can have some confidence that we've not missed something
here?
Hi David,
On the outstanding question: it is not unique to float8_mul() as a
function. The miscompile needs an inlined
isinf(result) && !isinf(a) && !isinf(b) check after both operands are
proven finite. float8_mul() is where we have the clear SQL case
(circle_ar).
A small C harness on gcc 15 -O2 also broke for the same nested shape on
float8_pl, float8_mi, and float8_div (e.g. pl(mul,mul),
mi(mul(r,r), -1e308), div(mul(r,r), 1e-10)). Lone mi/div were fine.
Nested float4 mul did not miscompile in that probe.
I looked for other SQL cases on unpatched REL_18 with gcc 15. Only
area(circle) with radius 1e154 returned Infinity. Similar-looking
calls (point_div, point_mul, diameter, box area) already raised
overflow without a patch. So extra regress tests would not show the
compiler bug: those call sites do not hit the bad CFG. The only solid
SQL canary remains area(circle).
That is why v3 applies the same return-through-non-noreturn treatment to
all float8_{pl,mi,mul,div} on REL_14 through REL_18, based on the C
repro of the class, while leaving float4_* alone. The geometry canary
for area(circle) 1e154 remains the SQL check for the known bug.
ср, 5 авг. 2026 г. в 07:30, David Rowley <dgrowleyml@gmail.com>:
On Wed, 5 Aug 2026 at 05:11, Andrey Rachitskiy <pl0h0yp1@gmail.com> wrote:
v2 drops escontext. The helpers are plain float8-returning wrappers
around the existing noreturn float_*_error() calls. The return 0.0 is
never reached. It is only there so the compiler treats the call as an
ordinary returning call. That CFG change is what keeps gcc 13+ jump
threading from deleting the outer isinf() check in float8_mul().IMO, this seems like a reasonably clean way to resolve the issue. I
think it's quite good that we don't need to consider when to remove
this once the GCC fix is patched out of existence. Since master has a
good reason for using this pattern, that's going to stay, and we can
let this fix die as the back branches age out.The outstanding question I have is, out of all the isinf(x) &&
!isinf(y) checks, is float8_mul() the only one that suffers from this?
Is it worth writing a set of regression tests that verify that's the
case, so we can have some confidence that we've not missed something
here?David
--
Regards,
Rachitskiy Andrey
Postgres Professional
Attachments:
v3-0001-Keep-float8-overflow-checks-alive-under-gcc-13-UNIVERSAL.patchtext/x-patch; charset=US-ASCII; name=v3-0001-Keep-float8-overflow-checks-alive-under-gcc-13-UNIVERSAL.patchDownload+43-7
ср, 5 авг. 2026 г., 09:41 Andrey Rachitskiy <pl0h0yp1@gmail.com>:
The outstanding question I have is, out of all the isinf(x) &&
!isinf(y) checks, is float8_mul() the only one that suffers from this?
Is it worth writing a set of regression tests that verify that's the
case, so we can have some confidence that we've not missed something
here?Hi David,
On the outstanding question: it is not unique to float8_mul() as a
function. The miscompile needs an inlined
isinf(result) && !isinf(a) && !isinf(b) check after both operands are
proven finite. float8_mul() is where we have the clear SQL case
(circle_ar).A small C harness on gcc 15 -O2 also broke for the same nested shape on
float8_pl, float8_mi, and float8_div (e.g. pl(mul,mul),
mi(mul(r,r), -1e308), div(mul(r,r), 1e-10)). Lone mi/div were fine.
Nested float4 mul did not miscompile in that probe.I looked for other SQL cases on unpatched REL_18 with gcc 15. Only
area(circle) with radius 1e154 returned Infinity. Similar-looking
calls (point_div, point_mul, diameter, box area) already raised
overflow without a patch. So extra regress tests would not show the
compiler bug: those call sites do not hit the bad CFG. The only solid
SQL canary remains area(circle).That is why v3 applies the same return-through-non-noreturn treatment to
all float8_{pl,mi,mul,div} on REL_14 through REL_18, based on the C
repro of the class, while leaving float4_* alone. The geometry canary
for area(circle) 1e154 remains the SQL check for the known bug.ср, 5 авг. 2026 г. в 07:30, David Rowley <dgrowleyml@gmail.com>:
On Wed, 5 Aug 2026 at 05:11, Andrey Rachitskiy <pl0h0yp1@gmail.com>
wrote:v2 drops escontext. The helpers are plain float8-returning wrappers
around the existing noreturn float_*_error() calls. The return 0.0 is
never reached. It is only there so the compiler treats the call as an
ordinary returning call. That CFG change is what keeps gcc 13+ jump
threading from deleting the outer isinf() check in float8_mul().IMO, this seems like a reasonably clean way to resolve the issue. I
think it's quite good that we don't need to consider when to remove
this once the GCC fix is patched out of existence. Since master has a
good reason for using this pattern, that's going to stay, and we can
let this fix die as the back branches age out.The outstanding question I have is, out of all the isinf(x) &&
!isinf(y) checks, is float8_mul() the only one that suffers from this?
Is it worth writing a set of regression tests that verify that's the
case, so we can have some confidence that we've not missed something
here?David
Dear David,
Circling back on this issue — we paused two weeks ago awaiting your final
confirmation to apply the fix.
--
Regards,
Rachitskiy Andrey