ytljitの型推論の説明(その2)

2回目はシグネチャーを作る話です。シグネチャーはメソッドの引数の型オブジェクトを配列でまとめたものです。型オブジェクトという未定義の言葉が出てきたので、これから説明します。

型オブジェクトはRubyのクラスをWrapしたオブジェクトです。型オブジェクトはBOXING/UNBOXINGの2種類があり、Rubyのクラスと独立して設定できます。つまり、BOXINGなFixnumもUNBOXINGなArrayなんてのも表現可能です。それぞれの型オブジェクトは、次のようなメソッドを持っていて、コード生成時にあんまり型による場合分けとかせずに、型推論の結果による最適化が出来るようになっています。

  1. gen_boxing (BOX化するアセンブラコードを生成する)
  2. gen_unboxing (UNBOX化するアセンブラコードを生成する)
  3. gen_copy (そのクラスのデータをコピーするコードを生成する)

例えば、FIXNUMによる足し算(c=a + b)は

atype.gen_unboxing(a)
btype.gen_unboxing(b)
c = a + b  # a + b はUNBOXのFixnumでのみ正しく計算できる
ctype.gen_boxing(c)

みたいな感じで定義しておけば、a, b, cの推論結果によって勝手にBOXING/UNBOXINGのコードを入れたり入れなかったりしてくれるわけです。細かい実装は型オブジェクトについてはytljitのコードのlib/ytljit 中のvm_type.rbを、gen_*についてはvm_type_gen.rbを見てみてください。

型オブジェクトの説明が終わって、次にシグネチャに非常に関連のある型推論contextについて説明します。
ノード(ytljit内部VMの命令の単位)毎に推論メソッド(collect_candidate_type)が定義してあり、型推論はこれを呼び出すことで行います。あるノードの推論メソッド中に別のノードの推論メソッドを呼び出して、その結果を使って自分の推論を行うとかやっています。このような構造になっているとどうしても型推論処理全体で参照できる変数が欲しくなります。このような変数をグローバル変数でとっても良いわけですが、嫌だったので型推論contextというオブジェクトにまとめて、引数として渡すようにしました。また、推論メソッドでは必ず型推論contextを返すようにして、ある推論メソッド中で書き換えるとその情報がその後のノードにも伝わるようになっています。
なお、このような構造は型推論だけではなく、プログラムの構造解析やコード生成もそうなっています。contextとせず、わざわざ型推論contextとしたのはコード生成contextとかもあるからです。
さて、この型推論contextにはこのようなインスタンス変数があります。

  1. @top_node           一番元となるノード
  2. @visit_top_node        クラス・メソッド・ブロックの一番先頭のノードのうち、型推論が行われた物が入る。同じ推論を何度も繰り返さないためのもの
  3. @convergent         推論が収束したか。型推論が終わってもこれがfalseならもう1度推論を繰り返す。
  4. @current_method_signature_node 現在推論中のノードのメソッド・ブロックの引数のノードの配列
  5. @current_method         現在推論中のノードのメソッド・ブロックの先頭のノードの配列

最後の2つがシグネチャと大きく関係します。

ちょっと長くなってきたのでここで中断します。

続く (といいなー)