あなとみー おぶ mrubyのJIT(その4)

今回は前回の予告通りstatusとは何かからです。

何度もmrubyには処理系の状態がmrb_state構造体でまとまっていていいよという話をしていますが、実はmrb_state構造体に入っている状態だけがすべてではないのです。mrb_stateに入っていない状態は何かというとmrb_runのローカル変数で実現されているVMの状態です。その1で説明した通り、Tracing JITではプログラムすべてをコンパイルするわけではないので途中でVMに戻る必要があります。戻った時にVMの状態であるmrb_runのローカル変数の内容も整合性が取れている必要があります。ところが、何もしないとネイティブコードからはどこにmrb_runのローカル変数の情報があるのか分からないので書き換えることはできません。*1

このような問題を解決するためにmrubyのJITではstatusという変数を用意しました。どういうものかはstatusの初期化処理をみれば一目瞭然です。

  mrbjit_vmstatus status = {
    &irep, &proc, &pc, &pool, &syms, &regs, &ai, 
    optable, gototable, &prev_jmp
  };

つまりローカル変数のアドレスをメンバーにした構造体です。それぞれの変数は型が違いますので配列ではなく構造体です。構造体の定義は、include/mruby/irep.hにあってこんな感じです。

typedef struct mrbjit_vmstatus {
  mrb_irep **irep;
  struct RProc **proc;
  mrb_code **pc;
  mrb_value **pool;
  mrb_sym **syms;
  mrb_value **regs;
  int *ai;
  void **optable;
  void **gototable;
  jmp_buf **prev_jmp;
} mrbjit_vmstatus;

ポインタのポインタになるので*がいっぱいついています。アドレスを取るとレジスタに割り当てられなくて遅くなるのでは?と思う人もいるかもしれませんが、そうでしょうけど大部分のところはネイティブコードで実行するので大丈夫じゃないかと思います。
それぞれのメンバーの紹介をします。

  • irep 現在実行中のメソッド・ブロックの各種情報(procのメンバーのコピー)
  • proc 現在実行中のメソッド・ブロックのProcオブジェクト
  • pc 現在実行中のVMの命令を指すポインタ
  • pool 定数テーブル(irepのメンバーのコピー)
  • syms シンボルテーブル(irepのメンバーのコピー)
  • regs レジスタの配列(irepのメンバーのコピー)
  • ai mrb_run開始時のアリーナのインデックス(GC関係の話)
  • opttable 命令ごとのラベルのアドレスの配列
  • gototable opttableに入っていないラベルのアドレスの配列
  • prev_jmp mrb_run開始時のmrb->jmp 例外時にスタック巻き戻しに使う

statusをネイティブコードには引数として渡してやるわけです。そうすることでコンパイラの変更やプログラムの変更を心配することなくVMの状態が書き換えられるわけです。ちなみに、mrb_state構造体は引数として渡しません。使わないの?というとそうではなく、JITコンパイラが動いている間にmrb_state構造体のアドレスは変わらないので直接そのアドレスをアクセスするように命令を生成してしまいます。

というところで、vm.cでの話は終わり。*2

次回「mrubyのjitの中心部jit.cの説明」私は、生き延びることができるか?

*1:コンパイラの生成コードを解析してローカル変数のスタック上の位置やレジスタ変数が保存されている場所を突き止めるのは可能です。ただし、変数を増やしたりコンパイラやオプションが変わっただけで動かなくなることでしょう

*2:実は少し残っていますが説明しないかもしれない。vm.cをmrbjit_で検索してもらえば分かります。static関数を呼び出すための苦肉の策です