あらすじ
tl;dr
- 自作WASM Runtimeでグレースケール処理を動かした
- グレースケール処理はRustで書いたもの
- WebAssembly specificationはテストケース付きで公開されてるので便利
やったこと
自作WASM Runtime(以下、 Wardite )でRustで書いたグレースケール処理がちゃんと動いた。
ちょっと前にWarditeでWASM 1.0の範囲の基本的な命令を一通り実装した (参照) んだけど、別の個人プロジェクトで作った、Rustで書いたWASMのグレースケール処理を動かそうとしたところ、普通に unreachable
(Rustのpanicは最終的にunreachable命令になる)になってしまっていた。その時点で簡単なWASMプログラムなら正常動作していたのだが、この処理は動かずにぐぬぬとなっていた。
その後、ゆるゆる調べて、
- そもそも色々正しく動いてなかった。たとえば関数のローカル変数定義を正しく読めてなかったので直した (コミット) とか
- 一番手間取ったのが、
memory.grow
を呼んだ時に自分のメモリの大きさを更新していなかったバグで、まるっきり気づいていなかったのだが、観察したら変なとこにmemsetするような動きをして線形メモリを破壊していたので直した (コミット)- これが全然わからず、ブラウザ側で同じグレースケール処理を動かして、関数エントリポイントごとにステップ実行して変数を確認して... ということをしていた。今思うと先に全部specを通しても良かった
- ここまで直してメモリが壊れなくなったので、一旦WASM側の処理を一つ一つ動かしてどこでpanicになっているのか当たりをつけることにした
WASM側の処理はざっくりいうと以下のようになっていて、
DataURL形式でPNGのデータを受け取る DataURLからbase64 decodeしてBlobデータを得る バイナリのPNGを image クレートでピクセルにする ピクセルを全部iterateして、RGBの平均でグレースケールにする ピクセルをPNGに戻す PNGのBlobをbase64 encodeする
調べると、base64 decode/encodeは正常に動きそう。ただ、PNGを読み込むところでエラーになっており、具体的なエラー文字列を引っ張り出したら DistanceTooFarBack
と書かれていた。
つまりPNGの中のdeflateの処理のようなんだけど、コードを見るとビットシフト操作が多めのようだった。
別件で、WebAssembly specificationにはテストケースがついており、それを実行してちゃんと実装されているか自動テストできそうと知った。
この記事で知った。ゴリラさんと言い、WebAssemblyランタイム自作勢の積極的なアウトプットに感謝(リスペクト)...。
試みに wg-1.0 のspecをもとにやっていくかな〜と調べた。
このspecにある i32.wast
に対し wast2json
を使えばテスト用のwasmバイナリと、テストケースを記述した i32.json
が生成される。
そのjsonをもとにRubyのtest-unitを使って自動でテストを回すようにしたものが以下:
これはめちゃくちゃ適当な書き捨てなのでぼちぼち整理するとして、一旦i32をどうにかしようと思った。
そうするとspecを読めばすぐわかるような実装ミスがいくつかあり、特にビットシフト周りが荒ぶっていたので一通り修正。たとえばこんなね...。
最終的にi32の正常系は通すようにした。
Finished in 0.226758 seconds. ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- 442 tests, 368 assertions, 0 failures, 0 errors, 0 pendings, 83 omissions, 0 notifications 100% passed ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- 1949.21 tests/s, 1622.88 assertions/s
ただ、typeが assert_invalid
とか assert_malformed
になっているテストケースはそもそも関数がexportされておらず、多分ロードした時に静的検査をして(たとえば引数の数が)壊れているのを判定するテストなのだと思う。静的検査はおいおいやるということにして一旦omitしてある。
この状態でgrayscale.wasmの再実行に挑戦したら、無事グレースケール処理が動いて自分のアイコンがグレースケールになった。
original | grayscaled |
---|---|
今後のやっていき
正しく動いたので偉い。が遅い。ある程度はそりゃ遅いんだろうけど、
そしてYJITの効果が割とえぐいという感じ 1/3 か〜...#Wardite pic.twitter.com/B21Nbg6zbQ
— Uchio Kondo💥 (@udzura) 2024年11月24日
YJITで3倍になった(本当に3x3じゃん)と喜んでいる場合でもない。ただ、この感じだと割と大きな見落としをしてるだけだろうので、粛々計測して直せばいいだろう。多分...。
そしてそもそも WASI preview 1 をサポートするというのも目標の一つだったのだ。これもなんか、頑張ってやるだけ(そうかな?)なのでやる。
そうこうしていれば、 ruby.wasm
が WASM on ruby で動くという面白い(?)ことを達成できるかもしれない。ruby.wasmのImportセクションが wasi_snapshot_preview1
だけなことと、ruby.wasmで使ってそうな命令は一通りWarditeで実装できてそうなことは見ているが果たして。
あと、Runtimeを実装して、VMの気持ちが1ミクロンわかった気がしたので、他のVMもRubyに移植したりもしたいなと思った。またいずれお話しするでしょう...。