在 WebAssembly 中使用 SIMD

Emscripten 支持 WebAssembly SIMD 功能。在 C/C++ 程序中利用 WebAssembly SIMD 有五种不同的方法

  1. 启用 LLVM/Clang SIMD 自动向量化器,以自动定位 WebAssembly SIMD,无需更改 C/C++ 源代码。

  2. 使用 GCC/Clang SIMD 向量扩展(__attribute__((vector_size(16))))编写 SIMD 代码

  3. 使用 WebAssembly SIMD 内部函数(#include <wasm_simd128.h>)编写 SIMD 代码

  4. 编译使用 x86 SSE、SSE2、SSE3、SSSE3、SSE4.1、SSE4.2 或 AVX 内部函数(#include <*mmintrin.h>)的现有 SIMD 代码

  5. 编译使用 ARM NEON 内部函数(#include <arm_neon.h>)的现有 SIMD 代码

这些技术可以在单个程序中自由组合。

要启用上述五种 SIMD 类型中的任何一种,请在编译时传递 WebAssembly 特定的 -msimd128 标志。这也会开启 LLVM 的自动向量化过程。如果不需要,请额外传递标志 -fno-vectorize -fno-slp-vectorize 以禁用自动向量化器。有关更多信息,请参阅 LLVM 中的自动向量化

WebAssembly SIMD 支持以下浏览器:

  • Chrome ≥ 91(2021 年 5 月),

  • Firefox ≥ 89(2021 年 6 月),

  • Safari ≥ 16.4(2023 年 3 月)和

  • Node.js ≥ 16.4(2021 年 6 月)。

有关其他虚拟机的详细信息,请参阅 WebAssembly 路线图

即将推出的 Relaxed SIMD 提案 将为 WebAssembly 添加更多 SIMD 指令。

GCC/Clang SIMD 向量扩展

在源代码级别,可以使用 GCC/Clang SIMD 向量扩展,并在可能的情况下将其降低为 WebAssembly SIMD 指令。

这使开发人员能够通过 typedef 创建自定义宽向量类型,并在向量化类型上使用算术运算符(+、-、*、/),并允许通过 vector[i] 表示法访问单个通道。但是,GCC 向量内置函数 不可用。请改用下面的 WebAssembly SIMD 内部函数。

WebAssembly SIMD 内部函数

LLVM 维护一个 WebAssembly SIMD 内部函数头文件,该文件随 Emscripten 提供,并为不同的支持向量类型添加类型定义。

#include <wasm_simd128.h>
#include <stdio.h>

int main() {
#ifdef __wasm_simd128__
  v128_t v1 = wasm_f32x4_make(1.2f, 3.4f, 5.6f, 7.8f);
  v128_t v2 = wasm_f32x4_make(2.1f, 4.3f, 6.5f, 8.7f);
  v128_t v3 = wasm_f32x4_add(v1, v2);
  // Prints "v3: [3.3, 7.7, 12.1, 16.5]"
  printf("v3: [%.1f, %.1f, %.1f, %.1f]\n",
         wasm_f32x4_extract_lane(v3, 0),
         wasm_f32x4_extract_lane(v3, 1),
         wasm_f32x4_extract_lane(v3, 2),
         wasm_f32x4_extract_lane(v3, 3));
#endif
}

可以在 wasm_simd128.h 在线浏览 Wasm SIMD 头文件。

在编译时传递标志 -msimd128 以启用 WebAssembly SIMD 内部函数的目标。C/C++ 代码可以使用内置预处理器定义 #ifdef __wasm_simd128__ 来检测何时使用启用了 WebAssembly SIMD 的构建。

传递 -mrelaxed-simd 以定位 WebAssembly Relaxed SIMD 内部函数。C/C++ 代码可以使用内置预处理器定义 #ifdef __wasm_relaxed_simd__ 来检测此目标何时处于活动状态。

限制和行为差异

移植原生 SIMD 代码时,应注意,由于可移植性问题,WebAssembly SIMD 规范并未公开所有原生 x86/ARM SIMD 指令的访问权限。特别存在以下更改

  • Emscripten 不支持 x86 或任何其他原生内联 SIMD 汇编或构建 .s 汇编文件,因此所有代码都应编写为使用 SIMD 内部函数或编译器向量扩展。

  • WebAssembly SIMD 无法控制管理浮点舍入模式或处理非规范化数。

  • 缓存行预取指令不可用,对这些函数的调用将编译,但将被视为无操作。

  • 非对称内存栅栏操作不可用,但在启用 SharedArrayBuffer (-pthread) 时将作为完全同步内存栅栏实现,或在未启用多线程(默认值)时作为无操作实现。

SIMD 相关的错误报告在 带有 SIMD 标签的 Emscripten 错误跟踪器 中进行跟踪。

优化注意事项

在开发使用 WebAssembly SIMD 的 SIMD 代码时,实现者应注意主机硬件和 WebAssembly 语义之间的语义差异;正如 WebAssembly 设计文档中所承认的,“这有时会导致性能不佳。”以下列表概述了在性能调优时需要注意的一些 WebAssembly SIMD 指令

对性能有影响的 WebAssembly SIMD 指令

WebAssembly SIMD 指令

架构

注意事项

[i8x16|i16x8|i32x4|i64x2].[shl|shr_s|shr_u]

x86, arm

使用常量移位量以避免额外的指令检查其是否在范围内。

i8x16.[shl|shr_s|shr_u]

x86

为了正交性而包含,这些指令没有等效的 x86 指令,并在 v8 中使用5-11 条 x86 指令进行模拟(即使用 16x8 移位)。

i64x2.shr_s

x86

为了正交性而包含,此指令没有等效的 x86 指令,并在 v8 中使用6-12 条 x86 指令进行模拟

i8x16.swizzle

x86

置零行为与 x86 不匹配(即,当索引超出范围时,此指令置零,而不是当最高有效位为 1 时置零);使用常量 swizzle 量(或 i8x16.shuffle)以避免在某些运行时中出现 3 条额外的 x86 指令。

[f32x4|f64x2].[min|max]

x86

与标量版本一样,NaN 传播语义强制运行时使用 7-10 条 x86 指令进行模拟(例如,参见v8 的模拟;如果可能,请改用 [f32x4|f64x2].[pmin|pmax](1 条 x86 指令)。

i32x4.trunc_sat_f32x4_[u|s]

x86

没有等效的 x86 语义;在 v8 中使用8-14 条 x86 指令进行模拟

i32x4.trunc_sat_f64x2_[u|s]_zero

x86

没有等效的 x86 语义;在 v8 中使用5-6 条 x86 指令进行模拟

f32x4.convert_f32x4_u

x86

没有等效的 x86 语义;在 v8 中使用8 条 x86 指令进行模拟

[i8x16|i64x2].mul

x86

为了正交性而包含,这些指令没有等效的 x86 指令,并在 v8 中使用10 条 x86 指令进行模拟

编译面向 x86 SSE* 指令集的 SIMD 代码

Emscripten 支持通过传递 -msimd128 标志以及以下其中一项来编译使用 x86 SSE 指令的现有代码库

  • SSE:传递 -msse#include <xmmintrin.h>。使用 #ifdef __SSE__ 来隔离代码。

  • SSE2:传递 -msse2#include <emmintrin.h>。使用 #ifdef __SSE2__ 来隔离代码。

  • SSE3:传递 -msse3#include <pmmintrin.h>。使用 #ifdef __SSE3__ 来隔离代码。

  • SSSE3:传递 -mssse3#include <tmmintrin.h>。使用 #ifdef __SSSE3__ 来隔离代码。

  • SSE4.1:传递 -msse4.1#include <smmintrin.h>。使用 #ifdef __SSE4_1__ 来隔离代码。

  • SSE4.2:传递 -msse4.2#include <nmmintrin.h>。使用 #ifdef __SSE4_2__ 来隔离代码。

  • AVX:传递 -mavx#include <immintrin.h>。使用 #ifdef __AVX__ 来隔离代码。

目前仅支持 SSE1、SSE2、SSE3、SSSE3、SSE4.1、SSE4.2 和 AVX 指令集。这些指令集中的每一个都建立在之前的指令集之上,因此,例如,当以 SSE3 为目标时,SSE1 和 SSE2 指令集也可用。

下表重点介绍了不同 SSE* 内在函数的可用性和预期性能。这有助于理解 Wasm SIMD 规范在 x86 硬件上运行时的性能限制。

有关每个 SSE 内在函数的详细信息,请访问优秀的英特尔关于 SSE1 的内在函数指南

以下图例用于突出显示各种指令的预期性能
  • ✅ Wasm SIMD 具有与 x86 SSE 指令匹配的原生操作码,应该产生原生性能

  • 💡 虽然 Wasm SIMD 规范没有提供适当的性能保证,但如果有足够智能的编译器和运行时 VM 路径,此内在函数应该能够生成相同的原生 SSE 指令。

  • 🟡 缺少一些信息(例如类型或对齐信息),无法保证 Wasm VM 能够重建预期的 x86 SSE 操作码。这可能会导致根据目标 CPU 硬件系列(尤其是在较旧的 CPU 世代上)而产生的性能损失。

  • ⚠️ 底层 x86 SSE 指令不可用,但它通过最多几个其他 Wasm SIMD 指令进行模拟,会导致少量性能损失。

  • ❌ Wasm SIMD 规范未公开底层 x86 SSE 指令,因此必须通过慢速路径进行模拟,例如一系列较慢的 SIMD 指令或标量实现。

  • 💣 Wasm SIMD 中没有底层 x86 SSE 操作码,并且实现必须采用如此缓慢的模拟路径,建议在更高级别上重新考虑算法的解决方法。

  • 💭 给定的 SSE 内在函数可用于编译应用程序,但不执行任何操作。

  • ⚫ 给定的 SSE 内在函数不可用。引用内在函数将导致编译器错误。

下表中的某些内在函数被标记为“虚拟”。这意味着实际上不存在用于实现它们的原生 x86 SSE 指令集操作码,但原生编译器提供该函数是为了方便。不同的编译器可能会为这些函数生成不同的指令序列。

除了查阅下表之外,还可以通过定义宏 #define WASM_SIMD_COMPAT_SLOW 来启用慢速模拟函数的诊断。如果您尝试使用任何慢速路径(对应于图例中的 ❌ 或 💣),这将打印出警告。

通过 #include <xmmintrin.h> 和 -msse 可用的 x86 SSE 内在函数

内在函数名称

WebAssembly SIMD 支持

_mm_set_ps

✅ wasm_f32x4_make

_mm_setr_ps

✅ wasm_f32x4_make

_mm_set_ss

💡 使用 wasm_f32x4_make 模拟

_mm_set_ps1 (_mm_set1_ps)

✅ wasm_f32x4_splat

_mm_setzero_ps

💡 使用 wasm_f32x4_const(0) 模拟

_mm_load_ps

🟡 wasm_v128_load。VM 必须猜测类型。
x86 CPU 上的未对齐加载。

_mm_loadl_pi

❌ 不支持 Wasm SIMD。
使用标量加载 + shuffle 模拟。

_mm_loadh_pi

❌ 不支持 Wasm SIMD。
使用标量加载 + shuffle 模拟。

_mm_loadr_ps

💡 虚拟。Simd 加载 + shuffle。

_mm_loadu_ps

🟡 wasm_v128_load。VM 必须猜测类型。

_mm_load_ps1 (_mm_load1_ps)

🟡 虚拟的。SIMD 加载 + 洗牌指令。

_mm_load_ss

❌ 使用 wasm_f32x4_make 模拟。

_mm_storel_pi

❌ 标量存储。

_mm_storeh_pi

❌ 洗牌指令 + 标量存储。

_mm_store_ps

🟡 wasm_v128_store。虚拟机必须猜测类型。
在 x86 CPU 上进行非对齐存储。

_mm_stream_ps

🟡 wasm_v128_store。虚拟机必须猜测类型。
Wasm SIMD 中没有缓存控制。

_mm_prefetch

💭 空操作。

_mm_sfence

⚠️ 在多线程构建中是一个完整的屏障。

_mm_shuffle_ps

🟡 wasm_i32x4_shuffle。虚拟机必须猜测类型。

_mm_storer_ps

💡 虚拟的。洗牌指令 + SIMD 存储。

_mm_store_ps1 (_mm_store1_ps)

🟡 虚拟的。使用洗牌指令模拟。
在 x86 CPU 上进行非对齐存储。

_mm_store_ss

💡 使用标量存储模拟。

_mm_storeu_ps

🟡 wasm_v128_store。虚拟机必须猜测类型。

_mm_storeu_si16

💡 使用标量存储模拟。

_mm_storeu_si64

💡 使用标量存储模拟。

_mm_movemask_ps

✅ wasm_i32x4_bitmask

_mm_move_ss

💡 使用洗牌指令模拟。虚拟机必须猜测类型。

_mm_add_ps

✅ wasm_f32x4_add

_mm_add_ss

⚠️ 使用洗牌指令模拟。

_mm_sub_ps

✅ wasm_f32x4_sub

_mm_sub_ss

⚠️ 使用洗牌指令模拟。

_mm_mul_ps

✅ wasm_f32x4_mul

_mm_mul_ss

⚠️ 使用洗牌指令模拟。

_mm_div_ps

✅ wasm_f32x4_div

_mm_div_ss

⚠️ 使用洗牌指令模拟。

_mm_min_ps

TODO: pmin 正常工作后实现。

_mm_min_ss

⚠️ 使用洗牌指令模拟。

_mm_max_ps

TODO: pmax 正常工作后实现。

_mm_max_ss

⚠️ 使用洗牌指令模拟。

_mm_rcp_ps

❌ Wasm SIMD 不支持。
使用全精度除法模拟。simd/#3

_mm_rcp_ss

❌ Wasm SIMD 不支持。
使用全精度除法+洗牌指令模拟。simd/#3

_mm_sqrt_ps

✅ wasm_f32x4_sqrt

_mm_sqrt_ss

⚠️ 使用洗牌指令模拟。

_mm_rsqrt_ps

❌ Wasm SIMD 不支持。
使用全精度除法+平方根模拟。simd/#3

_mm_rsqrt_ss

❌ Wasm SIMD 不支持。
使用全精度除法+平方根+洗牌指令模拟。simd/#3

_mm_unpackhi_ps

💡 使用洗牌指令模拟。

_mm_unpacklo_ps

💡 使用洗牌指令模拟。

_mm_movehl_ps

💡 使用洗牌指令模拟。

_mm_movelh_ps

💡 使用洗牌指令模拟。

_MM_TRANSPOSE4_PS

💡 使用洗牌指令模拟。

_mm_cmplt_ps

✅ wasm_f32x4_lt

_mm_cmplt_ss

⚠️ 使用洗牌指令模拟。

_mm_cmple_ps

✅ wasm_f32x4_le

_mm_cmple_ss

⚠️ 使用洗牌指令模拟。

_mm_cmpeq_ps

✅ wasm_f32x4_eq

_mm_cmpeq_ss

⚠️ 使用洗牌指令模拟。

_mm_cmpge_ps

✅ wasm_f32x4_ge

_mm_cmpge_ss

⚠️ 使用洗牌指令模拟。

_mm_cmpgt_ps

✅ wasm_f32x4_gt

_mm_cmpgt_ss

⚠️ 使用洗牌指令模拟。

_mm_cmpord_ps

❌ 使用 2xcmp+and 模拟。

_mm_cmpord_ss

❌ 使用 2xcmp+and+shuffle 模拟。

_mm_cmpunord_ps

❌ 使用 2xcmp+or 模拟。

_mm_cmpunord_ss

❌ 使用 2xcmp+or+shuffle 模拟。

_mm_and_ps

🟡 wasm_v128_and。虚拟机必须猜测类型。

_mm_andnot_ps

🟡 wasm_v128_andnot。虚拟机必须猜测类型。

_mm_or_ps

🟡 wasm_v128_or。虚拟机必须猜测类型。

_mm_xor_ps

🟡 wasm_v128_xor。虚拟机必须猜测类型。

_mm_cmpneq_ps

✅ wasm_f32x4_ne

_mm_cmpneq_ss

⚠️ 使用洗牌指令模拟。

_mm_cmpnge_ps

⚠️ 使用 not+ge 模拟。

_mm_cmpnge_ss

⚠️ 使用 not+ge+shuffle 模拟。

_mm_cmpngt_ps

⚠️ 使用 not+gt 模拟。

_mm_cmpngt_ss

⚠️ 使用 not+gt+shuffle 模拟。

_mm_cmpnle_ps

⚠️ 使用 not+le 模拟。

_mm_cmpnle_ss

⚠️ 使用 not+le+shuffle 模拟。

_mm_cmpnlt_ps

⚠️ 使用 not+lt 模拟。

_mm_cmpnlt_ss

⚠️ 使用 not+lt+shuffle 模拟。

_mm_comieq_ss

❌ 标量化。

_mm_comige_ss

❌ 标量化。

_mm_comigt_ss

❌ 标量化。

_mm_comile_ss

❌ 标量化。

_mm_comilt_ss

❌ 标量化。

_mm_comineq_ss

❌ 标量化。

_mm_ucomieq_ss

❌ 标量化。

_mm_ucomige_ss

❌ 标量化。

_mm_ucomigt_ss

❌ 标量化。

_mm_ucomile_ss

❌ 标量化。

_mm_ucomilt_ss

❌ 标量化。

_mm_ucomineq_ss

❌ 标量化。

_mm_cvtsi32_ss (_mm_cvt_si2ss)

❌ 标量化。

_mm_cvtss_si32 (_mm_cvt_ss2si)

💣 标量,具有复杂的模拟语义。

_mm_cvttss_si32 (_mm_cvtt_ss2si)

💣 标量,具有复杂的模拟语义。

_mm_cvtsi64_ss

❌ 标量化。

_mm_cvtss_si64

💣 标量,具有复杂的模拟语义。

_mm_cvttss_si64

💣 标量,具有复杂的模拟语义。

_mm_cvtss_f32

💡 标量获取。

_mm_malloc

✅ 分配具有指定对齐方式的内存。

_mm_free

✅ free() 的别名。

_MM_GET_EXCEPTION_MASK

✅ 始终返回所有被屏蔽的异常 (0x1f80)。

_MM_GET_EXCEPTION_STATE

❌ 不跟踪异常状态。始终返回 0。

_MM_GET_FLUSH_ZERO_MODE

✅ 始终返回 _MM_FLUSH_ZERO_OFF。

_MM_GET_ROUNDING_MODE

✅ 始终返回 _MM_ROUND_NEAREST。

_mm_getcsr

✅ 始终返回 _MM_FLUSH_ZERO_OFF
| _MM_ROUND_NEAREST | 所有被屏蔽的异常 (0x1f80)。

_MM_SET_EXCEPTION_MASK

⚫ 不可用。固定为所有异常都被屏蔽。

_MM_SET_EXCEPTION_STATE

⚫ 不可用。固定为零/清除状态。

_MM_SET_FLUSH_ZERO_MODE

⚫ 不可用。固定为 _MM_FLUSH_ZERO_OFF。

_MM_SET_ROUNDING_MODE

⚫ 不可用。固定为 _MM_ROUND_NEAREST。

_mm_setcsr

⚫ 不可用。

_mm_undefined_ps

✅ 虚拟的。

⚫ SSE1 指令集为 64 位宽 MMX 寄存器带来的以下扩展不可用:
  • _mm_avg_pu8, _mm_avg_pu16, _mm_cvt_pi2ps, _mm_cvt_ps2pi, _mm_cvt_pi16_ps, _mm_cvt_pi32_ps, _mm_cvt_pi32x2_ps, _mm_cvt_pi8_ps, _mm_cvt_ps_pi16, _mm_cvt_ps_pi32, _mm_cvt_ps_pi8, _mm_cvt_pu16_ps, _mm_cvt_pu8_ps, _mm_cvtt_ps2pi, _mm_cvtt_pi16_ps, _mm_cvttps_pi32, _mm_extract_pi16, _mm_insert_pi16, _mm_maskmove_si64, _m_maskmovq, _mm_max_pi16, _mm_max_pu8, _mm_min_pi16, _mm_min_pu8, _mm_movemask_pi8, _mm_mulhi_pu16, _m_pavgb, _m_pavgw, _m_pextrw, _m_pinsrw, _m_pmaxsw, _m_pmaxub, _m_pminsw, _m_pminub, _m_pmovmskb, _m_pmulhuw, _m_psadbw, _m_pshufw, _mm_sad_pu8, _mm_shuffle_pi16 和 _mm_stream_pi。

任何引用这些内在函数的代码都将无法编译。

下表重点介绍了不同 SSE2 内在函数的可用性和预期性能。请参阅英特尔关于 SSE2 的内在函数指南

可通过 #include <emmintrin.h> 和 -msse2 使用的 x86 SSE2 内在函数

内在函数名称

WebAssembly SIMD 支持

_mm_add_epi16

✅ wasm_i16x8_add

_mm_add_epi32

✅ wasm_i32x4_add

_mm_add_epi64

✅ wasm_i64x2_add

_mm_add_epi8

✅ wasm_i8x16_add

_mm_add_pd

✅ wasm_f64x2_add

_mm_add_sd

⚠️ 使用洗牌指令模拟。

_mm_adds_epi16

✅ wasm_i16x8_add_sat

_mm_adds_epi8

✅ wasm_i8x16_add_sat

_mm_adds_epu16

✅ wasm_u16x8_add_sat

_mm_adds_epu8

✅ wasm_u8x16_add_sat

_mm_and_pd

🟡 wasm_v128_and。虚拟机必须猜测类型。

_mm_and_si128

🟡 wasm_v128_and。虚拟机必须猜测类型。

_mm_andnot_pd

🟡 wasm_v128_andnot。虚拟机必须猜测类型。

_mm_andnot_si128

🟡 wasm_v128_andnot。虚拟机必须猜测类型。

_mm_avg_epu16

✅ wasm_u16x8_avgr

_mm_avg_epu8

✅ wasm_u8x16_avgr

_mm_castpd_ps

✅ 空操作。

_mm_castpd_si128

✅ 空操作。

_mm_castps_pd

✅ 空操作。

_mm_castps_si128

✅ 空操作。

_mm_castsi128_pd

✅ 空操作。

_mm_castsi128_ps

✅ 空操作。

_mm_clflush

💭 空操作。Wasm SIMD 中没有缓存提示。

_mm_cmpeq_epi16

✅ wasm_i16x8_eq

_mm_cmpeq_epi32

✅ wasm_i32x4_eq

_mm_cmpeq_epi8

✅ wasm_i8x16_eq

_mm_cmpeq_pd

✅ wasm_f64x2_eq

_mm_cmpeq_sd

⚠️ 使用洗牌指令模拟。

_mm_cmpge_pd

✅ wasm_f64x2_ge

_mm_cmpge_sd

⚠️ 使用洗牌指令模拟。

_mm_cmpgt_epi16

✅ wasm_i16x8_gt

_mm_cmpgt_epi32

✅ wasm_i32x4_gt

_mm_cmpgt_epi8

✅ wasm_i8x16_gt

_mm_cmpgt_pd

✅ wasm_f64x2_gt

_mm_cmpgt_sd

⚠️ 使用洗牌指令模拟。

_mm_cmple_pd

✅ wasm_f64x2_le

_mm_cmple_sd

⚠️ 使用洗牌指令模拟。

_mm_cmplt_epi16

✅ wasm_i16x8_lt

_mm_cmplt_epi32

✅ wasm_i32x4_lt

_mm_cmplt_epi8

✅ wasm_i8x16_lt

_mm_cmplt_pd

✅ wasm_f64x2_lt

_mm_cmplt_sd

⚠️ 使用洗牌指令模拟。

_mm_cmpneq_pd

✅ wasm_f64x2_ne

_mm_cmpneq_sd

⚠️ 使用洗牌指令模拟。

_mm_cmpnge_pd

⚠️ 使用 not+ge 模拟。

_mm_cmpnge_sd

⚠️ 使用 not+ge+shuffle 模拟。

_mm_cmpngt_pd

⚠️ 使用 not+gt 模拟。

_mm_cmpngt_sd

⚠️ 使用 not+gt+shuffle 模拟。

_mm_cmpnle_pd

⚠️ 使用 not+le 模拟。

_mm_cmpnle_sd

⚠️ 使用 not+le+shuffle 模拟。

_mm_cmpnlt_pd

⚠️ 使用 not+lt 模拟。

_mm_cmpnlt_sd

⚠️ 使用 not+lt+shuffle 模拟。

_mm_cmpord_pd

❌ 使用 2xcmp+and 模拟。

_mm_cmpord_sd

❌ 使用 2xcmp+and+shuffle 模拟。

_mm_cmpunord_pd

❌ 使用 2xcmp+or 模拟。

_mm_cmpunord_sd

❌ 使用 2xcmp+or+shuffle 模拟。

_mm_comieq_sd

❌ 标量化。

_mm_comige_sd

❌ 标量化。

_mm_comigt_sd

❌ 标量化。

_mm_comile_sd

❌ 标量化。

_mm_comilt_sd

❌ 标量化。

_mm_comineq_sd

❌ 标量化。

_mm_cvtepi32_pd

✅ wasm_f64x2_convert_low_i32x4

_mm_cvtepi32_ps

✅ wasm_f32x4_convert_i32x4

_mm_cvtpd_epi32

❌ 标量化。

_mm_cvtpd_ps

✅ wasm_f32x4_demote_f64x2_zero

_mm_cvtps_epi32

❌ 标量化。

_mm_cvtps_pd

✅ wasm_f64x2_promote_low_f32x4

_mm_cvtsd_f64

✅ wasm_f64x2_extract_lane

_mm_cvtsd_si32

❌ 标量化。

_mm_cvtsd_si64

❌ 标量化。

_mm_cvtsd_si64x

❌ 标量化。

_mm_cvtsd_ss

❌ 标量化。

_mm_cvtsi128_si32

✅ wasm_i32x4_extract_lane

_mm_cvtsi128_si64 (_mm_cvtsi128_si64x)

✅ wasm_i64x2_extract_lane

_mm_cvtsi32_sd

❌ 标量化。

_mm_cvtsi32_si128

💡 使用 wasm_i32x4_make 模拟

_mm_cvtsi64_sd (_mm_cvtsi64x_sd)

❌ 标量化。

_mm_cvtsi64_si128 (_mm_cvtsi64x_si128)

💡 使用 wasm_i64x2_make 模拟

_mm_cvtss_sd

❌ 标量化。

_mm_cvttpd_epi32

❌ 标量化。

_mm_cvttps_epi32

❌ 标量化。

_mm_cvttsd_si32

❌ 标量化。

_mm_cvttsd_si64 (_mm_cvttsd_si64x)

❌ 标量化。

_mm_div_pd

✅ wasm_f64x2_div

_mm_div_sd

⚠️ 使用洗牌指令模拟。

_mm_extract_epi16

✅ wasm_u16x8_extract_lane

_mm_insert_epi16

✅ wasm_i16x8_replace_lane

_mm_lfence

⚠️ 在多线程构建中是一个完整的屏障。

_mm_load_pd

🟡 wasm_v128_load。VM 必须猜测类型。
x86 CPU 上的未对齐加载。

_mm_load1_pd (_mm_load_pd1)

🟡 虚拟的。wasm_v64x2_load_splat,虚拟机必须猜测类型。

_mm_load_sd

❌ 使用 wasm_f64x2_make 模拟

_mm_load_si128

🟡 wasm_v128_load。VM 必须猜测类型。
x86 CPU 上的未对齐加载。

_mm_loadh_pd

❌ 不支持 Wasm SIMD。
使用标量加载 + shuffle 模拟。

_mm_loadl_epi64

❌ 不支持 Wasm SIMD。
使用标量加载 + shuffle 模拟。

_mm_loadl_pd

❌ 不支持 Wasm SIMD。
使用标量加载 + shuffle 模拟。

_mm_loadr_pd

💡 虚拟。Simd 加载 + shuffle。

_mm_loadu_pd

🟡 wasm_v128_load。VM 必须猜测类型。

_mm_loadu_si128

🟡 wasm_v128_load。VM 必须猜测类型。

_mm_loadu_si64

❌ 使用常量+标量加载+替换通道模拟

_mm_loadu_si32

❌ 使用常量+标量加载+替换通道模拟

_mm_loadu_si16

❌ 使用常量+标量加载+替换通道模拟

_mm_madd_epi16

✅ wasm_i32x4_dot_i16x8

_mm_maskmoveu_si128

❌ 标量化。

_mm_max_epi16

✅ wasm_i16x8_max

_mm_max_epu8

✅ wasm_u8x16_max

_mm_max_pd

TODO: 迁移到 wasm_f64x2_pmax

_mm_max_sd

⚠️ 使用洗牌指令模拟。

_mm_mfence

⚠️ 在多线程构建中是一个完整的屏障。

_mm_min_epi16

✅ wasm_i16x8_min

_mm_min_epu8

✅ wasm_u8x16_min

_mm_min_pd

TODO: 迁移到 wasm_f64x2_pmin

_mm_min_sd

⚠️ 使用洗牌指令模拟。

_mm_move_epi64

💡 使用洗牌指令模拟。虚拟机必须猜测类型。

_mm_move_sd

💡 使用洗牌指令模拟。虚拟机必须猜测类型。

_mm_movemask_epi8

✅ wasm_i8x16_bitmask

_mm_movemask_pd

✅ wasm_i64x2_bitmask

_mm_mul_epu32

⚠️ 使用 wasm_u64x2_extmul_low_u32x4 + 2 次 shuffle 模拟

_mm_mul_pd

✅ wasm_f64x2_mul

_mm_mul_sd

⚠️ 使用洗牌指令模拟。

_mm_mulhi_epi16

⚠️ 使用 2x SIMD extmul+通用 shuffle 模拟

_mm_mulhi_epu16

⚠️ 使用 2x SIMD extmul+通用 shuffle 模拟

_mm_mullo_epi16

✅ wasm_i16x8_mul

_mm_or_pd

🟡 wasm_v128_or。虚拟机必须猜测类型。

_mm_or_si128

🟡 wasm_v128_or。虚拟机必须猜测类型。

_mm_packs_epi16

✅ wasm_i8x16_narrow_i16x8

_mm_packs_epi32

✅ wasm_i16x8_narrow_i32x4

_mm_packus_epi16

✅ wasm_u8x16_narrow_i16x8

_mm_pause

💭 空操作。

_mm_sad_epu8

⚠️ 使用 11 条 SIMD 指令和常量模拟

_mm_set_epi16

✅ wasm_i16x8_make

_mm_set_epi32

✅ wasm_i32x4_make

_mm_set_epi64 (_mm_set_epi64x)

✅ wasm_i64x2_make

_mm_set_epi8

✅ wasm_i8x16_make

_mm_set_pd

✅ wasm_f64x2_make

_mm_set_sd

💡 使用 wasm_f64x2_make 模拟

_mm_set1_epi16

✅ wasm_i16x8_splat

_mm_set1_epi32

✅ wasm_i32x4_splat

_mm_set1_epi64 (_mm_set1_epi64x)

✅ wasm_i64x2_splat

_mm_set1_epi8

✅ wasm_i8x16_splat

_mm_set1_pd (_mm_set_pd1)

✅ wasm_f64x2_splat

_mm_setr_epi16

✅ wasm_i16x8_make

_mm_setr_epi32

✅ wasm_i32x4_make

_mm_setr_epi64

✅ wasm_i64x2_make

_mm_setr_epi8

✅ wasm_i8x16_make

_mm_setr_pd

✅ wasm_f64x2_make

_mm_setzero_pd

💡 使用 wasm_f64x2_const 模拟

_mm_setzero_si128

💡 使用 wasm_i64x2_const 模拟

_mm_shuffle_epi32

💡 使用通用 shuffle 模拟

_mm_shuffle_pd

💡 使用通用 shuffle 模拟

_mm_shufflehi_epi16

💡 使用通用 shuffle 模拟

_mm_shufflelo_epi16

💡 使用通用 shuffle 模拟

_mm_sll_epi16

❌ 标量化。

_mm_sll_epi32

❌ 标量化。

_mm_sll_epi64

❌ 标量化。

_mm_slli_epi16

💡 wasm_i16x8_shl
✅ 如果移位计数是立即常数。

_mm_slli_epi32

💡 wasm_i32x4_shl
✅ 如果移位计数是立即常数。

_mm_slli_epi64

💡 wasm_i64x2_shl
✅ 如果移位计数是立即常数。

_mm_slli_si128 (_mm_bslli_si128)

💡 使用通用 shuffle 模拟

_mm_sqrt_pd

✅ wasm_f64x2_sqrt

_mm_sqrt_sd

⚠️ 使用洗牌指令模拟。

_mm_sra_epi16

❌ 标量化。

_mm_sra_epi32

❌ 标量化。

_mm_srai_epi16

💡 wasm_i16x8_shr
✅ 如果移位计数是立即常数。

_mm_srai_epi32

💡 wasm_i32x4_shr
✅ 如果移位计数是立即常数。

_mm_srl_epi16

❌ 标量化。

_mm_srl_epi32

❌ 标量化。

_mm_srl_epi64

❌ 标量化。

_mm_srli_epi16

💡 wasm_u16x8_shr
✅ 如果移位计数是立即常数。

_mm_srli_epi32

💡 wasm_u32x4_shr
✅ 如果移位计数是立即常数。

_mm_srli_epi64

💡 wasm_u64x2_shr
✅ 如果移位计数是立即常数。

_mm_srli_si128 (_mm_bsrli_si128)

💡 使用通用 shuffle 模拟

_mm_store_pd

🟡 wasm_v128_store。虚拟机必须猜测类型。
在 x86 CPU 上进行非对齐存储。

_mm_store_sd

💡 使用标量存储模拟。

_mm_store_si128

🟡 wasm_v128_store。虚拟机必须猜测类型。
在 x86 CPU 上进行非对齐存储。

_mm_store1_pd (_mm_store_pd1)

🟡 虚拟的。使用洗牌指令模拟。
在 x86 CPU 上进行非对齐存储。

_mm_storeh_pd

❌ 洗牌指令 + 标量存储。

_mm_storel_epi64

❌ 标量存储

_mm_storel_pd

❌ 标量存储

_mm_storer_pd

❌ 洗牌指令 + 标量存储。

_mm_storeu_pd

🟡 wasm_v128_store。虚拟机必须猜测类型。

_mm_storeu_si128

🟡 wasm_v128_store。虚拟机必须猜测类型。

_mm_storeu_si64

💡 使用提取通道+标量存储模拟

_mm_storeu_si32

💡 使用提取通道+标量存储模拟

_mm_storeu_si16

💡 使用提取通道+标量存储模拟

_mm_stream_pd

🟡 wasm_v128_store。虚拟机必须猜测类型。
Wasm SIMD 中没有缓存控制。

_mm_stream_si128

🟡 wasm_v128_store。虚拟机必须猜测类型。
Wasm SIMD 中没有缓存控制。

_mm_stream_si32

🟡 wasm_v128_store。虚拟机必须猜测类型。
Wasm SIMD 中没有缓存控制。

_mm_stream_si64

🟡 wasm_v128_store。虚拟机必须猜测类型。
Wasm SIMD 中没有缓存控制。

_mm_sub_epi16

✅ wasm_i16x8_sub

_mm_sub_epi32

✅ wasm_i32x4_sub

_mm_sub_epi64

✅ wasm_i64x2_sub

_mm_sub_epi8

✅ wasm_i8x16_sub

_mm_sub_pd

✅ wasm_f64x2_sub

_mm_sub_sd

⚠️ 使用洗牌指令模拟。

_mm_subs_epi16

✅ wasm_i16x8_sub_sat

_mm_subs_epi8

✅ wasm_i8x16_sub_sat

_mm_subs_epu16

✅ wasm_u16x8_sub_sat

_mm_subs_epu8

✅ wasm_u8x16_sub_sat

_mm_ucomieq_sd

❌ 标量化。

_mm_ucomige_sd

❌ 标量化。

_mm_ucomigt_sd

❌ 标量化。

_mm_ucomile_sd

❌ 标量化。

_mm_ucomilt_sd

❌ 标量化。

_mm_ucomineq_sd

❌ 标量化。

_mm_undefined_pd

✅ 虚拟的。

_mm_undefined_si128

✅ 虚拟的。

_mm_unpackhi_epi16

💡 使用洗牌指令模拟。

_mm_unpackhi_epi32

💡 使用洗牌指令模拟。

_mm_unpackhi_epi64

💡 使用洗牌指令模拟。

_mm_unpackhi_epi8

💡 使用洗牌指令模拟。

_mm_unpachi_pd

💡 使用洗牌指令模拟。

_mm_unpacklo_epi16

💡 使用洗牌指令模拟。

_mm_unpacklo_epi32

💡 使用洗牌指令模拟。

_mm_unpacklo_epi64

💡 使用洗牌指令模拟。

_mm_unpacklo_epi8

💡 使用洗牌指令模拟。

_mm_unpacklo_pd

💡 使用洗牌指令模拟。

_mm_xor_pd

🟡 wasm_v128_xor。虚拟机必须猜测类型。

_mm_xor_si128

🟡 wasm_v128_xor。虚拟机必须猜测类型。

⚫ SSE2 指令集带到 64 位宽 MMX 寄存器的以下扩展不可用
  • _mm_add_si64, _mm_movepi64_pi64, _mm_movpi64_epi64, _mm_mul_su32, _mm_sub_si64, _mm_cvtpd_pi32, _mm_cvtpi32_pd, _mm_cvttpd_pi32

任何引用这些内在函数的代码都将无法编译。

下表重点介绍了不同 SSE3 内部函数的可用性和预期性能。请参阅英特尔关于 SSE3 的内部函数指南

通过 #include <pmmintrin.h> 和 -msse3 可用的 x86 SSE3 内部函数

内在函数名称

WebAssembly SIMD 支持

_mm_lddqu_si128

✅ wasm_v128_load。

_mm_addsub_ps

⚠️ 使用 SIMD add+mul+const 模拟

_mm_hadd_ps

⚠️ 使用 SIMD add+两次 shuffle 模拟

_mm_hsub_ps

⚠️ 使用 SIMD sub+两次 shuffle 模拟

_mm_movehdup_ps

💡 使用通用 shuffle 模拟

_mm_moveldup_ps

💡 使用通用 shuffle 模拟

_mm_addsub_pd

⚠️ 使用 SIMD add+mul+const 模拟

_mm_hadd_pd

⚠️ 使用 SIMD add+两次 shuffle 模拟

_mm_hsub_pd

⚠️ 使用 SIMD add+两次 shuffle 模拟

_mm_loaddup_pd

🟡 虚拟的。wasm_v64x2_load_splat,虚拟机必须猜测类型。

_mm_movedup_pd

💡 使用通用 shuffle 模拟

_MM_GET_DENORMALS_ZERO_MODE

✅ 始终返回 _MM_DENORMALS_ZERO_ON。即可以使用非规格化数。

_MM_SET_DENORMALS_ZERO_MODE

⚫ 不可用。固定为 _MM_DENORMALS_ZERO_ON。

_mm_monitor

⚫ 不可用。

_mm_mwait

⚫ 不可用。

下表重点介绍了不同 SSSE3 内部函数的可用性和预期性能。请参阅英特尔关于 SSSE3 的内部函数指南

通过 #include <tmmintrin.h> 和 -mssse3 可用的 x86 SSSE3 内部函数

内在函数名称

WebAssembly SIMD 支持

_mm_abs_epi8

✅ wasm_i8x16_abs

_mm_abs_epi16

✅ wasm_i16x8_abs

_mm_abs_epi32

✅ wasm_i32x4_abs

_mm_alignr_epi8

⚠️ 使用 SIMD or+两次移位模拟

_mm_hadd_epi16

⚠️ 使用 SIMD add+两次 shuffle 模拟

_mm_hadd_epi32

⚠️ 使用 SIMD add+两次 shuffle 模拟

_mm_hadds_epi16

⚠️ 使用 SIMD adds+两次 shuffle 模拟

_mm_hsub_epi16

⚠️ 使用 SIMD sub+两次 shuffle 模拟

_mm_hsub_epi32

⚠️ 使用 SIMD sub+两次 shuffle 模拟

_mm_hsubs_epi16

⚠️ 使用 SIMD subs+两次 shuffle 模拟

_mm_maddubs_epi16

⚠️ 使用 SIMD 饱和加法+四次移位+两次乘法+与+常量模拟

_mm_mulhrs_epi16

⚠️ 使用 SIMD 四次 widen+两次乘法+四次加法+复杂 shuffle+常量模拟

_mm_shuffle_epi8

⚠️ 使用 SIMD swizzle+与+常量模拟

_mm_sign_epi8

⚠️ 使用 SIMD 两次 cmp+两次逻辑+加法模拟

_mm_sign_epi16

⚠️ 使用 SIMD 两次 cmp+两次逻辑+加法模拟

_mm_sign_epi32

⚠️ 使用 SIMD 两次 cmp+两次逻辑+加法模拟

⚫ 处理 64 位宽 MMX 寄存器的 SSSE3 函数不可用
  • _mm_abs_pi8, _mm_abs_pi16, _mm_abs_pi32, _mm_alignr_pi8, _mm_hadd_pi16, _mm_hadd_pi32, _mm_hadds_pi16, _mm_hsub_pi16, _mm_hsub_pi32, _mm_hsubs_pi16, _mm_maddubs_pi16, _mm_mulhrs_pi16, _mm_shuffle_pi8, _mm_sign_pi8, _mm_sign_pi16 and _mm_sign_pi32

任何引用这些内在函数的代码都将无法编译。

下表重点介绍了不同 SSE4.1 内部函数的可用性和预期性能。请参阅英特尔关于 SSE4.1 的内部函数指南

x86 SSE4.1 内置函数,可通过 #include <smmintrin.h> 和 -msse4.1 使用

内在函数名称

WebAssembly SIMD 支持

_mm_blend_epi16

💡 使用通用 shuffle 模拟

_mm_blend_pd

💡 使用通用 shuffle 模拟

_mm_blend_ps

💡 使用通用 shuffle 模拟

_mm_blendv_epi8

⚠️ 使用 SIMD shr+and+andnot+or 模拟

_mm_blendv_pd

⚠️ 使用 SIMD shr+and+andnot+or 模拟

_mm_blendv_ps

⚠️ 使用 SIMD shr+and+andnot+or 模拟

_mm_ceil_pd

✅ wasm_f64x2_ceil

_mm_ceil_ps

✅ wasm_f32x4_ceil

_mm_ceil_sd

⚠️ 使用洗牌指令模拟。

_mm_ceil_ss

⚠️ 使用洗牌指令模拟。

_mm_cmpeq_epi64

⚠️ 使用 SIMD cmp+and+shuffle 模拟

_mm_cvtepi16_epi32

✅ wasm_i32x4_widen_low_i16x8

_mm_cvtepi16_epi64

⚠️ 使用 SIMD widen+const+cmp+shuffle 模拟

_mm_cvtepi32_epi64

⚠️ 使用 SIMD const+cmp+shuffle 模拟

_mm_cvtepi8_epi16

✅ wasm_i16x8_widen_low_i8x16

_mm_cvtepi8_epi32

⚠️ 使用两次 SIMD widen 模拟

_mm_cvtepi8_epi64

⚠️ 使用两次 SIMD widen+const+cmp+shuffle 模拟

_mm_cvtepu16_epi32

✅ wasm_u32x4_extend_low_u16x8

_mm_cvtepu16_epi64

⚠️ 使用 SIMD const+两次 shuffle 模拟

_mm_cvtepu32_epi64

⚠️ 使用 SIMD const+shuffle 模拟

_mm_cvtepu8_epi16

✅ wasm_u16x8_extend_low_u8x16

_mm_cvtepu8_epi32

⚠️ 使用两次 SIMD widen 模拟

_mm_cvtepu8_epi64

⚠️ 使用 SIMD const+三次 shuffle 模拟

_mm_dp_pd

⚠️ 使用 SIMD mul+add+setzero+2xblend 模拟

_mm_dp_ps

⚠️ 使用 SIMD mul+add+setzero+2xblend 模拟

_mm_extract_epi32

✅ wasm_i32x4_extract_lane

_mm_extract_epi64

✅ wasm_i64x2_extract_lane

_mm_extract_epi8

✅ wasm_u8x16_extract_lane

_mm_extract_ps

✅ wasm_i32x4_extract_lane

_mm_floor_pd

✅ wasm_f64x2_floor

_mm_floor_ps

✅ wasm_f32x4_floor

_mm_floor_sd

⚠️ 使用洗牌指令模拟。

_mm_floor_ss

⚠️ 使用洗牌指令模拟。

_mm_insert_epi32

✅ wasm_i32x4_replace_lane

_mm_insert_epi64

✅ wasm_i64x2_replace_lane

_mm_insert_epi8

✅ wasm_i8x16_replace_lane

_mm_insert_ps

⚠️ 使用通用的非 SIMD 映射 shuffle 模拟

_mm_max_epi32

✅ wasm_i32x4_max

_mm_max_epi8

✅ wasm_i8x16_max

_mm_max_epu16

✅ wasm_u16x8_max

_mm_max_epu32

✅ wasm_u32x4_max

_mm_min_epi32

✅ wasm_i32x4_min

_mm_min_epi8

✅ wasm_i8x16_min

_mm_min_epu16

✅ wasm_u16x8_min

_mm_min_epu32

✅ wasm_u32x4_min

_mm_minpos_epu16

💣 标量化

_mm_mpsadbw_epu8

💣 标量化

_mm_mul_epi32

⚠️ 使用 wasm_i64x2_extmul_low_i32x4 + 2 次 shuffle 模拟

_mm_mullo_epi32

✅ wasm_i32x4_mul

_mm_packus_epi32

✅ wasm_u16x8_narrow_i32x4

_mm_round_pd

✅ wasm_f64x2_ceil/wasm_f64x2_floor/wasm_f64x2_nearest/wasm_f64x2_trunc

_mm_round_ps

✅ wasm_f32x4_ceil/wasm_f32x4_floor/wasm_f32x4_nearest/wasm_f32x4_trunc

_mm_round_sd

⚠️ 使用洗牌指令模拟。

_mm_round_ss

⚠️ 使用洗牌指令模拟。

_mm_stream_load_si128

🟡 wasm_v128_load。VM 必须猜测类型。
x86 CPU 上的未对齐加载。

_mm_test_all_ones

❌ 标量化。

_mm_test_all_zeros

❌ 标量化。

_mm_test_mix_ones_zeros

❌ 标量化。

_mm_testc_si128

❌ 标量化。

_mm_testnzc_si128

❌ 标量化。

_mm_testz_si128

❌ 标量化。

下表重点介绍了不同 SSE4.2 内置函数的可用性和预期性能。请参阅英特尔关于 SSE4.2 的内置函数指南

x86 SSE4.2 内置函数,可通过 #include <nmmintrin.h> 和 -msse4.2 使用

内在函数名称

WebAssembly SIMD 支持

_mm_cmpgt_epi64

✅ wasm_i64x2_gt

⚫ 处理字符串比较和 CRC 计算的 SSE4.2 函数不可用
  • _mm_cmpestra, _mm_cmpestrc, _mm_cmpestri, _mm_cmpestrm, _mm_cmpestro, _mm_cmpestrs, _mm_cmpestrz, _mm_cmpistra, _mm_cmpistrc, _mm_cmpistri, _mm_cmpistrm, _mm_cmpistro, _mm_cmpistrs, _mm_cmpistrz, _mm_crc32_u16, _mm_crc32_u32, _mm_crc32_u64, _mm_crc32_u8

任何引用这些内在函数的代码都将无法编译。

下表重点介绍了不同 AVX 内置函数的可用性和预期性能。请参阅英特尔关于 AVX 的内置函数指南

x86 AVX 内置函数,可通过 #include <immintrin.h> 和 -mavx 使用

内在函数名称

WebAssembly SIMD 支持

_mm_broadcast_ss

✅ wasm_v32x4_load_splat

_mm_cmp_pd

⚠️ 使用 1-2 次 SIMD cmp+and/or 模拟

_mm_cmp_ps

⚠️ 使用 1-2 次 SIMD cmp+and/or 模拟

_mm_cmp_sd

⚠️ 使用 1-2 次 SIMD cmp+and/or+move 模拟

_mm_cmp_ss

⚠️ 使用 1-2 次 SIMD cmp+and/or+move 模拟

_mm_maskload_pd

⚠️ 使用 SIMD load+shift+and 模拟

_mm_maskload_ps

⚠️ 使用 SIMD load+shift+and 模拟

_mm_maskstore_pd

❌ 标量化。

_mm_maskstore_ps

❌ 标量化。

_mm_permute_pd

💡 使用通用 shuffle 模拟

_mm_permute_ps

💡 使用通用 shuffle 模拟

_mm_permutevar_pd

💣 标量化

_mm_permutevar_ps

💣 标量化

_mm_testc_pd

💣 使用复杂的 SIMD+标量序列模拟

_mm_testc_ps

💣 使用复杂的 SIMD+标量序列模拟

_mm_testnzc_pd

💣 使用复杂的 SIMD+标量序列模拟

_mm_testnzc_ps

💣 使用复杂的 SIMD+标量序列模拟

_mm_testz_pd

💣 使用复杂的 SIMD+标量序列模拟

_mm_testz_ps

💣 使用复杂的 SIMD+标量序列模拟

仅列出了 AVX 指令集中的 128 位指令。256 位 AVX 指令由两条 128 位指令模拟。

编译面向 ARM NEON 指令集的 SIMD 代码

Emscripten 支持通过将 -mfpu=neon 指令传递给编译器并包含头文件 <arm_neon.h> 来编译使用 ARM NEON 的现有代码库。

在性能方面,需要注意的是,只有对 128 位宽向量进行操作的指令才能得到完全支持。这意味着几乎所有不是“q”变体(即“vaddq”而不是“vadd”)的指令都将被标量化。

这些内容来自GitHub 上的 SIMDe 存储库。要使用最新的 SIMDe 版本更新 emscripten,请运行 tools/simde_update.py

下表重点介绍了各种 128 位宽内置函数的可用性。

与上述类似,使用以下图例
  • ✅ Wasm SIMD 具有与 NEON 指令匹配的原生操作码,应该产生原生性能

  • 💡 虽然 Wasm SIMD 规范没有提供适当的性能保证,但如果有足够智能的编译器和运行时 VM 路径,此内置函数应该能够生成相同的原生 NEON 指令。

  • ⚠️ 底层 NEON 指令不可用,但它通过最多几个其他 Wasm SIMD 指令模拟,导致轻微的性能损失。

  • ❌ Wasm SIMD 规范未公开底层 NEON 指令,因此必须通过慢速路径进行模拟,例如一系列较慢的 SIMD 指令或标量实现。

  • ⚫ 给定的 NEON 内置函数不可用。引用该内置函数将导致编译器错误。

有关每个内置函数的详细信息,请参阅NEON 内置函数参考

有关最新的 NEON 内置函数实现状态,请参阅SIMDe 实现状态

NEON 内置函数

内在函数名称

Wasm SIMD 支持

vaba

❌ 将使用慢速指令模拟或标量化

vabaq

⚠️ 没有直接实现,但使用快速 NEON 指令模拟

vabal

⚫ 未实现,将触发编译器错误

vabd

⚠️ 没有直接实现,但使用快速 NEON 指令模拟

vabdq

✅ 原生

vabdl

❌ 将使用慢速指令模拟或标量化

vabs

❌ 将使用慢速指令模拟或标量化

vabq

✅ 原生

vadd

❌ 将使用慢速指令模拟或标量化

vaddq_s & vaddq_f

✅ 原生

vaddhn

💡 取决于足够智能的编译器,但应该接近原生

vaddl

❌ 将使用慢速指令模拟或标量化

vaddlv

❌ 将使用慢速指令模拟或标量化

vaddv

❌ 将使用慢速指令模拟或标量化

vaddw

❌ 将使用慢速指令模拟或标量化

vand

✅ 原生

vbcaxq

⚠️ 没有直接实现,但使用快速 NEON 指令模拟

vbic

❌ 将使用慢速指令模拟或标量化

vbiq

✅ 原生

vbsl

✅ 原生

vcage

❌ 将使用慢速指令模拟或标量化

vcagt

⚠️ 没有直接实现,但使用快速 NEON 指令模拟

vceq

💡 取决于足够智能的编译器,但应该接近原生

vceqz

⚠️ 没有直接实现,但使用快速 NEON 指令模拟

vcge

✅ 原生

vcgez

⚠️ 没有直接实现,但使用快速 NEON 指令模拟

vcgt

✅ 原生

vcgtz

⚠️ 没有直接实现,但使用快速 NEON 指令模拟

vcle

✅ 原生

vclez

⚠️ 没有直接实现,但使用快速 NEON 指令模拟

vcls

❌ 将使用慢速指令模拟或标量化

vclt

✅ 原生

vcltz

⚠️ 没有直接实现,但使用快速 NEON 指令模拟

vcmla, vcmla_rot90, cmla_rot180, cmla_rot270

❌ 将使用慢速指令模拟或标量化

vcmlq

✅ 原生

vcnt

❌ 将使用慢速指令模拟或标量化

vclz

❌ 将使用慢速指令模拟或标量化

vcombine

❌ 将使用慢速指令模拟或标量化

vcreate

❌ 将使用慢速指令模拟或标量化

vdot

❌ 将使用慢速指令模拟或标量化

vdot_lane

❌ 将使用慢速指令模拟或标量化

vdup

❌ 将使用慢速指令模拟或标量化

vdup_n

✅ 原生

veor

✅ 原生

vext

❌ 将使用慢速指令模拟或标量化

vfma, vfma_lane, vfma_n

❌ 将使用慢速指令模拟或标量化

vget_lane

✅ 原生

vhadd

❌ 将使用慢速指令模拟或标量化

vhsub

❌ 将使用慢速指令模拟或标量化

vld1

✅ 原生

vld2

❌ 将使用慢速指令模拟或标量化

vld3

💡 取决于足够智能的编译器,但应该接近原生

vld4

💡 取决于足够智能的编译器,但应该接近原生

vld4_lane

❌ 将使用慢速指令模拟或标量化

vmax

✅ 原生

vmaxv

❌ 将使用慢速指令模拟或标量化

vmin

✅ 原生

vminv

❌ 将使用慢速指令模拟或标量化

vmla

⚠️ 没有直接实现,但使用快速 NEON 指令模拟

vmlal

❌ 将使用慢速指令模拟或标量化

vmlal_high_n

❌ 将使用慢速指令模拟或标量化

vmlal_lane

❌ 将使用慢速指令模拟或标量化

vmls

❌ 将使用慢速指令模拟或标量化

vmls_n

❌ 将使用慢速指令模拟或标量化

vmlsl

❌ 将使用慢速指令模拟或标量化

vmlsl_high

❌ 将使用慢速指令模拟或标量化

vmlsl_high_n

❌ 将使用慢速指令模拟或标量化

vmlsl_lane(向量乘法长整数,按通道)

❌ 将使用慢速指令模拟或标量化

vmovl(向量移动长整数)

✅ 原生

vmul(向量乘法)

✅ 原生

vmul_n(向量乘法,标量)

⚠️ 没有直接实现,但使用快速 NEON 指令模拟

vmull(向量乘法长整数)

⚠️ 没有直接实现,但使用快速 NEON 指令模拟

vmull_n(向量乘法长整数,标量)

⚠️ 没有直接实现,但使用快速 NEON 指令模拟

vmull_lane(向量乘法长整数,按通道)

❌ 将使用慢速指令模拟或标量化

vmull_high(向量乘法长整数,高半部分)

❌ 将使用慢速指令模拟或标量化

vmvn(向量取反)

✅ 原生

vneg(向量取负)

✅ 原生

vorn(向量或非)

❌ 将使用慢速指令模拟或标量化

vorr(向量或)

✅ 原生

vpadal(向量加法长整数,累加)

❌ 将使用慢速指令模拟或标量化

vpadd(向量加法)

❌ 将使用慢速指令模拟或标量化

vpaddl(向量加法长整数)

❌ 将使用慢速指令模拟或标量化

vpmax(向量最大值)

❌ 将使用慢速指令模拟或标量化

vpmin(向量最小值)

❌ 将使用慢速指令模拟或标量化

vpminnm(向量最小值,非 NaN)

⚫ 未实现,将触发编译器错误

vqabs(向量饱和绝对值)

❌ 将使用慢速指令模拟或标量化

vqabsb(向量饱和绝对值,字节)

❌ 将使用慢速指令模拟或标量化

vqadd(向量饱和加法)

💡 取决于足够智能的编译器,但应该接近原生

vqaddb(向量饱和加法,字节)

❌ 将使用慢速指令模拟或标量化

vqdmulh(向量饱和乘法高半部分,双倍)

❌ 将使用慢速指令模拟或标量化

vqdmulh_lane(向量饱和乘法高半部分,双倍,按通道)

❌ 将使用慢速指令模拟或标量化

vqneg(向量饱和取负)

❌ 将使用慢速指令模拟或标量化

vqnegb(向量饱和取负,字节)

❌ 将使用慢速指令模拟或标量化

vqrdmulh(向量饱和舍入乘法高半部分,双倍)

❌ 将使用慢速指令模拟或标量化

vqrdmulh_lane(向量饱和舍入乘法高半部分,双倍,按通道)

❌ 将使用慢速指令模拟或标量化

vqshl(向量饱和左移)

❌ 将使用慢速指令模拟或标量化

vqshlb(向量饱和左移,字节)

❌ 将使用慢速指令模拟或标量化

vqshrn_n(向量饱和右移,窄化)

❌ 将使用慢速指令模拟或标量化

vqshrun_n(向量饱和右移,窄化,无符号)

❌ 将使用慢速指令模拟或标量化

vqsub(向量饱和减法)

❌ 将使用慢速指令模拟或标量化

vqsubb(向量饱和减法,字节)

❌ 将使用慢速指令模拟或标量化

vqtbl1(向量查表,单表)

⚠️ 没有直接实现,但使用快速 NEON 指令模拟

vqtbl2(向量查表,双表)

⚠️ 没有直接实现,但使用快速 NEON 指令模拟

vqtbl3(向量查表,三表)

⚠️ 没有直接实现,但使用快速 NEON 指令模拟

vqtbl4(向量查表,四表)

⚠️ 没有直接实现,但使用快速 NEON 指令模拟

vqtbx1(向量查表,单表,带索引)

❌ 将使用慢速指令模拟或标量化

vqtbx2(向量查表,双表,带索引)

❌ 将使用慢速指令模拟或标量化

vqtbx3(向量查表,三表,带索引)

❌ 将使用慢速指令模拟或标量化

vqtbx4(向量查表,四表,带索引)

❌ 将使用慢速指令模拟或标量化

vrbit(向量反转位)

⚠️ 没有直接实现,但使用快速 NEON 指令模拟

vrecpe(向量倒数估计)

❌ 将使用慢速指令模拟或标量化

vrecps(向量倒数步进)

❌ 将使用慢速指令模拟或标量化

vreinterpret(向量重新解释)

💡 取决于足够智能的编译器,但应该接近原生

vrev16(向量反转16位)

✅ 原生

vrev32(向量反转32位)

✅ 原生

vrev64(向量反转64位)

✅ 原生

vrhadd(向量舍入半加)

⚠️ 没有直接实现,但使用快速 NEON 指令模拟

vrsh_n(向量舍入右移)

❌ 将使用慢速指令模拟或标量化

vrshn_n(向量舍入右移,窄化)

❌ 将使用慢速指令模拟或标量化

vrsqrte(向量平方根倒数估计)

❌ 将使用慢速指令模拟或标量化

vrsqrts(向量平方根倒数步进)

❌ 将使用慢速指令模拟或标量化

vrshl(向量舍入左移)

❌ 将使用慢速指令模拟或标量化

vrshr_n(向量舍入右移)

❌ 将使用慢速指令模拟或标量化

vrsra_n(向量舍入右移,累加)

❌ 将使用慢速指令模拟或标量化

vset_lane(向量设置通道)

✅ 原生

vshl(向量左移)

scalaried(标量化)

vshl_n(向量左移,标量)

❌ 将使用慢速指令模拟或标量化

vshll_n(向量左移长整数,标量)

❌ 将使用慢速指令模拟或标量化

vshr_n(向量右移,标量)

⚠️ 没有直接实现,但使用快速 neon 指令进行模拟

vshrn_n(向量右移,窄化,标量)

⚠️ 没有直接实现,但使用快速 neon 指令进行模拟

vsqadd(向量饱和加法,累加)

❌ 将使用慢速指令模拟或标量化

vsra_n(向量右移,累加,标量)

❌ 将使用慢速指令模拟或标量化

vsri_n(向量右移,插入,标量)

❌ 将使用慢速指令模拟或标量化

vst1(向量存储,单元素)

✅ 原生

vst1_lane(向量存储,单元素,按通道)

💡 取决于足够智能的编译器,但应该接近原生

vst2(向量存储,双元素)

❌ 将使用慢速指令模拟或标量化

vst2_lane(向量存储,双元素,按通道)

❌ 将使用慢速指令模拟或标量化

vst3(向量存储,三元素)

💡 取决于足够智能的编译器,但应该接近原生

vst3_lane(向量存储,三元素,按通道)

❌ 将使用慢速指令模拟或标量化

vst4(向量存储,四元素)

💡 取决于足够智能的编译器,但应该接近原生

vst4_lane(向量存储,四元素,按通道)

❌ 将使用慢速指令模拟或标量化

vsub(向量减法)

✅ 原生

vsubl(向量减法长整数)

⚠️ 没有直接实现,但使用快速 NEON 指令模拟

vsubl_high(向量减法长整数,高半部分)

⚠️ 没有直接实现,但使用快速 NEON 指令模拟

vsubn(向量减法,窄化)

❌ 将使用慢速指令模拟或标量化

vsubw(向量减法宽)

❌ 将使用慢速指令模拟或标量化

vtbl1(向量查表,单表)

❌ 将使用慢速指令模拟或标量化

vtbl2(向量查表,双表)

❌ 将使用慢速指令模拟或标量化

vtbl3(向量查表,三表)

❌ 将使用慢速指令模拟或标量化

vtbl4(向量查表,四表)

❌ 将使用慢速指令模拟或标量化

vtbx1(向量查表,单表,带索引)

❌ 将使用慢速指令模拟或标量化

vtbx2(向量查表,双表,带索引)

❌ 将使用慢速指令模拟或标量化

vtbx3(向量查表,三表,带索引)

❌ 将使用慢速指令模拟或标量化

vtbx4(向量查表,四表,带索引)

❌ 将使用慢速指令模拟或标量化

vtrn(向量转置)

❌ 将使用慢速指令模拟或标量化

vtrn1(向量转置1)

❌ 将使用慢速指令模拟或标量化

vtrn2(向量转置2)

❌ 将使用慢速指令模拟或标量化

vtst(向量测试)

❌ 将使用慢速指令模拟或标量化

vuqadd(向量无符号饱和加法)

❌ 将使用慢速指令模拟或标量化

vuqaddb(向量无符号饱和加法,字节)

❌ 将使用慢速指令模拟或标量化

vuzp(向量解压缩)

❌ 将使用慢速指令模拟或标量化

vuzp1(向量解压缩1)

❌ 将使用慢速指令模拟或标量化

vuzp2(向量解压缩2)

❌ 将使用慢速指令模拟或标量化

vxar(向量异或)

❌ 将使用慢速指令模拟或标量化

vzip(向量压缩)

❌ 将使用慢速指令模拟或标量化

vzip1(向量压缩1)

❌ 将使用慢速指令模拟或标量化

vzip2(向量压缩2)

❌ 将使用慢速指令模拟或标量化