[PATCH] D143074: [LangRef] improve documentation of SNaN in the default FP environment

Ralf via Phabricator via llvm-commits llvm-commits at lists.llvm.org
Sun Feb 5 09:34:07 PST 2023


RalfJung added a comment.

> Regarding what LLVM actually does, I think it would be more accurate to say that floating-point IR instructions are lowered to the closest target-specific counterpart, but optimizations assume that the lowering conforms to IEEE 754 semantics

The point of the LangRef is to describe the effective behavior of LLVM in the abstract, which then puts a boundary on the allowed optimizations. Exhaustively listing the optimizations is not going to work very well.

> we cannot allow LLVM to spuriously introduce sNaNs when the original code did not use any.

That is one of the things being discussed here, right? Right now at least on old MIPS LLVM does *not* satisfy this requirement (apfloat will produce sNaN for that platform). The LangRef says "No floating-point exception state is maintained in this environment" which I read as "we don't care at all about the sNaN vs qNaN distinction and will just do whatever".

> As a general rule, when an operation gets an sNaN as input, it raises an invalid exception immediately. The default behavior upon is to set the invalid bit in the status flags AND to trigger an immediate return of a qNaN -- even when a qNaN input value would've resulted in some other output.

I thought people were saying LLVM cares about none of that. Probably that was different people. ;)

> Of course, we may also get the wrong answer in the other direction: pow(1.0 * x, y) should result in 1.0 when passed x = sNaN, y = 0 (because 1.0 * sNaN -> qNaN, and pow(qNaN, 0) -> 1.0); but if we canonicalize away the multiplication to pow(x, y), we may up with qNaN as the result.

That reasoning definitely doesn't work, since `1.0 * sNaN` in LLVM is allowed to return an sNaN. That's what triggered this discussion in the first place.

> then we'd allow something like pow(2.0 * qNaN, 0.0) to non-deterministically result in qNaN or 1.0, which is not OK.

Looks like right now it is the case that `pow(1.0 * sNaN, 0.0)` non-deterministically returns a qNaN or 1.0 (depending on whether the multiplication returned an sNaN or qNaN, and assuming I understood correctly that `pow` on an sNaN returns a qNaN). That also sounds pretty bad?

I would expect that LLVM pow treats sNaN like qNaN, because otherwise these sNaN-returning arithmetic operations could have rather surprising long-distance effects. If that is *not* the case then for sure just saying "anything may return an sNaN whenever it returns any NaN" does not work. (I honestly find the behavior of `pow` that you describe extremely surprising. I would not expect an operation to have such wildly different behavior based on whether the input is an sNaN or qNaN. The vast majority of people calling `pow` will not even know that there are two kinds of NaN; the spec here will almost make sure that non-experts form a wrong mental model of what is happening.)


CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D143074/new/

https://reviews.llvm.org/D143074



More information about the llvm-commits mailing list