Spinel - Ruby AOTネイティブコンパイラ
(github.com/matz)- Rubyソースをプログラム全体の型推論の後にCコードへ変換し、スタンドアロンのネイティブバイナリを生成するAOTコンパイラ
- Rubyの生みの親であるmatzが自ら開発し、コンパイラバックエンド自体もRubyで書かれた自分自身をコンパイルするセルフホスティング構成
- miniruby(Ruby 4.1.0dev)比で約11.6倍高速、Conway's Game of Lifeは86.7倍、ackermannは74.8倍、mandelbrotは58.1倍の高速化
- コンパイルパイプラインはPrismベースのパーサでRubyをASTテキストに変換した後、self-hostingバックエンドが型推論とCコード生成を行い、標準Cコンパイラでスタンドアロンバイナリを作成
- クラス、継承、ブロック、例外処理、Fiber、組み込みNFA Regexpエンジン、自動昇格Bigint、パターンマッチングなど、幅広いRuby機能をサポート
- 8個以下のスカラーフィールドを持つ小さなクラスは値型として自動的にスタック割り当てされ、GCオーバーヘッドを完全に除去(100万件の割り当てが85ms → 2ms)
- 文字列チェイニング
a + b + c + dを単一のmallocで平坦化し、ループ内のsplitはsp_StrArrayを再利用して不要な割り当てを削除 - ループ不変の長さのホイスティング、定数伝播、3文以下のメソッド自動インライン化、反復推論の早期終了(ブートストラップ時間を約14%短縮)など、多数のコンパイル時最適化
- 生成されたバイナリはランタイム依存ゼロで、libc + libmだけで実行可能
eval、メタプログラミング、ThreadおよびMutexなどは未サポート(Fiberのみサポート)- MITライセンス
1件のコメント
Hacker Newsのコメント
Matzが作ったものなら、Ruby semanticsの限界もよく分かっているはずなので信頼できる
私の修士論文も AOT JS compiler だったが、動きはしたものの入力データの制約が大きく、結局やめた
当時のJS開発者は自発的に制約をきちんと守ることにあまり慣れておらず、
JSON.parseのように本質的に分からない入力が障害になった今ならTypeScriptのおかげで、当時よりずっと現実的かもしれない
一般的な lambda calculus を見ても型推論の限界は明らかで、Matt Might周辺の論文やShed-skin Pythonの取り組みでも似たような制約が見えてくる
eval,send,method_missing,define_methodが実際のRubyコードでどれほど一般的なのか気になるし、型なしのパース、たとえばJSON入力を普通どう扱うのかも気になるRubyのパースは変換そのものより難しいくらいなので Prism を使い、結果としてCを生成する
基本的なRuby semantics自体の実装は、そこまで難しくない
一方で私は純Ruby製の古い self-hosting AOT compiler を抱えていて、独自パーサーにこだわったせいでわざわざずっと険しい道を進んでしまった
初期の80%はざっくり作ってもかなりの量のRubyコードが動くことを早い段階で学んだが、本当に難しい「2つ目の80%」は、Matzが今回のプロジェクトやmrubyで外した部分、たとえばエンコーディングやありとあらゆる周辺機能に集中している
正直、Rubyには実際のコードで一度も見たことのない機能もかなりあるので、いくつかはdeprecatedになっても不思議ではないと思う
send,method_missing,define_methodはとてもよく使われる制約はmrubyに近く、その制約下でも使い道はある
send,method_missing,define_methodのサポートは比較的容易だ反対に eval() のサポートはとてつもなくつらい
ただしRubyにおける
eval()のかなりの割合は、静的に instance_evalのblock版 に還元できるので、その場合はAOTコンパイルがかなりやりやすくなるたとえば
eval()に入る文字列を静的に把握できる、あるいは分解できるなら、解決の余地は大きい実際には多くの
eval()利用は不要だったり、単純なintrospection回避に近かったりするので、静的検査で処理できる私のコンパイラでも、そこがボトルネックになったらまずそこから手を付けるつもりだ
型なしのJSON ingestionも、おそらくそうした仕組みを使っている可能性が高い
それを取り除けば、Crystalほど強い型付けではないが、公式Rubyほどメタプログラミングに依存もしない、小さくて読みやすい言語が残る
なので潜在力はかなり大きそうだが、結局は時間が経たないと判断できない
evalをよく使う側だ使わずに済ませることもできるが、私にとってはそのほうがより ergonomic だ
eval,exec,define_method、そしてClass.new,Struct.newで新しいクラスを作るパターンだこれらの利用の大半はアプリの boot時点 やファイルのrequire中に集中していて、ある意味ではすでにコンパイル段階に近い
これは RubyKaigi 2026 でMatzがたった今発表したものだ
実験的ではあるが、Claudeの助けを借りて約1か月で作られ、ライブデモも成功した
名前はMatzの新しい猫に由来し、その猫の名前はCard Captor Sakuraの猫の名前から来ていて、そこからさらにRubyという名前のキャラクターと対になる
Matzのような人にとっては、100xを500xまで押し上げることになるのかもしれない
https://en.wikipedia.org/wiki/Spinel
動画はまだライブではないようで、こちらのチャンネルに1本ずつ上がっているようだ
https://www.youtube.com/@rubykaigi4884/videos
プロジェクト名も感情的に付けられたように感じる
間違いなく非常に印象的だが、AI agent なしでは保守不可能に見える
spinel_codegen.rbは2万1千行あり、あるメソッドはネストが15段階に達しているもともとコンパイラのコードはきれいになりにくいが、それを差し引いてもこれは人間が管理するにはかなり厳しそうだ
コンパイラはサブシステムの境界が明確で、段階ごとのhandoffもはっきりしているので、むしろ最もmodularに作りやすい部類だ
問題はたいてい、まず動かすことを優先してリファクタリングする時間がなく、その結果として汚さが膨らみ続けることにある
spinel_codegen.rbはほとんど eldritch horror レベルだClaudeを使うと私もいつもこういうスパゲティコードになってしまうので、自分が何か間違っているのかと思っていた
でも、最高レベルのプログラマだと思っている人が作った本当に面白いプロジェクトでも、コード品質がところどころかなり悪いのを見て、自分だけではなかったのだと分かった
たとえば
infer_comparison_type()は最悪の例というほどではなく、読みにくくもないが、もっと単純で明快な実装があるのにClaudeはそこへ行けないSetに比較演算子をまとめてinclude?で処理すれば、もっと短く、速く、読みやすく、保守もしやすいなのにClaudeはいつも if-returnの連鎖 に流れ、if-elseすら苦手な感じがある
私のClaudeコードベースもそういうパターンだらけだが、もう自分だけではないと分かった
一方で他のファイルはかなり良く、とくに
libディレクトリはメインのRubyリポジトリのextディレクトリに対応しているようで、品質も悪くないAPIもMRI Rubyの影響を明確に受けており、実装はかなり違っていても、Matzが元のAPIに似せるよう誘導したことで出力がより整っているように見える
[1] https://github.com/matz/spinel/blob/98d1179670e4d6486bbd1547...
テストとベンチマークさえ通れば、ひとまず満足だ
ただ、巨大なファイルがAIにとっても扱いやすいのかは疑問だ
私はファイルを300行以内に抑えるようにしていて、人が理解しやすいコードは coding agents にとっても扱いやすいはずだと思っている
制約はこうらしい
No eval:
eval,instance_eval,class_evalNo metaprogramming:
send,method_missing,define_method(動的)No threads:
Thread,Mutex(Fiberはサポート)No encoding: UTF-8/ASCII前提
No general lambda calculus:
[]呼び出しを伴う深いネストの-> x { }UTF-8/ASCII前提は個人的には大きな制約ではないが、残りはかなり多くのプログラムで実際に制約になりそうだ
そして、これを再び入れるにはかなりの作業が必要に見える
Rubyを長く使ってきて、列挙された機能を全部使ったことのある立場からすると、むしろ進化の果てに自分が欲しくなったのは、こういう シンプルなRuby だ
よりシンプルで理解しやすいのに、Ruby特有の美意識は残っている
今はLLMのおかげでコード生成の生産性があまりに高く、昔のように開発者の生産性のためにメタプログラミングでboilerplateを減らす必要が薄れてきている
開発者が自分でコードを書く比重そのものが下がっているからだ
文法は似ていて静的型システムがあり、より効率的なコンパイル済みコードにつながる
evalがないのはむしろ良いと思うが、threadsとmutexes までないのは惜しいdefine_methodがないのは用途を考えれば理解できるただ
sendとmethod_missingは既存ライブラリでよく使われるし、実装もメモリ上のlookup tableをコンパイル時に構築するような形で、そこまで難しくなさそうに思えるなので意図的に外したのか、まだそこまで到達していないのか分からない
願わくは後者だが、少なくとも当面は互換性の面で実務投入は難しそうだ
読むべきコードを減らすこと だった
これは本当にすばらしく、私は長いこと Ruby向けAOT compiler を待っていた
ただ
evalやメタプログラミングのfallbackがないのは残念で、それでも小さく高性能なsubsetに集中するためにそうしたのだろうこのAOTコンパイラで作ったgemがMRIとうまく相互運用できるといいのだが
標準Rubyやgemをパッケージ化・バンドルする方向では、今でもtebako、kompo、ocranが必要で、以前にはruby-packer、traveling ruby、jruby warblerのようなプロジェクトもあった
選択肢がひとつ増えるのは良いことだが、より良い開発者UXを備えた 決定版 が出てきてほしいと思っている
あまりにも長い間更新されていなかったからだ
なぜ no threads なのか気になる
Ruby schedulerと下層のpthread実装はC側でもうまく動きそうだし、もしかしてzero dependencyを狙ったのだろうかと思う
optional extensionとして後から入れる予定なのか、それとも単にまだ外してあるだけでないなら、この選択は少し奇妙に感じる
たぶん、まだそこまで到達していないだけではないだろうか
マルチスレッドはもともときちんと作るのが非常に難しい
1か月少々で作ったというのは驚きだ
AIについて何と言おうと、腕のある開発者 の手に渡ればとてつもない速度向上を生み出す
Matzはただ
gem env|infoとfindだけで十分そうに見えるこれがMatzの作ったものだとなると、今後 Ruby core の一部になる可能性がどれくらい現実的なのか気になる
そして、そうなった場合Crystalにとってどれほど脅威になるのかも気になる
こうした特性は大きなプログラムをコンパイルし、保守するうえで事実上不可欠に近い
一方こちらは制限されたRuby subset向けなので、人気のあるRuby gemの多くはそのままでは動かないだろう
Cコンパイルを志向する言語subsetという点では PreScheme により近く見える
現時点では、両者が同じ領域で直接競合しているとは思わない
完全なRubyにはほぼ確実にJITが必要だ
[1]: https://prescheme.org/
Rational Unified ProcessやEnterprise Architectの復讐が始まるようなものだ
違いがあるとすれば、UMLダイアグラムの代わりにmarkdownファイルが来るくらいだ
これは infrastructure tools の分野で役立ちそうだ
たとえばRubyで書かれていながら静的コンパイルされるbundlerがあって、RVMのようなRubyインストールツールの役割まで兼ねる、と想像できる
既存のRuby buildpackはRubyで書かれているが、ブートストラップをbashでやらないといけないので面倒だし、edge caseも生まれる
CNBはその問題を避けるためにRustで書かれており、依存関係のない単一バイナリ を配布できるという発想は本当に強力だ