搭配 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 月)。

如需其他 VM 的詳細資訊,請參閱 WebAssembly 藍圖

即將推出的 寬鬆 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 寬鬆 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 錯誤追蹤器中追蹤。

最佳化考量

當開發 SIMD 程式碼以使用 WebAssembly 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 時);使用常數調整量 (或 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 內建函數的詳細資訊,請造訪出色的Intel Intrinsics Guide on 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 支援。
使用純量載入 + 混洗模擬。

_mm_loadh_pi

❌ 沒有 Wasm SIMD 支援。
使用純量載入 + 混洗模擬。

_mm_loadr_ps

💡 虛擬。Simd 載入 + 混洗。

_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。VM 必須猜測類型。
x86 CPU 上的未對齊儲存。

_mm_stream_ps

🟡 wasm_v128_store。VM 必須猜測類型。
Wasm SIMD 中沒有快取控制。

_mm_prefetch

💭 無操作。

_mm_sfence

⚠️ 在多執行緒建置中,會完全阻礙。

_mm_shuffle_ps

🟡 wasm_i32x4_shuffle。VM 必須猜測類型。

_mm_storer_ps

💡 虛擬。混洗 + Simd 儲存。

_mm_store_ps1 (_mm_store1_ps)

🟡 虛擬。使用混洗模擬。
x86 CPU 上的未對齊儲存。

_mm_store_ss

💡 使用純量儲存模擬

_mm_storeu_ps

🟡 wasm_v128_store。VM 必須猜測類型。

_mm_storeu_si16

💡 使用純量儲存模擬

_mm_storeu_si64

💡 使用純量儲存模擬

_mm_movemask_ps

✅ wasm_i32x4_bitmask

_mm_move_ss

💡 使用混洗模擬。VM 必須猜測類型。

_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+混洗模擬

_mm_cmpunord_ps

❌ 使用 2xcmp+or 模擬

_mm_cmpunord_ss

❌ 使用 2xcmp+or+混洗模擬

_mm_and_ps

🟡 wasm_v128_and。VM 必須猜測類型。

_mm_andnot_ps

🟡 wasm_v128_andnot。VM 必須猜測類型。

_mm_or_ps

🟡 wasm_v128_or。VM 必須猜測類型。

_mm_xor_ps

🟡 wasm_v128_xor。VM 必須猜測類型。

_mm_cmpneq_ps

✅ wasm_f32x4_ne

_mm_cmpneq_ss

⚠️ 使用混洗模擬

_mm_cmpnge_ps

⚠️ 使用 not+ge 模擬

_mm_cmpnge_ss

⚠️ 使用 not+ge+混洗模擬

_mm_cmpngt_ps

⚠️ 使用 not+gt 模擬

_mm_cmpngt_ss

⚠️ 使用 not+gt+混洗模擬

_mm_cmpnle_ps

⚠️ 使用 not+le 模擬

_mm_cmpnle_ss

⚠️ 使用 not+le+混洗模擬

_mm_cmpnlt_ps

⚠️ 使用 not+lt 模擬

_mm_cmpnlt_ss

⚠️ 使用 not+lt+混洗模擬

_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 內建函數的可用性和預期效能。請參閱Intel Intrinsics Guide on 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。VM 必須猜測類型。

_mm_and_si128

🟡 wasm_v128_and。VM 必須猜測類型。

_mm_andnot_pd

🟡 wasm_v128_andnot。VM 必須猜測類型。

_mm_andnot_si128

🟡 wasm_v128_andnot。VM 必須猜測類型。

_mm_avg_epu16

✅ wasm_u16x8_avgr

_mm_avg_epu8

✅ wasm_u8x16_avgr

_mm_castpd_ps

✅ no-op

_mm_castpd_si128

✅ no-op

_mm_castps_pd

✅ no-op

_mm_castps_si128

✅ no-op

_mm_castsi128_pd

✅ no-op

_mm_castsi128_ps

✅ no-op

_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+混洗模擬

_mm_cmpngt_pd

⚠️ 使用 not+gt 模擬

_mm_cmpngt_sd

⚠️ 使用 not+gt+混洗模擬

_mm_cmpnle_pd

⚠️ 使用 not+le 模擬

_mm_cmpnle_sd

⚠️ 使用 not+le+混洗模擬

_mm_cmpnlt_pd

⚠️ 使用 not+lt 模擬

_mm_cmpnlt_sd

⚠️ 使用 not+lt+混洗模擬

_mm_cmpord_pd

❌ 使用 2xcmp+and 模擬

_mm_cmpord_sd

❌ 使用 2xcmp+and+混洗模擬

_mm_cmpunord_pd

❌ 使用 2xcmp+or 模擬

_mm_cmpunord_sd

❌ 使用 2xcmp+or+混洗模擬

_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 支援。
使用純量載入 + 混洗模擬。

_mm_loadl_epi64

❌ 沒有 Wasm SIMD 支援。
使用純量載入 + 混洗模擬。

_mm_loadl_pd

❌ 沒有 Wasm SIMD 支援。
使用純量載入 + 混洗模擬。

_mm_loadr_pd

💡 虛擬。Simd 載入 + 混洗。

_mm_loadu_pd

🟡 wasm_v128_load。VM 必須猜測類型。

_mm_loadu_si128

🟡 wasm_v128_load。VM 必須猜測類型。

_mm_loadu_si64

❌ 使用 const+純量載入+取代通道模擬

_mm_loadu_si32

❌ 使用 const+純量載入+取代通道模擬

_mm_loadu_si16

❌ 使用 const+純量載入+取代通道模擬

_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

💡 使用混洗模擬。VM 必須猜測類型。

_mm_move_sd

💡 使用混洗模擬。VM 必須猜測類型。

_mm_movemask_epi8

✅ wasm_i8x16_bitmask

_mm_movemask_pd

✅ wasm_i64x2_bitmask

_mm_mul_epu32

⚠️ 使用 wasm_u64x2_extmul_low_u32x4 + 2 次洗牌模擬

_mm_mul_pd

✅ wasm_f64x2_mul

_mm_mul_sd

⚠️ 使用混洗模擬

_mm_mulhi_epi16

⚠️ 使用 2 倍 SIMD extmul+通用洗牌模擬

_mm_mulhi_epu16

⚠️ 使用 2 倍 SIMD extmul+通用洗牌模擬

_mm_mullo_epi16

✅ wasm_i16x8_mul

_mm_or_pd

🟡 wasm_v128_or。VM 必須猜測類型。

_mm_or_si128

🟡 wasm_v128_or。VM 必須猜測類型。

_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 指令 + const 模擬

_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

💡 使用通用洗牌模擬

_mm_shuffle_pd

💡 使用通用洗牌模擬

_mm_shufflehi_epi16

💡 使用通用洗牌模擬

_mm_shufflelo_epi16

💡 使用通用洗牌模擬

_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)

💡 使用通用洗牌模擬

_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)

💡 使用通用洗牌模擬

_mm_store_pd

🟡 wasm_v128_store。VM 必須猜測類型。
x86 CPU 上的未對齊儲存。

_mm_store_sd

💡 使用純量儲存模擬

_mm_store_si128

🟡 wasm_v128_store。VM 必須猜測類型。
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。VM 必須猜測類型。

_mm_storeu_si128

🟡 wasm_v128_store。VM 必須猜測類型。

_mm_storeu_si64

💡 使用提取通道+純量儲存模擬

_mm_storeu_si32

💡 使用提取通道+純量儲存模擬

_mm_storeu_si16

💡 使用提取通道+純量儲存模擬

_mm_stream_pd

🟡 wasm_v128_store。VM 必須猜測類型。
Wasm SIMD 中沒有快取控制。

_mm_stream_si128

🟡 wasm_v128_store。VM 必須猜測類型。
Wasm SIMD 中沒有快取控制。

_mm_stream_si32

🟡 wasm_v128_store。VM 必須猜測類型。
Wasm SIMD 中沒有快取控制。

_mm_stream_si64

🟡 wasm_v128_store。VM 必須猜測類型。
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。VM 必須猜測類型。

_mm_xor_si128

🟡 wasm_v128_xor。VM 必須猜測類型。

⚫ 以下 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 內建函數的可用性和預期效能。請參閱Intel 內建函數指南中的 SSE3

透過 #include <pmmintrin.h> 和 -msse3 提供的 x86 SSE3 內建函數

內建函數名稱

WebAssembly SIMD 支援

_mm_lddqu_si128

✅ wasm_v128_load。

_mm_addsub_ps

⚠️ 使用 SIMD 加法 + 乘法 + 常數模擬

_mm_hadd_ps

⚠️ 使用 SIMD 加法 + 兩次洗牌模擬

_mm_hsub_ps

⚠️ 使用 SIMD 減法 + 兩次洗牌模擬

_mm_movehdup_ps

💡 使用通用洗牌模擬

_mm_moveldup_ps

💡 使用通用洗牌模擬

_mm_addsub_pd

⚠️ 使用 SIMD 加法 + 乘法 + 常數模擬

_mm_hadd_pd

⚠️ 使用 SIMD 加法 + 兩次洗牌模擬

_mm_hsub_pd

⚠️ 使用 SIMD 加法 + 兩次洗牌模擬

_mm_loaddup_pd

🟡 虛擬化。wasm_v64x2_load_splat,虛擬機器必須猜測類型。

_mm_movedup_pd

💡 使用通用洗牌模擬

_MM_GET_DENORMALS_ZERO_MODE

✅ 總是回傳 _MM_DENORMALS_ZERO_ON。也就是說,次正規值可用。

_MM_SET_DENORMALS_ZERO_MODE

⚫ 不可用。固定為 _MM_DENORMALS_ZERO_ON。

_mm_monitor

⚫ 不可用。

_mm_mwait

⚫ 不可用。

下表重點說明了不同 SSSE3 內建函數的可用性和預期效能。請參閱Intel 內建函數指南中的 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 或 + 兩次位移模擬

_mm_hadd_epi16

⚠️ 使用 SIMD 加法 + 兩次洗牌模擬

_mm_hadd_epi32

⚠️ 使用 SIMD 加法 + 兩次洗牌模擬

_mm_hadds_epi16

⚠️ 使用 SIMD 加飽和 + 兩次洗牌模擬

_mm_hsub_epi16

⚠️ 使用 SIMD 減法 + 兩次洗牌模擬

_mm_hsub_epi32

⚠️ 使用 SIMD 減法 + 兩次洗牌模擬

_mm_hsubs_epi16

⚠️ 使用 SIMD 減飽和 + 兩次洗牌模擬

_mm_maddubs_epi16

⚠️ 使用 SIMD 飽和加法 + 四次位移 + 兩次乘法 + and + const 模擬

_mm_mulhrs_epi16

⚠️ 使用 SIMD 四次擴展 + 兩次乘法 + 四次加法 + 複雜洗牌 + const 模擬

_mm_shuffle_epi8

⚠️ 使用 SIMD 旋轉 + and + const 模擬

_mm_sign_epi8

⚠️ 使用 SIMD 兩次比較 + 兩次邏輯 + 加法模擬

_mm_sign_epi16

⚠️ 使用 SIMD 兩次比較 + 兩次邏輯 + 加法模擬

_mm_sign_epi32

⚠️ 使用 SIMD 兩次比較 + 兩次邏輯 + 加法模擬

⚫ 不可使用處理 64 位元寬 MMX 暫存器的 SSSE3 函數
  • _mm_abs_pi8, _mm_abs_pi16, _mm_abs_pi32, _mm_alignr_pi8, _mm_hadd

任何引用這些內建函數的程式碼都無法編譯。

下表重點說明了不同 SSE4.1 內建函數的可用性和預期效能。請參閱Intel SSE4.1 內建函數指南

x86 SSE4.1 內建函數可透過 #include <smmintrin.h> 和 -msse4.1 使用

內建函數名稱

WebAssembly SIMD 支援

_mm_blend_epi16

💡 使用通用洗牌模擬

_mm_blend_pd

💡 使用通用洗牌模擬

_mm_blend_ps

💡 使用通用洗牌模擬

_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 內建函數的可用性和預期效能。請參閱Intel 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 內建函數的可用性和預期效能。請參閱Intel 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

💡 使用通用洗牌模擬

_mm_permute_ps

💡 使用通用洗牌模擬

_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

⚫ 未實作,將觸發編譯器錯誤

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

✅ 原生

vrev32

✅ 原生

vrev64

✅ 原生

vrhadd

⚠️ 沒有直接實作,但使用快速 NEON 指令模擬

vrsh_n

❌ 將透過慢速指令模擬或純量化

vrshn_n

❌ 將透過慢速指令模擬或純量化

vrsqrte

❌ 將透過慢速指令模擬或純量化

vrsqrts

❌ 將透過慢速指令模擬或純量化

vrshl

❌ 將透過慢速指令模擬或純量化

vrshr_n

❌ 將透過慢速指令模擬或純量化

vrsra_n

❌ 將透過慢速指令模擬或純量化

vset_lane

✅ 原生

vshl

純量化

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

❌ 將透過慢速指令模擬或純量化

vtrn2

❌ 將透過慢速指令模擬或純量化

vtst

❌ 將透過慢速指令模擬或純量化

vuqadd

❌ 將透過慢速指令模擬或純量化

vuqaddb

❌ 將透過慢速指令模擬或純量化

vuzp

❌ 將透過慢速指令模擬或純量化

vuzp1

❌ 將透過慢速指令模擬或純量化

vuzp2

❌ 將透過慢速指令模擬或純量化

vxar

❌ 將透過慢速指令模擬或純量化

vzip

❌ 將透過慢速指令模擬或純量化

vzip1

❌ 將透過慢速指令模擬或純量化

vzip2

❌ 將透過慢速指令模擬或純量化