あなとみー おぶ mrubyのJIT (その8)
かなり間が空いちゃいました。その間にmrubyのJITをコーティングしたりお祭りの資料を作ったりしていました。結構内容が変わって、例えばこれまで説明していたものがjit.cからvm.cに移ったりしています。そんなこんなで結構速度が上がって(多くがwannabe53さんのおかげ)、もうすぐオリジナルの2倍速くらいか(単純なループなら4倍くらいだけどメソッドコールが入ると速度が落ちる)というところまで来ています。
さて、今回はjitcode.ccの説明です。
const void * mrbjit_emit_code(mrb_state *mrb, mrbjit_vmstatus *status) { MRBJitCode *code = (MRBJitCode *)mrb->compile_info.code_base; const void *rc = mrbjit_emit_code_aux(mrb, status, code); if (rc == NULL && code == NULL) { mrb->compile_info.code_base = NULL; } return rc; }
これが、コード生成部のトップレベルmrbjit_emit_codeです。最初に、
MRBJitCode *code = (MRBJitCode *)mrb->compile_info.code_base;
でCレベルで持っているvoid *型のXbyakのCodeGeneratorオブジェクトをC++のオブジェクトに変換します。変換って言っても単にキャストですが。なんか、すごい勢いで怒られそうなコードですがとりあえず動いているからいいかって感じです。
const void *rc = mrbjit_emit_code_aux(mrb, status, code); if (rc == NULL && code == NULL) { mrb->compile_info.code_base = NULL; }
ここで、実際の処理を行うmrbjit_emit_code_auxを呼び出します。mrbjit_emit_code_auxはコード生成をするとそのコードの命令列の先頭のアドレスを返します。コードが生成され合い場合はNULLを返します。
その後のif文はなぜいるのか忘れてしまった説明出来ないです。すみません・・・。これがないと動かないのですが…
次に、mrbjit_emit_code_auxです。
static const void * mrbjit_emit_code_aux(mrb_state *mrb, mrbjit_vmstatus *status, MRBJitCode *code) { mrb_irep *irep = *status->irep; mrb_value *regs = *status->regs; mrb_code **ppc = status->pc; const void *entry; if (code == NULL) { code = the_code; mrb->compile_info.code_base = code; entry = code->gen_entry(mrb, irep); }
初めてmrbjit_emit_codeを呼び出したとき、codeはNULLになっているのでCodeGeneratorオブジェクトを設定します。今のところ、CodeGeneratorオブジェクトは1つでthe_codeというグローバルのstatic変数に入っています。ただ、複数のCodeGeneratorオブジェクトを管理できるようにするため、the_codeを直接アクセスする箇所を出来る限り減らしています。code->gen_entry(mrb, irep)は今のところ何もしないのですが、CodeGenratorオブジェクトになんか設定する場合とかを想定しています。
switch(GET_OPCODE(**ppc)) { case OP_NOP: return code->emit_nop(mrb, irep, ppc); case OP_MOVE: return code->emit_move(mrb, irep, ppc); case OP_LOADL: return code->emit_loadl(mrb, irep, ppc);
次に実行しようとする命令に対応するコードを生成します。emit_*というメソッドはjitcode.hに定義されていてほとんどはそれを呼び出すだけです。
例外は、OP_ENTERとOP_RETURNでそれぞれ次のようになっています。
case OP_ENTER: mrb->compile_info.nest_level++; return code->emit_enter(mrb, status); case OP_RETURN: mrb->compile_info.nest_level--; if (mrb->compile_info.nest_level < 0) { return code->emit_return(mrb, status); } else { return code->emit_return_inline(mrb, status); }
OP_ENTERはメソッドの先頭、OP_RETURNはメソッドの最後の処理です。mrb->compile_info.nest_levelは現在のメソッドの呼び出しのネストレベルを表します。このレベルはネイティブコードで実行する場合で途中でVMに戻る場合は0に戻されます。また、メソッドがインライン化しない場合も0戻されます。つまり、OP_RETURNの時点でnest_levelが1以上の場合はOP_ENTERからOP_RETURNまでVMに戻らず、しかもインライン化するメソッドということになります。そういう場合は特別扱いして高速なコードを生成します。特別扱いするコード生成のメソッドが、emit_return_inlineです。
default: mrb->compile_info.nest_level = 0; return NULL; }
普通この手のdefaultはnot reachedでエラーチェックのためにあるって感じですが、現時点のmrubyのJITは普通に実行されます。サポートされていない命令はVMのJITで実行されます。ちなみに、emit_*においてもNULLを返すとその命令はVMで実行されます。例えば、OP_SENDで可変引数とかコード生成が超面倒な割には使用頻度が少ないのでNULLを返してVMで実行してもらっています。
そういうことで、今回は終わり
gdgdですがまあ仕方がない。次回はあるのか???
多分続く