How to Use SIMD Vector Math in WebAssembly

This article provides a practical guide on how to perform vector math and Single Instruction, Multiple Data (SIMD) operations in WebAssembly (Wasm). You will learn how to enable 128-bit vector operations, write SIMD code in C/C++ and Rust, compile it for the web, and verify browser compatibility to achieve near-native performance for computational tasks like graphics, audio processing, and machine learning.

Understanding WebAssembly SIMD

WebAssembly SIMD (Single Instruction, Multiple Data) leverages 128-bit vector registers to perform a single operation on multiple data points simultaneously. For example, a single 128-bit register (v128 type) can hold and process: * Four 32-bit floating-point numbers (float32x4) * Four 32-bit integers (int32x4) * Eight 16-bit integers (int16x8) * Sixteen 8-bit integers (int8x16)

Using SIMD can accelerate performance-critical mathematical operations, such as matrix multiplications and physics simulations, by up to 2x to 10x compared to scalar WebAssembly execution.


Implementing SIMD in C/C++ with Emscripten

To use SIMD in C or C++, you must include the <wasm_simd128.h> header file, which provides the intrinsic functions mapped directly to WebAssembly instruction sets.

1. The C/C++ Code

The following example demonstrates how to add two arrays of floats using 128-bit SIMD intrinsics:

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

void vector_add_simd(const float* a, const float* b, float* out, int size) {
    // Process 4 floats at a time (128 bits / 32 bits = 4)
    for (int i = 0; i < size; i += 4) {
        // Load 4 floats from memory into 128-bit registers
        v128_t va = wasm_v128_load(&a[i]);
        v128_t vb = wasm_v128_load(&b[i]);
        
        // Perform parallel addition
        v128_t vout = wasm_f32x4_add(va, vb);
        
        // Store the result back into memory
        wasm_v128_store(&out[i], vout);
    }
}

2. Compilation

Compile the code using Emscripten. You must explicitly pass the -msimd128 flag to enable SIMD code generation:

emcc main.cpp -o main.js -O3 -msimd128

Using -O3 allows the compiler to also perform auto-vectorization, automatically converting standard loops into SIMD instructions where possible.


Implementing SIMD in Rust

Rust natively supports WebAssembly SIMD through the standard library’s core::arch::wasm32 module.

1. The Rust Code

Here is how to perform the same 4-lane float addition in Rust:

#![feature(stdsimd)] // Required for certain experimental intrinsics, if applicable
use std::arch::wasm32::*;

#[no_mangle]
pub unsafe fn vector_add_simd(a: *const f32, b: *const f32, out: *mut f32, size: usize) {
    let mut i = 0;
    while i < size {
        // Load data into v128 registers
        let va = v128_load(a.add(i) as *const v128);
        let vb = v128_load(b.add(i) as *const v128);
        
        // Add the vectors
        let vout = f32x4_add(va, vb);
        
        // Store the result
        v128_store(out.add(i) as *mut v128, vout);
        
        i += 4;
    }
}

2. Compilation

To compile Rust code with SIMD enabled, pass the target feature flag to the Rust compiler:

RUSTFLAGS="-C target-feature=+simd128" cargo build --target wasm32-unknown-unknown --release

Feature Detection in the Browser

Before executing WebAssembly SIMD modules, you must ensure the user’s browser or runtime environment supports it. Modern versions of Chrome, Firefox, Edge, and Safari support SIMD, but a fallback is recommended for older clients.

You can perform feature detection in JavaScript using the following script:

async function isSimdSupported() {
    try {
        // A minimal WebAssembly binary containing a SIMD instruction (v128.const)
        const simdBytes = new Uint8Array([
            0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 0, 3, 2, 1, 0, 10, 9, 1, 7, 0, 65, 0, 253, 12, 11
        ]);
        const module = await WebAssembly.compile(simdBytes);
        return true;
    } catch (e) {
        return false;
    }
}

isSimdSupported().then((supported) => {
    if (supported) {
        console.log("SIMD is supported. Loading high-performance Wasm module...");
        // Load your SIMD-enabled Wasm module
    } else {
        console.log("SIMD is not supported. Loading fallback Wasm module...");
        // Load a standard scalar Wasm module compiled without -msimd128
    }
});