Windows ARM64 NEON Reference
Hand-write every NEON instruction from <arm_neon.h> (C/C++), core::arch::aarch64 (Rust), or System.Runtime.Intrinsics.Arm.* (.NET). Do NOT vendor sse2neon.h, simde, xsimd, highway, or any other SIMD translation/abstraction library.
Windows ARM64 baseline ISA (unconditionally available — no runtime check)
Every Windows-on-ARM SKU (Snapdragon 835 / 850 / 7c / 8c / 8cx / X Elite, Surface SQ1/SQ2/SQ3, Ampere, Cobalt) implements the following ARMv8.0-A extensions. Use them directly wherever the x86 path used the equivalent hardware extension. Do NOT guard with IsProcessorFeaturePresent for any of these.
| Feature |
NEON / ACLE intrinsic family |
x86 analogue to replace |
| ASIMD (NEON) |
<arm_neon.h> v*q_* |
SSE/SSE2/SSE3/SSSE3/SSE4.1/SSE4.2 |
| AES |
vaeseq_u8, vaesdq_u8, vaesmcq_u8, vaesimcq_u8 |
AES-NI (_mm_aesenc_si128, _mm_aesenclast_si128, _mm_aesdec_si128, _mm_aesdeclast_si128, _mm_aeskeygenassist_si128, _mm_aesimc_si128) |
| SHA1 |
vsha1cq_u32, vsha1pq_u32, vsha1mq_u32, vsha1h_u32, vsha1su0q_u32, vsha1su1q_u32 |
SHA-NI (_mm_sha1rnds4_epu32, _mm_sha1nexte_epu32, _mm_sha1msg1_epu32, _mm_sha1msg2_epu32) |
| SHA2 (SHA-256) |
vsha256hq_u32, vsha256h2q_u32, vsha256su0q_u32, vsha256su1q_u32 |
SHA-NI (_mm_sha256rnds2_epu32, _mm_sha256msg1_epu32, _mm_sha256msg2_epu32) |
| PMULL / PMULL2 |
vmull_p64, vmull_high_p64, vmull_p8, vmull_high_p8 |
CLMUL / PCLMULQDQ (_mm_clmulepi64_si128) — esp. GHASH/GCM, GF(2^n), CRC reflection |
| CRC32 (CRC32C + CRC32) |
__crc32b/h/w/d, __crc32cb/ch/cw/cd from <arm_acle.h> |
_mm_crc32_u8/u16/u32/u64, software CRC tables |
| FP16 storage |
vld1q_f16 / vst1q_f16 via _Float16 (MSVC 19.40+) |
— |
ARMv8.2+ optional features — gate at runtime
| Feature |
Intrinsic |
Runtime check |
Where present |
| DotProd |
vdotq_s32, vdotq_u32 (+ _lane variants) |
PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE |
Snapdragon 8cx Gen 3, SQ3, X Elite, Cobalt 100, Ampere Altra+ |
| FP16 arith |
vfmaq_f16, vaddq_f16, etc. |
PF_ARM_V82_FP16_INSTRUCTIONS_AVAILABLE |
Same as DotProd |
SVE/SVE2 (<arm_sve.h>) is NOT used on Windows ARM64 — MSVC support is limited and device support is uneven. No #pragma, -mfpu, or /arch: flag is needed for baseline ISA.
Arch guards (canonical)
#if defined(_M_X64) || defined(__x86_64__) || defined(_M_IX86) || defined(__i386__)
# include <immintrin.h>
#elif defined(_M_ARM64) || defined(__aarch64__)
# include <arm_neon.h>
# include <arm_acle.h>
#else
# error "Unsupported architecture"
#endif
Rust: #[cfg(all(target_arch = "aarch64", target_os = "windows"))]
.NET: if (AdvSimd.IsSupported) { ... } else { /* scalar */ }
SSE / SSE2 → NEON — float ops
| SSE |
NEON |
Notes |
_mm_set1_ps(x) |
vdupq_n_f32(x) |
Broadcast |
_mm_setzero_ps() |
vdupq_n_f32(0) |
Zero |
_mm_load_ps(p) |
vld1q_f32(p) |
Load 4 floats |
_mm_store_ps(p, v) |
vst1q_f32(p, v) |
Store 4 floats |
_mm_add_ps(a, b) |
vaddq_f32(a, b) |
|
_mm_sub_ps(a, b) |
vsubq_f32(a, b) |
|
_mm_mul_ps(a, b) |
vmulq_f32(a, b) |
|
_mm_div_ps(a, b) |
vdivq_f32(a, b) |
ARMv8 has native div |
_mm_min_ps(a, b) |
vminq_f32(a, b) |
|
_mm_max_ps(a, b) |
vmaxq_f32(a, b) |
|
_mm_sqrt_ps(a) |
vsqrtq_f32(a) |
|
_mm_and_ps(a, b) |
vreinterpretq_f32_u32(vandq_u32(...)) |
|
_mm_or_ps(a, b) |
vreinterpretq_f32_u32(vorrq_u32(...)) |
|
_mm_xor_ps(a, b) |
vreinterpretq_f32_u32(veorq_u32(...)) |
|
_mm_cmpeq_ps(a, b) |
vreinterpretq_f32_u32(vceqq_f32(a, b)) |
|
_mm_cmplt_ps(a, b) |
vreinterpretq_f32_u32(vcltq_f32(a, b)) |
|
_mm_shuffle_ps(a,b,i) |
complex — use vextq, vzip, vuzp, vtbl |
|
_mm_movemask_ps(a) |
no direct — use vshrn + manual bits |
|
SSE2 → NEON — integer ops
| SSE2 |
NEON |
_mm_set1_epi32(x) |
vdupq_n_s32(x) |
_mm_add_epi32(a, b) |
vaddq_s32(a, b) |
_mm_sub_epi32(a, b) |
vsubq_s32(a, b) |
_mm_mullo_epi32(a, b) |
vmulq_s32(a, b) |
_mm_and_si128(a, b) |
vandq_s32(a, b) |
_mm_or_si128(a, b) |
vorrq_s32(a, b) |
_mm_slli_epi32(a, n) |
vshlq_n_s32(a, n) |
_mm_srli_epi32(a, n) |
vshrq_n_u32(a, n) |
_mm_cmpeq_epi32(a, b) |
vceqq_s32(a, b) |
AVX / AVX2 → NEON
Hand-write the NEON equivalents (typically 2× 128-bit NEON ops per 256-bit AVX op). Do NOT include sse2neon or simde even to cover AVX.
Memory ordering
ARM64 is a weak memory model (vs x64's TSO). Use <atomic> / _Interlocked* — they generate correct barriers on both. Only add raw dmb ish/ish{ld,st} when using assembly or intrinsics directly.
Common pitfalls
_mm_movemask_ps / _mm_movemask_epi8: no direct NEON equivalent — expect multi-instruction emulation.
_mm_shuffle_ps with compile-time constant: NEON shuffle is more restrictive.
- Horizontal ops (
_mm_hadd_ps): NEON has vpaddq but semantics differ.
__rdtsc: use QueryPerformanceCounter on ARM64 (portable and correct).
- Stack alignment: ARM64 requires 16-byte alignment in hand-written asm.
- SEH works on ARM64 but
CONTEXT structure uses X0–X28, Fp, Lr, Sp, Pc.
- 128-bit atomics: use
_InterlockedCompareExchange128 (works on both x64 cmpxchg16b and ARM64 ldxp/stxp).
Processor feature detection
#include <windows.h>
bool has_neon = IsProcessorFeaturePresent(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE); // always true
bool has_crc32 = IsProcessorFeaturePresent(PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE); // baseline on WoA
bool has_crypto = IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); // baseline on WoA
bool has_atomics = IsProcessorFeaturePresent(PF_ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE);
bool has_dp = IsProcessorFeaturePresent(PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE);
bool has_jscvt = IsProcessorFeaturePresent(PF_ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE);
bool has_lrcpc = IsProcessorFeaturePresent(PF_ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE);
ARM64EC
cl /arm64EC (or <Platform>ARM64EC</Platform> / CMAKE_GENERATOR_PLATFORM=ARM64EC) lets an ARM64 binary call into emulated x64 code. Small transition overhead; prefer native ARM64 unless a hard x64 dependency blocks the port.
1---2name: wos-neon-reference-23description: SSE/AVX → ARM NEON translation reference for Windows ARM64. Load when actively converting x86 SIMD intrinsics to NEON, choosing between baseline ARMv8.0 and optional ARMv8.2+ extensions, or checking the Windows ARM64 baseline ISA (AES/SHA1/SHA2/PMULL/CRC32) — no runtime feature check needed for baseline. Do NOT load for general questions about ARM64; load only when a specific intrinsic must be translated.4---56# Windows ARM64 NEON Reference78Hand-write every NEON instruction from `<arm_neon.h>` (C/C++), `core::arch::aarch64` (Rust), or `System.Runtime.Intrinsics.Arm.*` (.NET). **Do NOT vendor `sse2neon.h`, `simde`, `xsimd`, `highway`, or any other SIMD translation/abstraction library.**910## Windows ARM64 baseline ISA (unconditionally available — no runtime check)1112Every Windows-on-ARM SKU (Snapdragon 835 / 850 / 7c / 8c / 8cx / X Elite, Surface SQ1/SQ2/SQ3, Ampere, Cobalt) implements the following ARMv8.0-A extensions. Use them directly wherever the x86 path used the equivalent hardware extension. **Do NOT guard with `IsProcessorFeaturePresent` for any of these.**1314| Feature | NEON / ACLE intrinsic family | x86 analogue to replace |15|---|---|---|16| ASIMD (NEON) | `<arm_neon.h>` `v*q_*` | SSE/SSE2/SSE3/SSSE3/SSE4.1/SSE4.2 |17| AES | `vaeseq_u8`, `vaesdq_u8`, `vaesmcq_u8`, `vaesimcq_u8` | AES-NI (`_mm_aesenc_si128`, `_mm_aesenclast_si128`, `_mm_aesdec_si128`, `_mm_aesdeclast_si128`, `_mm_aeskeygenassist_si128`, `_mm_aesimc_si128`) |18| SHA1 | `vsha1cq_u32`, `vsha1pq_u32`, `vsha1mq_u32`, `vsha1h_u32`, `vsha1su0q_u32`, `vsha1su1q_u32` | SHA-NI (`_mm_sha1rnds4_epu32`, `_mm_sha1nexte_epu32`, `_mm_sha1msg1_epu32`, `_mm_sha1msg2_epu32`) |19| SHA2 (SHA-256) | `vsha256hq_u32`, `vsha256h2q_u32`, `vsha256su0q_u32`, `vsha256su1q_u32` | SHA-NI (`_mm_sha256rnds2_epu32`, `_mm_sha256msg1_epu32`, `_mm_sha256msg2_epu32`) |20| PMULL / PMULL2 | `vmull_p64`, `vmull_high_p64`, `vmull_p8`, `vmull_high_p8` | CLMUL / PCLMULQDQ (`_mm_clmulepi64_si128`) — esp. GHASH/GCM, GF(2^n), CRC reflection |21| CRC32 (CRC32C + CRC32) | `__crc32b/h/w/d`, `__crc32cb/ch/cw/cd` from `<arm_acle.h>` | `_mm_crc32_u8/u16/u32/u64`, software CRC tables |22| FP16 storage | `vld1q_f16` / `vst1q_f16` via `_Float16` (MSVC 19.40+) | — |2324## ARMv8.2+ optional features — gate at runtime2526| Feature | Intrinsic | Runtime check | Where present |27|---|---|---|---|28| DotProd | `vdotq_s32`, `vdotq_u32` (+ `_lane` variants) | `PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE` | Snapdragon 8cx Gen 3, SQ3, X Elite, Cobalt 100, Ampere Altra+ |29| FP16 arith | `vfmaq_f16`, `vaddq_f16`, etc. | `PF_ARM_V82_FP16_INSTRUCTIONS_AVAILABLE` | Same as DotProd |3031SVE/SVE2 (`<arm_sve.h>`) is NOT used on Windows ARM64 — MSVC support is limited and device support is uneven. No `#pragma`, `-mfpu`, or `/arch:` flag is needed for baseline ISA.3233## Arch guards (canonical)3435```c36#if defined(_M_X64) || defined(__x86_64__) || defined(_M_IX86) || defined(__i386__)37# include <immintrin.h>38#elif defined(_M_ARM64) || defined(__aarch64__)39# include <arm_neon.h>40# include <arm_acle.h>41#else42# error "Unsupported architecture"43#endif44```4546Rust: `#[cfg(all(target_arch = "aarch64", target_os = "windows"))]`47.NET: `if (AdvSimd.IsSupported) { ... } else { /* scalar */ }`4849## SSE / SSE2 → NEON — float ops5051| SSE | NEON | Notes |52|---|---|---|53| `_mm_set1_ps(x)` | `vdupq_n_f32(x)` | Broadcast |54| `_mm_setzero_ps()` | `vdupq_n_f32(0)` | Zero |55| `_mm_load_ps(p)` | `vld1q_f32(p)` | Load 4 floats |56| `_mm_store_ps(p, v)` | `vst1q_f32(p, v)` | Store 4 floats |57| `_mm_add_ps(a, b)` | `vaddq_f32(a, b)` | |58| `_mm_sub_ps(a, b)` | `vsubq_f32(a, b)` | |59| `_mm_mul_ps(a, b)` | `vmulq_f32(a, b)` | |60| `_mm_div_ps(a, b)` | `vdivq_f32(a, b)` | ARMv8 has native div |61| `_mm_min_ps(a, b)` | `vminq_f32(a, b)` | |62| `_mm_max_ps(a, b)` | `vmaxq_f32(a, b)` | |63| `_mm_sqrt_ps(a)` | `vsqrtq_f32(a)` | |64| `_mm_and_ps(a, b)` | `vreinterpretq_f32_u32(vandq_u32(...))` | |65| `_mm_or_ps(a, b)` | `vreinterpretq_f32_u32(vorrq_u32(...))` | |66| `_mm_xor_ps(a, b)` | `vreinterpretq_f32_u32(veorq_u32(...))` | |67| `_mm_cmpeq_ps(a, b)` | `vreinterpretq_f32_u32(vceqq_f32(a, b))` | |68| `_mm_cmplt_ps(a, b)` | `vreinterpretq_f32_u32(vcltq_f32(a, b))` | |69| `_mm_shuffle_ps(a,b,i)` | complex — use `vextq`, `vzip`, `vuzp`, `vtbl` | |70| `_mm_movemask_ps(a)` | no direct — use `vshrn` + manual bits | |7172## SSE2 → NEON — integer ops7374| SSE2 | NEON |75|---|---|76| `_mm_set1_epi32(x)` | `vdupq_n_s32(x)` |77| `_mm_add_epi32(a, b)` | `vaddq_s32(a, b)` |78| `_mm_sub_epi32(a, b)` | `vsubq_s32(a, b)` |79| `_mm_mullo_epi32(a, b)` | `vmulq_s32(a, b)` |80| `_mm_and_si128(a, b)` | `vandq_s32(a, b)` |81| `_mm_or_si128(a, b)` | `vorrq_s32(a, b)` |82| `_mm_slli_epi32(a, n)` | `vshlq_n_s32(a, n)` |83| `_mm_srli_epi32(a, n)` | `vshrq_n_u32(a, n)` |84| `_mm_cmpeq_epi32(a, b)` | `vceqq_s32(a, b)` |8586## AVX / AVX2 → NEON8788Hand-write the NEON equivalents (typically 2× 128-bit NEON ops per 256-bit AVX op). Do NOT include `sse2neon` or `simde` even to cover AVX.8990## Memory ordering9192ARM64 is a weak memory model (vs x64's TSO). Use `<atomic>` / `_Interlocked*` — they generate correct barriers on both. Only add raw `dmb ish`/`ish{ld,st}` when using assembly or intrinsics directly.9394## Common pitfalls95961. `_mm_movemask_ps` / `_mm_movemask_epi8`: no direct NEON equivalent — expect multi-instruction emulation.972. `_mm_shuffle_ps` with compile-time constant: NEON shuffle is more restrictive.983. Horizontal ops (`_mm_hadd_ps`): NEON has `vpaddq` but semantics differ.994. `__rdtsc`: use `QueryPerformanceCounter` on ARM64 (portable and correct).1005. Stack alignment: ARM64 requires 16-byte alignment in hand-written asm.1016. SEH works on ARM64 but `CONTEXT` structure uses `X0`–`X28`, `Fp`, `Lr`, `Sp`, `Pc`.1027. 128-bit atomics: use `_InterlockedCompareExchange128` (works on both x64 `cmpxchg16b` and ARM64 `ldxp`/`stxp`).103104## Processor feature detection105106```c107#include <windows.h>108bool has_neon = IsProcessorFeaturePresent(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE); // always true109bool has_crc32 = IsProcessorFeaturePresent(PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE); // baseline on WoA110bool has_crypto = IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); // baseline on WoA111bool has_atomics = IsProcessorFeaturePresent(PF_ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE);112bool has_dp = IsProcessorFeaturePresent(PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE);113bool has_jscvt = IsProcessorFeaturePresent(PF_ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE);114bool has_lrcpc = IsProcessorFeaturePresent(PF_ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE);115```116117## ARM64EC118119`cl /arm64EC` (or `<Platform>ARM64EC</Platform>` / `CMAKE_GENERATOR_PLATFORM=ARM64EC`) lets an ARM64 binary call into emulated x64 code. Small transition overhead; prefer native ARM64 unless a hard x64 dependency blocks the port.