[Bug tree-optimization/126631] New: Jump threading drops isinf() overflow check after finite * constant

pl0h0yp1 at gmail dot com via Gcc-bugs <[email protected]> Tue, 04 Aug 2026 07:27:45 +0000
Newsgroups gmane.comp.gcc.bugs
Message-ID <[email protected]/bugzilla/>
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=3D126631

            Bug ID: 126631
           Summary: Jump threading drops isinf() overflow check after
                    finite * constant
           Product: gcc
           Version: 15.2.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: tree-optimization
          Assignee: unassigned at gcc dot gnu.org
          Reporter: pl0h0yp1 at gmail dot com
  Target Milestone: ---

Created attachment 65233
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=3D65233&action=3Dedit
Reduced testcase: -O2 drops isinf() check after finite * M_PI

Summary
=3D=3D=3D=3D=3D=3D=3D

At -O1 and above, jump threading (-fthread-jumps) can delete a live
overflow check of the form:

  r =3D a * b;
  if (isinf (r) && !isinf (a) && !isinf (b))
    overflow_error ();

when both operands are known finite =E2=80=94 e.g. after an earlier identic=
al
check, or when one operand is a finite constant such as M_PI.  The
generated code then returns +Inf instead of calling overflow_error().

This is wrong without -ffinite-math-only: IEEE 754 multiplication of two
finite values may overflow to infinity.  In particular, if
a =E2=88=88 (DBL_MAX/=CF=80, DBL_MAX], then a * M_PI is +Inf.


Testcase
=3D=3D=3D=3D=3D=3D=3D=3D

Attach pr-float-mul-isinf.c.

  gcc -O0  pr-float-mul-isinf.c -lm && ./a.out ; echo $?
  # =3D> ERROR:  value out of range: overflow
  #    exit 0

  gcc -O2  pr-float-mul-isinf.c -lm && ./a.out ; echo $?
  # =3D> prints Infinity, exit 1

  gcc -O2 -fno-thread-jumps pr-float-mul-isinf.c -lm && ./a.out ; echo $?
  # =3D> ERROR:  value out of range: overflow
  #    exit 0

  clang -O2 pr-float-mul-isinf.c -lm && ./a.out ; echo $?
  # =3D> ERROR:  value out of range: overflow
  #    exit 0


Analysis
=3D=3D=3D=3D=3D=3D=3D=3D

After inlining, before jump threading (e.g. after fre3), the CFG is still
correct (isinf(x) lowered to x u<=3D DBL_MAX):

  result_8 =3D r * r;
  if (result_8 u<=3D DBL_MAX) ... else maybe overflow_error();
  result_5 =3D result_8 * M_PI;
  if (result_5 u<=3D DBL_MAX)
    return result_5;
  else if (result_8 u<=3D DBL_MAX)
    overflow_error ();   // both operands finite =3D> must report overflow
  else
    return result_5;     // Inf * pi stays Inf

After pass threadfull1, on the path where result_8 is already known
finite, the check of the product is dropped entirely:

  result_5 =3D result_8 * M_PI;
  return result_5;       // unconditional; no isinf / u<=3D DBL_MAX test

i.e. threading treats "finite * M_PI overflows" as impossible.  A later
thread1 pass removes the now-unreachable outer overflow_error().
Optimized gimple for circle_ar() keeps the inner r*r overflow check but
drops the outer one and returns Inf:

  result_5 =3D result_8 * 3.14159...;
  return result_5;   // no isinf check

Related existing mitigation in range-op-float.cc (PR107608) avoids
folding an overflowing operation to a singleton =C2=B1Inf under
flag_trapping_math so that DOM does not elide a *hardware* FP
exception.  That does not protect an explicit user-written isinf()
check + call, which is what this test relies on.

Workaround: -fno-thread-jumps (or marking the multiply result volatile).


How found
=3D=3D=3D=3D=3D=3D=3D=3D=3D

PostgreSQL area(circle) uses:

  float8_mul (float8_mul (radius, radius), M_PI);

With gcc -O2, SELECT area(circle '<(0,0),1e154>') returns Infinity
instead of ERROR "value out of range: overflow".  The equivalent SQL
expression 1e154::float8 * 1e154::float8 * pi() still errors, because
it is not subject to the same C-level inlining/threading.


Minimal source
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D

See attached pr-float-mul-isinf.c (names mirror PostgreSQL float8_mul /
circle_ar; prints the same ERROR string / Infinity layout as psql).

Note: the radius must not be a compile-time constant in main(), or
gcc -O2 may constprop r*r and accidentally keep the outer check.
The attached test uses a volatile radius for that reason.=