From 79a98383f6f9104b0e8c97dc4e22d9d7f27dcc06 Mon Sep 17 00:00:00 2001 From: Jordan Peck Date: Wed, 14 Oct 2026 23:41:36 +0100 Subject: [PATCH 1/2] MSVC AVX512: copy f32x16/i32x16 through the native vector MSVC lowers copies of trivially copyable 64 byte structs as two 32 byte stores, which then get reloaded as one 512 bit register and stall store forwarding. Every composite op (Register{ f( a.v0 ), f( a.v1 ) }), operator copy and assignment hit it. A user-provided copy constructor and assignment that copy .native keep these copies in zmm registers. MSVC only; clang and GCC keep the trivial copies. --- include/FastSIMD/ToolSet/x86/512/f32x16.h | 6 ++++++ include/FastSIMD/ToolSet/x86/512/i32x16.h | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/include/FastSIMD/ToolSet/x86/512/f32x16.h b/include/FastSIMD/ToolSet/x86/512/f32x16.h index 09bc12a..0d3293a 100644 --- a/include/FastSIMD/ToolSet/x86/512/f32x16.h +++ b/include/FastSIMD/ToolSet/x86/512/f32x16.h @@ -18,6 +18,12 @@ namespace FS FS_FORCEINLINE Register() = default; FS_FORCEINLINE Register( NativeType v ) : native( v ) { } FS_FORCEINLINE Register( float v ) : native( _mm512_set1_ps( v ) ) { } +#if defined( _MSC_VER ) && !defined( __clang__ ) + // MSVC copies trivially copyable 64 byte structs as two 32 byte halves, reloading those as a 512 bit register + // stalls store forwarding. Copying the native vector keeps register copies in zmm registers + FS_FORCEINLINE Register( const Register& v ) : native( v.native ) { } + FS_FORCEINLINE Register& operator =( const Register& v ) { native = v.native; return *this; } +#endif FS_FORCEINLINE NativeType GetNative() const { diff --git a/include/FastSIMD/ToolSet/x86/512/i32x16.h b/include/FastSIMD/ToolSet/x86/512/i32x16.h index 00c07d3..4cd504b 100644 --- a/include/FastSIMD/ToolSet/x86/512/i32x16.h +++ b/include/FastSIMD/ToolSet/x86/512/i32x16.h @@ -18,6 +18,12 @@ namespace FS FS_FORCEINLINE Register() = default; FS_FORCEINLINE Register( NativeType v ) : native( v ) { } FS_FORCEINLINE Register( std::int32_t v ) : native( _mm512_set1_epi32( v ) ) { } +#if defined( _MSC_VER ) && !defined( __clang__ ) + // MSVC copies trivially copyable 64 byte structs as two 32 byte halves, reloading those as a 512 bit register + // stalls store forwarding. Copying the native vector keeps register copies in zmm registers + FS_FORCEINLINE Register( const Register& v ) : native( v.native ) { } + FS_FORCEINLINE Register& operator =( const Register& v ) { native = v.native; return *this; } +#endif FS_FORCEINLINE NativeType GetNative() const { From 9a765fa9f2b9a23e3e411d6140e8c7578ac1bcee Mon Sep 17 00:00:00 2001 From: Jordan Peck Date: Thu, 15 Oct 2026 00:21:06 +0100 Subject: [PATCH 2/2] MSVC AVX512: copy masks through the native mask, AnyMask with kortest MSVC writes copies of a 32 lane composite mask with two 16 bit stores and then reads both halves with one 32 bit load, which can't be store forwarded (FastNoise's cellular lane checks hit this twice per call). A user-provided copy constructor and assignment on the AVX512 mask keep the halves in registers. 32 lane AnyMask uses kortestw instead of moving both halves to general purpose registers. MSVC only. --- include/FastSIMD/ToolSet/x86/512/mNx16.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/include/FastSIMD/ToolSet/x86/512/mNx16.h b/include/FastSIMD/ToolSet/x86/512/mNx16.h index 70b2af9..0785204 100644 --- a/include/FastSIMD/ToolSet/x86/512/mNx16.h +++ b/include/FastSIMD/ToolSet/x86/512/mNx16.h @@ -26,6 +26,12 @@ namespace FS FS_FORCEINLINE Register() = default; FS_FORCEINLINE Register( NativeType v ) { this->native = v; } +#if defined( _MSC_VER ) && !defined( __clang__ ) + // MSVC writes copies of a 32 lane composite mask with two 16 bit stores and reads them back with one 32 bit load, + // which can't be store forwarded. Copying the native mask keeps the halves in registers + FS_FORCEINLINE Register( const Register& v ) { this->native = v.native; } + FS_FORCEINLINE Register& operator =( const Register& v ) { this->native = v.native; return *this; } +#endif FS_FORCEINLINE NativeType GetNative() const { @@ -67,4 +73,13 @@ namespace FS { return static_cast>( a.native ); } + +#if defined( _MSC_VER ) && !defined( __clang__ ) + // MSVC moves both mask halves to general purpose registers to OR them + template, 16, SIMD>>> + FS_FORCEINLINE bool AnyMask( const Register, 32, SIMD>& a ) + { + return !_mm512_kortestz( a.v0.native, a.v1.native ); + } +#endif } \ No newline at end of file