WebAssembly(Wasm)是一种二进制指令格式,设计为高级语言的可移植编译目标。它在 Web 浏览器及其他环境中实现接近原生的性能。自成为 W3C 推荐标准以来,WebAssembly 已成为计算密集型 Web 应用、服务端运行时和边缘计算的基础技术。本指南涵盖从 Wasm 基础到 SIMD、线程和未来提案的所有内容。
WebAssembly 是一种低级二进制格式,在所有主流浏览器中以接近原生速度运行。可从 Rust(wasm-pack)或 C/C++(Emscripten)编译,通过类型化的导入/导出从 JavaScript 调用 Wasm 函数,并利用 SIMD 和线程实现最佳性能。WASI 将 Wasm 扩展到浏览器之外的服务器和边缘环境。
- Wasm 在浏览器中提供 1.2 到 2 倍的原生速度,远超 JavaScript 处理计算密集型任务的能力。
- Rust(wasm-pack + wasm-bindgen)凭借零成本抽象提供最佳的 Wasm 开发体验。
- Emscripten 将 C/C++ 代码库编译为 Wasm,实现浏览器中的遗留代码复用。
- JavaScript 互操作使用导入/导出和 TypedArrays 与线性内存进行高效数据交换。
- WASI 提供类 POSIX 的系统接口,使 Wasm 模块能在浏览器外运行。
- SIMD 指令和 SharedArrayBuffer 线程解锁 Wasm 中的并行向量化计算。
什么是 WebAssembly?
WebAssembly 是一种基于栈式虚拟机的二进制指令格式。它被设计为编程语言的可移植编译目标,支持在 Web 上部署客户端和服务端应用。Wasm 代码以紧凑的二进制格式(.wasm 文件)交付,浏览器引擎对其解码和编译的速度远快于解析等效的 JavaScript。
WebAssembly 有两种表示形式:生产中使用的二进制格式(.wasm)和用于调试的文本格式(WAT)。模块是 Wasm 代码的基本单元,包含函数、表、内存和全局变量。
Binary Format (.wasm)
# A compiled Wasm binary header (magic number + version)
00 61 73 6d # \0asm (magic number)
01 00 00 00 # version 1
# Wasm uses section-based encoding:
# Section 1 - Type section (function signatures)
# Section 3 - Function section (function declarations)
# Section 7 - Export section (exported names)
# Section 10 - Code section (function bodies)Text Format (WAT)
(module
;; Define a function that adds two i32 values
(func $add (param $a i32) (param $b i32) (result i32)
local.get $a
local.get $b
i32.add
)
;; Export the function so JavaScript can call it
(export "add" (func $add))
;; Define and export a linear memory (1 page = 64KB)
(memory (export "memory") 1)
;; Global mutable variable
(global $counter (mut i32) (i32.const 0))
)内存模型
WebAssembly 使用线性内存模型。内存表示为连续的可调整大小的字节数组,以 64KB 页为单位分配,可在 Wasm 和 JavaScript 之间共享。线性内存在运行时进行边界检查,防止缓冲区溢出逃逸沙箱。
共享内存(使用 SharedArrayBuffer)使多个 Wasm 实例或 Web Workers 能够读写同一内存区域。原子操作确保跨线程的数据一致性。
(module
;; Declare linear memory: initial 1 page, max 10 pages
;; Each page = 64KB (65,536 bytes)
(memory (export "memory") 1 10)
;; Store a 32-bit integer at byte offset 0
(func $store_value (param $offset i32) (param $value i32)
local.get $offset
local.get $value
i32.store
)
;; Load a 32-bit integer from byte offset
(func $load_value (param $offset i32) (result i32)
local.get $offset
i32.load
)
;; Grow memory by 1 page, returns previous page count or -1
(func $grow (result i32)
i32.const 1
memory.grow
)
)Accessing Memory from JavaScript
// Access Wasm linear memory from JavaScript
const memory = instance.exports.memory;
// Create typed views into the memory buffer
const bytes = new Uint8Array(memory.buffer);
const ints = new Int32Array(memory.buffer);
const floats = new Float64Array(memory.buffer);
// Read/write values directly
ints[0] = 42; // Write i32 at byte offset 0
floats[1] = 3.14159; // Write f64 at byte offset 8
// Copy a string into Wasm memory
const encoder = new TextEncoder();
const encoded = encoder.encode("Hello, Wasm!");
bytes.set(encoded, 256); // Write at offset 256
// IMPORTANT: views are invalidated when memory grows
instance.exports.grow_memory();
// Must re-create views after growth
const newBytes = new Uint8Array(memory.buffer);从 Rust 编译
Rust 是最流行的 WebAssembly 开发语言,得益于零成本抽象、无垃圾回收器和优秀的 Wasm 工具链。wasm-pack 处理构建、测试和发布。wasm-bindgen 提供 Rust 类型和 JavaScript 之间的桥梁。
首先安装 Wasm 目标和 wasm-pack,创建新的库 crate 并配置 cdylib crate 类型。
# Install the Wasm target for Rust
rustup target add wasm32-unknown-unknown
# Install wasm-pack
cargo install wasm-pack
# Create a new library project
cargo new --lib my-wasm-lib
cd my-wasm-libCargo.toml Configuration
[package]
name = "my-wasm-lib"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
wasm-bindgen = "0.2"
[dependencies.web-sys]
version = "0.3"
features = ["console", "Document", "Element", "Window"]
[profile.release]
opt-level = "s" # Optimize for size
lto = true # Link-time optimizationRust Source with wasm-bindgen
use wasm_bindgen::prelude::*;
// Import JavaScript functions
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
#[wasm_bindgen(js_namespace = Math)]
fn random() -> f64;
}
// Export a greeting function to JavaScript
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
log(&format!("Hello from Wasm, {}!", name));
format!("Hello, {}! Random: {:.4}", name, random())
}
// Export a compute-intensive function
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u64 {
match n {
0 => 0,
1 => 1,
_ => {
let (mut a, mut b) = (0u64, 1u64);
for _ in 2..=n {
let temp = a + b;
a = b;
b = temp;
}
b
}
}
}
// Working with byte arrays
#[wasm_bindgen]
pub fn sha256_hash(data: &[u8]) -> Vec<u8> {
// Process byte data from JavaScript
let mut hash = vec![0u8; 32];
// ... hashing logic ...
hash
}Build and Use
# Build with wasm-pack (generates pkg/ directory)
wasm-pack build --target web --release
# The pkg/ directory contains:
# - my_wasm_lib_bg.wasm (compiled binary)
# - my_wasm_lib.js (JS glue code)
# - my_wasm_lib.d.ts (TypeScript types)
# - package.json (npm package)// Using the Rust Wasm module in JavaScript
import init, { greet, fibonacci } from "./pkg/my_wasm_lib.js";
async function main() {
// Initialize the Wasm module
await init();
// Call exported Rust functions
const message = greet("Developer");
console.log(message);
const fib50 = fibonacci(50);
console.log("fib(50) =", fib50);
}
main();从 C/C++ 编译
Emscripten 是将 C/C++ 编译为 WebAssembly 的主要工具链,提供完整的 SDK,包括编译器(emcc)、系统库和 JavaScript 胶水代码生成。
emcc 编译器支持多种优化标志。生产构建使用 -O2 或 -O3,调试使用 -g,Wasm 特定设置使用 -s 标志。
// image_filter.c - Example C code for Wasm compilation
#include <emscripten/emscripten.h>
#include <stdint.h>
#include <stdlib.h>
// Export function with EMSCRIPTEN_KEEPALIVE
EMSCRIPTEN_KEEPALIVE
void grayscale(uint8_t* pixels, int length) {
for (int i = 0; i < length; i += 4) {
uint8_t r = pixels[i];
uint8_t g = pixels[i + 1];
uint8_t b = pixels[i + 2];
uint8_t gray = (uint8_t)(0.299f * r
+ 0.587f * g + 0.114f * b);
pixels[i] = pixels[i+1] = pixels[i+2] = gray;
}
}
EMSCRIPTEN_KEEPALIVE
uint8_t* create_buffer(int size) {
return (uint8_t*)malloc(size);
}
EMSCRIPTEN_KEEPALIVE
void destroy_buffer(uint8_t* ptr) {
free(ptr);
}# Compile with Emscripten
emcc image_filter.c -o image_filter.js \
-s WASM=1 \
-s EXPORTED_FUNCTIONS="[\
'_grayscale', '_create_buffer', '_destroy_buffer'\
]" \
-s EXPORTED_RUNTIME_METHODS="['ccall','cwrap']" \
-s ALLOW_MEMORY_GROWTH=1 \
-O3
# For standalone Wasm (no JS glue):
emcc image_filter.c -o image_filter.wasm \
-s STANDALONE_WASM=1 \
-s EXPORTED_FUNCTIONS="['_grayscale']" \
--no-entry -O3JavaScript 互操作
WebAssembly 模块通过导入和导出与 JavaScript 通信。导出函数可从 JavaScript 调用,导入函数允许 Wasm 回调 JavaScript。数据交换通过线性内存使用 TypedArrays 进行。
TypedArrays(如 Uint8Array、Float32Array)提供对 WebAssembly 线性内存的视图,实现无序列化开销的数据传递。
// Loading and instantiating a Wasm module
async function loadWasm() {
// Method 1: Streaming compilation (preferred)
const response = await fetch("module.wasm");
const { instance } = await WebAssembly
.instantiateStreaming(response, {
env: {
// Import: JS function callable from Wasm
log_value: (value) => console.log("Wasm:", value),
get_time: () => Date.now(),
},
js: {
mem: new WebAssembly.Memory({
initial: 1, maximum: 10
}),
},
});
// Call exported Wasm functions
const result = instance.exports.add(10, 20);
console.log("10 + 20 =", result); // 30
return instance;
}
// Method 2: Module caching with IndexedDB
async function loadCachedWasm() {
const db = await openDB("wasm-cache", 1);
let module = await db.get("modules", "myModule");
if (!module) {
const response = await fetch("module.wasm");
module = await WebAssembly
.compileStreaming(response);
await db.put("modules", module, "myModule");
}
return WebAssembly.instantiate(module, imports);
}TypedArray Data Exchange
// Passing arrays between JavaScript and Wasm
function processImageData(instance, imageData) {
const { memory, process_pixels, alloc, dealloc } =
instance.exports;
const pixels = imageData.data; // Uint8ClampedArray
// Allocate memory in Wasm for the pixel data
const ptr = alloc(pixels.length);
// Copy pixels into Wasm memory
const wasmMemory = new Uint8Array(memory.buffer);
wasmMemory.set(pixels, ptr);
// Process in Wasm (much faster than JS)
process_pixels(ptr, pixels.length);
// Copy results back to JavaScript
// Re-create view in case memory grew
const result = new Uint8Array(memory.buffer);
imageData.data.set(
result.slice(ptr, ptr + pixels.length)
);
// Free Wasm memory
dealloc(ptr, pixels.length);
return imageData;
}WASI(WebAssembly 系统接口)
WASI 是 WebAssembly 的模块化系统接口,提供类 POSIX 的文件系统、网络、时钟等 API。它使 Wasm 模块能在 Wasmtime、Wasmer 等运行时中运行,采用基于能力的安全模型。
流行的 WASI 运行时包括 Wasmtime、Wasmer 和 WasmEdge,各自支持不同的 WASI 提案和嵌入场景。
// Rust WASI example: file I/O
use std::fs;
use std::io::Write;
fn main() {
// Read from stdin
let mut input = String::new();
std::io::stdin().read_line(&mut input)
.expect("Failed to read");
// Write to a file (pre-opened directory)
let mut file = fs::File::create("/output/result.txt")
.expect("Cannot create file");
write!(file, "Processed: {}", input.trim())
.expect("Write failed");
// Read environment variable
if let Ok(val) = std::env::var("CONFIG_PATH") {
println!("Config: {}", val);
}
println!("WASI module completed successfully");
}# Compile Rust to WASI target
rustup target add wasm32-wasi
cargo build --target wasm32-wasi --release
# Run with Wasmtime (capability-based access)
wasmtime run \
--dir /output::/tmp/output \
--env CONFIG_PATH=/etc/app.conf \
target/wasm32-wasi/release/my_app.wasm
# Run with Wasmer
wasmer run my_app.wasm --dir /output::/tmp/output
# Run with WasmEdge
wasmedge --dir /output:/tmp/output my_app.wasm与 JavaScript 的性能对比
WebAssembly 在计算密集型任务中始终优于 JavaScript。基准测试显示 Wasm 达到 1.2 到 2 倍的接近原生性能,而 JavaScript 通常比原生慢 3 到 10 倍。
性能因工作负载类型而异。对于加密、图像处理和数据压缩,Wasm 提供显著加速;对于 DOM 操作,JavaScript 可能更快。
| 任务 | Wasm | JavaScript | 加速比 |
|---|---|---|---|
| Fibonacci (n=45) | 0.8ms | 2.4ms | 3.0x |
| Matrix multiply 1024x1024 | 120ms | 380ms | 3.2x |
| SHA-256 (1MB) | 2.1ms | 5.8ms | 2.8x |
| Image grayscale (4K) | 3.2ms | 12.1ms | 3.8x |
| JSON parse (1MB) | 8.5ms | 6.2ms | 0.7x |
| Sorting 1M integers | 45ms | 95ms | 2.1x |
| Regex matching | 1.5ms | 1.2ms | 0.8x |
| Zlib compress (1MB) | 18ms | 52ms | 2.9x |
// Performance benchmarking example
async function benchmark() {
const { instance } = await WebAssembly
.instantiateStreaming(fetch("bench.wasm"));
// Warm up
for (let i = 0; i < 100; i++) {
instance.exports.fibonacci(40);
}
// Wasm benchmark
const wasmStart = performance.now();
for (let i = 0; i < 1000; i++) {
instance.exports.fibonacci(40);
}
const wasmTime = performance.now() - wasmStart;
// JavaScript benchmark
function jsFib(n) {
if (n <= 1) return n;
let a = 0, b = 1;
for (let i = 2; i <= n; i++) {
[a, b] = [b, a + b];
}
return b;
}
const jsStart = performance.now();
for (let i = 0; i < 1000; i++) jsFib(40);
const jsTime = performance.now() - jsStart;
console.log("Wasm:", wasmTime.toFixed(2), "ms");
console.log("JS:", jsTime.toFixed(2), "ms");
console.log("Speedup:", (jsTime/wasmTime).toFixed(1)+"x");
}使用场景
WebAssembly 擅长需要高性能、跨语言代码复用或沙箱执行的场景,包括图像处理、加密操作、游戏引擎、音视频编解码器和科学计算等。
| Category | Examples | Why Wasm |
|---|---|---|
| Image Processing | Photon, Squoosh, libvips | Pixel-level ops 3-5x faster |
| Cryptography | libsodium, OpenSSL | Constant-time, no GC pauses |
| Game Engines | Unity, Unreal, Godot | Port C++ engines to browser |
| Audio/Video | FFmpeg.wasm, Opus, AV1 | Real-time codec processing |
| Scientific Computing | Pyodide, SciPy, NumPy | Run Python/C libs in browser |
| CAD / 3D | AutoCAD Web, OpenSCAD | Heavy geometry calculations |
| PDF / Document | pdf.js, Poppler | Complex parsing and rendering |
| Plugin Systems | Figma, Shopify, Envoy | Sandboxed, language-agnostic |
线程
WebAssembly 线程使用 SharedArrayBuffer 和 Web Workers 实现并行执行。线程提案添加原子操作和共享线性内存。
线程需要跨域隔离头(COOP 和 COEP)来启用 SharedArrayBuffer。原子操作提供等待/通知原语用于线程同步。
// Setting up Wasm threading with Web Workers
// Required HTTP headers for cross-origin isolation:
// Cross-Origin-Opener-Policy: same-origin
// Cross-Origin-Embedder-Policy: require-corp
// Main thread: create shared memory and workers
const sharedMemory = new WebAssembly.Memory({
initial: 10,
maximum: 100,
shared: true, // Requires SharedArrayBuffer
});
const module = await WebAssembly.compileStreaming(
fetch("parallel.wasm")
);
// Create worker threads
const NUM_THREADS = navigator.hardwareConcurrency || 4;
const workers = [];
for (let i = 0; i < NUM_THREADS; i++) {
const worker = new Worker("wasm-worker.js");
worker.postMessage({
module,
memory: sharedMemory,
threadId: i,
totalThreads: NUM_THREADS,
});
workers.push(worker);
}// wasm-worker.js - Worker thread
self.onmessage = async (e) => {
const { module, memory, threadId, totalThreads } = e.data;
const instance = await WebAssembly.instantiate(module, {
env: { memory },
});
// Each worker processes its portion of data
instance.exports.process_chunk(
threadId,
totalThreads
);
self.postMessage({ done: true, threadId });
};SIMD 指令
WebAssembly SIMD 使用 128 位向量实现数据并行处理,可同时处理 4 个 float32 或 2 个 float64 值。特别适用于图像处理、音频 DSP 和矩阵运算。
固定宽度 128 位 SIMD 提案已在所有主流浏览器中支持。Rust 通过 std::arch::wasm32 模块暴露 SIMD。
// Rust SIMD for WebAssembly
use std::arch::wasm32::*;
// SIMD-accelerated vector addition (f32x4)
#[target_feature(enable = "simd128")]
pub unsafe fn add_vectors_simd(
a: &[f32], b: &[f32], result: &mut [f32]
) {
let len = a.len();
let simd_len = len / 4 * 4; // Process 4 at a time
for i in (0..simd_len).step_by(4) {
let va = v128_load(a[i..].as_ptr() as *const v128);
let vb = v128_load(b[i..].as_ptr() as *const v128);
let vr = f32x4_add(va, vb);
v128_store(result[i..].as_mut_ptr() as *mut v128, vr);
}
// Handle remaining elements
for i in simd_len..len {
result[i] = a[i] + b[i];
}
}// C/C++ SIMD with Emscripten
#include <wasm_simd128.h>
// SIMD dot product: 4 multiplies + horizontal add
float dot_product_simd(
const float* a, const float* b, int n
) {
v128_t sum = wasm_f32x4_const(0, 0, 0, 0);
int i;
for (i = 0; i + 3 < n; i += 4) {
v128_t va = wasm_v128_load(&a[i]);
v128_t vb = wasm_v128_load(&b[i]);
sum = wasm_f32x4_add(
sum, wasm_f32x4_mul(va, vb)
);
}
// Horizontal sum of the 4 lanes
float result = wasm_f32x4_extract_lane(sum, 0)
+ wasm_f32x4_extract_lane(sum, 1)
+ wasm_f32x4_extract_lane(sum, 2)
+ wasm_f32x4_extract_lane(sum, 3);
// Scalar remainder
for (; i < n; i++) result += a[i] * b[i];
return result;
}
// Compile: emcc -msimd128 -O3 simd.c -o simd.wasm调试
Chrome DevTools 提供原生 WebAssembly 调试支持,包括断点设置、线性内存检查、调用栈查看和指令步进。DWARF 调试格式支持源码级调试。
其他工具包括 wasm-tools、wasm-objdump、wasm-opt 和浏览器控制台日志。
# wasm-tools: Swiss Army knife for Wasm
# Install
cargo install wasm-tools
# Validate a Wasm module
wasm-tools validate module.wasm
# Print module structure
wasm-tools print module.wasm
# Dump section info
wasm-tools dump module.wasm
# Convert WAT to Wasm binary
wasm-tools parse module.wat -o module.wasm
# Strip debug info for production
wasm-tools strip module.wasm -o module.stripped.wasm
# Optimize with Binaryen
wasm-opt -O3 module.wasm -o module.opt.wasm
wasm-opt -Oz module.wasm -o module.small.wasm # Size// Debug logging: import console.log into Wasm
const imports = {
debug: {
log_i32: (val) => console.log("[wasm i32]", val),
log_f64: (val) => console.log("[wasm f64]", val),
log_mem: (ptr, len) => {
const bytes = new Uint8Array(
memory.buffer, ptr, len
);
console.log("[wasm mem]",
new TextDecoder().decode(bytes));
},
trace: (id) => {
console.trace("[wasm trace] checkpoint", id);
},
},
};
// Chrome DevTools Wasm debugging:
// 1. Open Sources panel
// 2. Find .wasm file in the file tree
// 3. Set breakpoints in WAT disassembly
// 4. Inspect locals, globals, memory in Scope panel
// 5. Enable DWARF for source-level C/Rust debugging:
// Install "C/C++ DevTools Support" extension未来提案
WebAssembly 生态系统持续发展,多个重要提案正在标准化的不同阶段。
GC (Garbage Collection) Proposal
GC(垃圾回收)提案为 Wasm 添加结构体、数组和引用类型,使带 GC 的语言(Java、Kotlin、Go)能编译为更小更快的 Wasm 模块。
;; GC proposal: struct and array types in WAT
(type $point (struct
(field $x f64)
(field $y f64)
))
(type $points (array (ref $point)))
;; Create a GC-managed struct
(func $make_point (param $x f64) (param $y f64)
(result (ref $point))
(struct.new $point
(local.get $x)
(local.get $y)
)
)
;; Access struct fields
(func $distance (param $p (ref $point)) (result f64)
(f64.sqrt
(f64.add
(f64.mul
(struct.get $point $x (local.get $p))
(struct.get $point $x (local.get $p)))
(f64.mul
(struct.get $point $y (local.get $p))
(struct.get $point $y (local.get $p)))))
)Component Model
组件模型定义了具有丰富类型的高级模块格式,使 Wasm 组件能通过 WIT 接口类型相互组合。
// WIT (Wasm Interface Type) definition
// image-processor.wit
package example:image-processor@1.0.0;
interface types {
record image {
width: u32,
height: u32,
pixels: list<u8>,
}
enum filter {
grayscale,
blur,
sharpen,
edge-detect,
}
}
world image-processor {
use types.{image, filter};
export apply-filter: func(
img: image, f: filter
) -> image;
export resize: func(
img: image, w: u32, h: u32
) -> image;
}Stack Switching
栈切换支持 Wasm 中的协程、async/await 和轻量级绿色线程。
最佳实践
- 通过批量操作和共享内存最小化 Wasm-JS 边界穿越。
- 使用 Binaryen 的 wasm-opt 在编译后优化 Wasm 二进制文件。
- 使用 WebAssembly.compileStreaming() 流式编译大模块以加快启动。
- 在 IndexedDB 中缓存编译后的模块,避免重复编译。
- 使用浏览器 DevTools 分析瓶颈在 Wasm 还是 JS 互操作中。
- 对图像处理和数值计算等数据并行工作负载使用 SIMD。
- 仅在需要时启用线程,并确保设置跨域隔离头。
- 通过链接时优化(LTO)排除未使用的函数来保持 Wasm 模块小巧。
常见问题
WebAssembly 能替代 JavaScript 吗?
不能。WebAssembly 旨在补充 JavaScript 而非替代它。Wasm 擅长计算密集型任务,JavaScript 处理 DOM 操作和 UI 逻辑。大多数应用同时使用两者。
WebAssembly 安全吗?
是的。WebAssembly 在与 JavaScript 相同的沙箱中运行。它不能直接访问 DOM、发起网络请求或访问文件系统。线性内存有边界检查。
应该用什么语言开发 WebAssembly?
Rust 是最流行的选择。C/C++ 适合移植现有代码库。AssemblyScript 提供较低的学习曲线。Go、Kotlin 和 C# 也支持 Wasm。
WebAssembly 比 JavaScript 快多少?
对于计算密集型任务通常快 1.5 到 3 倍。具体加速取决于工作负载类型。
WebAssembly 能访问 DOM 吗?
不能直接访问。Wasm 必须通过 JavaScript 导入来操作 DOM。wasm-bindgen 和 Emscripten 提供了便捷的 DOM 访问抽象。
什么是 WASI?
WASI 是一个标准化 API,让 Wasm 模块访问文件 I/O、网络等操作系统功能,使 Wasm 能在服务器和边缘环境中运行。
WebAssembly 支持多线程吗?
支持。Wasm 线程提案通过 SharedArrayBuffer 启用共享线性内存和原子操作。在浏览器中映射到 Web Workers。
WebAssembly 的主要限制是什么?
当前限制包括不能直接访问 DOM、频繁 Wasm-JS 边界穿越的开销、有限的调试支持、无内置垃圾回收,以及线程需要跨域隔离。