Closed12

Rustアプリにwasmerを埋め込む

nazo6nazo6
  • dioxusを使ってwebでもdesktopでも動くアプリを作りたい
  • プラグインシステムを作りたい
  • けどプラグインもRustで書きたい
  • → wasm
  • wasmerにはwasmer-jsというものがあってどうやらwasm環境でも動かせる?
nazo6nazo6

プロジェクト作成

構成はこんな感じにする

app
│ public
│ src
│ └ main.rs
│ Cargo.toml
└ Dioxus.toml
plugins
└ sample
  │ src
  │ └ lib.rs
  └ Cargo.toml
Cargo.toml

(どうでもいいけどこういうときvimだとファイラーのテキスト直接コピーできて便利だな)

app以下はdioxus-cliで作る

$ dioxus create
nazo6nazo6

ワークスペースのルートCargo.toml

root/Cargo.toml
[workspace]
resolver = "2"
members = ["app", "plugins/sample"]

ここでresolver = "2"を指定するのが重要。こうしないとcfgでfeature出し分けができない(超ハマった)

nazo6nazo6

appのCargo.toml

root/app/Cargo.toml
[package]
name = "<app name>"
version = "0.1.0"
edition = "2021"

[target.'cfg(target_arch = "wasm32")'.dependencies]
dioxus = { version = "0.1.8", features = ["web"] }
wasmer = { version = "2.0", features = [
  "js-default",
], default-features = false }
wasm-logger = "0.2.0"
console_error_panic_hook = "0.1.7"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
dioxus = { version = "0.1.8", features = ["desktop"] }
wasmer = { version = "2.0" }

これでビルド時に切り替えられる

参考:
https://docs.rs/crate/wasmer/2.2.0-rc1

nazo6nazo6

あれ?ワークスペースごとにターゲットの設定できるんだっけと思ったら案の定nightly限定機能みたいなのでしょうがないのでnightlyにする

https://github.com/rust-lang/cargo/issues/7004
https://doc.rust-lang.org/cargo/reference/unstable.html#per-package-target

workspace/rust-toolchain.toml
[toolchain]
channel = "nightly"
targets = ["wasm32-unknown-unknown"]
workspace/plugins/sample/Cargo.toml
cargo-features = ["per-package-target"]

[package]
name = "plugin-sample"
version = "0.1.0"
edition = "2021"
default-target = "wasm32-unknown-unknown"
[dependencies]

[lib]
crate-type = ["cdylib"]
nazo6nazo6

とりあえずプラグイン側だけビルドしてwatを見てみる

$ cargo build -p plugin-sample
$ paru -S wabm # wasm2watコマンドを入れる
$ wasm2wat ./target/wasm32-unknown-unknown/debug/plugin_sample.wasm > plugin_sample.wat

結果

plugin_sample.wat
(module
  (type (;0;) (func (param i32 i32)))
  (type (;1;) (func (param i32 i32) (result i32)))
  (type (;2;) (func))
  (import "env" "print_str" (func $print_str (type 0)))
  (func $_ZN4core3str21_$LT$impl$u20$str$GT$3len17hd6f34eff50e5c25bE (type 1) (param i32 i32) (result i32)
    (local i32 i32 i32 i32)
    global.get 0
    local.set 2
    i32.const 32
    local.set 3
    local.get 2
    local.get 3
    i32.sub
    local.set 4
    local.get 4
    local.get 0
    i32.store offset=8
    local.get 4
    local.get 1
    i32.store offset=12
    local.get 4
    local.get 0
    i32.store offset=16
    local.get 4
    local.get 1
    i32.store offset=20
    local.get 4
    local.get 0
    i32.store offset=24
    local.get 4
    local.get 1
    i32.store offset=28
    local.get 4
    i32.load offset=24
    drop
    local.get 4
    i32.load offset=28
    local.set 5
    local.get 5
    return)
  (func $_ZN4core3str21_$LT$impl$u20$str$GT$6as_ptr17hc3437732d3c877b4E (type 1) (param i32 i32) (result i32)
    (local i32 i32 i32)
    global.get 0
    local.set 2
    i32.const 16
    local.set 3
    local.get 2
    local.get 3
    i32.sub
    local.set 4
    local.get 4
    local.get 0
    i32.store offset=8
    local.get 4
    local.get 1
    i32.store offset=12
    local.get 0
    return)
  (func $hello_wasm (type 2)
    (local i32 i32 i32 i32 i32 i32 i32 i32 i32 i32)
    i32.const 0
    local.set 0
    local.get 0
    i32.load offset=1048592
    local.set 1
    i32.const 0
    local.set 2
    local.get 2
    i32.load offset=1048596
    local.set 3
    local.get 1
    local.get 3
    call $_ZN4core3str21_$LT$impl$u20$str$GT$6as_ptr17hc3437732d3c877b4E
    local.set 4
    i32.const 0
    local.set 5
    local.get 5
    i32.load offset=1048592
    local.set 6
    i32.const 0
    local.set 7
    local.get 7
    i32.load offset=1048596
    local.set 8
    local.get 6
    local.get 8
    call $_ZN4core3str21_$LT$impl$u20$str$GT$3len17hd6f34eff50e5c25bE
    local.set 9
    local.get 4
    local.get 9
    call $print_str
    return)
  (table (;0;) 1 1 funcref)
  (memory (;0;) 17)
  (global (;0;) (mut i32) (i32.const 1048576))
  (global (;1;) i32 (i32.const 1048600))
  (global (;2;) i32 (i32.const 1048608))
  (export "memory" (memory 0))
  (export "hello_wasm" (func $hello_wasm))
  (export "__data_end" (global 1))
  (export "__heap_base" (global 2))
  (data (;0;) (i32.const 1048576) "Hello, World!\00\00\00\00\00\10\00\0d\00\00\00"))

普通のアセンブリは全然わかんないけどwatは割と見ただけでわかる感じ

nazo6nazo6

よくわからないけどdioxusはスレッドセーフではなくてwasmからstateにアクセスできない?
Rust初心者すぎて何もわからないのでとりあえずdioxusとの連携はなかったことにする

nazo6nazo6

数時間の格闘の末についにHello Worldの出力に成功した
wasmerは昔の情報しかなくてつらみがあった

$ cargo build -p plugin-sample
$ cargo run
workspace/app/src/main.rs
use dioxus::prelude::*;

use wasmer::*;

fn main() {
    launch();
}

#[derive(WasmerEnv, Clone, Default)]
pub struct Env {
    #[wasmer(export)]
    memory: LazyInit<Memory>,
}

fn call_wasm() {
    static wasm_bytes: &'static [u8] =
        include_bytes!("../../target/wasm32-unknown-unknown/debug/plugin_sample.wasm");
    let store = Store::default();
    let module = Module::new(&store, wasm_bytes).unwrap();

    fn print_str(env: &Env, ptr: u32, length: u32) {
        println!("ptr:{}, length:{}", ptr, length);
        let wasmptr: WasmPtr<u8, Array> = WasmPtr::new(ptr);
        let str = wasmptr
            .get_utf8_string(env.memory_ref().unwrap(), length)
            .unwrap();
        println!("{}", str);
    }

    let print_str_func = Function::new_native_with_env(&store, Env::default(), print_str);
    let import_object = imports! {
        "env" => {
             "print_str" => print_str_func,
        }
    };
    let instance = Instance::new(&module, &import_object).unwrap();
    let hello = instance
        .exports
        .get_function("hello_wasm")
        .unwrap()
        .native::<(), ()>()
        .unwrap();
    hello.call();
}

#[cfg(target_arch = "wasm32")]
fn luanch() {
    wasm_logger::init(wasm_logger::Config::default());
    console_error_panic_hook::set_once();

    dioxus::web::launch(app);
}
#[cfg(not(target_arch = "wasm32"))]
fn launch() {
    dioxus::desktop::launch_cfg(app, |c| {
        c.with_window(|w| {
            w.with_resizable(true).with_inner_size(
                dioxus::desktop::wry::application::dpi::LogicalSize::new(400.0, 800.0),
            )
        })
    });
}

fn app(cx: Scope) -> Element {
    let (text, set_text) = use_state(&cx, || "");

    cx.render(rsx!(
        button {onclick: |_|{call_wasm();} , "print" }
        p { "{text}" }
    ))
}
このスクラップは2022/02/22にクローズされました