LiveSplit の Wasm 版 AutoSplitter を構築する

WebAssembly で AutoSplitter を構築できる らしいのでお試し
通常は Rust の テンプレート を使用して作るが、Wasm としてコンパイルできるならどの言語でも良いとのこと
Wasm の勉強も兼ねてやる

livesplit-auto-splitting にあるように、API として update()
が公開されている WASM ならば認識されそう
AssemblyScript で以下のように記述してコンパイルしたものを asr-debugger に読み込ませられることを確認
export function update(): void {}

エコシステムが整っているのは Rust だが、せっかくなので Moonbit でやってみたい
https://discuss.moonbitlang.com/t/the-moonbit-update-0401/197#build-system-update-3 に従ってビルドしたものを読み込ませると、
Failed starting the auto splitter.
Caused by:
The WebAssembly module has no exported memory called `memory`, which is a requirement.
とのこと
以下はそのコード
{
"link": {
"wasm": {
"exports": [
"update"
]
}
}
}
pub fn update() -> Unit {
()
}

In addition the WebAssembly module is expected to export a memory called memory.
とあった(見落とし)
https://zenn.dev/mizchi/articles/moonbit-202404-202408#webassembly.memory-の初期化方法 に従ってコンパイルターゲットを WasmGC に変更し、memory
を export するようにしたら適切に読み込まれた
{
"link": {
"wasm-gc": {
"export-memory-name": "memory",
"exports": ["update"]
}
}
}
wasm-gc ? -> https://zenn.dev/cybozu_frontend/articles/20231214_wasmgc
- VM にガベージコレクタを含む言語を Wasm へ移植する際にこの機能の恩恵を受けることができます

Auto Splitter Runtime 側の API を使う
とりあえずこれでテキストの出力 API を呼び出せた
fn runtime_print_message(text_ptr : UInt, text_len : UInt) = "env" "runtime_print_message"
pub fn update() -> Unit {
let _ = runtime_print_message(0, 8)
}
もちろん runtime_print_message
の第一引数はポインタなので、 moonbit 側でメモリを確保してポインタを渡す方法を調査する
ところで moonbit はまだ async / await が使えないそうなので試せることを試したら一旦お流れになる可能性がある

(どうすんじゃいポインタ渡し)
ということで Wasm と Moonbit の触感をなんとなく知れたので、素直に
https://github.com/LiveSplit/auto-splitter-template の恩恵を預かることにした

ここ読んだらわかった気がしたので後日検証
とりあえず AutoSplitter の実装をする

時間が経ったが AssemblyScript 版
ArrayBuffer が内部的にはポインタなので、以下で問題なく文字列を扱えた
@external("env", "runtime_print_message")
declare function runtimePrintMessage(text_ptr: ArrayBuffer, text_len: usize): void;
export function update(): void {
const str = String.UTF8.encode('sample');
runtimePrintMessage(str, str.byteLength)
}

https://github.com/peter-jerry-ye/memory/blob/main/memory.wasm.mbt の get_offset
を拝借して文字列の出力ができた
extern "wasm" fn get_offset(bytes : Bytes) -> Int =
#|(func (param i32) (result i32) local.get 0 call $moonbit.decref local.get 0 i32.const 8 i32.add)
fn runtime_print_message(text_ptr : Int, text_len : Int) = "env" "runtime_print_message"
pub fn update() -> Unit {
let bytes = b"aaa"
runtime_print_message(get_offset(bytes), bytes.length())
}
勉強不足が祟って思ったより wat が読めなかったので $moonbit.decref
の中身を読むのをやめてしまったが、引っ張ってきているアドレスと実態は見えたのでまあよし