diff --git a/dispatch/cmake/ClassSIMD.cmake b/dispatch/cmake/ClassSIMD.cmake index 7646021..be3f614 100644 --- a/dispatch/cmake/ClassSIMD.cmake +++ b/dispatch/cmake/ClassSIMD.cmake @@ -114,6 +114,15 @@ function(fastsimd_create_dispatch_library simd_library_name) if(fastsimd_create_dispatch_library_RELAXED) target_compile_definitions(${simd_library_name} PUBLIC FASTSIMD_IS_RELAXED=1) set(relaxed_log_msg " (RELAXED)") + else() + # Non relaxed results must match across feature sets, stop the compiler fusing mul + add into FMA + # -mno-fma isn't enough, AVX512F has its own FMA instructions + # MSVC is also set for clang-cl, which ignores a bare -ffp-contract=off + if(MSVC AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + target_compile_options(${simd_library_name} PRIVATE /clang:-ffp-contract=off) + elseif(NOT MSVC) + target_compile_options(${simd_library_name} PRIVATE -ffp-contract=off) + endif() endif() set(feature_set_list "") diff --git a/include/FastSIMD/ToolSet/ARM/128/f32x4.h b/include/FastSIMD/ToolSet/ARM/128/f32x4.h index f64ba49..60f91be 100644 --- a/include/FastSIMD/ToolSet/ARM/128/f32x4.h +++ b/include/FastSIMD/ToolSet/ARM/128/f32x4.h @@ -186,13 +186,29 @@ namespace FS template>> FS_FORCEINLINE f32<4, SIMD> Min( const f32<4, SIMD>& a, const f32<4, SIMD>& b ) { - return vminq_f32( a.native, b.native ); + if constexpr( FastSIMD::IsRelaxed() ) + { + return vminq_f32( a.native, b.native ); + } + else + { + // Same as std::min for +-0 and NaN, which vminq is not + return Select( b < a, b, a ); + } } template>> FS_FORCEINLINE f32<4, SIMD> Max( const f32<4, SIMD>& a, const f32<4, SIMD>& b ) { - return vmaxq_f32( a.native, b.native ); + if constexpr( FastSIMD::IsRelaxed() ) + { + return vmaxq_f32( a.native, b.native ); + } + else + { + // Same as std::max for +-0 and NaN, which vmaxq is not + return Select( a < b, b, a ); + } } template>> diff --git a/include/FastSIMD/ToolSet/ARM/NEON.h b/include/FastSIMD/ToolSet/ARM/NEON.h index 00f39dc..3d48a06 100644 --- a/include/FastSIMD/ToolSet/ARM/NEON.h +++ b/include/FastSIMD/ToolSet/ARM/NEON.h @@ -19,24 +19,50 @@ namespace FS return vcvtq_f32_s32( a.native ); } - template>> - FS_FORCEINLINE Register Cast( const Register& a, TypeDummy ) + namespace impl { - if constexpr( - std::is_same_v::NativeType, float32x4_t> && - std::is_same_v::NativeType, int32x4_t> ) + // Native casts go through uint32x4_t and pick the reinterpret by element type, not native type + // With the MSVC ARM64 ABI every NEON vector type is the same __n128, so native types can't be told apart + template + FS_FORCEINLINE uint32x4_t NeonCastToU32( V a ) { - return vreinterpretq_s32_f32( a.GetNative() ); + if constexpr( std::is_same_v ) + { + return vreinterpretq_u32_f32( a ); + } + else if constexpr( std::is_same_v ) + { + return vreinterpretq_u32_s32( a ); + } + else + { + static_assert( std::is_same_v> || std::is_same_v>, "FastSIMD: FS::Cast not supported with provided types" ); + return a; + } } - else if constexpr( - std::is_same_v::NativeType, int32x4_t> && - std::is_same_v::NativeType, float32x4_t> ) - { - return vreinterpretq_f32_s32( a.GetNative() ); - } - else + + template + FS_FORCEINLINE auto NeonCastFromU32( uint32x4_t a ) { - return a.GetNative(); + if constexpr( std::is_same_v ) + { + return vreinterpretq_f32_u32( a ); + } + else if constexpr( std::is_same_v ) + { + return vreinterpretq_s32_u32( a ); + } + else + { + static_assert( std::is_same_v> || std::is_same_v>, "FastSIMD: FS::Cast not supported with provided types" ); + return a; + } } } + + template>> + FS_FORCEINLINE Register Cast( const Register& a, TypeDummy ) + { + return impl::NeonCastFromU32( impl::NeonCastToU32( a.GetNative() ) ); + } } diff --git a/include/FastSIMD/ToolSet/Generic/Functions.h b/include/FastSIMD/ToolSet/Generic/Functions.h index 217aeb8..b8c46d1 100644 --- a/include/FastSIMD/ToolSet/Generic/Functions.h +++ b/include/FastSIMD/ToolSet/Generic/Functions.h @@ -634,7 +634,7 @@ namespace FS RegisterF xx = value * value; y *= xx; - y *= e * RegisterF( -2.12194440e-4f ); + y += e * RegisterF( -2.12194440e-4f ); y -= xx * RegisterF( 0.5f ); value += y; diff --git a/include/FastSIMD/ToolSet/Generic/Register.h b/include/FastSIMD/ToolSet/Generic/Register.h index c676fa6..6b58d51 100644 --- a/include/FastSIMD/ToolSet/Generic/Register.h +++ b/include/FastSIMD/ToolSet/Generic/Register.h @@ -175,70 +175,72 @@ namespace FS DoubledType v0, v1; }; + // Binary operators copy lhs into a temporary rather than taking it by value + // MSVC x86 doesn't copy over-aligned by value arguments, so modifying a by value lhs wrote into the caller's register template - FS_FORCEINLINE static Register operator +( Register lhs, const Register& rhs ) + FS_FORCEINLINE static Register operator +( const Register& lhs, const Register& rhs ) { - return lhs += rhs; + return Register( lhs ) += rhs; } template - FS_FORCEINLINE static Register operator -( Register lhs, const Register& rhs ) + FS_FORCEINLINE static Register operator -( const Register& lhs, const Register& rhs ) { - return lhs -= rhs; + return Register( lhs ) -= rhs; } template - FS_FORCEINLINE static Register operator *( Register lhs, const Register& rhs ) + FS_FORCEINLINE static Register operator *( const Register& lhs, const Register& rhs ) { - return lhs *= rhs; + return Register( lhs ) *= rhs; } template - FS_FORCEINLINE static Register operator /( Register lhs, const Register& rhs ) + FS_FORCEINLINE static Register operator /( const Register& lhs, const Register& rhs ) { - return lhs /= rhs; + return Register( lhs ) /= rhs; } template - FS_FORCEINLINE static Register operator &( Register lhs, const Register& rhs ) + FS_FORCEINLINE static Register operator &( const Register& lhs, const Register& rhs ) { - return lhs &= rhs; + return Register( lhs ) &= rhs; } template - FS_FORCEINLINE static Register operator |( Register lhs, const Register& rhs ) + FS_FORCEINLINE static Register operator |( const Register& lhs, const Register& rhs ) { - return lhs |= rhs; + return Register( lhs ) |= rhs; } template - FS_FORCEINLINE static Register operator ^( Register lhs, const Register& rhs ) + FS_FORCEINLINE static Register operator ^( const Register& lhs, const Register& rhs ) { - return lhs ^= rhs; + return Register( lhs ) ^= rhs; } template - FS_FORCEINLINE static Register operator <<( Register lhs, int rhs ) + FS_FORCEINLINE static Register operator <<( const Register& lhs, int rhs ) { - return lhs <<= rhs; + return Register( lhs ) <<= rhs; } template - FS_FORCEINLINE static Register operator >>( Register lhs, int rhs ) + FS_FORCEINLINE static Register operator >>( const Register& lhs, int rhs ) { - return lhs >>= rhs; + return Register( lhs ) >>= rhs; } template - FS_FORCEINLINE static Register operator<<( Register lhs, Register rhs ) + FS_FORCEINLINE static Register operator<<( const Register& lhs, const Register& rhs ) { - return lhs <<= rhs; + return Register( lhs ) <<= rhs; } template - FS_FORCEINLINE static Register operator>>( Register lhs, Register rhs ) + FS_FORCEINLINE static Register operator>>( const Register& lhs, const Register& rhs ) { - return lhs >>= rhs; + return Register( lhs ) >>= rhs; } template diff --git a/include/FastSIMD/ToolSet/WASM/128/f32x4.h b/include/FastSIMD/ToolSet/WASM/128/f32x4.h index 1dbd036..1b3fde1 100644 --- a/include/FastSIMD/ToolSet/WASM/128/f32x4.h +++ b/include/FastSIMD/ToolSet/WASM/128/f32x4.h @@ -168,7 +168,8 @@ namespace FS } else { - return wasm_f32x4_min( a.native, b.native ); + // pmin is b < a ? b : a, matching std::min for +-0 and NaN, which min does not + return wasm_f32x4_pmin( a.native, b.native ); } } @@ -181,7 +182,8 @@ namespace FS } else { - return wasm_f32x4_max( a.native, b.native ); + // pmax is a < b ? b : a, matching std::max for +-0 and NaN, which max does not + return wasm_f32x4_pmax( a.native, b.native ); } } diff --git a/include/FastSIMD/ToolSet/x86/128/f32x4.h b/include/FastSIMD/ToolSet/x86/128/f32x4.h index 9c51613..badd308 100644 --- a/include/FastSIMD/ToolSet/x86/128/f32x4.h +++ b/include/FastSIMD/ToolSet/x86/128/f32x4.h @@ -204,13 +204,15 @@ namespace FS template>> FS_FORCEINLINE f32<4, SIMD> Min( const f32<4, SIMD>& a, const f32<4, SIMD>& b ) { - return _mm_min_ps( a.native, b.native ); + // minps returns its 2nd operand for equal (+-0) or NaN inputs, swapped to match std::min which returns a + return _mm_min_ps( b.native, a.native ); } template>> FS_FORCEINLINE f32<4, SIMD> Max( const f32<4, SIMD>& a, const f32<4, SIMD>& b ) { - return _mm_max_ps( a.native, b.native ); + // maxps returns its 2nd operand for equal (+-0) or NaN inputs, swapped to match std::max which returns a + return _mm_max_ps( b.native, a.native ); } template>> diff --git a/include/FastSIMD/ToolSet/x86/256/f32x8.h b/include/FastSIMD/ToolSet/x86/256/f32x8.h index 1c6513d..c1e7008 100644 --- a/include/FastSIMD/ToolSet/x86/256/f32x8.h +++ b/include/FastSIMD/ToolSet/x86/256/f32x8.h @@ -86,7 +86,7 @@ namespace FS FS_FORCEINLINE MaskType operator !=( const Register& rhs ) const { - return _mm256_cmp_ps( native, rhs.native, _CMP_NEQ_OQ ); + return _mm256_cmp_ps( native, rhs.native, _CMP_NEQ_UQ ); } FS_FORCEINLINE MaskType operator >=( const Register& rhs ) const @@ -165,13 +165,15 @@ namespace FS template>> FS_FORCEINLINE f32<8, SIMD> Min( const f32<8, SIMD>& a, const f32<8, SIMD>& b ) { - return _mm256_min_ps( a.native, b.native ); + // minps returns its 2nd operand for equal (+-0) or NaN inputs, swapped to match std::min which returns a + return _mm256_min_ps( b.native, a.native ); } template>> FS_FORCEINLINE f32<8, SIMD> Max( const f32<8, SIMD>& a, const f32<8, SIMD>& b ) { - return _mm256_max_ps( a.native, b.native ); + // maxps returns its 2nd operand for equal (+-0) or NaN inputs, swapped to match std::max which returns a + return _mm256_max_ps( b.native, a.native ); } template>> diff --git a/include/FastSIMD/ToolSet/x86/512/f32x16.h b/include/FastSIMD/ToolSet/x86/512/f32x16.h index 9711645..09bc12a 100644 --- a/include/FastSIMD/ToolSet/x86/512/f32x16.h +++ b/include/FastSIMD/ToolSet/x86/512/f32x16.h @@ -86,7 +86,7 @@ namespace FS FS_FORCEINLINE MaskType operator !=( const Register& rhs ) const { - return _mm512_cmp_ps_mask( native, rhs.native, _CMP_NEQ_OQ ); + return _mm512_cmp_ps_mask( native, rhs.native, _CMP_NEQ_UQ ); } FS_FORCEINLINE MaskType operator >=( const Register& rhs ) const @@ -164,13 +164,15 @@ namespace FS template>> FS_FORCEINLINE f32<16, SIMD> Min( const f32<16, SIMD>& a, const f32<16, SIMD>& b ) { - return _mm512_min_ps( a.native, b.native ); + // minps returns its 2nd operand for equal (+-0) or NaN inputs, swapped to match std::min which returns a + return _mm512_min_ps( b.native, a.native ); } template>> FS_FORCEINLINE f32<16, SIMD> Max( const f32<16, SIMD>& a, const f32<16, SIMD>& b ) { - return _mm512_max_ps( a.native, b.native ); + // maxps returns its 2nd operand for equal (+-0) or NaN inputs, swapped to match std::max which returns a + return _mm512_max_ps( b.native, a.native ); } template>>