wasm-pack の最小出力を探す
wasm の現在の課題はビルドサイズであり、可能な限り小さいビルドサイズにしたい。それをオプションをガチャガチャやりながら探す
M1 Mac での準備
M1 mac の場合、wasm-pack 標準のインストールだと wasm-opt の prebuilt なバイナリが見つけられずビルド後の最適化ができずに.wasm の出力が大きくなる。
こちらの fork をインストールするとうまくいったが…ちょっと古いバージョンになってしまうので、解消してほしい
cargo install wasm-pack --git https://github.com/d3lm/wasm-pack --rev 713868b204f151acd1989c3f29ff9d3bc944c306
あと binaryen もいれておく
brew install bynaryen
まず、普通にビルドしてみる。
$ wasm-pack build
[INFO]: 🎯 Checking for the Wasm target...
[INFO]: 🌀 Compiling to Wasm...
Compiling proc-macro2 v1.0.36
Compiling unicode-xid v0.2.2
Compiling syn v1.0.86
Compiling log v0.4.14
Compiling wasm-bindgen-shared v0.2.79
Compiling cfg-if v1.0.0
Compiling lazy_static v1.4.0
Compiling bumpalo v3.9.1
Compiling wasm-bindgen v0.2.79
Compiling quote v1.0.15
Compiling wasm-bindgen-backend v0.2.79
Compiling wasm-bindgen-macro-support v0.2.79
Compiling wasm-bindgen-macro v0.2.79
Compiling console_error_panic_hook v0.1.7
Compiling mywasm v0.1.0 (/Users/kotaro.chikuba/mizchi/rust-tokenizer/mywasm)
warning: function is never used: `set_panic_hook`
--> src/utils.rs:1:8
|
1 | pub fn set_panic_hook() {
| ^^^^^^^^^^^^^^
|
= note: `#[warn(dead_code)]` on by default
warning: `mywasm` (lib) generated 1 warning
Finished release [optimized] target(s) in 5.51s
[INFO]: ⬇️ Installing wasm-bindgen...
[INFO]: found wasm-opt at "/Users/kotaro.chikuba/brew/bin/wasm-opt"
[INFO]: Optimizing wasm binaries with `wasm-opt`...
[INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended
[INFO]: ✨ Done in 5.69s
[INFO]: 📦 Your wasm pkg is ready to publish at /Users/kotaro.chikuba/mizchi/rust-tokenizer/mywasm/pkg.
[WARN]: ⚠️ There's a newer version of wasm-pack available, the new version is: 0.10.2, you are using: 0.10.0. To update, navigate to: https://rustwasm.github.io/wasm-pack/installer/
出力を見る
$ ls -lh pkg/*.wasm | awk '{print $9,$5}'
pkg/mywasm_bg.wasm 253B
binaryen で入れた wasm2wat で中身をみてみる
wasm2wat pkg/mywasm_bg.wasm
(module
(type (;0;) (func (param i32 i32)))
(type (;1;) (func))
(import "./mywasm_bg.js" "__wbg_alert_8093a2ef8ddf8b97" (func (;0;) (type 0)))
(func (;1;) (type 1)
i32.const 1048576
i32.const 14
call 0)
(memory (;0;) 17)
(export "memory" (memory 0))
(export "greet" (func 1))
(data (;0;) (i32.const 1048576) "Hello, mywasm!\00\00\04"))
ブラウザで実行する環境を作っておく
yarn init -y
yarn add -D vite typescript
import init, {greet} from "./mywasm/pkg/mywasm.js";
await init();
greet();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>hello</h1>
<script type="module" src="./main.tsx"></script>
</body>
</html>
alert だと同期ブロックして面倒なので、conosle.log に変えておく
use wasm_bindgen::prelude::*;
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen]
extern {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[wasm_bindgen]
pub fn init_debug() {
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}
#[wasm_bindgen]
pub fn greet() {
log("Hello, mywasm!");
}
target に web を指定して実行する
(cd mywasm && wasm-pack build --target web --release)
yarn vite
console.log で Hello, mywasm! と出ていれば成功
ビルドサイズのメモ
この状態でオプションの有無のビルドサイズを見てみる
-
features: [console_error_panic_hook]
: 30k -
features: []
: 1.3k - `features: [wee_alloc] 258b
プロダクションは wee_alloc 有効、 panic_hook 無効にしたほうが良さそう
もっと現実的なコードにしてみる。今回は https://github.com/mizchi/mints/tree/main/packages/mints の parser generator を書き直したくて、 https://zenn.dev/nojima/articles/05cb9ffa0f993b の記事を読みながら、一番最初の digits のパースだけのコードを入れてみる。
use wasm_bindgen::prelude::*;
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen]
extern {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[wasm_bindgen]
pub fn init_debug() {
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}
// https://zenn.dev/nojima/articles/05cb9ffa0f993b
#[wasm_bindgen]
pub fn parse(s: &str) -> i64 {
let (parsed, _rest) = digits(s).unwrap();
parsed
}
// s の先頭にある整数をパースし、整数値と残りの文字列を返す。
pub fn digits(s: &str) -> Option<(i64, &str)> {
let end = s.find(|c: char| !c.is_ascii_digit()).unwrap_or(s.len());
match s[..end].parse() {
Ok(value) => Some((value, &s[end..])),
Err(_) => None,
}
}
これで std の Option や string 周りのコード等がこれでビルドされるようになり、ビルドサイズが増える。
$ wasm-pack build --target web -- --features wee_alloc && ls -lh pkg/*.wasm
[INFO]: 🎯 Checking for the Wasm target...
[INFO]: 🌀 Compiling to Wasm...
Finished release [optimized] target(s) in 0.01s
[INFO]: ⬇️ Installing wasm-bindgen...
[INFO]: found wasm-opt at "/Users/kotaro.chikuba/brew/bin/wasm-opt"
[INFO]: Optimizing wasm binaries with `wasm-opt`...
[INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended
[INFO]: ✨ Done in 0.22s
[INFO]: 📦 Your wasm pkg is ready to publish at /Users/kotaro.chikuba/mizchi/rust-tokenizer/mywasm/pkg.
[WARN]: ⚠️ There's a newer version of wasm-pack available, the new version is: 0.10.2, you are using: 0.10.0. To update, navigate to: https://rustwasm.github.io/wasm-pack/installer/
-rw-r--r-- 1 kotaro.chikuba staff 15K 1 27 15:34 pkg/mywasm_bg.wasm
一気に 15k。 やりがいがでてきた。
ブラウザ側から呼べることを確認する
import init, {parse, init_debug} from "./mywasm/pkg/mywasm.js";
init().then(() => {
init_debug();
console.log(parse("123yen"));
});
123n
と出る。i64 だと返り値が JS 側で受け取ると BigNum になる。
rust をi32 に修正
use wasm_bindgen::prelude::*;
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen]
extern {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[wasm_bindgen]
pub fn init_debug() {
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}
// https://zenn.dev/nojima/articles/05cb9ffa0f993b
#[wasm_bindgen]
pub fn parse(s: &str) -> i32 {
let (parsed, _rest) = digits(s).unwrap();
parsed
}
// s の先頭にある整数をパースし、整数値と残りの文字列を返す。
// パースに失敗した場合は None を返す。
pub fn digits(s: &str) -> Option<(i32, &str)> {
let end = s.find(|c: char| !c.is_ascii_digit()).unwrap_or(s.len());
match s[..end].parse() {
Ok(value) => Some((value, &s[end..])),
Err(_) => None,
}
}
https://zenn.dev/dozo/articles/14b76b561f3b45 を読んで、最適化オプションをいじってみる
[profile.release]
lto = true
codegen-units = 1
opt-level = "z"
$ wasm-pack build --target web --release -- --features wee_alloc && ls -lh pkg/*.wasm
[INFO]: 🎯 Checking for the Wasm target...
[INFO]: 🌀 Compiling to Wasm...
Compiling mywasm v0.1.0 (/Users/kotaro.chikuba/mizchi/rust-tokenizer/mywasm)
Finished release [optimized] target(s) in 0.15s
[INFO]: ⬇️ Installing wasm-bindgen...
[INFO]: found wasm-opt at "/Users/kotaro.chikuba/brew/bin/wasm-opt"
[INFO]: Optimizing wasm binaries with `wasm-opt`...
[INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended
[INFO]: ✨ Done in 0.40s
[INFO]: 📦 Your wasm pkg is ready to publish at /Users/kotaro.chikuba/mizchi/rust-tokenizer/mywasm/pkg.
[WARN]: ⚠️ There's a newer version of wasm-pack available, the new version is: 0.10.2, you are using: 0.10.0. To update, navigate to: https://rustwasm.github.io/wasm-pack/installer/
-rw-r--r-- 1 kotaro.chikuba staff 16K 1 27 15:45 pkg/mywasm_bg.wasm
15k => 16k 増えてしまった。
一旦 wasm-pack をやめて、 nostd でいってみる。
nostd を試す
// src/lib.rs
#![no_std]
#![no_main]
#[panic_handler]
fn handle_panic(_: &core::panic::PanicInfo) -> ! {
loop {}
}
#[no_mangle]
pub extern fn the_answer() -> u32 {
42
}
$ rustc -C opt-level=s --target wasm32-unknown-unknown ./src/lib.rs && wasm-opt -Oz -o out.wasm lib.wasm
...
$ ls -lh *.wasm | awk '{print $9,$5}'
lib.wasm 153B
out.wasm 103B
これが理論上最小っぽいが、やはり色々とコードを追加すると増えていく
雑に deno で読んでみた
$ deno
Deno 1.18.0
exit using ctrl+d or close()
> const m = await WebAssembly.instantiate(await Deno.readFile('lib.wasm'));
undefined
> m.instance.exports.the_answer()
42