プログラマー三大勉強はしたけど、実装はしたことないものといえば CPU、OS、コンパイラーなのです1が、先日 ruby30th 誕生日会のキーノートで matz が “Static Compiler for Ruby” という今はまだない vaporware として 挙げていたのでこの static compiler を作ろうとなりました。

katsyoshi/vaporware - GitHub

Goal

Ruby のコードを static compile できるようにする。

コンパイル先のターゲットは x86 とします。 ARMRISC-V などは今回の実装ではターゲットにしないです。

とはいえすべての Ruby の機能を実装すると時間がかかりすぎるので個人で無理のない範囲 で作ろうとします。無理のない実装範囲は以下なのかなと

  1. 四則演算
  2. 変数
  3. メソッド
  4. 制御構文
  5. プリミティブ型

この5つの機能を実装する予定です。

実現する機能以外のことについて

5つの機能を実現すること以上のことはやらない予定です。 やらないこととしては 最適化GC 、 外部ファイルで定義したメソッドやクラスの読み込みは実装しない予定です。 実装しない個人的意見を以下に書いていきます。

最適化は Ruby のコードを単純に 機械語 におとしただけでは現在の RubyVM より速くならないと考えているからです(要確認)。 LLVM IR などへの変換ではなく、 機械語 なのは LLVM をインストールする必要があるなどして 面倒なのが大きいです2。あとバージョン毎に LLVM IR が異なるのも現状では対応しにくい点となっています。

GC (Garbage Collection: ガベージコレクション) についてはそもそもクラスをサポートできない、 変数などのメモリを確保しておく時間が長いプログラムを対象としないので今回はスコープ外としています。

外部ファイル読み込みについてですが、外部ファイルの読み込みして コンパイル するだけなら そこまで問題にはならないと考えていますが、外部ファイルで定義された メソッドクラス を 事前に コンパイル して最後に リンク するのは型が不定になるのでサポートするのは難しいと考えています。

実装方針

Goal までの実装は 低レイヤを知りたい人のためのCコンパイラ作成入門 を参考にすすめていきます。 最初に コンパイラー を実装するものとして C 言語 がたぶん勉強してきてうかぶと思います3C 言語 だと 機械語VM のバイトコードへ落とすことのできる資料が多いので選択しています。 とは言っても Ruby からの脳内変換はある程度必要なので慣れているというのもあります。

実装としては AST(Abstract Syntax Tree: 構文抽象木) から愚直に x86 アセンブラ をファイルへ書き出し、 そこから C コンパイラー (gcc or clang) をつかって機械語へ コンパイル します。 フルフルの Ruby を実装するわけじゃないので依存する gem の依存も極力減らしたいです。

実装環境

  • CPU: Ryzen Thread Ripper 1950x
  • gcc: 12.2.1
  • clang: 16.0.0
  • OS: Gentoo Linux
  • Linux Kernel: 6.2 系

パーサー

まず、AST を得るために パーサー が必要なのですが、 Ruby の構文は複雑なのでここは頑張らないようにします。ここをどうやって解決するのかというと RubyVM::AbstractSyntaxTreeRipper をつかうのか、parser.gem をつかうのかを決めるひつようがあります。今回というかしばらくは parser.gem を利用して AST を得ることにします。 ゆくゆくは RubyVM::AbstractSyntaxTree への置き替えはするよていです。

ここはそのまま parser.gem のチュートリアルどおりにすれば AST が得られます。

require "parser/current"

puts Parser::CurrentRuby.parse("(1 + 2) * 3 / (5 - 4)")

きょうはここまで

とりあえず手を動かしはじめましたが、ななななんと、似たような機能が実は Ruby 3.3 向けに JIT として入ったようです4。 ということでねこの話はね、勉強の話しかないんですが一旦 Goal まで作ってみましょうね。


  1. 個人差があります。 

  2. Gentoo Linux 使っているので毎回コンパイルしているので本当にめんどう。バイナリあるよとかそういう正論は受け付けていないです。 

  3. 時代によって変わるかも。Java だったり、Lisp だったりする人がいるかも。 

  4. https://github.com/ruby/ruby/pull/7448