趣味は vaporware 造りですv0.0.0
プログラマー三大勉強はしたけど、実装はしたことないものといえば
CPU、OS、コンパイラーなのです1が、先日 ruby30th 誕生日会のキーノートで
matz が “Static Compiler for Ruby” という今はまだない vaporware
として
挙げていたのでこの static compiler を作ろうとなりました。
Goal
Ruby のコードを static compile できるようにする。
コンパイル先のターゲットは x86 とします。 ARM や RISC-V などは今回の実装ではターゲットにしないです。
とはいえすべての Ruby の機能を実装すると時間がかかりすぎるので個人で無理のない範囲 で作ろうとします。無理のない実装範囲は以下なのかなと
- 四則演算
- 変数
- メソッド
- 制御構文
- プリミティブ型
この5つの機能を実装する予定です。
実現する機能以外のことについて
5つの機能を実現すること以上のことはやらない予定です。 やらないこととしては 最適化 、 GC 、 外部ファイルで定義したメソッドやクラスの読み込みは実装しない予定です。 実装しない個人的意見を以下に書いていきます。
最適化は Ruby のコードを単純に 機械語 におとしただけでは現在の RubyVM より速くならないと考えているからです(要確認)。 LLVM IR などへの変換ではなく、 機械語 なのは LLVM をインストールする必要があるなどして 面倒なのが大きいです2。あとバージョン毎に LLVM IR が異なるのも現状では対応しにくい点となっています。
GC (Garbage Collection: ガベージコレクション) についてはそもそもクラスをサポートできない、 変数などのメモリを確保しておく時間が長いプログラムを対象としないので今回はスコープ外としています。
外部ファイル読み込みについてですが、外部ファイルの読み込みして コンパイル するだけなら そこまで問題にはならないと考えていますが、外部ファイルで定義された メソッド や クラス を 事前に コンパイル して最後に リンク するのは型が不定になるのでサポートするのは難しいと考えています。
実装方針
Goal までの実装は 低レイヤを知りたい人のためのCコンパイラ作成入門 を参考にすすめていきます。 最初に コンパイラー を実装するものとして C 言語 がたぶん勉強してきてうかぶと思います3。 C 言語 だと 機械語 や 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::AbstractSyntaxTree
や Ripper
をつかうのか、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 まで作ってみましょうね。