iseq_translate_direct_threaded_code @ compile.c
いよいよステップ5です。YARVの命令処理は「ダイレクトスレッデッドコード」という方式で高速化されています。これについては YARV Maniacs 【第 3 回】 が詳しくてしかもわかりやすいです。それまで他の解説読んでもスレッデッドコードの意味がわからなくって、これ読んですごい感激した記憶があります。
要するに、マシン語の命令ワードを
[命令ID][引数...]
じゃなくて
[命令を処理するコードのアドレス][引数...]
にしちゃえば、VMの実装を
ip = 0; Next: switch( generated_iseq[ip] ) { // 現在の命令ポインタipの命令IDで分岐 case BRANCHIF: ..処理..; goto Next; case GETLOCAL: ..処理..; goto Next; ... } }
じゃなくて
goto generated_iseq[0]; BRANCHIF: ..処理..; goto generated_iseq[ip]; GETLOCAL: ..処理..; goto generated_iseq[ip]; ...
こうできるので、ループの先頭に戻るジャンプと、命令IDを使ってswitchのジャンプテーブルを引く処理が消えるから高速になる!という最適化ですね。
iseq_translate_direct_threaded_code関数は、さっき作ったiseq->iseqの[命令ID]の部分を[命令を処理するコードのアドレス]に置き換えたiseq_encodedというワード列を生成します。
static int iseq_translate_direct_threaded_code(yarv_iseq_t *iseq) { #if OPT_DIRECT_THREADED_CODE || OPT_CALL_THREADED_CODE #if OPT_DIRECT_THREADED_CODE void **table = (void **)th_eval(0); #else extern void **insns_address_table(); void **table = get_insns_address_table(); #endif int i; iseq->iseq_encoded = ALLOC_N(VALUE, iseq->size); MEMCPY(iseq->iseq_encoded, iseq->iseq, VALUE, iseq->size); for (i = 0; i < iseq->size; /* */ ) { int insn = iseq->iseq_encoded[i]; int len = insn_len(insn); iseq->iseq_encoded[i] = (VALUE)table[insn]; i += len; } #else iseq->iseq_encoded = iseq->iseq; #endif return COMPILE_OK; }
th_evalはYARV VMの実行関数ですが、引数0で呼び出すと、その関数内の、各命令の処理コードについたラベルのテーブルを返します。あとは、その表をつかって、insn を table[insn] に置き換えて回っています。OPT_CALL_THREADED_CODEというのは各命令の処理を(1つの関数内のgotoラベルではなく)別々の関数として実装する場合で、その場合は関数ポインタの表を取得して置き換えています。ラベルのアドレスを取るのはgcc拡張なので、それができない他のコンパイラの場合に使う実装だと思います。