LoginSignup
1
0

wasm-toolsでWebAssemblyコンポーネントを合成する

Posted at

wasm-tools の compose コマンド(wasm-compose)を使った WebAssembly コンポーネントの合成を試してみました。

ここでは、itemapp という 2つの WebAssembly コンポーネントを作成し、wasm-compose を使って app へ item を合成します。

なお、WebAssembly コンポーネントの作成には cargo component を使用します。

item コンポーネント作成

item 側を作成します。

とりあえず、WIT はこのようにしてみました。
指定した商品の内容を取得する処理を itemfind インターフェースとして定義し、それを export しています。

item/wit/world.wit
package component:item;

interface itemfind {
    type item-id = string;
    type amount = s32;

    record item {
        id: item-id,
        price: amount,
    }

    find-item: func(id: item-id) -> option<item>;
}

world item {
    export itemfind;
}

固定の内容を返すように実装しました。

item/src/lib.rs
#[allow(warnings)]
mod bindings;

use bindings::exports::component::item::itemfind::Guest;
use bindings::exports::component::item::itemfind::{Amount, Item, ItemId};

struct Component;

impl Guest for Component {
    fn find_item(id: ItemId) -> Option<Item> {
        let price: Amount = 123;
        let item = Item { id, price };

        Some(item)
    }
}

bindings::export!(Component with_types_in bindings);

Cargo.toml はこのようになります。

item/Cargo.toml
[package]
name = "item"
version = "0.1.0"
edition = "2021"

[dependencies]
bitflags = "2.5.0"
wit-bindgen-rt = "0.24.0"

[lib]
crate-type = ["cdylib"]

[package.metadata.component]
package = "component:item"

[package.metadata.component.dependencies]

これをビルドして WebAssembly コンポーネントを作成しておきます。

itemのビルド
$ cd item
$ cargo component build --target wasm32-unknown-unknown --release

app コンポーネント作成

次に、app 側を作成します。

こちらの WIT では、item 側で export した itemfind を import します。

また、「cargo componentでWASI Preview 2を使ったコンポーネントを作成」と同じ使い方で WASI Preview 2 を利用しています。1

app/wit/world.wit
package component:app;

world app {
    import component:item/itemfind;

    import wasi:cli/stdout@0.2.0;

    export wasi:cli/run@0.2.0;
}

実装はこのようにしてみました。

itemfind インターフェースの find-item(Rust 上は find_item)で取得した item の内容を wasi:cli/stdout を使って標準出力しています。

app/src/lib.rs
#[allow(warnings)]
mod bindings;

use bindings::component::item::itemfind::find_item;
use bindings::exports::wasi::cli::run::Guest;
use bindings::wasi::cli::stdout::get_stdout;

struct Component;

impl Guest for Component {
    fn run() -> Result<(), ()> {
        let item = find_item(&"item-1".to_string()).ok_or(())?;

        log(&format!("id={}, price={}", item.id, item.price)).map_err(|_| ())?;

        Ok(())
    }
}

fn log(msg: &str) -> Result<(), Box<dyn std::error::Error>> {
    let stdout = get_stdout();

    stdout.write(msg.as_bytes())?;
    stdout.flush()?;

    Ok(())
}

bindings::export!(Component with_types_in bindings);

Cargo.toml はこのようになります。

item の world.wit を参照する必要があるので package.metadata.component.target.dependencies で設定しています。

app/Cargo.toml
[package]
name = "app"
version = "0.1.0"
edition = "2021"

[dependencies]
bitflags = "2.5.0"
wit-bindgen-rt = "0.24.0"

[lib]
crate-type = ["cdylib"]

[package.metadata.component]
package = "component:app"

[package.metadata.component.target.dependencies]
"component:item" = { path = "../item/wit" }

"wasi:cli" = { path = "./WASI/preview2/cli" }
...省略

こちらもビルドしておきます。

appのビルド
$ cd app
$ cargo component build --target wasm32-unknown-unknown --release

合成処理

それでは、本題の合成を実施します。

wasm-compose では YAML 形式の設定ファイルで合成内容を指定できます。

今回のケースでは次のような設定を行う必要がありました。
こうする事で、指定の import 箇所へ instance で指定した WebAssembly コンポーネントを合成します。

合成の設定内容
instantiations:
  root:
    arguments:
      <importの名称>:
        instance: <wasmファイル名(拡張子を除く)>

実際はこのようになります。
item.wasm ファイルを見つけられるように search-paths を設定しています。

config.yaml
search-paths:
  - ./item/target/wasm32-unknown-unknown/release

instantiations:
  root:
    arguments:
      component:item/itemfind:
        instance: item

instance 部分を省略する事も可能でした。

config.yaml(省略形)
search-paths:
  - ./item/target/wasm32-unknown-unknown/release

instantiations:
  root:
    arguments:
      component:item/itemfind: item

上記 config.yaml の内容で app.wasm へ合成した結果を compose.wasm ファイルへ出力してみます。

コンポーネントの合成
$ wasm-tools compose -c config.yaml -o compose.wasm app/target/wasm32-unknown-unknown/release/app.wasm

...省略
[2024-04-15T13:59:15Z WARN ] instance `wasi:cli/stdout@0.2.0` will be imported because a dependency named `wasi:cli/stdout@0.2.0` could not be found
composed component `compose.wasm`

WASI Preview 2 の合成は設定していないので2、WARN ログが出力されていますが特に問題ありません。

合成結果の確認

compose.wasm を確認してみます。

wasm-tools で WIT を出力してみると、component:item/itemfind の import が消えており、合成は成功しているように見えます。

WITの確認
$ wasm-tools component wit compose.wasm

package root:component;

world root {
  import wasi:io/error@0.2.0;
  import wasi:io/streams@0.2.0;
  import wasi:cli/stdout@0.2.0;

  export wasi:cli/run@0.2.0;
}

wasmtime で実行してみると、合成した item から取得した内容が無事に出力されました。

compose.wasm 実行(動作確認)
$ wasmtime compose.wasm

id=item-1, price=123

ちなみに、app.wasm を wasmtime で実行すると次のようなエラーになります。

app.wasm 実行
$ wasmtime app/target/wasm32-unknown-unknown/release/app.wasm

Error: failed to run main module `app/target/wasm32-unknown-unknown/release/app.wasm`

Caused by:
    0: import `component:item/itemfind` has the wrong type
    1: instance export `find-item` has the wrong type
    2: expected func found nothing
  1. wasm32-wasi でビルドして WASI Preview 2 へ自動適応させる方法もあるようですが、ここでは使っていません。また、次の Rust 1.78 から WASI Preview 2 に対応した wasm32-wasip2 ターゲットが導入されるようです。

  2. ランタイム側が提供してくれるので合成する必要がない

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0