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()); }
おわりだよー