-
Notifications
You must be signed in to change notification settings - Fork 15.5k
[LangRef] make consequences of NaN rules for pow(i) more explicit #170177
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
Conversation
|
@llvm/pr-subscribers-llvm-ir Author: Ralf Jung (RalfJung) ChangesThe NaN section says > 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 seems worth also spelling out in the section for Full diff: https://github.com/llvm/llvm-project/pull/170177.diff 1 Files Affected:
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 02865f8a29c67..292f70f062bd2 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -16269,6 +16269,11 @@ Semantics:
This function returns the first value raised to the second power with an
unspecified sequence of rounding operations.
+Note that due to how :ref:`LLVM treats NaN values <floatnan>`, the special case
+of `powi(SNaN, 0.0)` can non-deterministically return *either* some NaN value
+(using the usual NaN propagation rules, so in particular the result could be
+either a signaling NaN or a quiet NaN), *or* the value `1.0`.
+
.. _t_llvm_sin:
'``llvm.sin.*``' Intrinsic
@@ -16831,6 +16836,12 @@ trapping or setting ``errno``.
When specified with the fast-math-flag 'afn', the result may be approximated
using a less accurate calculation.
+Note that due to how :ref:`LLVM treats NaN values <floatnan>`, the special cases
+of `pow(1.0, SNaN)` and `pow(SNaN, 0.0)` can non-deterministically return
+*either* some NaN value (using the usual NaN propagation rules, so in particular
+the result could be either a signaling NaN or a quiet NaN), *or* the value
+`1.0`.
+
.. _int_exp:
'``llvm.exp.*``' Intrinsic
|
llvm/docs/LangRef.rst
Outdated
| @@ -16831,6 +16836,11 @@ trapping or setting ``errno``. | |||
| When specified with the fast-math-flag 'afn', the result may be approximated | |||
| using a less accurate calculation. | |||
|
|
|||
| Note that due to how :ref:`LLVM treats NaN values <floatnan>`, the special cases | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see the issue; how is this nondeterministic? The signalingness shouldn't matter, the special cases don't need to do anything with the nan argument
pow(+1, exponent) returns 1 for any exponent, even when exponent is NaN
pow(base, ±0) returns 1 for any base, even when base is NaN
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://llvm.org/docs/LangRef.html#behavior-of-floating-point-nan-values has the basic rule: "Floating-point math operations are allowed to treat all NaNs as if they were quiet NaNs."
It explicit calls out "For example, 'pow(1.0, SNaN)' may be simplified to 1.0." Or in other words, pow(1.0, SNaN) is non-deterministic: it returns either 1.0 (if we fold it), or some NaN from the allowed set of NaNs (if we don't fold it).
That said, maybe we should go into less detail here to avoid explaining the rules in multiple places. Just say "note this operation can return a non-NaN result given an SNaN input; see https://llvm.org/docs/LangRef.html#behavior-of-floating-point-nan-values for details".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@arsenm
When those documents talk about "NaN", they mean "quiet NaN" (similar to how some documents say that fmin/fmax return the non-NaN argument if the other one is NaN, but only refer to quiet NaNs when they say that). Some pow implementations return QNaN for pow(1, SNaN); for instance, the one that comes with the GNU libc. musl returns 1 for the same inputs. (See https://rust.godbolt.org/z/chsbv5v4d.)
That's why pow is even mentioned in the NaN section of the docs: usually this is an operation where QNaN vs SNaN can make a big difference (one returns a NaN, the other does not), but LLVM is allowed to treat SNaN as QNaN and so that can lead to very surprising behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That said, maybe we should go into less detail here to avoid explaining the rules in multiple places. Just say "note this operation can return a non-NaN result given an SNaN input; see https://llvm.org/docs/LangRef.html#behavior-of-floating-point-nan-values for details".
I'm open to making this shorter, but I think we should be very explicit about this being non-determinism, to avoid any doubt about what "can return" means. It's not just some backend choice or so. It means that we can call the same function with the same inputs twice and get different results (depending on whether LLVM can const-propagate everything or whether a libcall is used).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IEEE 754-2019 has the general catch-all rule for operations in section 9.2 (which include pow and powi, assuming powi maps to IEEE 754 pown)
An operation that returns a floating-point result shall return a quiet NaN as a result if there is a signaling NaN among the operation’s operands. An operation that returns a floating-point result shall return a quiet NaN as a result if there is a quiet NaN among the operation’s operands, except in the cases stated otherwise in this subclause.
glibc gives pow(1, snan) as nan and pow(snan, 0) as nan (see https://godbolt.org/z/KMrzMzKMo).
The other non-NaN-propagating functions are hypot (hypot(1, nan) == 1) and compound, neither of which are an LLVM intrinsic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IEEE 754-2019 has the general catch-all rule for operations in section 9.2
minimumNumber/maximumNumber are exceptions from this, right? Strangely the first sentence does not acknowledge the existence of exceptions.
Maybe "note this operation can non-deterministically return a non-NaN result given an SNaN input"? Not really attached to the exact wording.
Well, this can only happen in very specific cases. I take it you're not happy with mentioning 0^x/x^1? We could say something more general, like "note that if some inputs are signaling NaNs, the function may non-deterministically treat some or all of them as quiet NaNs, which can lead to non-NaN results". The rest follows as an emergent property. However, quoting @nikic
Because LLVM’s (non-strictfp) NaN semantics allow treating sNaN as qNaN, and allow the omission of canonicalizing operations that would turn sNaN into qNaN, it is not actually possible to guarantee a specific sNaN behavior in any sensible way. By itself, this is not a problem – this just means that the result is going to be non-deterministic. But I think this is something that needs to be explicitly acknowledged in the LangRef wording, not just treated as an emergent property.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minimumNumber/maximumNumberare exceptions from this, right? Strangely the first sentence does not acknowledge the existence of exceptions.
The first sentence of the quoted paragraph is actually about exceptions, but I elided it because exceptions aren't particularly relevant here, when we're talking about non-strictfp semantics.
Section 9.2 is just the category of libm functions I find difficult to group under a common name but fall into the general category of "computational, not-correctly rounded." The minimumNumber/maximumNumber functions fall under section 9.6, which lacks such a catch-all rule (as the functions intentionally have very different NaN behavior from one another).
We could say something more general, like "note that if some inputs are signaling NaNs, the function may non-deterministically treat some or all of them as quiet NaNs, which can lead to non-NaN results"
This is a quick stab at wording, so take it with a grain of salt: "The pow function is one where replacing an sNaN with a qNaN changes the result. Due to LLVM's NaN-propagation rules [link], this means that the function result is effectively nondeterministic when called with an sNaN argument." (I was going to add something about strictfp, and then I realized that was going to just add more confusion given the current state of affairs there.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see IEEE has the signaling nan case explicitly called out in the pow cases unlike the libm docs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a quick stab at wording, so take it with a grain of salt: "The pow function is one where replacing an sNaN with a qNaN changes the result. Due to LLVM's NaN-propagation rules [link], this means that the function result is effectively nondeterministic when called with an sNaN argument."
I think without an example, this will be very confusing to most readers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jcranmer-intel I have edited the text, to more clearly separate the example from the rule. Is that better?
efriedma-quic
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
jcranmer-intel
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I keep on going back and forth over the amount of detail that's useful here, namely what you need to do if you want sNaN to not act as qNaN. At the end of the day, though, I think that's best handled with a distinct document on floating-point semantics that's already in my queue to write.
My biggest concern is that the eventual move away from constrained intrinsics will require modification of this wording, but it's probably best to deal with that when we make that move rather than trying to do the wording for that scenario right now.
llvm/docs/LangRef.rst
Outdated
| @@ -16269,6 +16269,12 @@ Semantics: | |||
| This function returns the first value raised to the second power with an | |||
| unspecified sequence of rounding operations. | |||
|
|
|||
| Note that the `powi` function is unusual in that NaN inputs can lead to non-NaN | |||
| results, and this depends on the kind of NaN (quiet vs signaling). Due to how | |||
| :ref:`LLVM treats NaN values <floatnan>`, the function may non-deterministically | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should at least mention "non-constrained" here as an additional hint. E.g. "Due to how LLVM treats NaN values in non-constrained functions".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's also strictfp, should that also be mentioned? (Not sure if it has an effect on this.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need to mention strictfp as well if we mention constrained intrinsics explicitly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The constrained intrinsics have their own separate docs though, don't they? E.g. here. They don't even refer to the basic intrinsics for their semantics.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sentence now says "Due to how LLVM treats NaN values", which implies a generality/universality which seems misleading given that LLVM treats NaNs differently for constrained functions. That's why I suggest to clarify by adding a couple more words.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, thanks for explaining. I have added a comment.
Yes, we should not word it for the eventual future, but for the now. And making the semantics clearer now will help ensure everyone's on the same page as to the semantics we need to preserve in such future-state. |
The NaN section says
This seems worth also spelling out in the section for
pow(andpowiwhich has a similar situation), to have everything concerning those operations in a single place.