PSP上でWASMを実行するデモ
タイトル通りです。デモリポジトリを用意したので、そちらも参照してください。
ランタイムとしてはRustを用いています。rust-pspというライブラリおよびツールを用いているのですが、基本的には no_std
かつ no_main
な環境になるので、一般的なライブラリが使えなかったりするという不便があります。
この問題を解決するためにずいぶん頭を悩ませていろいろな道を探ってみたのですが、灯台下暗し、wasmiというWASMランタイムが no_std
に対応しているということを後々になってから気づき、こちらを採用しました。
とりあえずデモとして、以下のようなWASMを作りました。
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
加算して返すだけの単純な関数 add
をエクスポートしています。
ランタイム側は以下の通りです。 かなりテキトーですが...。
#![no_std]
#![no_main]
use anyhow::Result;
use wasmi::{Config, Engine, Extern, Linker, Module, StackLimits, Store};
psp::module!("sample_module", 1, 1);
type HostState = u32;
fn real_main() -> Result<()> {
let mut config = Config::default();
config.set_stack_limits(StackLimits::new(256, 512, 128).unwrap());
let engine = Engine::new(&config);
let wasm_binary: &'static [u8] = include_bytes!("add.wasm");
let module = Module::new(&engine, &wasm_binary[..]).unwrap();
let mut store = Store::new(&engine, 42);
let linker = <Linker<HostState>>::new(&engine);
let instance = linker
.instantiate(&mut store, &module)
.unwrap()
.start(&mut store)
.unwrap();
let add = instance
.get_export(&store, "add")
.and_then(Extern::into_func)
.expect("could not find function \"add\"")
.typed::<(i32, i32), i32>(&mut store)
.unwrap();
let num = add.call(&mut store, (1, 2)).unwrap();
psp::dprintln!("Got {}", num);
Ok(())
}
fn psp_main() {
psp::enable_home_button();
match real_main() {
Ok(_) => psp::dprint!("Success"),
Err(e) => psp::dprint!("Error: {:?}", e),
}
}
include_bytes
でWASMのバイナリを埋め込めるのが便利ですね。今回はじめて知った機能です...。
さて、 let num = add.call(&mut store, (1, 2)).unwrap();
という記述のとおり、今回は 1 + 2
を計算させています。これがうまく動けば Got 3
という表示がされるはずです。
ということでビルドした EBOOT.PBP
をPPSSPPで実行すると...
完璧ですね!なお、実機確認はしていません。持っていないので。
今後の展望ですが、psp
クレートの各種関数などをWASM側から叩けるようにエクスポートして、本格的にWASMを用いたPSP Homebrew開発ができるようにしたいですね。個人的にはGolangをPSP上で動かしたいという欲求があるので、そうした成果物ができたら、またいずれ公開したいと思います。
追記(2024/12/17)
この記事で公開している内容だと、エミュレータでは動作しますが実機ではエラーになってしまうという問題がありました。
そこで、違うアプローチを考えてついに実機でも動作するものが出来上がったので、改めて記事を書きました。是非ご覧ください。
Discussion