Emscripten 支持 WebAssembly SIMD 功能。在 C/C++ 程序中利用 WebAssembly SIMD 有五种不同的方法
启用 LLVM/Clang SIMD 自动向量化器,以自动定位 WebAssembly SIMD,无需更改 C/C++ 源代码。
使用 GCC/Clang SIMD 向量扩展(__attribute__((vector_size(16)))
)编写 SIMD 代码
使用 WebAssembly SIMD 内部函数(#include <wasm_simd128.h>
)编写 SIMD 代码
编译使用 x86 SSE、SSE2、SSE3、SSSE3、SSE4.1、SSE4.2 或 AVX 内部函数(#include <*mmintrin.h>
)的现有 SIMD 代码
编译使用 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 向量扩展,并在可能的情况下将其降低为 WebAssembly SIMD 指令。
这使开发人员能够通过 typedef 创建自定义宽向量类型,并在向量化类型上使用算术运算符(+、-、*、/),并允许通过 vector[i] 表示法访问单个通道。但是,GCC 向量内置函数 不可用。请改用下面的 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 指令 |
架构 |
注意事项 |
---|---|---|
[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 指令进行模拟。 |
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
来启用慢速模拟函数的诊断。如果您尝试使用任何慢速路径(对应于图例中的 ❌ 或 💣),这将打印出警告。
内在函数名称 |
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 必须猜测类型。 |
_mm_loadl_pi |
❌ 不支持 Wasm SIMD。 |
_mm_loadh_pi |
❌ 不支持 Wasm SIMD。 |
_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。虚拟机必须猜测类型。 |
_mm_stream_ps |
🟡 wasm_v128_store。虚拟机必须猜测类型。 |
_mm_prefetch |
💭 空操作。 |
_mm_sfence |
⚠️ 在多线程构建中是一个完整的屏障。 |
_mm_shuffle_ps |
🟡 wasm_i32x4_shuffle。虚拟机必须猜测类型。 |
_mm_storer_ps |
💡 虚拟的。洗牌指令 + SIMD 存储。 |
_mm_store_ps1 (_mm_store1_ps) |
🟡 虚拟的。使用洗牌指令模拟。 |
_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 不支持。 |
_mm_rcp_ss |
❌ Wasm SIMD 不支持。 |
_mm_sqrt_ps |
✅ wasm_f32x4_sqrt |
_mm_sqrt_ss |
⚠️ 使用洗牌指令模拟。 |
_mm_rsqrt_ps |
❌ Wasm SIMD 不支持。 |
_mm_rsqrt_ss |
❌ Wasm SIMD 不支持。 |
_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_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 |
✅ 虚拟的。 |
_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 的内在函数指南。
内在函数名称 |
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 必须猜测类型。 |
_mm_load1_pd (_mm_load_pd1) |
🟡 虚拟的。wasm_v64x2_load_splat,虚拟机必须猜测类型。 |
_mm_load_sd |
❌ 使用 wasm_f64x2_make 模拟 |
_mm_load_si128 |
🟡 wasm_v128_load。VM 必须猜测类型。 |
_mm_loadh_pd |
❌ 不支持 Wasm SIMD。 |
_mm_loadl_epi64 |
❌ 不支持 Wasm SIMD。 |
_mm_loadl_pd |
❌ 不支持 Wasm SIMD。 |
_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。虚拟机必须猜测类型。 |
_mm_store_sd |
💡 使用标量存储模拟。 |
_mm_store_si128 |
🟡 wasm_v128_store。虚拟机必须猜测类型。 |
_mm_store1_pd (_mm_store_pd1) |
🟡 虚拟的。使用洗牌指令模拟。 |
_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。虚拟机必须猜测类型。 |
_mm_stream_si128 |
🟡 wasm_v128_store。虚拟机必须猜测类型。 |
_mm_stream_si32 |
🟡 wasm_v128_store。虚拟机必须猜测类型。 |
_mm_stream_si64 |
🟡 wasm_v128_store。虚拟机必须猜测类型。 |
_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。虚拟机必须猜测类型。 |
_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 的内部函数指南。
内在函数名称 |
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 的内部函数指南。
内在函数名称 |
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+两次逻辑+加法模拟 |
_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 的内部函数指南。
内在函数名称 |
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 必须猜测类型。 |
_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 的内置函数指南。
内在函数名称 |
WebAssembly SIMD 支持 |
---|---|
_mm_cmpgt_epi64 |
✅ wasm_i64x2_gt |
_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 的内置函数指南。
内在函数名称 |
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 位指令模拟。
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 实现状态。
内在函数名称 |
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) |
❌ 将使用慢速指令模拟或标量化 |