Ribbon: ブートストラップの準備

処理系を記述するには色々な作戦が考えられるが、今回は 可能な限りSchemeで書き、各実装で共有する 方針でいくことにした。例えばコマンドラインのパースといったインタプリタの基本的な挙動を含めてSchemeで記述し、C++とかJavaではコマンドラインを list
してVMに通知するだけという仕組みにしたい。
最終的な処理系は2段ロケットで起動する。つまり、
- ブートストラップVM 。コマンドラインをパースしてライブラリのパスや起動対象のSchemeプログラムを特定し、内蔵したVMコンパイラでSchemeプログラムをVM命令列に変換する。
- アプリケーションVM 。ブートストラップVMが生成したVM命令列を実行する。
... じゃぁブートストラップVMが実行するVM命令列は誰が生成するんだよという問題があるが、これは既存のScheme処理系で生成する。これを コールドスタートVM と呼ぶ。
3段ロケットじゃん。
将来的には、Schemeプログラムのデバッグは専用の デバッガVM を用意してそちらに担当させようと思っているのでVMは都合4つ出てくることになる。超ややこしい。

ヒープの共有
もうひとつややこしい問題としてヒープの共有がある。
現在は、Gauche(Scheme処理系)上でシステムのプロトタイプをしていて、構成としては コールドスタートVMに相当する外部Scheme処理系(= Gauche) が生成したVM命令列を直接 アプリケーションVM で実行する構成となっている。
このとき、RibbonのヒープはGaucheから見てもSchemeオブジェクトに見えているものの、(Ribbitに由来する) "rib" 構造のオブジェクトになっているためVM間でやりとりするためには相互に変換する必要がある。
Gaucheのpair = (<car> . <cdr>)
Gaucheから見たRibbonのpair = #(<car> <cdr> 0) ;; 0 = pair-type
Gauche上の実装ではribを長さ = 3のvectorで表現しており、末尾はそのribが表現しているオブジェクトの型IDが入る。pairにはゼロ番がアサインされている。
この違いがあるため、コールドスタートVMとブートストラップVM間ではオブジェクト形式の変換が必要となる。
変換は2つの戦略を使用している:
- 直接変換( https://github.com/okuoku/yuniribbit-proto/blob/f0f43c1e1ff836a9566e1ed6f64d6193e5a86a96/yuniribbit/rvm.sls#L82-L110 )
- 一旦専用の形式にシリアライズしてしまう( https://github.com/okuoku/yuniribbit-proto/blob/f0f43c1e1ff836a9566e1ed6f64d6193e5a86a96/emul/ribbon/glue/vm.sls#L20-L28 )
直接変換は、いわゆるFFIとしてGauche側のファイルI/Oやコンソール出力、数学関数を使用する際に使っている。
後者の場合はSchemeオブジェクトは単なるバイト列になるため、VM命令列など、循環参照を含む可能性があるオブジェクトをやりとりする際に使用する。

何故かまだ未実装が見つかる
マジでなんで。。?

起動だけで19分掛かる
とりあえず、完全なブートストラップをやってみたらどれくらい掛かるのかやってみた。
real 19m7.924s
user 19m59.795s
sys 0m25.670s
... これヤバいのでは。。VMをGaucheで実装しているのでC++に移植した場合には10倍くらいのパフォーマンス向上が見込めるけどそれでも2分掛かることになる。
明かに遅いのは(Schemeで書かれた)readerなので、真面目に最適化しないとダメかな。。yuniとして許容するS式の構文は殆ど固まっているので、今後はあんまりメンテナンス性とか気にしなくても良いし。。
あとreaderのバグが出たので起票。