Wardite
というWASM Runtimeを作っていました。
今の所、「RustでWasm Runtimeを実装する」の内容を一通り実装した段階です。
fibまで動くようになったので、ゴリラさんのコースの範囲は一旦完成
— Uchio Kondo💥 (@udzura) 2024年10月28日
あとはwasm坂を登り続けるだけだ... pic.twitter.com/65m8qbSvap
以下のようなwasmプログラムを動かせます。
(module (func $fib (export "fib") (param $n i32) (result i32) (if (i32.lt_s (local.get $n) (i32.const 2)) (then (return (i32.const 1))) ) (return (i32.add (call $fib (i32.sub (local.get $n) (i32.const 2))) (call $fib (i32.sub (local.get $n) (i32.const 1))) ) ) ) )
$ gem install wardite $ wat2wasm fib.wat $ wardite ./fib.wasm fib 20 return value: 10946
Warditeは以下のようなコンセプトで開発をしています。
- Pure Ruby実装(動作時、標準ライブラリ以外への依存は一切なし。C拡張ビルドもなし)
- Fully Type-annotated (rbs-inlineを使う。今のところ実装した部分はほぼ全部型付けした)
- WASI preview 1に対応予定
WASM GCやComponent Modelその他対応も、上記が一通りできてきたら検討に検討を重ねようかと。
先行実装としてtechnohippyさんが WebAssembly Core Specification v1.0 の基本的なセクションと命令をRubyで実装したらしいものがあるのですが...。
こちらはもうアクティブにはメンテナンスされなそうなのと、WASIには対応していないので、その辺含めWarditeはちまちまやろうと思っています。
実装して思ったこと
- rbs-inline 、これは手放せない...
今回それなり複雑なソフトウェアを書いていますが、 rbs-inline --output && steep check
して通過するというのは安心感があります(まだ自動テストが全然足りないのですが、それでもsteep checkの結果を元に一定以上には安心して効率よく変更を加えられます)。
また、エディタでのメソッド補完、変数の型の補完、定義ジャンプ、書き損じの指摘その他が思った以上にちゃんと動き、メチャクチャに生産性が上がっているのを感じます(なおエディタは今回からVSCodeにしています...)。
僕は非常に型の表現力と機能に富む言語であるところのRustも書いているのですが、Rustはエディタの支援も本当に素晴らしいです。ですが、Rubyでも十二分に便利にエディタ支援が受けれるようになっているのでは! と感じています。
また、個人的にはメソッドの定義と型の宣言が離れていると読む際にも書く際にも辛いのでは、と思っていたので、rbs-inlineのある世界は素晴らしい、最高!という気持ちに溢れています。型を書くための記法も1時間ぐらいで全部慣れました。今後もクラスやメソッドの型が綺麗になるよう意識して実装していきたいところです。
- YJIT 他試したい
ちゃんと IPS(Instruction per second)を計測できるようにして、YJIT含め色々な条件でパフォーマンス向上を試したいと思っています。ただ、前闇雲に測って微妙な気持ちになった ので、命令類を一通りサポートしてからやろうかなと。
- VMの気持ちが1ミクロンわかった
別件でmrubyのVMも自作しているのですが、正直コールスタックとかフレームとかの概念が何もわからなかったところ、若干友達になれた気がします。VMってこう作るんだね、とにかく何でもかんでもスタックなんだなみたいな...。
また、Rustはだいぶ慣れたつもりでいたんですが、今回RBS有効であってもRubyの方が高速に書けるなと気付いてしまったため、mruby/c などを参考にしながらmruby VMを一度Rubyで作るといいのではないかという気持ちになっているところです*1。余談でした。
- mruby でも動かせるといいかもしれない
Warditeの中で、今の所CRubyで普通に使えてmrubyで標準でついてない機能で使っているのは StringIO
と pack/unpack
ぐらいです。この辺はpolyfillを書いたりそもそも書き換えたり(int32のLE表現をパースしてるだけだったりなので)すればすんなりいけそうな感じがあります。これは気が向いたら...。
mrubyで動けば普通に(dynamic linkedですが)ワンバイナリにもできますからね。
Goによるゼロ依存WASM Runtime wazero の使われ方をみていると、言語ランタイム以外に依存がない組み込みWASM Runtimeは需要があるのではないかと思わされます。ということでこちらもちまちま開発していくぞ〜と思っています。
// ruby.wasm をWASM on Rubyの上で動作できるようにするあたりが一つのマイルストーンかなと...。
また、ゴリラさんの「RustでWasm Runtimeを実装する」は非常によくできていて、 LEB128 周りなどの罠も一通り解説されているのですんなりと読み進めることができます。Rustでの写経もとても良さそうですが、みなさんのお好きな言語に移し替えて実装するのも面白いと思います。素晴らしい電子書籍をありがとうございました!
*1:読み返すと何言ってんだって感じのことをしようとしている...