Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[LangRef] Specify NaN behavior more precisely #66579

Merged
merged 8 commits into from
Oct 4, 2023
95 changes: 89 additions & 6 deletions llvm/docs/LangRef.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3394,17 +3394,81 @@ Floating-Point Environment
The default LLVM floating-point environment assumes that traps are disabled and
status flags are not observable. Therefore, floating-point math operations do
not have side effects and may be speculated freely. Results assume the
round-to-nearest rounding mode.
round-to-nearest rounding mode, and subnormals are assumed to be preserved.
RalfJung marked this conversation as resolved.
Show resolved Hide resolved

Running LLVM code in an environment where these assumptions are not met can lead
to undefined behavior. The ``strictfp`` and ``denormal-fp-math`` attributes as
well as :ref:`Constrained Floating-Point Intrinsics <constrainedfp>` can be used
to weaken LLVM's assumptions and ensure defined behavior in non-default
floating-point environments; see their respective documentation for details.

.. _floatnan:

Behavior of Floating-Point NaN values
-------------------------------------
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wasn't really about the "FP environment" any more so I moved this all into a new section on NaN values.


A floating-point NaN value consists of a sign bit, a quiet/signaling bit, and a
payload (which makes up the rest of the mantissa except for the quiet/signaling
bit). LLVM assumes that the quiet/signaling bit being set to ``1`` indicates a
quiet NaN (QNaN), and a value of ``0`` indicates a signaling NaN (SNaN). In the
following we will hence just call it the "quiet bit"

The representation bits of a floating-point value do not mutate arbitrarily; in
particular, if there is no floating-point operation being performed, NaN signs,
quiet bits, and payloads are preserved.

For the purpose of this section, ``bitcast`` as well as the following operations
are not "floating-point math operations": ``fneg``, ``llvm.fabs``, and
``llvm.copysign``. These operations act directly on the underlying bit
representation and never change anything except possibly for the sign bit.

For floating-point math operations, unless specified otherwise, the following
rules apply when a NaN value is returned: the result has a non-deterministic
sign; the quiet bit and payload are non-deterministically chosen from the
following set of options:

- The quiet bit is set and the payload is all-zero. ("Preferred NaN" case)
- The quiet bit is set and the payload is copied from any input operand that is
nunoplopes marked this conversation as resolved.
Show resolved Hide resolved
a NaN. ("Quieting NaN propagation" case)
- The quiet bit and payload are copied from any input operand that is a NaN.
("Unchanged NaN propagation" case)
- The quiet bit is set and the payload is picked from a target-specific set of
nunoplopes marked this conversation as resolved.
Show resolved Hide resolved
"extra" possible NaN payloads. The set can depend on the input operand values.
This set is empty on x86 and ARM, but can be non-empty on other architectures.
(For instance, on wasm, if any input NaN does not have the preferred all-zero
payload or any input NaN is an SNaN, then this set contains all possible
payloads; otherwise, it is empty. On SPARC, this set consists of the all-one
payload.)

In particular, if all input NaNs are quiet (or if there are no input NaNs), then
the output NaN is definitely quiet. Signaling NaN outputs can only occur if they
are provided as an input value. For example, "fmul SNaN, 1.0" may be simplified
to SNaN rather than QNaN. Similarly, if all input NaNs are preferred (or if
there are no input NaNs) and the target does not have any "extra" NaN payloads,
then the output NaN is guaranteed to be preferred.

Floating-point math operations are allowed to treat all NaNs as if they were
quiet NaNs. For example, "pow(1.0, SNaN)" may be simplified to 1.0. This also
means that SNaN may be passed through a math operation without quieting. For
example, "fmul SNaN, 1.0" may be simplified to SNaN rather than QNaN. However,
SNaN values are never created by math operations. They may only occur when
provided as a program input value.
quiet NaNs. For example, "pow(1.0, SNaN)" may be simplified to 1.0.

Code that requires different behavior than this should use the
:ref:`Constrained Floating-Point Intrinsics <constrainedfp>`.
In particular, constrained intrinsics rule out the "Unchanged NaN propagation"
case; they are guaranteed to return a QNaN.

Unfortunately, due to hard-or-impossible-to-fix issues, LLVM violates its own
specification on some architectures:
- x86-32 without SSE2 enabled may convert floating-point values to x86_fp80 and
back when performing floating-point math operations; this can lead to results
with different precision than expected and it can alter NaN values. Since
optimizations can make contradicting assumptions, this can lead to arbitrary
miscompilations. See `issue #44218
<https://github.com/llvm/llvm-project/issues/44218>`_.
- x86-32 (even with SSE2 enabled) may implicitly perform such a conversion on
values returned from a function for some calling conventions. See `issue
#66803 <https://github.com/llvm/llvm-project/issues/66803>`_.
- Older MIPS versions use the opposite polarity for the quiet/signaling bit, and
LLVM does not correctly represent this. See `issue #60796
<https://github.com/llvm/llvm-project/issues/60796>`_.

.. _fastmath:

Expand Down Expand Up @@ -9085,6 +9149,9 @@ Semantics:
""""""""""

The value produced is a copy of the operand with its sign bit flipped.
The value is otherwise completely identical; in particular, if the input is a
NaN, then the quiet/signaling bit and payload are perfectly preserved.

This instruction can also take any number of :ref:`fast-math
flags <fastmath>`, which are optimization hints to enable otherwise
unsafe floating-point optimizations:
Expand Down Expand Up @@ -11240,6 +11307,11 @@ The '``fptrunc``' instruction casts a ``value`` from a larger
This instruction is assumed to execute in the default :ref:`floating-point
environment <floatenv>`.

NaN values follow the usual :ref:`NaN behaviors <floatnan>`, except that _if_ a
NaN payload is propagated from the input ("Quieting NaN propagation" or
"Unchanged NaN propagation" cases), then the low order bits of the NaN payload
which cannot fit in the resulting type are discarded.

Example:
""""""""

Expand Down Expand Up @@ -11280,6 +11352,11 @@ The '``fpext``' instruction extends the ``value`` from a smaller
*no-op cast* because it always changes bits. Use ``bitcast`` to make a
*no-op cast* for a floating-point cast.

NaN values follow the usual :ref:`NaN behaviors <floatnan>`, except that _if_ a
NaN payload is propagated from the input ("Quieting NaN propagation" or
"Unchanged NaN propagation" cases), then it is copied to the high order bits of
the resulting payload, and the remaining low order bits are zero.

Example:
""""""""

Expand Down Expand Up @@ -15092,6 +15169,9 @@ Semantics:

This function returns the same values as the libm ``fabs`` functions
would, and handles error conditions in the same way.
The returned value is completely identical to the input except for the sign bit;
in particular, if the input is a NaN, then the quiet/signaling bit and payload
are perfectly preserved.

.. _i_minnum:

Expand Down Expand Up @@ -15307,6 +15387,9 @@ Semantics:

This function returns the same values as the libm ``copysign``
functions would, and handles error conditions in the same way.
The returned value is completely identical to the first operand except for the
sign bit; in particular, if the input is a NaN, then the quiet/signaling bit and
payload are perfectly preserved.

.. _int_floor:

Expand Down