ブラウザでRubyを動かす夢

何に使うわけでもないけど、とにかくブラウザで Ruby を動かしたかったんです。

その夢が、ついにかなった気がします。

振り返ってみると、ここに来るまで 6 年もかかったようです。ちょっと嬉しくなったので経緯を書き残します。

EmscriptenRuby をビルドする

2018 年、ふと思い立って、EmscriptenRuby をビルドできるようにしました。

Emscripten は、要するに C/C++ プログラムを JavaScript や Wasm に変換してくれるコンパイラです。C で書かれた RubyEmscripten でビルドすれば、ブラウザで動く Ruby が作れるはず。

意外と微修正だけで miniruby *1 がビルドできて、簡単なコードなら動かせるようになりました。

mametter.hatenablog.com

安定した Emscripten'ed Ruby をメンテナンスする

ただ、ちょっと凝ったコードを走らせると落ちたり刺さったりして、まともに使えるクオリティにはできませんでした。また、RubyEmscripten も変わっていくので、しばらくしたらビルドできなくなってしまいました。

しょうがないので放置していたら、kateinoigakukun さんっていうスーパーハッカーがさっそうと現れて、Ruby の Wasm/WASI 対応をはじめてくれました。RubyKaigi 2022のキーノートは記憶に新しいですね。

rubykaigi.org

kateinoigakukun さんは自分と違って Wasm にちゃんと超詳しいので、不安定な挙動に対して場当たり的に対応するのではなく、まじめに根本原因を調べて改善することをやってくれました。また、ビルドシステムのメンテナンス力も高く、Wasm/WASI Ruby の nightly ビルドを配布してくれるようになりました。

github.com

katei さんの興味は WASI *2 のはずですが、なぜか Emscripten ビルドも提供してくれました。このおまけにより、そこそこ安定的な RubyEmscripten ビルドを無料で得られるようになったのでした。最高。

Emscripten プログラムを xterm.js につなぐ

ブラウザで Ruby が動くようになったので、いくつかアプリを書けました。楽しい。

ruby-puzzles-2022.cookpad.tech mame.github.io

ただ、別に Ruby でブラウザアプリが書きたかったわけではないんですよ。やっぱり REPL を動かしたい。何に使うわけでもないけど。

そのためには、ブラウザで動く端末につないで irb を動かさなきゃいけない。ブラウザで動く端末エミュレータには xterm.js があり、これは vscode でも使われている超安定ライブラリなので、あとは Emscripten'ed Ruby とつなぐだけでした。

これがまた大変でした。

Emscripten プログラムに限らないのですが、端末エミュレータとプログラムは通常、直結していません。Linux でも、C プログラムが printf("Hello\n"); とやると、端末エミュレータには Hello\r\n という文字列が渡されます。\r が挿入されていることに注意。これを挿入するのは、プログラムでも端末エミュレータでもなく、実は Linux カーネルです。Line discipline という機能がそれです。*3

xterm.js で Linux プログラムを動かす場合、node-pty という定番ライブラリがあり、vscode などもこれを使っているようです。が、これは Linux の pty のラッパなので、ブラウザでは動きません。Emscripten のために、ブラウザの上で動く pty がほしいという声はちらほらあるようでしたが、作った人はいないようでした。

ないなら作るかってことで、xterm-pty という Emscripten プログラムとつなぐための xterm.js アドオンを作りました。

github.com

これは要するに Line discipline を気合で実装したものです。ブラウザで Ruby を動かしたいと思ったら、いつのまにか Line discipline を JavaScript で実装していた。

まあ、xterm-pty で Vim が動いたときはなかなかの達成感でした。Vim のデモは↓を参照。

xterm-pty.netlify.app

なお、Vim を Emscripten すること自体は既出でした。あちらは Vim に特化したレンダラを自作したのに対し、こちらは xterm.js とつないで動かしたところが新規性。汎用的なので Vim 以外の CUI プログラムも動きます。詳しくはデモを参照。

xterm-pty をまともにする

で、irb を動かすために作った xterm-pty でしたが、実際に irb と満足につなぐには課題がありました。

Line discipline は、IO のやり取りだけでなく、シグナルを投げる役割もあります。Ctrl+C が押されたときに SIGINT を投げるのは、実は Line discipline です。しかし Emscripten プログラムにはシグナルという概念そのものがなかったので、投げようがありませんでした。

また、xterm-pty と Emscripten プログラムとつなぐ部分が極めて不安定でした。Emscripten ランタイムをモンキーパッチして read/write/select などのシステムコールインターセプトしていたので、Emscripten がちょっと変数名を変えると動かなくなる。

そんなわけで、しょうがなく放置していたのですが、なんか Ingvar Stepanyan っていう Emscripten に超詳しいスーパーハッカーがさっそうと現れて、「Emscripten にシグナルを実装した」「モンキーパッチでなく Emscriptenプラグイン的に xterm-pty を使えるようにした」と言う、ほとんど作り直しに近い PR をくれたので、突如解決しました。最高。

github.com

ついに Ruby の REPL がブラウザで動く

刷新された xterm-pty をリリースしたので、組み合わせて irb を動かしてみたら、なんと一切のパッチを必要とせずに動きました。すごい、すごすぎる。6 年前と比べると別世界。

補完も出るし、イースターエッグのアニメーションも動きます。

twitter.com

いやー、これが見たかったんですよ。夢がかなった瞬間。何に使うわけでもないけど。

まとめ

ブラウザで Ruby を動かす夢がかないました。かなったんじゃないかな。

まあ、Thread.new が動かないとか、拡張ライブラリをロードできないとか、まだまだ課題はあるわけですが。

適当なものを作って放置してたらスーパーハッカーたちが直してくれる人生だったので、さらなるスーパーハッカーの登場を待ちたい。

おまけ

実は、kateinoigakukun さん自身が irb.wasm をやっています。

irb-wasm.vercel.app

これは Emscripten ではなく WASI で実現されています。

irb.wasm は jquery-terminal を使っているそうで、補完などは出ません。が、RubyWorld Conference 2022 で picoruby/picoirb を発表してた hasumikin さんに相談したら irb.wasm で xterm.js を使うモードを実装してくれたので、そっちなら補完が出ます。ただ、pty をまじめに模倣しているわけではなく、irb や reline にモンキーパッチをあててどうにかしているようなので、再現性はやや微妙かも。たとえば例のイースターエッグは(まだ)動かなかった。irb.wasm で xterm-pty を使うようにするとよさそうだけど、できるのかな?

*1:Ruby のビルド時に中間的に作られる簡易な Ruby インタプリタ。拡張ライブラリがロードできないなど、制限がある。

*2:WASI は、Wasm のポータブルなシステムインターフェイスEmscripten がブラウザ特化の Wasm を出力するのに対し、WASI に基づいた Wasm はブラウザだけでなく配布用実行ファイルやエッジコンピューティング環境などで共通して使える。

*3:pty とか termios とかのキーワードのほうがわかりやすいかも。pty は Line discipline で繋がれたマスター・スレーブのペアで、termios はスレーブ側から Line discipline を制御するための API 、だと思ってますが、正確な定義は自信ない。