100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
WebAssembly

Wasm Threads and SIMD

How WebAssembly's threads proposal (shared memory and atomics) and fixed-width SIMD unlock parallel and data-level performance for compute-heavy workloads.

Advanced TopicsAdvanced9 min readJul 10, 2026
Analogies

Parallelism and Vectorization in WebAssembly

By default, a WebAssembly instance runs single-threaded with memory private to that instance. Two proposals extend this for performance-critical workloads: the threads proposal, which adds shared linear memory plus atomic read-modify-write, wait, and notify instructions so multiple Wasm instances (typically one per Web Worker) can cooperate on the same data; and fixed-width SIMD, which adds a 128-bit v128 value type and vector instructions (add, multiply, shuffle, compare) that operate on four 32-bit lanes or sixteen 8-bit lanes at once. Together they let compiled code approach native performance for workloads like video codecs, physics simulation, and machine-learning inference that would otherwise be bottlenecked by single-lane, single-threaded execution.

🏏

Cricket analogy: It's like the difference between one groundstaff worker mowing an entire outfield alone versus a full ground crew working four mowers in parallel across the outfield at once, finishing a job that scales with workers rather than staying fixed to one pace.

Shared Memory and Atomics

Threaded Wasm memories are declared shared in the module and backed by a JavaScript SharedArrayBuffer on the host side; multiple Wasm instances running in different Web Workers can then read and write the same bytes. Because ordinary loads and stores are not synchronized, the threads proposal adds atomic instructions: atomic.rmw operations (add, sub, and, xchg, cmpxchg) for lock-free read-modify-write updates, and memory.atomic.wait32/wait64 paired with memory.atomic.notify to implement blocking synchronization primitives like mutexes and condition variables directly in Wasm. Browsers require the page to be served with Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers before SharedArrayBuffer is even available, a restriction added after the Spectre disclosures to reduce the risk of high-resolution timing side-channel attacks.

🏏

Cricket analogy: This is like a shared scoreboard console at the ground that both the official scorer and the broadcast statistician update; without a strict protocol for who writes when, they'd overwrite each other's run tally mid-over, so they use a locking handshake (atomics) before each update.

SIMD 128-bit Vectorization

Fixed-width SIMD adds the v128 type, which a compiler like LLVM can pack with four f32 lanes, four i32 lanes, eight i16 lanes, or sixteen i8 lanes and process with a single instruction such as f32x4.add or i8x16.shuffle. Source code doesn't need explicit intrinsics to benefit in many cases: compiling with a target like -msimd128 in Clang/Emscripten or target-feature=+simd128 in Rust lets LLVM's auto-vectorizer transform tight numeric loops (image convolution kernels, audio sample processing, matrix math) into SIMD instructions automatically, though manually written intrinsics via std::arch::wasm32 in Rust or wasm_simd128.h in C typically outperform auto-vectorization for hot paths. Unlike threads, SIMD support doesn't require special HTTP headers or SharedArrayBuffer, making it the lower-friction of the two performance proposals to adopt.

🏏

Cricket analogy: This is like a bowling machine that fires four balls simultaneously down four parallel practice lanes instead of one ball at a time, letting four batters each face a delivery in the time it took to face one previously.

wat
;; Shared memory declaration for the threads proposal
(memory (export "memory") 1 1 shared)

;; Atomic increment of a shared counter at address 0
(func $increment (export "increment")
  (drop (i32.atomic.rmw.add (i32.const 0) (i32.const 1))))

;; SIMD: add two v128 vectors of four f32 lanes
(func $add_vec4 (param $a v128) (param $b v128) (result v128)
  (f32x4.add (local.get $a) (local.get $b)))

Relaxed SIMD extends fixed-width SIMD with instructions like relaxed_madd whose exact rounding behavior is implementation-defined for a speed gain, trading bit-for-bit determinism across engines for extra throughput on workloads like ML inference where slight precision variance is acceptable.

SharedArrayBuffer, and therefore Wasm threads, only works when the page is served with Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp headers. Omitting these headers silently disables SharedArrayBuffer in the browser, so threaded builds should always feature-detect rather than assume availability.

  • Wasm threads add shared linear memory (backed by SharedArrayBuffer) plus atomic instructions for safe cross-instance coordination.
  • Atomic operations include read-modify-write ops (add, cmpxchg) and blocking wait/notify primitives for building mutexes.
  • Browsers require COOP/COEP headers before exposing SharedArrayBuffer, a post-Spectre mitigation.
  • Fixed-width SIMD introduces the v128 type processed as 4x f32/i32, 8x i16, or 16x i8 lanes per instruction.
  • Compilers can auto-vectorize tight loops with flags like -msimd128, though hand-written intrinsics often outperform auto-vectorization on hot paths.
  • SIMD requires no special HTTP headers, making it easier to adopt than threads.
  • Relaxed SIMD trades strict determinism for extra performance on tolerant workloads like ML inference.

Practice what you learned

Was this page helpful?

Topics covered

#WebAssembly#WebAssemblyStudyNotes#WebDevelopment#WasmThreadsAndSIMD#Wasm#Threads#SIMD#Parallelism#Concurrency#StudyNotes#SkillVeris