ひとり勉強会

ひとり楽しく勉強会

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拡張なので、それができない他のコンパイラの場合に使う実装だと思います。