ProcオブジェクトのMarshal(その1)

YTLJitはコンパイル速度を上げるために、コンパイル途中の情報(VMと呼んでいる)をファイルに保存できるようにする予定です。VMはProcオブジェクトを多用しているので、ProcオブジェクトをMarshal出来るようにしました。結構込み入った話で、忘れそうなので仕組みを書いてみます。ProcオブジェクトのMarshalのソースはytljitの

 ext/ytljit.c
  lib/ytljit/ytljit/marshal.rb

にあります。ytljitのソースコードは、
http://github.com/miura1729/ytljit
です。

Procオブジェクトはオブジェクトが生成された環境を保持しているので、ProcオブジェクトをMarshalするにはその環境もMarshalしなければなりなせん。ところが、環境そのものはbindingメソッドで生成できるfirst class objectなのですが、RubyレベルではMarshalはもちろん、中身をアクセスる事もできません。そこで、ちょっとしたCの拡張ライブラリを作りました。それが、binding#to_aです。

a = 1
b = lambda {}
p b.binding.to_a

の内容のfoo.rbを実行すると

[[main, 1, #<Proc:0x1045fc58@c:/tmp/foo.rb:2 (lambda)>, nil, 136733434], [main, nil, 0]]

と、出力されます。
配列の配列になっているのは、環境に階層構造があるからです。mainはself(トップレベルオブジェクト)、1はaの内容、Procオブジェクトはbの内容です。このように環境が配列で得られます。

もう1つ拡張してあります。それは、Proc#to_iseqです。もうメソッド名からどんな悪どいメソッドが一目瞭然ですが、サンプルを出してみると、

a = 1
b = lambda {}
p b.to_iseq.to_a

が、次のように出力されます。

["YARVInstructionSequence/SimpleDataFormat", 1, 2, 1, {:arg_size=>0, :local_size=>1, :stack_max=>1}, "block in <main>", "c:/tmp/foo.rb", "c:/tmp/foo.rb", 2, :block, [], 0, [[:redo, nil, :label_0, :label_1, :label_0, 0], [:next, nil, :label_0, :label_1, :label_1, 0]], [:label_0, 2, [:putnil], :label_1, [:leave]]]

つまり、これまでできなかった、実行中のProcオブジェクトのYARVバイトコードを得ることができるのです。

さらに、wanabeさんのiseqモジュールを使わせてもらっています。 http://github.com/wanabe/iseq
これは、現在使用できなくなっている、RubyVM::InstructionSequence#loadを使用可能にするモジュールです。

使っている、Cの拡張ライブラリは以上です。次回以降はこれらを組み合わせてどうProcオブジェクトのMarshalを実現しているか書こうと思います。