はじめに
WebAssembly (略して Wasm) では WASI や WIT、 Component Model など様々な仕様があります。
それぞれが登場した背景、モチベーションなどを理解することでなんとなく概要を掴んでいくことができるのではないかと考えたため、過去・現在・未来と時間軸で整理してみました。
まず Wasm とその特徴に関して簡単に紹介した後、Wasm の過去として生まれた背景やモチベーションを紹介します。
そして現在の Wasm がなぜ注目を集めているのか、そして現在策定中の仕様と目指している未来について紹介します。
WebAssembly とはなにか
WebAssembly はスタックベースの仮想マシン用バイナリ命令フォーマットの仕様です。Wasm と略されます。
Wasm ファイル(Wasm モジュール)は一般に .wasm
という拡張子で表されるバイナリファイルで、Wasm ランタイムと呼ばれるスタックベースの仮想マシンによって実行されます。
Wasm は W3C によってその仕様が標準化されています。
Wasm は以下のような代表的な特徴を持っています。以下それぞれを紹介します。
- Language-independent (言語非依存)
- Portable/Platform-independent (ポータブル/プラットフォーム非依存)
- Safe (安全)
- Fast (高速)
Language-Independent
Wasm には Language-Independent (言語非依存) であるという特徴があります。
これは、Rust、Go、C/C++、Zig など、様々なプログラミング言語から Wasm モジュール .wasm
にコンパイルできることを意味しています。
特に、多くの言語のコンパイラ基盤として使用されている LLVM が Wasm をサポートしているため、Wasm にコンパイルできる言語の範囲が広がっています。
Portable/Platform-independent
Wasm のもう一つの大きな特徴は、Portable (ポータブル) であり、Platform-independent (プラットフォーム非依存) であることです。
Wasm モジュールは、特定のプラットフォームに依存していません。
例えば、サーバ上で Wasmtime という Wasm ランタイムを使用して動作させたり、Google Chrome の V8 エンジンなどのブラウザで実装されている Wasm ランタイムを利用してブラウザ上で動かすことが可能です。
また、Wasm は OS、 CPU アーキテクチャに依存していません。
OS (Windows、Mac、Linux) や CPU アーキテクチャ (x86-64、ARM) が異なる環境でも動作します。
Wasm ランタイムが OS や CPU アーキテクチャの差異を吸収しているため Wasm アプリケーションでは意識する必要がありません。
Safe
Wasm モジュールはサンドボックス内で動作するため、安全であるという特徴を持っています。
ここでいうサンドボックスとは以下のことを指しています。
- 与えられたホスト関数しか実行できない
- 与えられたメモリにしかアクセスできない
与えられたホスト関数しか実行できない
ホスト関数(Host functions) 1 とは Wasm ランタイムから Wasm モジュールに渡されるものです。
Wasm モジュールが入出力など外部システムへ影響を与える必要がある場合はこのホスト関数を経由する必要があります。
Wasm モジュールは利用するホスト関数を明示的に import として記述する必要があります。
これによって、そもそも危険なホスト関数を、 Wasm モジュールに渡さないようにしたり、明らかに過剰なホスト関数を要求している Wasm モジュールは怪しいと判断することができます。
例えば Wasm ランタイムが Wasm モジュールに対して getrandom
関数のみを提供する場合、この Wasm モジュールは getrandom
関数のみを利用でき、
open
や write
など他の関数を実行することはできない、といったような具合です。
与えられたメモリにしかアクセスできない
Wasm モジュールはプロセスの全メモリにアクセスできるわけではなく、割り当てられたメモリ領域内のみで操作を行うことができます。
このため、他のメモリ領域に秘密情報が存在していたとしても、Wasm モジュールはこれにアクセスすることができず、安全性が保たれます。
もちろん Wasm ランタイムにバグがあった場合はこの限りではありません。
Fast
Wasm は、ネイティブコードに近い速度を目指しています。
ブラウザでは、 JavaScript よりも高速に実行することが可能です。
ブラウザ外での使われ方ではよくコンテナと比較されていますが、コンテナよりも起動速度が速いとされています。
なぜ起動速度が速いかというと Wasm は Wasm ランタイムさえあれば動作するため依存がすくなく、 また Wasm モジュールは一般的にコンテナイメージよりもサイズが小さいため、ダウンロードやロードに時間がかからないこと、さらに Wasm はすべての読み込みを待たずに起動することができるためです。
JVM との違い
Wasm はしばしば JVM(Java Virtual Machine)と比較されます。JVM と似ている点は「Write once, run anywhere」という点です。
しかし、 Wasm は対応している言語が多い、一つの企業に独占されていない、 サンドボックス環境である、起動速度が速い、メモリフットプリントが小さい、言語仕様が小さい、という点で異なります。
実際に Wasm Module を作ってみる
ここでは、実際に簡単な Wasm モジュールを作成してみます。
Go言語でもバージョン1.21から後述する WASI をサポートしているので、Wasmtime などの Wasm ランタイムで動作する Wasm モジュールを作成することができます。
以下は Go 言語で記述されたシンプルな Hello World
と出力するプログラムです。
package main
import "fmt"
func main() {
fmt.Println("Hello World")
}
Go で Wasm モジュールをビルドする際には、GOOS=wasip1
という環境変数を設定します。これは WASI の preview 1 というスナップショットを意味しています。GOARCH
には wasm
を指定します。
ビルドされたWasmモジュールは、wasmtime
や wasmer
などのWasmランタイムを使用して実行することができます。以下はそのビルドと実行のコマンド例です。
# コンパイルして hello.wasm を出力
$ GOOS=wasip1 \
GOARCH=wasm \
go build -o hello.wasm \
main.go
# wasmtime(Wasm ランタイム)で hello.wasm を実行
$ wasmtime hello.wasm
Hello World
# hello.wasm はバイナリになっている
$ xxd hello.wasm | head
00000000: 0061 736d 0100 0000 00f2 8080 8000 0a67 .asm...........g
00000010: 6f3a 6275 696c 6469 64ff 2047 6f20 6275 o:buildid. Go bu
00000020: 696c 6420 4944 3a20 2243 4a48 3844 6c4c ild ID: "CJH8DlL
00000030: 3061 4f64 5578 3033 7679 7647 482f 4e36 0aOdUx03vyvGH/N6
00000040: 3732 4f46 7853 654c 7465 534a 5274 4945 72OFxSeLteSJRtIE
00000050: 7464 2f73 4c4b 7730 6b7a 544f 4b67 534c td/sLKw0kzTOKgSL
00000060: 4239 6768 6849 472f 5279 6644 4c44 5672 B9ghhIG/RyfDLDVr
00000070: 7941 2d70 3136 4f50 3255 6c50 220a 20ff yA-p16OP2UlP". .
00000080: 01d0 8080 8000 0e60 017f 017f 6000 017f .......`....`...
00000090: 6001 7f00 6002 7f7f 017f 6003 7f7e 7f01 `...`.....`..~..
Wasm の過去
Wasm はブラウザ上での Javascript よりも高速な実行環境を求めた結果として誕生しました。
2008年に JavaScript の JIT コンパイラが開発され、ブラウザ上での実行環境の高速化の歴史が始まりました。 しかし JavaScript は動的な言語であり、既にその高速化には限界があることが明らかになっていました。
そこで、異なるアプローチでブラウザでの高速な実行環境を開発する試みが 2 つ始まりました。一つは Google が主導する NaCl(Native Client)プロジェクトで、もう一つは Emscripten と asm.js の組み合わせです。
NaCl
NaClは、プロセスのサンドボックス下でネイティブコードを実行し、高速化と安全性を実現することを目的としていました。
しかし、このプロジェクトは 2 つの問題に直面しました。
一つは、当時既にレガシーとされていたプラグインAPIに依存しているという問題で、結果的にコミュニティにうまく受け入れられませんでした。
もう一つはポータビリティの問題で、 x86 や ARM など CPU アーキテクチャごとに異なるマシンコードが必要であったことです。
Emscripten + asm.js
NaCl と同時期に、 Mozilla の Alon Zakai によって始められたのが Emscripten です。
Emscripten は Alon Zakai が趣味で開始したものだったのですが、後に雇用主の Mozilla のプロジェクトとして行うようになりました。
Emscripten は C++ コードを JavaScript コードにコンパイルするツールです。
asm.js は JavaScriptのサブセットで、型を強制することにより最適化を可能にするものです。しかし、asm.js もまた、拡張性に関する問題や、 JavaScript のシンタックスに依存しているためパースが遅いという問題がありました。
asm.js は以下のようなイメージです。
function add(x, y){
x = x | 0; // `| 0` (or 0)を付けることで値を変えずに
y = y | 0; // 符号付き32bit整数型であることを型証明&強制している。
return x + y | 0; // 戻り値も同様
}
asm.js はこのように型アノテーション(| 0
) をつけて、値を変えずに、符号付き32bit整数型であることを型宣言&強制しています。
これによって型が定まるので最適化が可能となるため高速に動作させることができるのが asm.js です。
JavaScript のシンタックスの範囲内で型アノテーションをつけるというのが個人的に面白いなと思いました。
Google と Mozilla が協力
2013年に Google と Mozilla で協力を開始し、2015年には共同で WebAssembly プロジェクトを立ち上げました。
なので Wasm は NaCl と Emscripten + asm.js の良い点を取り入れています。
Wasm は asm.js のように JavaScript ベースとせず、 NaCl のようにバイナリコードベースとしています。
asm.js の良い点であるDOMとして同じプロセスで動作すること、そしてJavaScriptを通じてWeb APIを直接呼び出せる点を取り入れています。
過去の WebAssembly 利用例
Wasm はブラウザの高速化のために生まれた技術でした。
この当時の WebAssembly の利用例はブラウザにとどまります。
代表的な利用例としては Unity などのゲームエンジンや、 Google Earth が上げられます。
これらのアプリケーションは Wasm の高速性を活用してブラウザ上でよりリッチな体験を提供する事ができました。
Wasm の現在
Wasmは現在、ブラウザ外での実行環境として注目を集めています。
それはどういう経緯なのかを見ていきます。
WASI (WebAssembly System Interface) の登場
2019年には WebAssembly System Interface、略して WASI がアナウンスされました。WASI はWasm がシステムと会話するためのインターフェースを標準化するもので、現在は preview 1という仕様が広く利用されています。
WASI が出来た背景
WASI が開発された背景には、Wasm がブラウザを超えて使用され始めたことがあります。Fastly 2 や Node.js などが実際に Wasm をブラウザ外で利用し始めていたため、ブラウザ外のシステムと会話する方法が必要とされ、システムインターフェースの標準化が進められました。
WASI の具体例としては、fd_write
(FileDescriptorに書き込む)、random_get
(ランダムな値をバッファに入れる)、environ_get
(環境変数を取得する)などがあります。
詳細はWebAssembly/WASI の仕様を御覧ください。
WASI によるポータビリティ
WASI により、Wasm のポータビリティの特徴を損なうことなくシステムとの会話が可能になります。
WASI はシステムコールの抽象化のようなものです。
Wasm のレイヤから OS ごとのシステムコール、 Windows だったら Windows API などと呼び分ける必要はありません。
Wasm モジュールは WASI を呼び出せばよく、 Wasm ランタイムが OS ごとの差異を吸収してくれています。
WASI による安全性
Wasm のサンドボックスの特徴で説明したように、Wasm モジュールは利用する依存関係を明示的にインポートする必要があります。WASI の関数に関しても同様で、インポートされていないものは使用できません。
そのため Wasm のサンドボックスの特徴によるセキュリティも WASI にそのまま適用できます。
Wasm ユースケースの変化
WASIのアナウンスにより、Wasmのユースケースはブラウザ外へと広がりました。
Docker の創始者は、2008年に WASM と WASI があれば Docker を作る必要がなかったと述べています。
If WASM+WASI existed in 2008, we wouldn't have needed to created Docker. That's how important it is. Webassembly on the server is the future of computing. A standardized system interface was the missing link. Let's hope WASI is up to the task! https://t.co/wnXQg4kwa4
— Solomon Hykes / @shykes@hachyderm.io (@solomonstre) March 27, 2019
具体的には、アプリケーションの実行環境としては Azure では AzureKubernetesService が Wasm の実行をサポートしていたり、Fastly では Edge コンピューティング環境での Wasm の実行をサポートしていたり、 Fermyon という Wasm を動かす PaaS もあります。
また、プラグインシステムとしての利用例として Envoy が対応していたり、 kube-scheduler では採用を検討していたりします。
こちらに関しては プラグイン実行エンジンとしてのWasm も参考になるかと思います。
Wasm の未来
Wasm は将来、ソフトウェア開発のエコシステムに革命をもたらす可能性を秘めています。
特に注目されているのが、現在策定中の Component Model という仕様です。
Component Model
Component Model は、Wasm モジュール間の連携を可能にするための追加仕様です。
Component Model は2つの要素から構成されています。
一つ目は WIT(WebAssembly Interface Type)で、もう一つはそのバイナリ表現である Canonical ABIです。
Component Model の背景
Component Model は、Wasm モジュールの再利用性を高めるために仕様の策定が進められています。
実は Wasm Core の仕様は型が少なくinteger, float それぞれ 32bit, 64bit の 4 つと、 SIMD 用の 128bit の vector 型のみです。
これではモジュール間で文字列や構造体などの複雑なデータ型の受け渡しをするのが困難というのが課題でした。
これを解決するために、WebAssembly Interface Type (WIT) が開発されました。
WIT (WebAssembly Interface Type)
WIT は、Wasm モジュールが何をインポートし、何をエクスポートしているのかを記述できる IDL (Interface Definition Language) です。 ProtocolBuffer のようなものを想像すればわかりやすいのではないかなと思います。
これにより、String、Struct、List などのリッチなデータ型を Wasm Core の基本型以外で使用できるようになります。
例えば、次の WIT の例では my-world
というコンポーネントが log
関数(stringをパラメータとして受け取る)をインポートし、 run
関数をエクスポートしています。これにより、外部から log
関数を提供することを期待し、外部から run
関数を呼び出せることを表現しています。
package local:demo
world my-world {
import log: func(message: string)
export run: func()
}
Component を組み合わせる
各 Component が WIT で import, export を定義しているので、それを満たすように Component を組み合わせてあげることが必要になります。
下の例では、右側の app というコンポーネントが add
, log
という関数を import しているので、
add
関数を export している calc
というコンポーネント、
log
関数を export している logger
というコンポーネントを渡すことで
app の依存を満たしています。
Component Model と Bytecode Alliance
Component Model が解決しようとしている問題については、Bytecode Alliance のブログポストが参考になります。
Bytecode Alliance は 2019年に設立された団体です。
初期メンバーは Mozilla, Fastly, Intel, Red Hat です。
Bytecode Alliance の目的は WebAssembly, WASI の標準の実装や提案を行って、ブラウザ外の WebAssembly の未来を作ることです。
また、 Wasmtime など主要な Wasm ランタイムなどのメンテナンスを行っております。
Bytecode Alliance の考える課題
Bytecode Alliance は現在のソフトウェア開発では、開発したソフトウェアの 80%が他人のコード(ライブラリ)で構成されており、これによりセキュリティ上のリスクが生じると主張しています。
現在のソフトウェアの実行の仕組みではソフトウェアに与えられる権限は、依存しているライブラリにも同様に等しく渡されるため、これらのライブラリが悪意を持っている、もしくは脆弱性がある可能性があり危険であるということを主張しています。
確かに、ライブラリに悪意あるコードが紛れ込ませられていたという問題は見聞きしたことがありますし、ライブラリの脆弱性が報告されたためアップデートする必要が発生するというのはよくあることのように思います。
Component Model が解決する問題
Component Model は、Wasm モジュール間の相互運用性を高めることによって、この問題を解決します。
Wasm モジュールの相互運用性が向上することによって Wasm モジュールをライブラリとして使いやすくなります。
Wasm モジュールをライブラリとして利用できるようにすると、 Wasm の特徴である安全性によってこの問題を解決できます。
Wasm の特徴で話したように Wasm モジュールのメモリ空間は他と別れています。
なので重要な情報をアプリケーションが持っていても、それを渡さなければ悪意のあるライブラリはそこにアクセスができません。
また、Wasm モジュールはそれぞれ独立したサンドボックスで動作し、インポートされた外部依存しか利用できません。
なので重要な外部依存(e.g. ファイルアクセス、ネットワークなど)をリスクの高いコンポーネントに渡さないということも将来的に可能になるそうです。
また、Component Modelにより、プログラミング言語のサイロ化を解決することができます。
いままでは基本的には様々な言語で同様なライブラリが作られていました。
しかし Component Model により、異なるプログラミング言語で作成された Wasm モジュールを組み合わせて使用することが可能になります。
これにより、例えば Go で作成されたモジュールを C++ やRustから使用することが可能になります。
まとめ
本記事では、WebAssembly (Wasm) の概要、生まれた背景、現在注目されている理由、WASI、Component Model とその未来について見ていきました。
Wasm は多言語対応、プラットフォーム非依存、安全性、高速性など多くの特徴を持ち、これらの特徴が組み合わさって独自の技術エコシステムを築いていっています。
特に、WASIの導入によりブラウザ外での使用が可能になり、Wasm の用途はさらに拡大していますし、 最近は WASI の一部として進められている wasi-nn を利用して LLM の実行環境として利用する例なども流行っている印象です。
また Component Model などの新しい仕様は、Wasmモジュール間の相互運用性を高め、サードパーティライブラリのセキュリティ問題を緩和し、プログラミング言語のサイロ化を解決して開発のエコシステムを変える可能性があるかもしれません。
これからの Wasm の発展に期待したいですね。
参考
- https://webassembly.org/
- https://wasi.dev/
- https://bytecodealliance.org/
- https://zenn.dev/hodagi/articles/4925afbeb3c4dc
- https://www.youtube.com/watch?v=XuZt1OCCQTg
- https://www.youtube.com/watch?v=6_BRLqxiZPU
- https://bytecodealliance.org/articles/announcing-the-bytecode-alliance
- https://component-model.bytecodealliance.org/
- https://github.com/WebAssembly/spec
- https://github.com/WebAssembly/WASI
- https://github.com/WebAssembly/component-model
- https://logmi.jp/tech/articles/324956