wit-bindgen で WebAssembly Component Model のコンポーネントを作ってみる

[wit-bindgen](https://github.com/bytecodealliance/wit-bindgen) で WebAssembly Component Model のコンポーネントを作って実行してみました。

WebAssembly Component Model は、WebAssembly で作ったコンポーネントを組み合わせてアプリケーションを作るための仕組みで、基本的には WIT（Wasm Interface Type）と呼ばれる IDL を使ってコンポーネントのインターフェースを定義するようになっているようです。

個人的には、以前からマイクロサービスの次は WebAssembly によるビジネスロジック等のコンポーネント化が来るだろうなと考えていたので、この技術には期待しています。

# (a) コンポーネントの作成

とりあえず現時点では、次のような手順でコンポーネントを作れば良さそうです。

1. wit-bindgen を使ってコンポーネントの元となる WebAssembly を作成
2. 作成した WebAssembly を [wasm-tools](https://github.com/bytecodealliance/wasm-tools) で処理してコンポーネントを作成

## WIT の作成

wit-bindgen はデフォルトで ```./wit``` ディレクトリを参照するようになっているようなので、そこへ次のようなファイルを作成しました。

import で外部からインポートする関数を、export で外部へエクスポートする関数を定義します。

なお、今回のバージョンでは関数名の一部に ```-``` や ```_``` を使うと上手くいかなかったので使わないようにしています。

##### [sample/wit/sample.wit](https://github.com/fits/try_samples/tree/master/blog/20230225/wasm-component-model/sample/wit/sample.wit)
```wit
default world sample {
    enum log-level {
        debug,
        info,
    }

    record item {
        id: string,
        value: u32,
    }

    import log: func(level: log-level, msg: string)
    import getitem: func(id: string) -> item

    export run: func()
}
```

## 実装

```wit_bindgen::generate!``` マクロで WIT ファイルに基づいたコードを生成してくれるので、これを使ってコンポーネントの処理を実装します。

##### [sample/src/lib.rs](https://github.com/fits/try_samples/tree/master/blog/20230225/wasm-component-model/sample/src/lib.rs)
```rust
wit_bindgen::generate!("sample");

struct Component;

impl Sample for Component {
    // エクスポートする関数の実装
    fn run() {
        // インポート関数 log の呼び出し
        log(LogLevel::Debug, "called run");
        // インポート関数 getitem の呼び出し
        let r = getitem("item-1");

        log(LogLevel::Info, &format!("item: {:?}", r));
    }
}

export_sample!(Component);
```

## ビルドとコンポーネント作成

次のような Cargo.toml を使用しました。

##### [sample/Cargo.toml](https://github.com/fits/try_samples/tree/master/blog/20230225/wasm-component-model/sample/Cargo.toml)
```toml
[package]
name = "sample"
version = "0.1.0"
edition = "2021"

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

[dependencies]
wit-bindgen = "0.3"

[profile.release]
opt-level = "s"
lto = true
```

今回は WASI を使っていないので ```wasm32-unknown-unknown``` でビルドします。

##### ビルド例
```sh
$ cargo build --release --target wasm32-unknown-unknown
```

ビルド結果の WebAssembly を ```wasm-tools component new``` で処理してコンポーネントを作成します。

##### コンポーネント作成例
```sh
$ wasm-tools component new target/wasm32-unknown-unknown/release/sample.wasm -o sample-component.wasm
```

作成された ```sample-component.wasm``` がコンポーネントとなります。

# (b) コンポーネントの実行

作成したコンポーネントを実行してみます。

現時点では WebAssembly Component Model に対応したランタイムを使う必要があるようなので、今回は [wasmtime](https://github.com/bytecodealliance/wasmtime) を使いました。

wasmtime v6.0 では実行できなかったので github から取得（現時点では v7.0）するようにしました。

また、コンポーネントを実行するには features で ```component-model``` を指定する必要がありました。

##### [run_component/Cargo.toml](https://github.com/fits/try_samples/tree/master/blog/20230225/wasm-component-model/run_component/Cargo.toml)
```toml
[package]
name = "run_component"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1"
wasmtime = { git = 'https://github.com/bytecodealliance/wasmtime', features = ['component-model'] }
```

wit_bindgen と同様に ```wasmtime::component::bindgen!``` マクロで WIT ファイルに基づいたコードを生成してくれるので、それを使って実装します。

こちらもデフォルトでは ```./wit``` ディレクトリを参照するようになっていますが、```in``` を使って任意のディレクトリを指定できます。

コンポーネントを実行するには Config の ```wasm_component_model``` を true に設定します。

##### [run_component/src/main.rs](https://github.com/fits/try_samples/tree/master/blog/20230225/wasm-component-model/run_component/src/main.rs)
```rust
use anyhow::Result;
use wasmtime::component::{Component, Linker};
use wasmtime::{Config, Engine, Store};

use std::env;

wasmtime::component::bindgen!("sample" in "../sample/wit");

#[derive(Default)]
struct State {}

// コンポーネントがインポートする関数の実装
impl SampleImports for State {
    fn log(&mut self, level: LogLevel, msg: String) -> anyhow::Result<()> {
        println!("[{:?}] {}", level, msg);
        Ok(())
    }

    fn getitem(&mut self, id: String,) -> wasmtime::Result<Item> {
        Ok(Item { id: id.clone(), value: 123 })
    }
}

fn main() -> Result<()> {
    let file = env::args().skip(1).next().unwrap_or_default();

    let mut config = Config::new();
    // コンポーネントの有効化
    config.wasm_component_model(true);

    let engine = Engine::new(&config)?;

    let component = Component::from_file(&engine, file)?;

    let mut linker = Linker::new(&engine);

    Sample::add_to_linker(&mut linker, |s: &mut State| s)?;

    let mut store = Store::new(&engine, State::default());

    let (bindings, _) = Sample::instantiate(&mut store, &component, &linker)?;
    // コンポーネントの run 関数呼び出し
    bindings.call_run(&mut store)?;

    Ok(())
}
```

(a) で作成したコンポーネントを実行すると次のようになりました。

##### 実行例
```sh
$ cargo run ../sample/sample-component.wasm
```
```
[LogLevel::Debug] called run
[LogLevel::Info] item: Item { id: "item-1", value: 123 }
```