[llvm-dev] Should isnan be optimized out in fast-math mode?

Serge Pavlov via llvm-dev llvm-dev at lists.llvm.org
Wed Sep 8 10:02:36 PDT 2021


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.

1. -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.
2. `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.
3. 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?

Thanks,
--Serge
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20210909/59513c10/attachment.html>


More information about the llvm-dev mailing list