nazo6 notememo

Rustアプリにwasmerを埋め込む

作成:2022/02/13

更新:2022/02/13

(2022-02-13)

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

環境 (2022-02-13)

  • arch on wsl
  • Rustはなるべくnightlyを使わないようにしたい

プロジェクト作成 (2022-02-13)

構成はこんな感じにする
app
│ public
│ src
│ └ main.rs
│ Cargo.toml
└ Dioxus.toml
plugins
└ sample
│ src
│ └ lib.rs
└ Cargo.toml
Cargo.toml
(どうでもいいけどこういうときvimだとファイラーのテキスト直接コピーできて便利だな)
app以下はdioxus-cliで作る
$ dioxus create

(2022-02-13)

ワークスペースのルートCargo.toml
root/Cargo.toml
[workspace]
resolver = "2"
members = ["app", "plugins/sample"]
ここでresolver = "2"を指定するのが重要。こうしないとcfgでfeature出し分けができない(超ハマった)

(2022-02-13)

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" }
これでビルド時に切り替えられる

プラグイン側wasmを作る (2022-02-13)

とりあえずこの記事を参考にして作る
@gist
unsafeだけどひとまず我慢

(2022-02-13)

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

(2022-02-13)

とりあえずプラグイン側だけビルドしてwatを見てみる
$ cargo build -p plugin-sample
## 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は割と見ただけでわかる感じ

(2022-02-13)

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

(2022-02-13)

数時間の格闘の末についに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-13)

https://storage.googleapis.com/zenn-user-upload/c8758ccfd2cf-20220213.gif

こんな感じ

(2022-02-13)

File not found · zellij-org/zellij

A terminal workspace with batteries included. Contribute to zellij-org/zellij development by creating an account on GitHub.

github.com

これを見るとzellijではメモリ操作みたいな面倒くさいことをせずにwasiの標準入出力でデータをやり取りしているみたい
こっちのほうが大分楽そう