🎁

WebAssembly は次世代のコンテナ技術になれるか?

2024/04/23に公開
1

色々あって WebAssembly の component model を調べていたら、未来が見えた気がしたのでここに書いておきます。

「今の WebAssembly」 とは何か

WebAssembly の Web の部分は忘れてください。これは単に JVM version 20xx です。ポータブルなバイナリ仕様です。

実行にあたっては今はホスト言語として JS が使われていますが、実際にはホストがJSである必要すらなく、なんならホストが不要なスタンドアロン環境すらあります。(wasmtime/wasmer)

じゃあ WebAssembly は何かというと、サンドボックスで実行される VM の仕様です。比較的高水準なバイナリで、 V8 や Spider Monkey に付属する WebAssembly Runtime や、 Wasmtime や Wasmer といった WebAssembily 専用のランタイムがあります。

WebAssembly にサンドボックスがある、というより、デフォルトでは割り当てられたメモリに書き込むことと、数値またはポインタ(externref)を返すことしかできません。

WebAssembly でシステムコールを可能にする(ように見える) wasi は、単にシステムコールできるAPI群を特定の命名で import する仕様に過ぎません。そして wasi 実装の実体はシステムコールである必要ですらなく、そのエミュレータで構いません。

今の WebAssembly に期待されていること

最初期の WebAssembly は JSのサブセットを高速に実行する asm.js の反省として始まり、高速にパースが可能なローレベルなバイナリフォーマットとして始まりました。asm.js は単にJSのサブセットで生成コード量が大きく、パース時間が極悪でした。なので、最初期の wasm は Unity や UnrealEngine といった環境でコードを生成することを想定しています。

ですが、今の Wasm の用途は、サンドボックス化された安全なバイナリとしての用途が大きくなっています。

Docker のような仮想化はOSそのものをサンドボックスに閉じ込めていますが、 これは新しいインスタンスを起動するためのオーバーヘッドが大きく、スケールに難があります。 WebAssembly を実行対象にすれば、 wasm バイナリだけで完結します。

wasm をホスティングする PaaS はすでにいくつか出てきています。

https://wasmedge.org/

https://www.fermyon.com/spin

https://www.fastly.com/jp/products/compute

クラウドベンダーとしてこれが魅力的なのは、ユーザーコードをサンドボックス環境で動かせる点でしょう。

Cloudflare Workers は V8 Isolates をサンドボックスとして用いていて、その上に WebAssembly Runtime が存在していますが発想は同じです。

WebAssembly Ecosystem の展望

WebAssembly の持つ外部インターフェースは、だいたいJavaScript の ES Modules と同じです。外からの import と、外へ提供する export を持ちます。

WebAssembly テキストフォーマットの例で見ていきます。

(module
  (import "console" "log" (func $log (param i32 i32)))
  (import "js" "mem" (memory 1))
  (data (i32.const 0) "Hi")
  (func (export "writeHi")
    i32.const 0 ;; pass offset 0 to log
    i32.const 2 ;; pass length 2 to log
    call $log))

ただ、これだけだと数値しか受け渡せません。例えば、文字列を受け渡すには次のような処理が必要になります。

  • i32.store8 命令でメモリにバイト列を書き込む
  • export した関数A で文字列開始位置の offset を返す
  • export した関数B で文字列終了の length を返す
  • ホスト側でメモリの範囲をデコードする

現状でもできなくはないですが、面倒です。
この問題を解決するための仕様が component model と WIT IDL です。

Component Model

component model は、wasm のヘッダに構造体のインターフェースを書き込みます。

(component
  (component
    (core module (func (export "one") (result i32) (i32.const 1)))
    (core module (func (export "two") (result f32) (f32.const 2)))
  )
  (core module (func (export "three") (result i64) (i64.const 3)))
  (component
    (component
      (core module (func (export "four") (result f64) (f64.const 4)))
    )
  )
  (component)
)

https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md

これによって、文字列のような構造化データや JSON のような構造化データを入出力に使えるようになります。

また、これらの定義でバイナリエンコードする際の ABI の仕様もあります。

https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md

WIT IDL

WIT は component model を生成するための、可読性があるフォーマットです。

https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md

package local:demo;

interface host {
  log: func(msg: string);
}

Rust の Guest (使われる側) の実装が wit-bindgen
https://github.com/bytecodealliance/wit-bindgen

JS を host として使うときの実装が jco
https://github.com/bytecodealliance/jco

https://github.com/bytecodealliance/cargo-component と jco で簡単に wit を試すことができます。

Polyglot の未来

今はグルー言語として主にJSが使われていますが、そもそも wasm 同士で直接 import/export することを想定すると、ホスト言語は何であっても構いません。

将来的に、異なる言語から生成された .wasm の component-model を参照し、 wit を人間が読む IDL として、 wasm バイナリの組み合わせでアプリケーション全体が構築できるようになるはずです。そして、そのためのパッケージレジストリが生まれるはずです。 https://wasmer.io/products/registry がすでにそれっぽいですね。

.wasm を使う環境はブラウザでもクラウドでも CDN Edge でも何でもよく、ホストから呼ぶ際は wasi 等のインターフェースで特定のシステムコールを潰すことで安全性を担保できます。

「コンテナ技術を置き換えるための WebAssembly」 という側面が昨今は強く出ていて、セキュリティサンドボックスをこのレイヤーで全部になってしまおうという気配を感じます。

これらを実現する仕様群はすでに揃っていて、あとはそれぞれの実装の安定化とツーリングの問題というところまで来ている感じですね。

今の WebAssembly の問題

  • ネイティブバイナリに比べて低速
    • 同じ表現をしたとして、およそ C の 1.7~2.5倍ぐらい低速と言われている
    • 自分が聞いた話だとメモリアクセスの度にメモリ境界チェックが挟まって低速化している
    • 悪い発想だと、メモリ境界チェックをサボる WebAssembly Runtime で高速化の余地が残されている?
  • 生成コード量が大きくなりがち
    • 言語仕様がコンパクトな反面、それを生成するコードが大きくなりがち
    • ビルドごとにホスト言語のコアランタイムが include され、GC付き言語はいずれも生成量が厳しい
    • WebAssembly GC でだいぶ減らせるが、すべての言語のGCを代替できるようなものでものない
  • Thread の仕様が安定していない
  • 複雑化に伴ってランタイムごとの実行特性が顕著になりそう
    • どのランタイムを選ぶか問題

現状、言語ランタイムの重複は、ホスト側で一括でビルドすることである程度解決されます。例えば wasm-bindgen を使う際は rust crate を大量に読んだ状態でビルドされることが多いですが、これは単一の .wasm の最適化レベルが高い反面、ライブラリとして提供する際には他のバイナリとの重複が想定されます。

この問題のために、おそらく .wasm をビルドする仕様や、 依存の import/export 解析して treeshake する WebAssembly Bundle ツールが出現すると思っています。結局今のフロントエンドのバンドルツールと同じですね。

フロントエンドの人間としての発想だと、それぞれのライブラリは 10kb 未満であってほしいので、依存されるライブラリとしては、rust の no-std か wasm-gc 前提の専用言語が必要になる気がしてます。

まとめ

  • Wasm は実行環境を選ばないバイナリフォーマット
  • 今はおそらくコンテナ技術に矛先が向いている
  • component-model + WIT で構造化データをやりとりできる
  • 今後おそらく component-model をベースとした wasm 同士のimport/exportが可能になる
  • その上にホスト言語に関係ない .wasm 前提のエコシステムが生まれそう

Discussion