LoginSignup
0
0

wasm-encoder で WebAssembly を生成する

Posted at

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
0
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
0
0