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

祝その7、前回(ytljitの型推論)も前々回(yarv2llvm)も第6回くらいで挫折したんだよな。まああいつらよりは説明しやすいのですが。

そういうことで、無事にかは知らんけど拡張asmを超えて続きを説明します。ちなみに、今やっているのはjit.cのmrbjit_dispatchです。前回2回使って70行くらいしか説明していなんだな。どうでもいいことだけど。

ネイティブコードを実行して、無事戻ってきたところからです。

      irep = *status->irep;
      regs = *status->regs;
      n = ISEQ_OFFSET_OF(*ppc);
      if (irep->ilen < NO_INLINE_METHOD_LEN) {
	caller_pc = mrb->ci->pc;
      }
      else {
	caller_pc = NULL;
	mrb->compile_info.nest_level = 0;
      }
      if (rc) {
	mrb->compile_info.prev_pc = *ppc;
	return rc;
      }
      ci = search_codeinfo_prev(irep->jit_entry_tab + n, prev_pc, caller_pc);

ごちゃごちゃと後始末が続きます。
あれ、これって見たことがあると思った方、記憶力がいいですね。うらやましい。mrbjit_dispatchの先頭で出てきました。ローカル変数の初期化とcode_infoのサーチ処理です。ネイティブコードがirep, regsなどを書き換えているため、ローカル変数を再設定しているのです。

      if (rc) {
	mrb->compile_info.prev_pc = *ppc;
	return rc;
      }

これは出てないです。rcはネイティブコードから返される値の1つです。内容は、例外など特別に対処が必要なことがあるとそのハンドラのアドレスが返ってきます。ハンドラはmrb_runのgotoのラベルで、第3回(http://d.hatena.ne.jp/miura1729/20130211/1360607189)で説明した通り、mrbjit_dispatchの戻り値はその後mrb_run中でgotoされますからrcの値をそのまま返します。rcがNULLなら正常運転なので次に続きます。

ここからは実際にコンパイルするところです。ちなみにネイティブコードを呼んだ場合もここを通ります。

  if (irep->prof_info[n]++ > COMPILE_THRESHOLD) {
    //      printf("size %x %x %x\n", irep->jit_entry_tab[n].size, *ppc, prev_pc);
    if (ci == NULL) {
      //printf("p %x %x\n", *ppc, prev_pc);
      ci = add_codeinfo(mrb, irep->jit_entry_tab + n);
      ci->prev_pc = prev_pc;
      ci->caller_pc = caller_pc;
      ci->code_base = mrb->compile_info.code_base;
      ci->entry = NULL;
      ci->used = -1;
    }

    if (ci->used < 0) {
      entry = mrbjit_emit_code(mrb, status);
      if (prev_entry && entry) {
	//printf("patch %x %x \n", prev_entry, entry);
	cbase = mrb->compile_info.code_base;
	mrbjit_gen_jmp_patch(cbase, prev_entry, entry);
      }

      if (entry) {
	ci->entry = entry;
	ci->used = 1;
      }
      else {
	/* record contination patch entry */
	if (cbase) {
	  ci->entry = mrbjit_get_curr(cbase);
	}
	//	printf("set %x %x \n", ci->entry, entry);
	ci->used = -1;
	// printf("%x %x %x\n", ci->entry, *ppc, ci);
      }
    }
  }

コメントアウトされたprintfが痛いですね。

  if (irep->prof_info[n]++ > COMPILE_THRESHOLD) {

何回ここを通ったかをカウントします。現状ではCOMPILE_THRESHOLDの値は1000です。

    if (ci == NULL) {
      //printf("p %x %x\n", *ppc, prev_pc);
      ci = add_codeinfo(mrb, irep->jit_entry_tab + n);
      ci->prev_pc = prev_pc;
      ci->caller_pc = caller_pc;
      ci->code_base = mrb->compile_info.code_base;
      ci->entry = NULL;
      ci->used = -1;
    }

何度も出てきてます、既存のcode infoがあるかどうかのチェックです。無い場合は新たに作ります。usedが-1なのは確保されているけどネイティブコードは入っていないという意味です。

    if (ci->used < 0) {
      entry = mrbjit_emit_code(mrb, status);
      if (prev_entry && entry) {
	//printf("patch %x %x \n", prev_entry, entry);
	cbase = mrb->compile_info.code_base;
	mrbjit_gen_jmp_patch(cbase, prev_entry, entry);
      }

usedがさっそく出てきました。コンパイルは当然(でもないが)まだコンパイルしていない場所が対象です。

mrbjit_emit_codeはネイティブコード生成の心臓部です。今後説明する予定ですが、jitcode.ccで定義されているので興味のある人は見てみてください。少なくとも今説明しているmrbjit_dispatchよりは読みやすいと思います。mrbjit_emit_codeは生成したネイティブコードの先頭を返します。これをentryという変数に入れます。

     if (prev_entry && entry) {
	//printf("patch %x %x \n", prev_entry, entry);
	cbase = mrb->compile_info.code_base;
	mrbjit_gen_jmp_patch(cbase, prev_entry, entry);
      }

prev_entryとは何でしょう?これは、ネイティブコードからの戻り値の1つで終了処理の先頭アドレスが入っています。つまり、ネイティブコードが実行されていなければこの値はNULLです。
このことから、このif文は次のような条件の判定になります。

 何らかの理由でコンパイル出来ず一旦VMに戻るコードを生成してしまったけど、
再度挑戦したらコンパイルに成功して続けてネイティブコードで実行出来るようになった

その場合は、邪魔な終了処理をJMP命令に書き換えて(出た!)VMに戻らずコードを直通させるようにしています。

      if (entry) {
	ci->entry = entry;
	ci->used = 1;
      }
      else {
	/* record contination patch entry */
	if (cbase) {
	  ci->entry = mrbjit_get_curr(cbase);
	}
	//	printf("set %x %x \n", ci->entry, entry);
	ci->used = -1;
	// printf("%x %x %x\n", ci->entry, *ppc, ci);
      }

ネイティブコードが生成出来たらcode infoにそのエントリーを入れます。あとは、ネイティブコードが入っているよっていう印のusedに1を入れます。
else節って意味あったけかな?いろいろ試行錯誤した跡が残ってしまった感じだから良く分からない (ヲイ)

mrbjit_dispatchの終わりのところです。

  if (cbase && entry == NULL) {
    /* Finish compile */
    mrbjit_gen_exit(cbase, mrb, irep, ppc);
    mrb->compile_info.code_base = NULL;
    mrb->compile_info.nest_level = 0;
  }

if文はコンパイル中でネイティブコードが作られなかった場合という条件で、この場合はVMに戻る処理を生成して、もはやコンパイル中ではないということを表すためcode_baseをNULLにします。そして、nest_levelを0にしてこのメソッドの中でOP_RETURNが出てきてもコンパイルしないようにします。

  mrb->compile_info.prev_pc = *ppc;

prev_pcに現在のpcの値を入れておきます。つぎに、mrbjit_dispatchが呼ばれた時はmrb_runでpcが更新されて、prev_pcと*ppcは違う値になっているはずです。

  return status->optable[GET_OPCODE(**ppc)];

正常に実行された場合のとび先を求めてmrb_runに返します。

いやー面倒なところが終わった!次は緩くjitcode.ccに行きます。OP_SENDまでは楽出来そうだ。

続く♪