A translator from Intel SSE intrinsics to Arm/Aarch64 NEON implementation
C++
1,531
1,554 commits
updated Sep 13, 2026
A C/C++ header file that converts Intel SSE intrinsics to Arm/Aarch64 NEON intrinsics.
sse2neon translates Intel SSE (Streaming SIMD Extensions) intrinsics to Arm NEON,
enabling rapid porting of x86 SIMD code to Arm platforms.
The header file sse2neon.h provides NEON-based implementations of functions from Intel intrinsic headers (e.g., <xmmintrin.h>),
preserving the original semantics.
| Header file | Extension |
|---|---|
<mmintrin.h> | MMX |
<xmmintrin.h> | SSE |
<emmintrin.h> | SSE2 |
<pmmintrin.h> | SSE3 |
<tmmintrin.h> | SSSE3 |
<smmintrin.h> | SSE4.1 |
<nmmintrin.h> | SSE4.2 |
<wmmintrin.h> | AES |
sse2neon supports SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2, and AES extensions.
Some SSE intrinsics map directly to a single NEON intrinsic (e.g., _mm_loadu_si128 → vld1q_s32),
while others require multiple NEON instructions (e.g., _mm_maddubs_epi16 uses 22 instructions).
See perf-tier.md for detailed performance tier classification of all intrinsics.
Some conversions produce different results than SSE due to IEEE-754 handling differences.
For example, _mm_rsqrt_ps:
__m128 _mm_rsqrt_ps(__m128 in)
{
float32x4_t out = vrsqrteq_f32(vreinterpretq_f32_m128(in));
out = vmulq_f32(
out, vrsqrtsq_f32(vmulq_f32(vreinterpretq_f32_m128(in), out), out));
return vreinterpretq_m128_f32(out);
}
This NEON conversion returns NaN for input 0.0 because rsqrt(0) = INF, and the subsequent INF × 0 = NaN.
The SSE intrinsic returns INF instead.
Enable the compile-time precision flags below when exact SSE compatibility is required.
Architecture: Little-endian ARM only. Big-endian ARM is not supported and will produce a compile-time error.
Compilers:
| Compiler | Minimum Version | Notes |
|---|---|---|
| GCC | 10+ | Earlier versions have vector instruction bugs |
| Clang | 11+ | Earlier versions have vector instruction bugs |
| MSVC | 2019+ (v142) | ARM64 and ARM64EC targets supported |
| Apple Clang | 12+ | macOS ARM64 (Apple Silicon) |
Earlier GCC/Clang versions contain bugs in vector instruction generation that cause incorrect assembly output for certain NEON intrinsics (e.g., rev16, rev32 with invalid operand combinations).
ARM64EC is a hybrid ABI allowing ARM64 code to interoperate with x64 code.
Copy sse2neon.h into your source directory.
Replace SSE headers with sse2neon:
// Before
#include <xmmintrin.h>
#include <emmintrin.h>
// After
#include "sse2neon.h"
This also replaces {p,t,s,n,w}mmintrin.h.
Add the appropriate compiler flags:
| Target | Compiler Flag |
|---|---|
| ARMv8-A AArch64 | -march=armv8-a+fp+simd+crypto+crc |
| ARMv8-A AArch32 | -mfpu=neon-fp-armv8 |
| ARMv7-A | -mfpu=neon |
Remove +crypto and/or +crc if unsupported by your target.
For Windows ARM64EC (hybrid x64/ARM64 mode):
sse2neon.h automatically skips <intrin.h> when _M_ARM64EC is detected__m128 type is not ABI-compatible with x64 code; users needing
cross-ABI SIMD interop should use MSVC's softintrin instead(Optional) To reduce the header file size for your specific target architecture and accelerate compilation, you can use the unifdef tool to remove unused conditional compilation paths. For example, to generate a reduced version for AArch64:
unifdef -DSSE2NEON_ARCH_AARCH64=1 -D__aarch64__=1\
-D__ARM_FEATURE_DIRECTED_ROUNDING=1 -D__ARM_FEATURE_FMA=1
-D__ARM_FEATURE_CRYPTO=1 -D__ARM_FEATURE_CRC32=1 -U__ARM_FEATURE_FRINT\
sse2neon.h > sse2neon_aarch64.h
NEON trades IEEE-754 compliance for performance when handling denormals and NaNs.
Define these macros as 1 before including sse2neon.h to enable precise (but slower) implementations:
| Macro | Effect |
|---|---|
SSE2NEON_PRECISE_MINMAX | Correct NaN handling in _mm_min_{ps,pd} and _mm_max_{ps,pd} |
SSE2NEON_PRECISE_DIV | Extra Newton-Raphson iteration for _mm_rcp_ps and _mm_div_ps |
SSE2NEON_PRECISE_SQRT | Extra Newton-Raphson iteration for _mm_sqrt_ps and _mm_rsqrt_ps |
SSE2NEON_PRECISE_DP | Conditional multiplication in _mm_dp_pd |
SSE2NEON_UNDEFINED_ZERO | Force zero for _mm_undefined_{ps,pd,si128} (MSVC already does this) |
All precision flags are disabled by default to maximize performance.
| Use Case | Flags | Rationale |
|---|---|---|
| Graphics/Rendering | MINMAX, SQRT (+DIV if using rcp) | NaN handling and normalization; Blender enables all three |
| DSP/Audio | None (defaults) | Throughput over precision; inaudible differences |
| Cryptography | None (defaults) | Integer-focused; FP precision irrelevant |
| Scientific/Numerical | MINMAX, SQRT, DP | Reduces x86 divergence; see caveats below |
| Game Physics | MINMAX | Prevents NaN propagation in collision detection |
| Machine Learning | MINMAX for inference | NaN handling for determinism; training tolerates defaults |
Flags use SSE2NEON_PRECISE_ prefix (e.g., MINMAX → SSE2NEON_PRECISE_MINMAX).
Architecture notes:
DIV flag affects _mm_rcp* (reciprocal approximation), not _mm_div* which uses native IEEE-754 division on ARMv8. Enable DIV on ARMv7 or when using reciprocal intrinsics.SSE2NEON_UNDEFINED_ZERO=1. Some divergences (FTZ/DAZ, NaN payloads) cannot be fully eliminated.Example configuration for graphics applications:
#define SSE2NEON_PRECISE_MINMAX 1
#define SSE2NEON_PRECISE_SQRT 1
#include "sse2neon.h"
Memory from _mm_malloc() must be freed with _mm_free(), not free(). Mixing allocators causes heap corruption on Windows.
ARM has no userspace equivalent for x86 address-range monitoring.
_mm_monitor is a no-op; _mm_mwait behavior is controlled by SSE2NEON_MWAIT_POLICY:
| Value | Behavior |
|---|---|
0 (default) | yield - Safe everywhere, never blocks |
1 | wfe - Event wait, may trap in EL0 |
2 | wfi - Interrupt wait, may trap in EL0 |
Policies 1/2 do not provide "wake on store" semantics and may trap on Linux/iOS/macOS.
See sse2neon.h for detailed usage guidance.
Test cases are in the tests directory with runtime-specified input data.
# Basic test run
make check
# Enable crypto and CRC features
make FEATURE=crypto+crc check
# Target specific CPU
make ARCH_CFLAGS="-mcpu=cortex-a53 -mfpu=neon-vfpv4" check
Requires QEMU for non-Arm hosts.
# ARMv8-A AArch64
make CROSS_COMPILE=aarch64-linux-gnu- check
# ARMv7-A
make CROSS_COMPILE=arm-linux-gnueabihf- check
# ARMv8-A AArch32
make CROSS_COMPILE=arm-linux-gnueabihf- \
ARCH_CFLAGS="-mcpu=cortex-a32 -mfpu=neon-fp-armv8" check
See tests/README.md for details.
Compiler optimizations (-O1, -O2, etc.) may cause unexpected behavior with frequent rounding mode changes or repeated _MM_SET_DENORMALS_ZERO_MODE() calls.
The project prioritizes performance over these edge cases—developers should handle them explicitly when needed.
Open source projects using sse2neon for Arm/Aarch64 support (partial list):
sse2neon is freely redistributable under the MIT License.
(top 24 of 42)
265 followers · starred Dec 2023
2,372 followers · starred Sep 2020
13,380 followers · starred Aug 2023
463 followers · starred Feb 2021
A translator from Intel SSE intrinsics to Arm/Aarch64 NEON implementation
C++
1,531
1,554 commits
updated Sep 13, 2026
A C/C++ header file that converts Intel SSE intrinsics to Arm/Aarch64 NEON intrinsics.
sse2neon translates Intel SSE (Streaming SIMD Extensions) intrinsics to Arm NEON,
enabling rapid porting of x86 SIMD code to Arm platforms.
The header file sse2neon.h provides NEON-based implementations of functions from Intel intrinsic headers (e.g., <xmmintrin.h>),
preserving the original semantics.
| Header file | Extension |
|---|---|
<mmintrin.h> | MMX |
<xmmintrin.h> | SSE |
<emmintrin.h> | SSE2 |
<pmmintrin.h> | SSE3 |
<tmmintrin.h> | SSSE3 |
<smmintrin.h> | SSE4.1 |
<nmmintrin.h> | SSE4.2 |
<wmmintrin.h> | AES |
sse2neon supports SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2, and AES extensions.
Some SSE intrinsics map directly to a single NEON intrinsic (e.g., _mm_loadu_si128 → vld1q_s32),
while others require multiple NEON instructions (e.g., _mm_maddubs_epi16 uses 22 instructions).
See perf-tier.md for detailed performance tier classification of all intrinsics.
Some conversions produce different results than SSE due to IEEE-754 handling differences.
For example, _mm_rsqrt_ps:
__m128 _mm_rsqrt_ps(__m128 in)
{
float32x4_t out = vrsqrteq_f32(vreinterpretq_f32_m128(in));
out = vmulq_f32(
out, vrsqrtsq_f32(vmulq_f32(vreinterpretq_f32_m128(in), out), out));
return vreinterpretq_m128_f32(out);
}
This NEON conversion returns NaN for input 0.0 because rsqrt(0) = INF, and the subsequent INF × 0 = NaN.
The SSE intrinsic returns INF instead.
Enable the compile-time precision flags below when exact SSE compatibility is required.
Architecture: Little-endian ARM only. Big-endian ARM is not supported and will produce a compile-time error.
Compilers:
| Compiler | Minimum Version | Notes |
|---|---|---|
| GCC | 10+ | Earlier versions have vector instruction bugs |
| Clang | 11+ | Earlier versions have vector instruction bugs |
| MSVC | 2019+ (v142) | ARM64 and ARM64EC targets supported |
| Apple Clang | 12+ | macOS ARM64 (Apple Silicon) |
Earlier GCC/Clang versions contain bugs in vector instruction generation that cause incorrect assembly output for certain NEON intrinsics (e.g., rev16, rev32 with invalid operand combinations).
ARM64EC is a hybrid ABI allowing ARM64 code to interoperate with x64 code.
Copy sse2neon.h into your source directory.
Replace SSE headers with sse2neon:
// Before
#include <xmmintrin.h>
#include <emmintrin.h>
// After
#include "sse2neon.h"
This also replaces {p,t,s,n,w}mmintrin.h.
Add the appropriate compiler flags:
| Target | Compiler Flag |
|---|---|
| ARMv8-A AArch64 | -march=armv8-a+fp+simd+crypto+crc |
| ARMv8-A AArch32 | -mfpu=neon-fp-armv8 |
| ARMv7-A | -mfpu=neon |
Remove +crypto and/or +crc if unsupported by your target.
For Windows ARM64EC (hybrid x64/ARM64 mode):
sse2neon.h automatically skips <intrin.h> when _M_ARM64EC is detected__m128 type is not ABI-compatible with x64 code; users needing
cross-ABI SIMD interop should use MSVC's softintrin instead(Optional) To reduce the header file size for your specific target architecture and accelerate compilation, you can use the unifdef tool to remove unused conditional compilation paths. For example, to generate a reduced version for AArch64:
unifdef -DSSE2NEON_ARCH_AARCH64=1 -D__aarch64__=1\
-D__ARM_FEATURE_DIRECTED_ROUNDING=1 -D__ARM_FEATURE_FMA=1
-D__ARM_FEATURE_CRYPTO=1 -D__ARM_FEATURE_CRC32=1 -U__ARM_FEATURE_FRINT\
sse2neon.h > sse2neon_aarch64.h
NEON trades IEEE-754 compliance for performance when handling denormals and NaNs.
Define these macros as 1 before including sse2neon.h to enable precise (but slower) implementations:
| Macro | Effect |
|---|---|
SSE2NEON_PRECISE_MINMAX | Correct NaN handling in _mm_min_{ps,pd} and _mm_max_{ps,pd} |
SSE2NEON_PRECISE_DIV | Extra Newton-Raphson iteration for _mm_rcp_ps and _mm_div_ps |
SSE2NEON_PRECISE_SQRT | Extra Newton-Raphson iteration for _mm_sqrt_ps and _mm_rsqrt_ps |
SSE2NEON_PRECISE_DP | Conditional multiplication in _mm_dp_pd |
SSE2NEON_UNDEFINED_ZERO | Force zero for _mm_undefined_{ps,pd,si128} (MSVC already does this) |
All precision flags are disabled by default to maximize performance.
| Use Case | Flags | Rationale |
|---|---|---|
| Graphics/Rendering | MINMAX, SQRT (+DIV if using rcp) | NaN handling and normalization; Blender enables all three |
| DSP/Audio | None (defaults) | Throughput over precision; inaudible differences |
| Cryptography | None (defaults) | Integer-focused; FP precision irrelevant |
| Scientific/Numerical | MINMAX, SQRT, DP | Reduces x86 divergence; see caveats below |
| Game Physics | MINMAX | Prevents NaN propagation in collision detection |
| Machine Learning | MINMAX for inference | NaN handling for determinism; training tolerates defaults |
Flags use SSE2NEON_PRECISE_ prefix (e.g., MINMAX → SSE2NEON_PRECISE_MINMAX).
Architecture notes:
DIV flag affects _mm_rcp* (reciprocal approximation), not _mm_div* which uses native IEEE-754 division on ARMv8. Enable DIV on ARMv7 or when using reciprocal intrinsics.SSE2NEON_UNDEFINED_ZERO=1. Some divergences (FTZ/DAZ, NaN payloads) cannot be fully eliminated.Example configuration for graphics applications:
#define SSE2NEON_PRECISE_MINMAX 1
#define SSE2NEON_PRECISE_SQRT 1
#include "sse2neon.h"
Memory from _mm_malloc() must be freed with _mm_free(), not free(). Mixing allocators causes heap corruption on Windows.
ARM has no userspace equivalent for x86 address-range monitoring.
_mm_monitor is a no-op; _mm_mwait behavior is controlled by SSE2NEON_MWAIT_POLICY:
| Value | Behavior |
|---|---|
0 (default) | yield - Safe everywhere, never blocks |
1 | wfe - Event wait, may trap in EL0 |
2 | wfi - Interrupt wait, may trap in EL0 |
Policies 1/2 do not provide "wake on store" semantics and may trap on Linux/iOS/macOS.
See sse2neon.h for detailed usage guidance.
Test cases are in the tests directory with runtime-specified input data.
# Basic test run
make check
# Enable crypto and CRC features
make FEATURE=crypto+crc check
# Target specific CPU
make ARCH_CFLAGS="-mcpu=cortex-a53 -mfpu=neon-vfpv4" check
Requires QEMU for non-Arm hosts.
# ARMv8-A AArch64
make CROSS_COMPILE=aarch64-linux-gnu- check
# ARMv7-A
make CROSS_COMPILE=arm-linux-gnueabihf- check
# ARMv8-A AArch32
make CROSS_COMPILE=arm-linux-gnueabihf- \
ARCH_CFLAGS="-mcpu=cortex-a32 -mfpu=neon-fp-armv8" check
See tests/README.md for details.
Compiler optimizations (-O1, -O2, etc.) may cause unexpected behavior with frequent rounding mode changes or repeated _MM_SET_DENORMALS_ZERO_MODE() calls.
The project prioritizes performance over these edge cases—developers should handle them explicitly when needed.
Open source projects using sse2neon for Arm/Aarch64 support (partial list):
sse2neon is freely redistributable under the MIT License.
(top 24 of 42)
265 followers · starred Dec 2023
2,372 followers · starred Sep 2020
13,380 followers · starred Aug 2023
463 followers · starred Feb 2021