2013-02-13
あなとみー おぶ mrubyのJIT(その5)
|いよいよここまで来たか。ということで、今回はjit.cの中でも中心となる、mrbjit_dispatchの説明をします。大まかな動きについては大体説明したと思いますので、プログラムを細かく見ていきます。
void * mrbjit_dispatch(mrb_state *mrb, mrbjit_vmstatus *status)
mrb_irep *irep = *status->irep; mrb_code **ppc = status->pc; mrb_value *regs = *status->regs; size_t n; mrbjit_code_info *ci; mrbjit_code_area cbase; mrb_code *prev_pc; mrb_code *caller_pc; void *(*entry)() = NULL; void *(*prev_entry)() = NULL;
変数の宣言です。statusをいちいち参照していると遅いのでショートカットという意味の変数もあります。そういうのはここで初期化しておきます。
if (mrb->compile_info.disable_jit) { return status->optable[GET_OPCODE(**ppc)]; }
disable_jitが真の時はコンパイルもネイティブコードの実行もせずに即座に終わります。
prev_pc = mrb->compile_info.prev_pc;
prev_pcは前回ここを通った時のpcの値です。どこから飛んできたのかで別々のcode_infoを割り当てるためにこうしています。code_infoはエントリーアドレスとかレジスタの使用情報などが入った構造体で、簡単に言うと、code_infoが異なるとたとえmrubyのVMのレベルでは同じ命令でも全然別の機械語コードになります。
if (irep->ilen < NO_INLINE_METHOD_LEN) { caller_pc = mrb->ci->pc; } else { caller_pc = NULL; mrb->compile_info.nest_level = 0; }
メソッドをインライン化するかを判定します。NO_INLINE_METHOD_LENはデフォルトでは5でgetter/setterをインライン化するのを想定しています。caller_pcはメソッドの呼び出し元のpcの値で、インライン化の時は呼び出し元毎にcode_infoを用意します。インライン化しない場合はNULLにして呼び出し元関係なく共有します。インライン化しない場合はnest_levelを0にしてOP_RETURNをコンパイルしないようにします。つまりメソッドの終わりに必ずVMに戻るわけです。OP_SENDでは呼び出し元のアドレスを保存しないため、callinfoを共有した場合、呼び出し元が分からなくなります。詳しくは、OP_SEND, OP_RETURNのコード生成の時に説明します。ちなみに、OP_SEND, OP_RETURNを実装して正月がつぶれました。
mrb->ci->pcはcallerの呼び出し元を表します。mrb->ciはメソッド・ブロックの呼び出し履歴が入っています。
cbase = mrb->compile_info.code_base; n = ISEQ_OFFSET_OF(*ppc); if (prev_pc) { ci = search_codeinfo_prev(irep->jit_entry_tab + n, prev_pc, caller_pc); } else { ci = NULL; }
ここで、code_infoを検索します。これを書いていて、mrb_runではciはcallinfoなので紛らわしいなと気付きました。直す可能性大です。
nはppcで指す命令がメソッドの先頭から何番目かを表しています。search_codeinfo_prevはprev_pc, caller_pcが一致するcode_infoを探すための関数です。アルゴリズムはリニアサーチで効率が悪いのですが、普通は要素数が少ないので問題ないと思います。2つキーがあるので効率を高めるのは結構面倒そうです。
if (ci) { if (cbase) { if (ci->used > 0) { mrbjit_gen_jump_block(cbase, ci->entry); cbase = mrb->compile_info.code_base = NULL; } }
code_info(ci)が見つかったかどうかチェックします。見つかった場合はそれにちゃんとネイティブコードが入っていれば既にコンパイル済みとうことです。いろんな都合でネイティブコードが入っていない場合もあって、これはci->usedでチェックします。これが0より大きいとコンパイル済みでネイティブコードが入っています。
cbaseはXbyakのオブジェクトのポインタでこれがNULLではない場合はコンパイルモードというわけです。
そういうわけで内側の条件判定
if (ci->used > 0) { mrbjit_gen_jump_block(cbase, ci->entry); cbase = mrb->compile_info.code_base = NULL; }
はコンパイル中に既にコンパイル済みのコードが出てきたら、ジャンプ命令(X86機械語の)を生成してコンパイル済みのコードと合流してコンパイルを終わるという処理になります。
if (cbase == NULL && ci->used > 0) { void *rc; prev_pc = *ppc;
さて、コンパイル中ではなく、ネイティブコードがある、いつネイティブコードを呼び出すの今でしょう、ということでネイティブコードを呼び出す処理が続きます。
asm volatile("mov %0, %%ecx\n\t" "mov %1, %%ebx\n\t" "mov %2, -0x4(%%esp)\n\t" : : "g"(regs), "g"(ppc), "d"(status) : "%ecx", "%ebx", "memory"); asm volatile("sub $0x4, %%esp\n\t" "call *%0\n\t" "add $0x4, %%esp\n\t" : : "g"(ci->entry) : "%edx"); asm volatile("mov %%eax, %0\n\t" : "=c"(rc)); asm volatile("mov %%edx, %0\n\t" : "=c"(prev_entry));
たぶん、何やっているか分からないでしょう。私も分かってもらえるような説明が出来る自信はないのですが、次回頑張ります。これだけは、言っておきたいので書きます。gccの拡張asmを使う場合の座右の銘「下手な考え休むに似たり」。
それでは、次はgccの拡張asm講座になりそう気もしますが次回に続きます。
たぶん、続く
- 42 http://t.co/I8oEayYQ
- 17 https://www.google.co.jp/
- 16 http://www.rubyist.net/~kazu/samidare/
- 13 http://t.co/vNvhtLKt
- 10 http://t.co/jd5UkZ1b
- 10 http://translate.googleusercontent.com/translate_c?depth=1&hl=de&ie=UTF8&prev=_t&rurl=translate.google.com&sl=auto&tl=en&u=http://d.hatena.ne.jp/miura1729/20130209/1360384727&usg=ALkJrhh2xazo2vYDUyScozJCCl296nnVFg
- 5 http://www.google.com/url?sa=D&q=http://d.hatena.ne.jp/miura1729/20130211/1360607189&usg=AFQjCNEOGhbmL235m5_uRf-nKoiI0FQXTw
- 5 http://yavaeye.com/
- 4 http://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=2&ved=0CDkQFjAB&url=http://d.hatena.ne.jp/miura1729/&ei=ZtscUZ-HGeTAmQWVgYG4Cg&usg=AFQjCNEW_nxeFj6y1FC4eN6rD-y5cMCQ4g&sig2=FtgHTHqaqGyAMi9PpByhcg&bvm=bv.42452523,d.dGY
- 4 http://www.kt.rim.or.jp/~kbk/zakkicho/