wasm-tools に含まれる wasm-encoder
を使うと WebAssembly や WebAssembly Component Model を直接生成する事ができます。
今回は wasm-encoder を使って単純な WebAssembly を生成してみました。
はじめに
まずは、wasm-encoder を追加します。
$ cargo add wasm-encoder
wasm-encoder では生成した内容が(WebAssemblyとして)妥当かどうかをチェックしてくれないので wasmparser も追加しておきます。
$ cargo add wasmparser
Cargo.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 で処理内容の定義(今回は関数の実装)
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 用のスクリプトを用意しました。
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 は特に問題なく動作しました。
$ deno run --allow-read run_wasm.js sample.wasm calc 2 3
10
$ deno run --allow-read run_wasm.js sample.wasm calc 6 7
26