You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
stdarch has started using the simd_* LLVM intrinsics for many operations, which is great news for backends like cranelift and Miri. However, it also means we get extra optimizations that would otherwise not apply: LLVM will constant-fold these operations using its usual NaN rules, which means that the sign and payload of a NaN can be different from what would happen if the vendor intrinsic was implemented by an inline assembly block. Furthermore, SNaNs can be non-deterministically treated like QNaNs, which can make a difference for operations where QNaN input lead to non-NaN results.
Which differences are acceptable?
I assume we definitely want to tolerate differences in NaN payloads/signs. Without that, we couldn't use any portable SIMD operations in stdarch. Arguably, this is already covered by the existing general float NaN docs, though not everyone might expect that to apply even to vendor intrinsics.
What about differences in whether a NaN is returned? This is relevant e.g. for some variants of min/max where the vendor may say that an SNaN input always produces a NaN result, but the portable LLVM intrinsic makes no such guarantee and could produce a non-NaN result. See Revert "Merge pull request #1871 from folkertdev/aarch64-float-min-max" stdarch#2052 for a concrete example where such SNaN behavior was fixed.
On targets like ARM32 where subnormal numbers do not work properly for SIMD vectors, we might also see differences in the actual numerical results -- if LLVM constant-folds those operations, that will be done with proper subnormal support. In fact, LLVM itself mistreats those operations on ARM32, leading to miscompilations (32-bit ARM NEON intrinsics are unsound due to subnormal flushing #129880).
Currently, I don't think we properly document this anywhere? It doesn't seem too unlikely that someone might expect the vendor intrinsics to always behave exactly like the corresponding asm instructions, so we better clarify the expectations here. Once a decision has been made, the module-level docs might be a good place to document this.
Cc @rust-lang/libs-api
View all comments
stdarch has started using the
simd_*LLVM intrinsics for many operations, which is great news for backends like cranelift and Miri. However, it also means we get extra optimizations that would otherwise not apply: LLVM will constant-fold these operations using its usual NaN rules, which means that the sign and payload of a NaN can be different from what would happen if the vendor intrinsic was implemented by an inline assembly block. Furthermore, SNaNs can be non-deterministically treated like QNaNs, which can make a difference for operations where QNaN input lead to non-NaN results.Which differences are acceptable?
Currently, I don't think we properly document this anywhere? It doesn't seem too unlikely that someone might expect the vendor intrinsics to always behave exactly like the corresponding asm instructions, so we better clarify the expectations here. Once a decision has been made, the module-level docs might be a good place to document this.
Cc @rust-lang/libs-api