LabHub

ブログ

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

한국어English日本語

はじめに — 「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 をエンジンにしている。

本稿で扱うテーマ。


1. 誕生 — asm.js が残した宿題

JavaScript の不運な寿命

2010 年代初頭、ブラウザは事実上「JavaScript しか走らない OS」だった。だが JS には問題があった。

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 オフセットに過ぎない。mallocfree も 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 は安全か

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)
脱出攻撃面カーネル CVEVM 仕様の形式検証
システムコール数百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 の上に再構築された。

言語中立のインターフェース定義 (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 の上に「コンポーネント」という上位単位を置く。コンポーネント間で以下が可能になる。

利点

  1. 言語 X で作ったコンポーネントを言語 Y のコンポーネントに挿せる
  2. WIT インターフェースの semver によるバージョン管理
  3. warg.io のようなコンポーネントパッケージレジストリ

実例として、Rust 製の画像処理コンポーネントを Go 製 HTTP ハンドラが import し、JavaScript 製オーケストレータが組み立てるといった構成。「npm + Docker + gRPC の融合」と評される。


6. Wasm ランタイム三傑

Wasmtime (Bytecode Alliance)

Wasmer

WasmEdge (CNCF Sandbox)

性能比較 (2025 Bytecode Alliance ベンチ)

ランタイムStartupThroughputMemory
Wasmtime (Cranelift)0.3ms100%基準
Wasmer (LLVM)2s コンパイル / 0.1ms 再実行115%1.3x
WasmEdge0.5ms95%0.8x

選定基準は、標準準拠なら Wasmtime、極限性能なら Wasmer LLVM、省メモリと AI なら WasmEdge。


7. Cloudflare Workers — Isolate が主、Wasm も

よくある誤解

「Cloudflare Workers = Wasm」と思われがちだが、実際は以下。

なぜ Isolate + Wasm か

Fastly Compute@Edge — 完全 Wasm

サブミリ秒のコールドスタートは 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 — ゴールデンスタンダード

Go — TinyGo が救世主

JavaScript/TypeScript — AssemblyScript

Python — Pyodide、py2wasm

C/C++ — Emscripten、wasi-sdk


10. 性能 — 「ネイティブの 80%」は本当か

理論

Wasm バイトコードは実行時に JIT/AOT で機械語になる。同じ C ソースを Native (gcc) と Wasm (clang → wasm → Wasmtime AOT) で比較すると、多くのベンチでネイティブの 80 から 95 パーセント。

遅延要因

  1. 境界チェック — 線形メモリ参照ごとの bounds check。現代ランタイムは mmap と guard page でほぼゼロオーバーヘッド
  2. SIMD — 128bit Wasm SIMD が標準化。Rust の std::simd の一部が対応
  3. Atomics とスレッド — 共有メモリとアトミック操作、Web Worker で並列
  4. GC 提案 — ホスト GC に依存しない Wasm 用 GC、TypeScript/Kotlin/Java を効率的に

現実 — ボトルネックは FFI

「Wasm がネイティブより遅い」ベンチはほぼ FFI 回数が多いケースだ。ホスト関数を頻繁に呼ぶと境界切り替えのコストがかかる。対策は、Wasm 内で可能な限り完結させる、バッチ呼び出し、共有メモリ IPC。


11. 実例 — 誰がどう使っているか


12. 落とし穴と限界

スレッド

ネットワーク

ファイルシステム整合性

デバッグ

バイナリサイズ

エッジのコールドスタートには数十 KB が目標だが、Rust は panic チェーンだけでも太る。


13. セキュリティ考慮事項

  1. Wasm 自体は安全でも import は危険 — 「Wasm ランタイムとホストバインディングが安全であるべき」が正確
  2. DoS — 無限ループは Fuel または Epoch、メモリ暴走は Memory limit、スタックは Wasm 仕様による制限
  3. サイドチャネル — Spectre 系タイミング攻撃は可能、ランタイム側でタイマ精度を丸める
  4. サプライチェーン — コンポーネントレジストリ (warg) の署名検証、WIT 互換検証

14. 未来 — どこへ向かうか

近未来の標準: WASI 0.3 (非同期/ストリーム強化)、Exception Handling、Stack Switching、GC Proposal (Chrome 119+/Firefox 120+ 対応済)、Relaxed SIMD。

トレンド予測。

  1. Serverless ネイティブ — 起動重視の領域が Wasm へ
  2. プラグインシステム共通言語 — Kafka Connect、VSCode 拡張などが Wasm ベースに再誕
  3. Edge AI — WasmEdge と ONNX でエッジ推論
  4. Kubernetes の Pod 代替 — Wasm ワークロードが一部を置換
  5. ブラウザとサーバの統合 — 同一 Wasm モジュールを両方で実行

限界の実名: JavaScript 生態系の重力、POSIX コードベースの膨大さ、デバッグと観測性成熟にあと 2 から 3 年。


15. 実務チェックリスト 12 項目

  1. 使用ケースが明確なときのみ採用 — プラグイン、Serverless、高性能クライアント
  2. バイナリサイズ最適化 — --releasewasm-opt -Os、strip
  3. WASI 0.2 Component Model 基準で選定 — 将来互換性
  4. Wasmtime をリファレンスランタイムに
  5. Capability 最小原則 — 必要な fd のみ開く
  6. Fuel や Epoch で実行時間制限 — DoS 防御
  7. wasm-opt チェーン — 30% 以上のサイズ削減
  8. 言語はチームの熟練度で選ぶ — Rust が最成熟、TinyGo は Go 親和
  9. ホットパスは Wasm、コールドパスは JS またはホスト — 混成設計
  10. 観測性 — ホストがログとメトリクス提供
  11. メモリ制限と OOM ハンドリングをホストで包む
  12. 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 が駆け回る。」

コメント

まだコメントはありません。

ログインするとコメントできます