- Published on
WebAssembly と WASI 完全解剖 — バイトコード、Sandbox、Component Model、Wasmtime、エッジランタイム
- Authors

- Name
- Youngju Kim
- @fjvbn20031
はじめに — 「Docker より小さく、JVM より速い」
2019 年、Docker 創設者 Solomon Hykes のツイートが話題になった。
"If WASM+WASI existed in 2008, we wouldn't have needed to create Docker."
賛否は割れたが、核は明確だ。Wasm はブラウザ専用技術ではない。2025 年現在、Cloudflare Workers、Fastly Compute@Edge、Shopify Functions、Figma プラグイン、さらに Kubernetes 上の SpinKube まで、すべて Wasm をエンジンにしている。
本稿で扱うテーマ。
- WebAssembly の誕生 — asm.js から Wasm 1.0 へ
- バイトコードの構造とスタック仮想マシンの実行モデル
- Sandbox の数学的保証
- WASI — ブラウザ外の Wasm システムインターフェース
- Wasmtime / Wasmer / WasmEdge ランタイム比較
- Component Model — 言語中立モジュールの仕組み
- なぜ Cloudflare Workers は Isolate と Wasm の両方を持つのか
- Kubernetes + Wasm — runwasi、SpinKube
- 実務上の落とし穴と未来
1. 誕生 — asm.js が残した宿題
JavaScript の不運な寿命
2010 年代初頭、ブラウザは事実上「JavaScript しか走らない OS」だった。だが JS には問題があった。
- 動的型により JIT が型仮定を何度も再確認する
- GC の遅延
- 高性能な数値演算に不向き
asm.js (Mozilla, 2013)
答えは JavaScript の「サブセット」としての asm.js。
function add(x, y) {
x = x|0; // ビット OR で整数に強制変換
y = y|0;
return (x + y)|0;
}
|0 のヒントで型を事前確定すれば、ブラウザが機械語へ最適コンパイル可能。Emscripten が C/C++ を asm.js に変換し、Unreal Engine がブラウザで動いた。
ただし asm.js には限界があった。パースコスト(依然テキスト)、可読性ゼロ、仕様が非公式。
Wasm 1.0 (2017, W3C 標準)
ブラウザベンダ 4 社(Mozilla、Google、Microsoft、Apple)が集まり公式バイトコードを設計。2019 年に W3C 勧告。名称は WebAssembly。
- バイナリフォーマット(高速パース)
- 静的型による安全性
- スタックベース仮想マシン
- 単一の線形メモリとコールスタックの分離
2. Wasm モジュールの解剖
最小例
(module
(func $add (param $a i32) (param $b i32) (result i32)
local.get $a
local.get $b
i32.add
)
(export "add" (func $add))
)
WAT (WebAssembly Text format) で記述。コンパイルすると .wasm バイナリになる。ホストから add(3, 5) を呼べる。
セクション構造
.wasm はセクションの連なりだ。
[0] Custom - メタデータ
[1] Type - 関数シグネチャ一覧
[2] Import - 外部から取り込む項目
[3] Function - 関数と型インデックスの対応
[4] Table - 関数ポインタテーブル
[5] Memory - 線形メモリ定義
[6] Global - グローバル変数
[7] Export - 外部に公開する項目
[8] Start - モジュール初期化関数
[9] Element - Table 初期化
[10] Code - 関数本体 (バイトコード)
[11] Data - Memory 初期化
線形メモリ (Linear Memory)
Wasm プログラムはひとつの巨大な連続バイト配列をメモリとして見る。64KB 単位のページで、最小と最大を宣言する。
(memory 1 10) ;; 最小 1 ページ (64KB)、最大 10 ページ
C/C++/Rust が「ポインタ」と呼ぶものはこの配列への i32 オフセットに過ぎない。malloc も free も Wasm へコンパイルされたコードだ。
重要なのは、Wasm はこの配列の外へ決してアクセスできない点だ。ホストのメモリにも、他モジュールのメモリにも触れない。これが Sandbox の第一防衛線となる。
スタックマシン
Wasm はスタックベース VM。
local.get $a ;; スタックに a を push
local.get $b ;; スタックに b を push
i32.add ;; 2 つを pop、加算して push
レジスタベースと違い、スタック命令は短く、JIT/AOT の最適化余地が大きい。
3. Sandbox の数学的保証
なぜ Wasm は安全か
- メモリ隔離 — 線形メモリ外にアクセス不可
- Control-flow 整合性 — 関数ポインタは Table 経由のみ
- デフォルトでシステムコール不可 — import された関数のみ呼び出せる
- 決定論的 — 同一入力から同一出力(NaN ビットの正規化など)
Capability ベースセキュリティ
コンテナは「全権限 ON → 危険なものだけ OFF」(deny-list)。Wasm は逆で、「何もできない → 明示的に Capability を渡せば可能」(allow-list)。
ファイル読み込みの例では、モジュールはデフォルトでファイルシステムに触れない。ホストが fd_read を import に提供し、ホストが開いた fd のみアクセスできる。../etc/passwd のような経路操作も、ホストがどのディレクトリを root fd として渡したかで制限される。
コンテナ vs Wasm セキュリティ比較
| 観点 | コンテナ | Wasm |
|---|---|---|
| 隔離方式 | Linux namespace/cgroup | 仮想マシン + 型安全 |
| デフォルト権限 | 多い (deny-list) | なし (allow-list) |
| 脱出攻撃面 | カーネル CVE | VM 仕様の形式検証 |
| システムコール | 数百 | import されたもののみ |
| 起動時間 | 100ms から数秒 | 1ms 未満 |
Fastly は、発見された Wasm サンドボックス脱出は「他の Wasm モジュールへの攻撃」ではなく「Wasmtime バグで OOM」程度だと報告している。
4. WASI — ブラウザの外へ
問題
初期の Wasm はブラウザ API (fetch、DOM) に依存しており、サーバでファイルを読んだりソケットを開いたりする標準が存在しなかった。
WASI 0.1 (2019) — POSIX 風
Mozilla の Lin Clark が主導。POSIX 風のシステムインターフェースを Wasm に。
wasi_snapshot_preview1
- fd_read / fd_write
- fd_seek
- path_open
- clock_time_get
- random_get
- args_get / environ_get
ファイルディスクリプタはホストが明示的に渡したものだけ使える (Capability)。
WASI 0.2 (2024) — Component Model ベース
0.1 の限界は同期 POSIX のみ、HTTP/sockets 標準の不在、C スタイル固定インターフェース。
0.2 は Component Model の上に再構築された。
wasi:http— HTTP 要求/応答wasi:sockets— TCP/UDPwasi:io/streams— 非同期ストリームwasi:cli— CLI 用 stdio
言語中立のインターフェース定義 (WIT)。
WIT の例
package wasi:http@0.2.0;
interface types {
variant method {
get,
post,
put,
delete,
other(string),
}
record request {
method: method,
uri: string,
headers: list<tuple<string, string>>,
body: option<body>,
}
}
Rust、Go、JavaScript、Python のそれぞれにバインディング生成可能で、言語間の標準 API となる。
5. Component Model — 言語中立の鍵
モジュールだけでは足りない
Wasm 1.0 モジュールは数値とバイトしかやりとりできない。文字列、配列、構造体は線形メモリのオフセットで直接調整する必要があり、言語ごとに規則が違うため相互運用が悪夢だった。
例: Rust から JavaScript への文字列受け渡し。Rust は UTF-8 のポインタと長さ、JS は UTF-16。変換コードは手書きだった。
Component Model (2024 安定化)
Wasm の上に「コンポーネント」という上位単位を置く。コンポーネント間で以下が可能になる。
- 高水準型 (string、record、variant、list、option、result)
- 自動 ABI 変換
- import/export を WIT で宣言
利点
- 言語 X で作ったコンポーネントを言語 Y のコンポーネントに挿せる
- WIT インターフェースの semver によるバージョン管理
- warg.io のようなコンポーネントパッケージレジストリ
実例として、Rust 製の画像処理コンポーネントを Go 製 HTTP ハンドラが import し、JavaScript 製オーケストレータが組み立てるといった構成。「npm + Docker + gRPC の融合」と評される。
6. Wasm ランタイム三傑
Wasmtime (Bytecode Alliance)
- エンジン: Cranelift
- 言語: Rust
- WASI 標準実装のリファレンス
- 安定性とセキュリティ監査品質が最上級
wasmtime run module.wasmの一行で動く- Fastly Compute のバックエンド
Wasmer
- 言語: Rust
- コンパイラ差し替え可 — Singlepass、Cranelift、LLVM
- wapm パッケージマネージャ
- C、Go、Python など多様な埋め込み API
- クラウド版 Wasmer Edge を提供
WasmEdge (CNCF Sandbox)
- 言語: C++
- AI ワークロード特化 — Tensor 演算、ONNX
- Docker + Wasm 統合 (runwasi) で活発
- 組み込み/IoT 対応が強い
性能比較 (2025 Bytecode Alliance ベンチ)
| ランタイム | Startup | Throughput | Memory |
|---|---|---|---|
| Wasmtime (Cranelift) | 0.3ms | 100% | 基準 |
| Wasmer (LLVM) | 2s コンパイル / 0.1ms 再実行 | 115% | 1.3x |
| WasmEdge | 0.5ms | 95% | 0.8x |
選定基準は、標準準拠なら Wasmtime、極限性能なら Wasmer LLVM、省メモリと AI なら WasmEdge。
7. Cloudflare Workers — Isolate が主、Wasm も
よくある誤解
「Cloudflare Workers = Wasm」と思われがちだが、実際は以下。
- 基本は V8 Isolate — JavaScript ランタイムを軽量隔離
- 1 Isolate あたり約 3MB メモリ、5ms コールドスタート
- Wasm は Isolate の中で動く
なぜ Isolate + Wasm か
- JavaScript 生態系 (npm) をそのまま使う
- 性能クリティカルな経路を Wasm で置き換える
- 例として Cloudflare Pages の画像変換は Rust を Wasm に
Fastly Compute@Edge — 完全 Wasm
- リクエスト毎に新しい Wasm インスタンス
- 言語: Rust、TinyGo、JS、AssemblyScript
- コールドスタート 35 マイクロ秒
サブミリ秒のコールドスタートは AOT コンパイル成果物の再利用と、Sandbox の軽さに由来する。
8. Kubernetes で Wasm を動かす
runwasi
containerd の shim に入り、OCI イメージとして梱包された Wasm をコンテナのように実行する。既存の Kubernetes 生態系にそのまま乗せられる。
apiVersion: v1
kind: Pod
spec:
runtimeClassName: wasmtime
containers:
- image: ghcr.io/example/hello-wasm:latest
name: hello
SpinKube (Fermyon, 2024)
Spin は Fermyon の Wasm フレームワーク。SpinKube は Kubernetes CRD として Wasm アプリを扱う。
利点は Pod が数ミリ秒で立ち上がること、Deno/Node よりはるかに小さなメモリ、常時起動のような Serverless。欠点はデバッグ道具の未熟さ、すべてのライブラリが Wasm ビルド対応ではないこと、WASI 0.2 ネイティブ対応が過渡期であること。
KWasm (Liquid Reply)
DaemonSet として Wasm ランタイムをノードに配る。既存クラスタを Wasm 対応にアップグレードできる。
9. 言語ごとの Wasm 事情
Rust — ゴールデンスタンダード
cargo build --target wasm32-wasiwasm-bindgenでブラウザ JS FFIwit-bindgenで Component Model- クレイト生態系の多くが Wasm 対応
Go — TinyGo が救世主
- 標準 Go は GC や goroutine の大きなランタイムを含む
- Go 1.21 から
GOOS=wasip1 GOARCH=wasmで WASI 公式対応 - バイナリ数 MB は依然大きい
- TinyGo は LLVM ベースで数十 KB、ただし一部標準ライブラリ非対応
JavaScript/TypeScript — AssemblyScript
- TypeScript 文法で Wasm を書く
- 学習曲線は緩やかだが JS 生態系は丸ごと使えない
Python — Pyodide、py2wasm
- CPython を Wasm 化(10MB 超)
- Serverless には重いが教育やデータ可視化には有用
C/C++ — Emscripten、wasi-sdk
- asm.js 時代から対応
- Unity/Unreal の Wasm ビルドも
10. 性能 — 「ネイティブの 80%」は本当か
理論
Wasm バイトコードは実行時に JIT/AOT で機械語になる。同じ C ソースを Native (gcc) と Wasm (clang → wasm → Wasmtime AOT) で比較すると、多くのベンチでネイティブの 80 から 95 パーセント。
遅延要因
- 境界チェック — 線形メモリ参照ごとの bounds check。現代ランタイムは
mmapと guard page でほぼゼロオーバーヘッド - SIMD — 128bit Wasm SIMD が標準化。Rust の
std::simdの一部が対応 - Atomics とスレッド — 共有メモリとアトミック操作、Web Worker で並列
- GC 提案 — ホスト GC に依存しない Wasm 用 GC、TypeScript/Kotlin/Java を効率的に
現実 — ボトルネックは FFI
「Wasm がネイティブより遅い」ベンチはほぼ FFI 回数が多いケースだ。ホスト関数を頻繁に呼ぶと境界切り替えのコストがかかる。対策は、Wasm 内で可能な限り完結させる、バッチ呼び出し、共有メモリ IPC。
11. 実例 — 誰がどう使っているか
- Figma — QuickJS と Wasm でプラグイン、限られた Capabilities のみ公開
- Shopify Functions — 決済ロジックを Rust から Wasm でアップロード
- Adobe Photoshop Web — C++ Photoshop カーネルを Emscripten で Wasm へ
- 1Password — ブラウザ拡張の暗号化を Rust から Wasm へ
- Envoy Proxy — WASM フィルタで動的拡張 (Istio の標準機構)
12. 落とし穴と限界
スレッド
- メッセージベース通信が基本 (Web Worker)
- 再入不可なランタイムが多い
- goroutine や async Rust に比べ貧弱
ネットワーク
- WASI 0.2 で
wasi:sockets導入 - ブラウザは依然 fetch のみ (CORS、Same-Origin)
- サーバ Wasm のライブラリ対応もまちまち
ファイルシステム整合性
- Capability ベース fd は安全だが、経路ベースコードの移植は面倒
- POSIX
fork/exec不在 (意図的)
デバッグ
- DWARF 対応は向上しているが
gdb/lldb体験はまだネイティブ水準でない - Chrome DevTools の Wasm デバッグはソースマップに限界
- スタックトレースが読みにくい
バイナリサイズ
- Rust hello world: 約 200KB (wasm32-wasi)
- TinyGo: 約 50KB
- AssemblyScript: 約 10KB
- CPython: 約 10MB
エッジのコールドスタートには数十 KB が目標だが、Rust は panic チェーンだけでも太る。
13. セキュリティ考慮事項
- Wasm 自体は安全でも import は危険 — 「Wasm ランタイムとホストバインディングが安全であるべき」が正確
- DoS — 無限ループは Fuel または Epoch、メモリ暴走は Memory limit、スタックは Wasm 仕様による制限
- サイドチャネル — Spectre 系タイミング攻撃は可能、ランタイム側でタイマ精度を丸める
- サプライチェーン — コンポーネントレジストリ (warg) の署名検証、WIT 互換検証
14. 未来 — どこへ向かうか
近未来の標準: WASI 0.3 (非同期/ストリーム強化)、Exception Handling、Stack Switching、GC Proposal (Chrome 119+/Firefox 120+ 対応済)、Relaxed SIMD。
トレンド予測。
- Serverless ネイティブ — 起動重視の領域が Wasm へ
- プラグインシステム共通言語 — Kafka Connect、VSCode 拡張などが Wasm ベースに再誕
- Edge AI — WasmEdge と ONNX でエッジ推論
- Kubernetes の Pod 代替 — Wasm ワークロードが一部を置換
- ブラウザとサーバの統合 — 同一 Wasm モジュールを両方で実行
限界の実名: JavaScript 生態系の重力、POSIX コードベースの膨大さ、デバッグと観測性成熟にあと 2 から 3 年。
15. 実務チェックリスト 12 項目
- 使用ケースが明確なときのみ採用 — プラグイン、Serverless、高性能クライアント
- バイナリサイズ最適化 —
--release、wasm-opt -Os、strip - WASI 0.2 Component Model 基準で選定 — 将来互換性
- Wasmtime をリファレンスランタイムに
- Capability 最小原則 — 必要な fd のみ開く
- Fuel や Epoch で実行時間制限 — DoS 防御
wasm-optチェーン — 30% 以上のサイズ削減- 言語はチームの熟練度で選ぶ — Rust が最成熟、TinyGo は Go 親和
- ホットパスは Wasm、コールドパスは JS またはホスト — 混成設計
- 観測性 — ホストがログとメトリクス提供
- メモリ制限と OOM ハンドリングをホストで包む
- CI で複数ランタイム検証 — Wasmtime、Wasmer、WasmEdge
次回予告 — CDN とエッジコンピューティングのアーキテクチャ
Wasm が「サーバなきサーバ」のエンジンなら、それを世界に配るインフラが CDN とエッジネットワークだ。次回は、CDN の誕生 (Akamai 1998 から Cloudflare、Fastly まで)、Anycast ルーティング、キャッシュ階層戦略、Edge コンピューティング (Lambda@Edge vs Workers vs Compute@Edge)、キャッシュ無効化、DDoS 防御、画像/動画のエッジ処理、Zero Trust ネットワーク、CDN ベンダ比較 (Cloudflare/Akamai/Fastly/CloudFront) を扱う。
「インターネットは平らではない。地球の曲率に沿って CDN が敷かれ、その上を Wasm が駆け回る。」