Nick Fitzgerald氏は先頃、wasmtimeにWebAssemblyのReference Typeプロポーザルを実装したと発表した。Reference Typeを使うことで、整数値や浮動小数点値に制限されることなく、複雑なホストオブジェクト(DOMノードやファイルハンドルなど)の参照を処理できるようになるだけでなく、インターフェースや型のインポート、ガベージコレクション、モジュールリンクなど、将来的なWebAssembly機能への道を開くことにもなる。
WebAssemblyの最初のバージョン(現在はWeb標準)は、ブラウザのコンテンツ内で使用するための、最小の実行可能な機能(MVP)として公開された。このバージョンのWebAssemblyは、整数型と浮動小数点型のみを理解する(numtype := i32 | i64 | f32 | f64
)。
そこで、ホスト言語の複雑な型をこれらの基本的な型に変換する、グルー(glue)コードを記述する必要が生じる。この処理は、ユーザが指定するアノテーションを通じて、コンパイラによって実行される場合もある(Rustの#[wasm_bindgen]
など)。このグルーコードは、モジュールが実行される可能性のあるホスト環境毎(Rust、C++など)に開発しなければならないため、Wasmモジュールの作者にとって、大きな労力を伴うものになる可能性がある。また一方では、Wasmモジュールの共用APIを基本型のみで構成しなければならないため、直感的でないインターフェースを公開することになり、これもまた、モジュール利用者にとって不便な点となる。
WebAssembly Interface Typeプロポーザルは、Wasmモジュールとホスト環境や他のモジュールとのやり取りに複雑な型を使用できるようにすることで、このようなWasmモジュールのユーザと作者双方の苦労を取り除こうというものだ。Interface Typeを使用するWasmモジュールは、中間表現(現在の固定的な基本型とは対照的な抽象型)として使用する型に対するマッピングを提供する。Wasmランタイムはこれら抽象型を使用して、指定されたホスト環境を対象としたコードを生成する。
WebAssembly Reference Typeプロポーザルは、ホスト環境との相互運用の、よりシンプルなユースケースに対処するものだ。Interface Typeに関する講義的なプレゼンテーションの中で、MozillaのLin Clark氏が、そのようなユースケースについて説明している。
JavaScipt関数からWebAssemby関数に文字列を渡して、それを別のJavaScript関数に渡すものとしましょう。この処理に必要なのは、次のようなことです。
- 最初のJavaScript関数が文字列をJSグルーコードに渡す
- JSグルーコードが文字列を複数の数値に変換した上で、その数値を連続するメモリに配置する
- 数値(文字列の先頭へのポインタ)をWebAssemblyに渡す
- WebAssembly関数がその数値を、逆方向のJSグルーコードに渡す
- 2番目のJavaScript関数がそれらの数値を連続メモリから引き出し、デコードして文字列オブジェクトに戻す
- その文字列を2番目のJS関数に与える
つまり、JSグルーコードの一方は、もう一方が行ったことを取り消しているだけなのです。基本的に同じオブジェクトを再生するために、多大な労力が払われていることになります。
Reference Typeでは、複雑なオブジェクト(Clark氏の例では文字列)をexternref
という不透明型(opaque type)としてWebAssemblyモジュールに渡したり、同じようにホスト関数に戻すことが可能になる。先に述べたような、複雑なプロセスを実行する必要はない。
Fitzgerald氏は次のような、オープンされたファイルへの参照を処理するWasmモジュールの例を提供している。
;; hello.wat
(module
;; Import the write syscall from a hypothetical (and
;; simplified) future version of WASI.
;;
;; It takes a host reference to an open file, an address
;; in memory, and byte length and then writes
;; `memory[address..(address+length)]` to the file.
(import "future-wasi" "write"
(func $write (param externref i32 i32) (result i32)))
;; Define a memory that is one page in size (64KiB).
(memory (export "memory") 1)
;; At offset 0x42 in the memory, define our data string.
(data (i32.const 0x42) "Hello, Reference Types!\n")
;; Define a function that writes our hello string to a
;; given open file handle.
(func (export "hello") (param externref)
(call $write
;; The open file handle we were given.
(local.get 0)
;; The address of our string in memory.
(i32.const 0x42)
;; The length of our string in memory.
(i32.const 24))
;; Ignore the return code.
drop))
上記のWasmモジュールがエクスポートするhello
関数は、ホストの提供するファイルハンドルを使って、ホストの提供するwrite
関数をコールする。hello
関数の最初のパラメータはparam externref
と記述されていて、local.get 0で取得する。
Wasmモジュールは$write
関数をインポートしているが、この関数の最初のパラメータ(ファイルハンドル)はexternref
と記述されている。$write
関数はexternref
ホスト参照と2つのパラメータ(Wasmモジュールのメモリ内にある書き込み文字列の先頭アドレス、文字列の長さ)を持って呼び出される。externref
は提供も利用もホスト環境によってそのまま行われるので、Wasmモジュールで複雑な操作を行う必要はない。
Fitzgerald氏はさらに、前述のWasmモジュールをPython、Rust、あるいはWebAssemb環境で、グルーコードを必要とせずに使用する例も提供している。
wasmtimeは、マシンコードにトランスパイル(JIT形式)したWasmコードを実行するWebAssembly用のスタンドアロンのランタイムで、さまざまなホスト言語(Rust、C、Python、.NET、Goなど)で使用することができる。wasmtimeを学ぶための出発点が、wasmtime guideとして提供されている。