Skip to content

Conversation

@RalfJung
Copy link
Contributor

@RalfJung RalfJung commented Dec 1, 2025

The 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 pow (and powi which has a similar situation), to have everything concerning those operations in a single place.

@llvmbot llvmbot added the llvm:ir label Dec 1, 2025
@llvmbot
Copy link
Member

llvmbot commented Dec 1, 2025

@llvm/pr-subscribers-llvm-ir

Author: Ralf Jung (RalfJung)

Changes

The 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 pow (and powi which has a similar situation), to have everything concerning those operations in a single place.


Full diff: https://github.com/llvm/llvm-project/pull/170177.diff

1 Files Affected:

  • (modified) llvm/docs/LangRef.rst (+11)
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

@@ -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
Copy link
Contributor

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

Copy link
Collaborator

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".

Copy link
Contributor Author

@RalfJung RalfJung Dec 2, 2025

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@efriedma-quic

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).

Copy link
Contributor

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.

Copy link
Contributor Author

@RalfJung RalfJung Dec 2, 2025

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.

Copy link
Contributor

@jcranmer-intel jcranmer-intel Dec 2, 2025

Choose a reason for hiding this comment

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

minimumNumber/maximumNumber are 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.)

Copy link
Contributor

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

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 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.

Copy link
Contributor Author

@RalfJung RalfJung Dec 3, 2025

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?

Copy link
Collaborator

@efriedma-quic efriedma-quic left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Contributor

@jcranmer-intel jcranmer-intel left a 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.

@@ -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
Copy link
Member

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".

Copy link
Contributor Author

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.)

Copy link
Contributor

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.

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 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.

Copy link
Member

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.

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 see, thanks for explaining. I have added a comment.

@jyknight
Copy link
Member

jyknight commented Dec 4, 2025

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.

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.

@nikic nikic merged commit 136e5e2 into llvm:main Dec 10, 2025
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

floating-point Floating-point math llvm:ir

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants