wasm-encoder で WebAssembly を生成する

[wasm-tools](https://github.com/bytecodealliance/wasm-tools) に含まれる ```wasm-encoder``` を使うと WebAssembly や WebAssembly Component Model を直接生成する事ができます。

今回は wasm-encoder を使って単純な WebAssembly を生成してみました。

# はじめに

まずは、wasm-encoder を追加します。

```sh
$ cargo add wasm-encoder
```

wasm-encoder では生成した内容が（WebAssemblyとして）妥当かどうかをチェックしてくれないので wasmparser も追加しておきます。

```sh
$ cargo add wasmparser
```

Cargo.toml の内容は次のようになりました。

```toml:Cargo.toml
[package]
name = "sample"
version = "0.1.0"
edition = "2021"

[dependencies]
wasm-encoder = "0.31.1"
wasmparser = "0.110.0"
```

# WebAssembly の生成

次のような内容の WebAssembly を生成し、ファイルへ出力する処理を実装してみました。

* 2つの引数を合算して 2倍する関数を ```calc``` という名前でエクスポート

wasm-encoder では次のようにして WebAssembly を組み立てる事ができます。

* Module へ各セクションを追加して、```finish``` を呼び出すと WebAssembly のバイナリを生成
* TypeSection で型定義（今回は関数の型）
* FunctionSection で関数定義
* ExportSection でエクスポートの定義
* CodeSection で処理内容の定義（今回は関数の実装）

```rust:src/main.rs
use wasm_encoder::{
    CodeSection, ExportKind, ExportSection, Function, FunctionSection,
    Instruction, Module, TypeSection, ValType,
};

use std::env;
use std::fs::File;
use std::io::Write;

type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;

fn main() -> Result<()> {
    let mut module = Module::new();

    let mut types = TypeSection::new();
    // 関数の引数と戻り値の型を定義
    types.function(vec![ValType::I32, ValType::I32], vec![ValType::I32]);
    
    module.section(&types);

    let mut funcs = FunctionSection::new();
    funcs.function(0);

    module.section(&funcs);

    // calc という名前で関数をエクスポート
    let mut exports = ExportSection::new();
    exports.export("calc", ExportKind::Func, 0);

    module.section(&exports);

    // 処理内容の定義
    let mut codes = CodeSection::new();
    let mut calc = Function::new(vec![]);

    calc.instruction(&Instruction::LocalGet(0));
    calc.instruction(&Instruction::LocalGet(1));
    calc.instruction(&Instruction::I32Add);
    calc.instruction(&Instruction::I32Const(2));
    calc.instruction(&Instruction::I32Mul);
    calc.instruction(&Instruction::End);

    codes.function(&calc);
    module.section(&codes);

    // WebAssembly 生成
    let wasm_b = module.finish();
    // 検証
    wasmparser::validate(&wasm_b)?;

    let file_name = env::args().nth(1).unwrap_or("sample.wasm".to_string());

    let mut file = File::create(file_name)?;
    // ファイルへ出力
    file.write_all(&wasm_b)?;

    Ok(())
}
```

次のようにビルド・実行すると ```sample.wasm``` ファイルが生成されました。

```
$ cargo run
```

# 動作確認

sample.wasm の動作確認のために次のような Deno 用のスクリプトを用意しました。

```javascript:run_wasm.js
import { readAll } from "https://deno.land/std@0.197.0/streams/read_all.ts"

const file = await Deno.open(Deno.args[0])

const func = Deno.args[1] ?? 'calc'
const param1 = parseInt(Deno.args[2] ?? '10')
const param2 = parseInt(Deno.args[3] ?? '20')

const buf = await readAll(file)

file.close()

const instance = (await WebAssembly.instantiate(buf, {})).instance
// エクスポート関数の実行
const res = instance.exports[func](param1, param2)

console.log(res)
```

実行結果は次の通りで、sample.wasm は特に問題なく動作しました。

```sh
$ deno run --allow-read run_wasm.js sample.wasm calc 2 3
10
```

```sh
$ deno run --allow-read run_wasm.js sample.wasm calc 6 7
26
```