bigywater.tech
Rust · 150

WASI 与 WebAssembly

WebAssembly 和 WASI 到底是什么?这篇不讲概念,而是真的把 Rust 编译成 .wasm,检查它的导入导出,从 JS 调用,往它的线性内存里写字节,再让它通过 WASI 打印一行字,最后看宿主不提供能力时会怎样。

585 字节
三个函数的 Rust wasm 模块大小
BigInt
wasm 的 i64 在 JS 里的类型
0 个导入
纯计算模块需要宿主提供的导入数量(WASI 版本需要 2 个)

编译成 wasm 并从 JS 调用

实测 · 本机执行输出
  w1.wasm 大小 585 字节(包含 3 个函数)
  导出: ["memory:memory","add:function","checked_div:function","fib:function"]
  导入: []
  add(2, 3) = 5  返回类型 number
  fib(50) = 12586269025n  返回类型 bigint (i64 在 JS 里是 BigInt)
  add(2147483647, 1) = -2147483648 (i32 回绕:wasm 整数加法不检查溢出)
  checked_div(1, 0) → RuntimeError: unreachable
  陷入 trap 之后实例还能用:add(1, 1) = 2

本机的 brew rustc 没有 wasm32 的预编译标准库,所以用 -Z build-std=core(RUSTC_BOOTSTRAP=1)从源码构建 core,链接器用 Zig 里自带的 wasm-ld(通过 pip 安装的 ziglang 提供,只装在实验用的虚拟环境里)。因此这里是 no_std 的模块,不是带 std 的 wasm32-wasip1 程序。panic 的行为见panic!、unwrap 与 expect。

线性内存

实测 · 本机执行输出
  buf_ptr() = 1048576 (线性内存里的偏移);内存大小 1114112 字节 = 17 页(每页 64 KiB)
  JS 写入 10 字节后 sum(10) = 1004  JS 里算 = 1004
  upper(10) 在 Rust 里就地改写后,JS 读到: HELLO WASM

裸指针的概念见原始指针解引用。

WASI 与沙箱

实测 · 本机执行输出
  导入: ["wasi_snapshot_preview1.fd_write","wasi_snapshot_preview1.proc_exit"]  导出: ["memory","_start"]
  hello from Rust compiled to WASI
  wasi.start 返回(退出码) 0
  宿主不提供 WASI 导入时实例化: TypeError: WebAssembly.Instance(): Import #0 "wasi_snapshot_preview1": module is not an object or function
  (node:64239) ExperimentalWarning: WASI is an experimental feature and might change at any time
  (Use `node --trace-warnings ...` to show where the warning was created)

分步演示

WASI 与 WebAssembly
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。Node.js v26.10(WebAssembly 与 node:wasi);wasm-ld 来自 ziglang 0.16(LLD 21.1)
    • 没有验证:带 std 的 wasm32-wasip1 程序(缺 wasi-libc)、浏览器里运行、wasm-bindgen / wasm-pack、组件模型(WASI preview2)、wasmtime 与 wasmer 等独立运行时、线程与 SIMD 扩展、wasm 文件优化工具 wasm-opt
    京ICP备2026067502号-1京公网安备11010502063487号