Rubiniusをhackしたことは特にないが、中を軽く読んでおこうという気持ちになったので読んだ内容をメモしておく。今日初めて読んだ状態なのであまり参考にならないかもしれない。
歴史
2007あたり開発開始
v1 (2010)
- 「Rubyで実装されたRuby」
- バイトコード仮想マシンはC++/LLVM
- LLVM JIT
- 世代別GC
- JRubyに次ぐ3番目にRailsが走ったRuby処理系
v2 (2013)
- 当時まだ出ていなかったRuby 2.1互換
- GILなし、マルチスレッドサポートの向上
- JITにより2-4倍程度まで高速化を達成
v3 (2016)
- ビルドにclang/clang++しかサポートしなくなった
- AOTコンパイルによるバイナリ生成もサポート
- 実行時にRubyを使わないようにした
- JITはバグが多かったため取り除かれた
特徴
- 2017/4/12時点ではRuby 2.3.1をターゲットにしている
- バイトコード仮想マシンはC++で実装されている
- 標準ライブラリはRubyで実装されている
- C拡張向けにC APIはある
- GILなしでネイティブスレッドを使える
- continuation, ripper, tracepoint, tracerなどの標準ライブラリをサポートしない
- refinementsと$SAFEもサポートしない
- Windowsをサポートしていない
実装の概要
Rubiniusのビルド
-
core rake taskでリポジトリ直下の
core/ディレクトリに入っているRubyのコードを、同ファイル内のCodeDBCompiler.compileがコンパイルする- 内部的には
Rubinius::ToolSets::Build::Compilerを使ってコンパイルしている- このクラスは別リポジトリrubinius/rubinius-codeにgemがあってCRubyのgemとしてC++ extensionがビルドされ実行される。
- CRubyって書いてるけど既にrbxのバイナリがあればbootstrapはできるかもしれない
- パーサー、コンパイラはrubinius-codeの方のリポジトリを見る必要がある
- このクラスは別リポジトリrubinius/rubinius-codeにgemがあってCRubyのgemとしてC++ extensionがビルドされ実行される。
- 内部的には
rbxコマンド起動によるmain()の実行
machine/drivers/cli.cppにrbx(1)のmain()がある
- Environnmentのコンストラクタでargc, argvを保存
- ここでrubinius::Stateも作られる。中にVMを保持しており、このStateインスタンスはいろんな場所で引き回される。
STATEは#define STATE rubinius::State* state。
- ここでrubinius::Stateも作られる。中にVMを保持しており、このStateインスタンスはいろんな場所で引き回される。
-
Environment#boot()を実行
- bootstrap_ontologyで最初に必要なクラス、メソッド、オブジェクトを用意
- load_argvでARG0, ARGVオブジェクトの読み込み
-
main用スレッドを起動。ここでJITとかも起動している
- Thread::main_threadがload_coreを呼び、
runtime/coreディレクトリにあるファイルをこのへんでsignature, data, index, initializeなど読み込む- ここで呼ばれてるThread::createはこれ。
typedef Object* (*ThreadFunction)(STATE);のため。 - 読み込む、というのは、Rubiniusのビルド時にruntime/core以下に吐いていたものは
rubinius::CompiledCode(Rubiniusのバイトコード表現だと思われる)をシリアライズしたもの(ビルド後runtime/core/dataを見ればわかるが、バイナリではない)で、これをデシリアライズ後実行している。- その後
code->execute_script(state)で実行するが、code->executeの実態をどこでセットしているかはTODO - おそらくRubiniusのバイトコードをインタプリタ実行する
- その後
- ここで呼ばれてるThread::createはこれ。
-
Rubinius::Loaderをinstantiateして#mainを叩く。実行中はこの行より後には行かない。
-
Rubinius::Loaderを取得する際に呼ばれるget_constの実装。rubinius::Moduleが再帰的にsuperclassを探索する。 -
.newや#mainを呼ぶ時のObject::sendの実装。allow_primitiveはデフォルトtrue。- 内部でDispatch::sendを呼び出す。これがmethod_missingとかをハンドルしている
-
method->executeで参照されているのはExecutableのexecuteフィールド。 - executor型は
typedef Object* (*executor)(State*, Executable* exec, Module* mod, Arguments& args);なので、この型の関数を呼び出している
-
- Thread::main_threadがload_coreを呼び、
Rubinius::Loader#mainの実行
いろいろメソッドを呼び出すが、読んだところだけコメントする。
なお、このクラスはRubiniusのビルド時にCRuby + rubinius-code gemによってコンパイルされているので、このクラスの実行時点でRubinius自体がRuby言語のパーサやコンパイラを持っている必要はない
- preamble
- system_load_path
- signals
- run_compiled
-
ENV["RBX_RUN_COMPILED"]が指定されてなければスキップされるのであまり気にしなくて良い - ARGVを全て
Rubinius.run_scriptにかける。-
Rubinius.run_scriptはRubinius.primitive :vm_run_scriptを呼び出す。System::vm_run_scriptが呼び出す実態はこれで、Rubiniusのバイトコードを実行するのではないか
-
-
- load_compiler
- こいつがコンパイル済のrubinius-code gemを読み込んでいる。ここで初めてRubiniusのランタイムにパーサ/コンパイラが出現する。
- rubinius-code gem自体をコンパイルするのをどこでやってるかは読んでないけど多分ビルド時にやるのではないか
-
Kernel.#requireの実態はRubinius::CodeLoader.requireであり、load_fileがRubinius::ToolSets::Runtime::Compilerを使うので、load_compilerをして初めてrequireできるようになる- 事実、これより前のメソッドはrequireをしていない
- こいつがコンパイル済のrubinius-code gemを読み込んでいる。ここで初めてRubiniusのランタイムにパーサ/コンパイラが出現する。
- preload
- detect_alias
- options
- 指定したスクリプトが
@scriptに入る
- 指定したスクリプトが
- load_paths
- deprecations
- rubygems
- gemfile
-
-Gオプションでrequire 'bundler/setup'するっぽい
-
- debugger
- profiler
- requires
-
-rオプションの処理
-
- evals
-
-eオプションの処理
-
- script
- optionsで処理した
@scriptをRubyのスクリプトのファイル名として、CodeLoader.load_scriptを呼び出す- 前述した
load_fileがここでも使われ、その後RubiniusのバイトコードをRubinius.run_scriptする
- 前述した
- これがよく使う
ruby foo.rbみたいなメインの処理と思われる
- optionsで処理した
- repl
まだ読めていないもの
- CompiledCodeや、core/dataからデシリアライズしたメソッドのexecuteをどこでセットしてるか
- Rubiniusのバイトコードインタプリタがどうやってコードを実行するのか
まとめ
RubiniusがRubyのスクリプトを読み込んで実行するまで:
- Rubiniusのビルド時に、ランタイム用にRuby言語で記述された
Rubinius::LoaderやRubinius::CodeLoaderをCRubyとrubinius-code gemでコンパイルし、Rubiniusのバイトコードインタプリタが理解できる状態にしておく - rbxコマンドが実行されると、argc, argvからARGVを作り、VMの準備後メインのスレッドが立ち上がり、そのスレッドでコンパイル済のバイトコードを実行することで
Rubinius::Loader#mainが呼ばれる -
Rubinius::Loader#mainは#load_compilerを実行し、CRubyによってコンパイル済のrubinius-code gem(Rubinius用のRuby言語のパーサ、コンパイラ)を読み込む -
Rubinius::Loader#optionsで読み込むスクリプトのファイル名をARGVから取得し、rubinius-code gemに入っているRubinius::ToolSets::Runtime::CompilerがRubyをバイトコードに変換し、実行する