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/