9月8日から9月10日までの3日間RubyKaigi 2022が三重県津市で開催されました。今年はRubyKaigi 2019以来、3年ぶりの現地開催で非常に盛り上がったカンファレンスとなりました。
初日のキーノートではRubyコミッターのYuta Saitoさんが
Saitoさんはインターネット上では主に@kateinoigakukunという名前で活動しており、Swiftコミッターとしてもよく知られたエンジニアです。CRubyのWebAssembly移植を進め、2022年1月にRubyコミッターとなっています。今回のキーノートはCRubyのWebAssembly対応でなにができるようになったか、その対応をどのように進めたかという内容でした。
RubyをWebAssembly上で動かす動機
はじめに、RubyをWebAssembly上で動かす動機について話がありました。
Rubyの良さ
Rubyはプログラマが楽しくなるようにデザインされている言語です。Gemのエコシステムもよくできていますし、素早くプログラムを書くこともできます。
Rubyの難しさ
一方でRubyには難しさもあります。ウェブブラウザやモバイルデバイスのような制限された環境ではRubyのインストールはできませんし、Rubyで書いたプログラムをユーザに配布して動かしてもらうのは簡単ではありません。
初心者にとってはインストールも難しいです。Rubyでプログラムを書いてきた人ならばGemのインストール失敗ログを何度も見たことがあるはずです。
Ruby 🤝 WebAssembly
そのRubyの動作環境の問題をどのように解決できるでしょう? その答えの一つがWebAssemblyです。
WebAssemblyはゲームチェンジャーです。WebAssemblyはスタックベースマシンの実行バイナリフォーマットであり、ポータビリティ、言語非依存、バイナリサイズ、ロード効率、サンドボックスによる安全性などを意識して設計されています。
現代ではブラウザはあらゆる環境で動きますし、Rubyをインストールせずブラウザで試せるようになります。CRubyをWebAssemblyに変換したruby.
ここで壇上のSaitoさんは、ブラウザ内で実際にCRubyのIRBが動作するirb-wasmを用いてライブデモを実施しました。デモはGemをインストールして利用したりと非常に応用的なもので、実際にブラウザ内で自然にRubyが動いていることが伝わるものでした。
WebAssemblyとWASI
ここまではRuby 3.
WebAssemblyはどのように動くか
WebAssemblyそのものにはファイルシステムも、時刻も、ネットワークも定義されていません。しかしWebAssemblyにはJavaScriptの関数が提供されており、WebAssembly版のCRubyからするとシステムコールの代わりにJavaScriptの関数を利用しています。
そしてWebAssemblyはWebだけのものではありません。サンドボックスによる安全性、アーキテクチャ非依存、多言語対応という特徴は次の領域などでも有用です。
- サーバレスプラットフォーム
- プラグインシステム
WASI
WebAssemblyはJavaScriptの上でだけ動くものではありません。WASIはWebAssemblyに提供するシステムコールインタフェースを標準化したものです。WASIによる互換性は様々な環境、言語に対応しています。今回追加されたRubyもまたその一つです。
WASI + VFS = Portable Ruby App
CRuby自体は.wasmバイナリにすることでポータブルなプログラムになりました。しかしRubyで書かれたプログラムも配布する必要がありますし、多くのWebAssembly関連ツールはワンバイナリを要求します。
そこでSaitoさんはインメモリなリードオンリーの仮想ファイルシステムwasi-vfsを作成しました。wasi-vfsはWASI上で動き、WebAssemblyアプリケーションに対しファイルシステムインターフェースを提供します。これによってCRubyを変換した.wasmバイナリと配布Rubyプログラムを結合して、単一のWebAssemblyプログラムとして配布できるようになりました。
Ruby 3.2のWebAssembly/WASIサポート
Ruby 3.
ここではFastlyのCompute@Edgeというエッジコンピューティング環境上でRubyで書かれたWebAssemblyプログラムが動作する様子をデモしました。このデモはサンプルページで実際に動いている様子を確認できます。
CRubyをどのようにWebAssembly/WASI環境に移植したか
当初Saitoさんは
- 🐲例外
- 🐲Fiber
- 🐲保守的GC
例外の実装
CRubyの例外実装はsetjmp/
例外の実装のためには現在の実行状態の保存と、保存された実行状態への巻き戻しが必要となります。
Fiberの実装
Fiberはコルーチンのようなもので、プログラムの停止と再開が実現できます。Fiberはresume/
Fiberの実装のためには現在の実行状態の保存と、保存された実行状態の復元が必要となります。
保守的GCの実装
CRubyの保守的GCはマシンレジスタとマシンスタックもスキャンします。WebAssemblyの実行モデルではコールフレーム内のValueスタックや保存されたローカル変数はスキャンできません。
保守的GCの実装のためには現在の実行状態の保存と、保存された実行状態の復元、コールフレームからローカル変数とValueスタックを読めなければいけません。
Asyncifyの活用
Asyncifyはそれらの足りなかったパーツを埋めてくれます。Asyncifyは非同期なJavaScript関数と同期的なC関数のためにAlon Zakaiに設計された、WebAssemblyでも実行可能な低レベルなプログラムの停止と再開の実装方法です。
AsyncifyはWebAssemblyスタックのWebAssemblyレジスタへの書き出し、call命令前後にスキップと巻き戻しのためのコード追加、WebAssemblyレジスタの保存と復元のコード追加により実現されます。
例外、Fiber、保守的GCの実装のために欠けていた次のものはAsyncifyにより埋められました。
- 現在の実行状態の保存
- 保存された実行状態の復元
- ローカル変数とValueスタックのスキャン
こうしてAsyncifyを使うことでCRubyはWebAssemblyの上で動くようになりました。
FAQとRecap
発表の流れで触れられなかったことをいくつか補足しました。
- スレッド関連APIはWebAssembly/
WASIにスレッドAPIが未実装のため未対応 - C拡張ライブラリは静的リンクする必要がある
- .wasmバイナリサイズは標準ライブラリも含めるとBrotli圧縮して5.
0M程度 - 実行速度はAsyncifyがオーバーヘッドになっており、mrubyに近い
まとめ
Ruby 3.
発表内容で触れられた成果物はかなりの完成度で、各所で活用のアイデアが活発に議論されています。正直