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
70 changes: 64 additions & 6 deletions llvm/docs/LangRef.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3394,17 +3394,75 @@ 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
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
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``. They act directly on the underlying bit representation and
never change anything except for the sign bit.

When a floating-point math operation produces a NaN value, the result has a
Copy link
Contributor

Choose a reason for hiding this comment

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

I would prepend "Unless otherwise specified here", and add notes to fneg, llvm.fabs, and llvm.copysign that they are guaranteed to not affect the NaN payload or qNaN/sNaN status.

Copy link
Member

Choose a reason for hiding this comment

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

Those 3 shouldn't be considered floating point math operations (as the term is used here and before). We definitely need to make it clear in their definitions -- it has implications beyond just the NaN behavior.

In particular, they all ought to be specified as: "This operation never raises a floating-point exception, and the result is an exact copy of the input, other than the sign bit. It is not considered a floating-point math operation, but rather, bit-manipulation which operates on floating-point 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.

The paragraph above says that FP exceptions cannot be observed anyway, so it seems confusing/misleading to now talk about some operations not raising FP exceptions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am explicitly calling out bitcasts and fnet, fabs, copysign as not being affected by the NaN non-determinism now.

Copy link
Member

Choose a reason for hiding this comment

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

I'd like to see some text added to the specification sections for the fneg/fabs/copysign operations. Noting there that exceptions cannot be signaled (in addition to them only touching the sign bit) is meaningful -- it means that you may use them in a "strictfp" function. Unlike other operations, they do not need a "constrained" variant, because their behavior is fully defined regardless.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would still add "unless otherwise specified", because you have cases like llvm.canonicalize which is an FP operation that can arbitrarily mutate NaN payloads but requires the output to be qNaN, even if the input is sNaN.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fair. That can then also cover fmin/fmax, should we need to add an exception for them.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd like to see some text added to the specification sections for the fneg/fabs/copysign operations.

Done.

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
further possible NaN payloads. The set can depend on the payloads of the input
NaNs. 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, 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, 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.

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 contradiction 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.
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
- 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