wasmerでランタイム上からホスト関数を呼び出すメモ

注意) 絶対他に最適な方法があると思うんだけど, まぁ動くのでヨシ

コードはここ

やりたいこと

WASMランタイムからホストのメモリ領域のデータを取りたい!!!!!!

メモリ領域をいい感じに共有できる方法があると思うけど, どうすればいいのか分からない

そこでホストでデータを取ってくる関数を実装して, そいつを呼び出したらいいじゃんってなったので, とりあえず実装してみた話

ランタイムにはwasmerを使った

wasmプログラムの用意

プログラム書く前にwasm32-wasiのターゲットを追加しておきます

rustup target add wasm32-wasi

計算するだけのプログラムを書きます.

readがホスト環境で実装される関数です.

i64にキャストされたポインタが, 実行時にcalc関数に渡されるので, それをreadに渡してあげればホストからデータが取れます.

#![allow(unused)]
#![no_main]

fn main() {
extern {
    pub fn read(pointer: i64, offset: u8) -> i32;
}

#[no_mangle]
pub fn calc(pointer: i64, input: u8) -> i32 {
    let val = unsafe { read(pointer, 2) };
    input as i32 + val
}
}

コンパイルします. そのままじゃ"read"の実装がないやんけ!って怒られるので, --emit=objをつけて黙らせます

rustc -O --emit=obj  --target wasm32-wasi calc.rs

できたcalc.o(wasm)をwatに変換して見てみる

# wasm2wat calc.o
(module
  (type (;0;) (func (param i64 i32) (result i32)))
  (import "env" "__linear_memory" (memory (;0;) 0))
  (import "env" "read" (func (;0;) (type 0)))
  (func $calc (type 0) (param i64 i32) (result i32)
    local.get 0
    i32.const 2
    call 0
    local.get 1
    i32.const 255
    i32.and
    i32.add))

これでできてる気がしますが, exportがないのでランタイムからcalcを呼べません.

コンパイラくんにexportを付与してもらう方法が, 分からなかったので無理やりつけます.

wasm2wat calc.o | sed '$ s/)$/\(export "calc" \(func $calc\)\)\)/' > tmp.wat
wat2wasm tmp.wat -o calc.wasm

wasm2watとwat2wasmは, wabtにあります.

ランタイムを用意して実行

use std::fs::File;
use std::io::prelude::*;
use wasmer::{ Store, Module, Function, Memory, MemoryType, Instance, imports, Value };


fn main() {
    /**
     * wasmファイル読み込み
     */
    let mut f = File::open("../wasm/calc.wasm").unwrap();
    let metadata = std::fs::metadata("../wasm/calc.wasm").unwrap();
    let mut wasm = vec![0;metadata.len() as usize];
    f.read(&mut wasm).unwrap();

    /**
     * wasmから呼び出すホスト関数と仮データの用意
     */
    let mut data: Vec<u8> = Vec::new();
    data.push(0);
    data.push(1);
    data.push(2);
    data.push(3);
    let data_ptr = data.as_ptr();

    fn read(pointer: i64, offset: u8) -> i32 {
        unsafe {
            // 生ポインタに戻してアクセスする
            (*(pointer as *const u8).offset(offset as isize)) as i32
        }
    }

    /**
     * wasmer用意
     */
    // storeの役割がいまいちわからない
    // とりあえず分けてみた
    let store = Store::default();
    let read_fn_store = Store::default();
    let memory_store = Store::default();

    let module = Module::from_binary(&store, &wasm).unwrap();
    let read_fn = Function::new_native(&read_fn_store, read);
    let linear_memory = Memory::new(&memory_store, MemoryType::new(1, None, false)).unwrap();

    // ホストのread関数をimportしてあげる
    let import_object = imports! {
        "env" => {
            "read" => read_fn,
            "__linear_memory" => linear_memory,
        },
    };

    let instance = Instance::new(&module, &import_object).unwrap();

    /**
     * 実行フェイズ
     */
    let calc_fn = instance.exports.get_function("calc").unwrap();
    // 引数を設定
    let mut calc_args: Vec<Value> = Vec::new();
    calc_args.push(Value::I64(data_ptr as i64));
    calc_args.push(Value::I32(20));

    // 実行
    let result = calc_fn.call(&calc_args).unwrap();
    // result: 22 
    println!("result: {}", result[0].unwrap_i32());
}

おわりだよー