Hi all,
One of the purposes of llvm::isnan
was to help preserve the check made by isnan
if fast-math mode is
specified (https://reviews.llvm.org/D104854). I’d like to describe reason for that and propose to use the behavior
implemented in that patch.
The option -ffast-math
is often used when performance is important, as it allows a compiler to generate faster code.This option itself is a collection of different optimization techniques, each having its own option. For this topic only the
option -ffinite-math-only
is of interest. With it the compiler treats floating point numbers as mathematical real numbers,
so transformations like 0 * x -> 0
become valid.
In clang documentation (https://clang.llvm.org/docs/UsersManual.html#cmdoption-ffast-math) this option is described as:
“Allow floating-point optimizations that assume arguments and results are not NaNs or ±Inf.”
GCC documentation (https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html) is a bit more concrete:
“Allow optimizations for floating-point arithmetic that assume that arguments and results are not NaNs or ±Infs.”
What is the issue?
C standard defines a macro isnan
, which can be mapped to an intrinsic function provided by the compiler. For both
clang and gcc it is __builtin_isnan
. How should this function behave if -ffinite-math-only
is specified? Should it make a
real check or the compiler can assume that it always returns false?
GCC optimizes out isnan
. It follows from the viewpoint that (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=50724#c1):
“With -ffinite-math-only you are telling that there are no NaNs and thus GCC optimizes isnan (x) to 0.”
Such treatment of -ffinite-math-only
has sufficient drawbacks. In particular it makes it impossible to check validity of
data: a user cannot write
assert(!isnan(x));
because the compiler replaces the actual function call with its expected value. There are many complaints in GCC bug
tracker (for instance https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84949 or https://gcc.gnu.org/bugzilla/show_bug.cgi?id=50724)
as well as in forums (https://stackoverflow.com/questions/47703436/isnan-does-not-work-correctly-with-ofast-flags or
https://stackoverflow.com/questions/22931147/stdisinf-does-not-work-with-ffast-math-how-to-check-for-infinity). Proposed
solutions are using integer operations to make the check, to turn off -ffinite-math-only
in some parts of the code or to
ensure that libc function is called. It clearly demonstrates that isnan
in this case is useless, but users need its functionality
and do not have a proper tool to make required checks. The similar direction was criticized in llvm as well (https://reviews.llvm.org/D18513#387418).
Why imposing restrictions on floating types is bad?
If -ffinite-math-only
modifies properties of double
type, several issues arise, for instance:
- What should return
std::numeric_limits<double>::has_quiet_NaN()
? - What body should have this function if it is used in a program where some functions are compiled with
fast-math
and some without? - Should inlining of a function compiled with
fast-math
to a function compiled without it be prohibited in inliner? - Should
std::isnan(std::numeric_limits<float>::quiet_NaN())
be true?
If the type double
cannot have NaN value, it means that double
and double
under -ffinite-math-only
are different types
(https://gcc.gnu.org/pipermail/gcc-patches/2020-April/544641.html). Such a way can solve these problems but it is so expensive
that hardly it has a chance to be realized.
The solution
Instead of modifying properties of floating point types, the effect of -ffinite-math-only
can be expressed as a restriction on
operation usage. Actually clang and gcc documentation already follows this way. Fast-math flags in llvm IR also are attributes
of instructions. The only question is whether isnan
and similar functions are floating-point arithmetic.
From a practical viewpoint, treating non-computational functions as arithmetic does not add any advantage. If a code extensively
uses isnan
(so could profit by their removal), it is likely it is not suitable for -ffinite-math-only. This interpretation however creates
the problems described above. So it is profitable to consider isnan
and similar functions as non-arithmetical.
Why is it safe to leave isnan
?
The probable concern of this solution is deviation from gcc behavior. There are several reasons why this is not an issue.
- -ffinite-math-only is an optimization option. A correct program compiled with -ffinite-math-only and without it should behave
identically, if conditions for using -ffinite-math-only are fulfilled. So making the check cannot break functionality. -
isnan
is implemented by libc, which can map it to a compiler builtin or use its own implementation, depending on
configuration options.isnan
implemented in libc obviously always does the real check. - ICC and MSVC preserve
isnan
in fast-math mode.
The proposal is to not consider isnan
and other such functions as arithmetic operations and do not optimize them out
just because -ffinite-math-only is specified. Of course, there are cases when isnan
may be optimized out, for instance,
isnan(a + b)
may be optimized if -ffinite-math-only is in effect due to the assumption (result of arithmetic operation is not NaN).
What are your opinions?