何に使うわけでもないけど、とにかくブラウザで Ruby を動かしたかったんです。
その夢が、ついにかなった気がします。
長年の念願だった Emscripten と xterm.js でブラウザで irb を動かすやつがついに(一応)できたhttps://t.co/ubentOzj7p
— Yusuke Endoh (@mametter) 2024年1月27日
振り返ってみると、ここに来るまで 6 年もかかったようです。ちょっと嬉しくなったので経緯を書き残します。
Emscripten で Ruby をビルドする
2018 年、ふと思い立って、Emscripten で Ruby をビルドできるようにしました。
Emscripten は、要するに C/C++ プログラムを JavaScript や Wasm に変換してくれるコンパイラです。C で書かれた Ruby を Emscripten でビルドすれば、ブラウザで動く Ruby が作れるはず。
意外と微修正だけで miniruby *1 がビルドできて、簡単なコードなら動かせるようになりました。
安定した Emscripten'ed Ruby をメンテナンスする
ただ、ちょっと凝ったコードを走らせると落ちたり刺さったりして、まともに使えるクオリティにはできませんでした。また、Ruby も Emscripten も変わっていくので、しばらくしたらビルドできなくなってしまいました。
しょうがないので放置していたら、kateinoigakukun さんっていうスーパーハッカーがさっそうと現れて、Ruby の Wasm/WASI 対応をはじめてくれました。RubyKaigi 2022のキーノートは記憶に新しいですね。
kateinoigakukun さんは自分と違って Wasm にちゃんと超詳しいので、不安定な挙動に対して場当たり的に対応するのではなく、まじめに根本原因を調べて改善することをやってくれました。また、ビルドシステムのメンテナンス力も高く、Wasm/WASI Ruby の nightly ビルドを配布してくれるようになりました。
katei さんの興味は WASI *2 のはずですが、なぜか Emscripten ビルドも提供してくれました。このおまけにより、そこそこ安定的な Ruby の Emscripten ビルドを無料で得られるようになったのでした。最高。
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 アドオンを作りました。
これは要するに Line discipline を気合で実装したものです。ブラウザで Ruby を動かしたいと思ったら、いつのまにか Line discipline を JavaScript で実装していた。
まあ、xterm-pty で Vim が動いたときはなかなかの達成感でした。Vim のデモは↓を参照。
なお、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 をくれたので、突如解決しました。最高。
ついに Ruby の REPL がブラウザで動く
刷新された xterm-pty をリリースしたので、組み合わせて irb を動かしてみたら、なんと一切のパッチを必要とせずに動きました。すごい、すごすぎる。6 年前と比べると別世界。
補完も出るし、イースターエッグのアニメーションも動きます。
twitter.comemscripten irb でイースターエッグが安定的に動くようになってきたhttps://t.co/ubentOzj7p pic.twitter.com/wkRb2DxYeF
— Yusuke Endoh (@mametter) 2024年1月31日
いやー、これが見たかったんですよ。夢がかなった瞬間。何に使うわけでもないけど。
まとめ
ブラウザで Ruby を動かす夢がかないました。かなったんじゃないかな。
まあ、Thread.new が動かないとか、拡張ライブラリをロードできないとか、まだまだ課題はあるわけですが。
適当なものを作って放置してたらスーパーハッカーたちが直してくれる人生だったので、さらなるスーパーハッカーの登場を待ちたい。
おまけ
実は、kateinoigakukun さん自身が irb.wasm をやっています。
これは 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 、だと思ってますが、正確な定義は自信ない。