wasi:cli/commandをRustで動かすには
WebAssemblyランタイムの1つであるWasmtimeには、Rustにランタイムを埋め込むためのクレートがあります。そのクレートを使って、wasi:cli/commandワールドを実装したWasmコンポーネントを実行するまでをまとめました。
なお、この記事はWasmtime28.0.0を対象にしています。
TL;DR:
- wasmtime-wasiクレートもあわせて利用します
- WasmコンポーネントからCommand構造体を作成することで、詳細を隠蔽できます
実行用のプログラムの準備
テスト用に実行するwasi:cli/commandの実装を用意します。次のようなバイナリークレートをビルドして用意します:
利用するクレート
次のクレートを依存関係に追加します:
wasmtime
wasmtime-wasi
tokio
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