wasi:cli/commandをRustで動かすには
WebAssemblyランタイムの1つであるWasmtimeには、Rustにランタイムを埋め込むためのクレートがあります。そのクレートを使って、wasi:cli/commandワールドを実装したWasmコンポーネントを実行するまでをまとめました。
なお、この記事はWasmtime28.0.0を対象にしています。
TL;DR:
- wasmtime-wasiクレートもあわせて利用します
- WasmコンポーネントからCommand構造体を作成することで、詳細を隠蔽できます
実行用のプログラムの準備
テスト用に実行するwasi:cli/commandの実装を用意します。次のようなバイナリークレートをビルドして用意します:
利用するクレート
次のクレートを依存関係に追加します:
wasmtimewasmtime-wasitokio
wasmtime-wasiクレートはwasi:cli/stdinのようなwasi:cli/commandが依存するインターフェースの実装を提供します。またwasi:cli/commandの定義から生成されたラッパーであるCommand構造体も提供します。
Command構造体はいくつかの機能を非同期メソッドとして提供しています。この記事では非同期処理の処理系としてtokioを利用します。ただしtokioでなければならない理由はありません。任意の処理系を利用いただけます。
私の作成したプログラムでは、anyhowやclapを上記以外に利用しています:
Wasm処理のための基本的なツール
WasmtimeでWasmを処理するには、次の3つの構造体を利用します:
Storeオブジェクトはインスタンス化されたWasmモジュールやコンポーネントだけでなく、Wasmを実行するコード(ホスト側)の状態を表したオブジェクトも保持します。次の例では、ホスト側の状態をPreview2Hostという名前のオブジェクトで表しています:
wasmtime-wasiの利用
wasmtime-wasiクレートが提供する各インターフェースの実装を利用するには、add_to_linker_async関数、もしくはadd_to_linker_sync関数を利用してLinkerオブジェクトにインターフェースの実装を登録します:
add_to_linker_asyncはLinkerオブジェクトが内部で参照しているホスト側の状態を表したオブジェクトがWasiViewトレイトを実装していることを期待しています。最小の状態オブジェクトは次のように定義できます:
WasiCtxはWASIの実行コンテキストで、ResoruceTableがゲストコードから利用されているリソースと、その実体との対応表です。
WasiCtxオブジェクトはビルダーオブジェクトを利用して作成します。例えばinherit_stdioメソッドを呼ぶことで、ターミナルの標準入出力をWasmコンポーネントから利用できるようになります。
これにWasiViewトレイトを実装すると、次のようになります:
非同期処理の有効化
add_to_linker_async関数などの非同期処理を利用するためには、tokioなどの処理系を依存関係に追加する以外に、Engineオブジェクトも非同期処理を有効にします。非同期処理を有効にしたConfigオブジェクトを引数に指定してnew関数を呼ぶことで、非同期処理が有効になったEngineオブジェクトを作成できます。
Configオブジェクトは次のように作成できます:
Commandオブジェクトの作成
CommandオブジェクトはWasmコンポーネントを表すComponentオブジェクトから作成します:
Componentオブジェクトはバイト列やファイルから作成できます。ファイルからの作成は次のように行います:
まとめ
Wasmモジュールの実行とほぼ同じ流れで実行できました。コツは次のの3点でしょうか。
-
wasmtime_wasiを依存関係に追加する -
WasiViewトレイトを実装する -
async_supportを有効にしたEngineを作成する
preview1以前の場合は同期的なコードを書くこともできましたが、preview2ではWASI実装の関係で非同期処理向けのコードを書くこととなります。同期的なコードを書かなければならない場合は、prewview2をpreview1のインターフェースに変えるアダプターを使ってpreview1を使うコンポーネントを作成し、それを実行することになるでしょう。
Discussion