ひとり勉強会

ひとり楽しく勉強会

set_exception_table @ compile.c

次は、set_sequenceで計算したラベルの位置情報を使って、例外ハンドラのテーブルを構築します。コンパイル時に「このラベルからこのラベルの間で例外が発生したら・・・」式のテーブルを作ってあったので、これを単純に、ラベル情報からアドレス情報を取得します。
コンパイル時のラベル登録はADD_CATCH_ENTRY (YARVソースコード勉強会 (5)あたり参照) マクロで行いました。以下のように、[種類,開始ラベル,終了ラベル,例外ハンドラの命令列,例外ハンドル後に行く先のラベル]という配列という形で、catch_table_ary にラベルの組が記憶されています。

#define ADD_CATCH_ENTRY(type, ls, le, iseqv, lc) \
  (tmp = rb_ary_new(),                               \
   rb_ary_push(tmp, type),                           \
   rb_ary_push(tmp, (VALUE) ls | 1),                 \
   rb_ary_push(tmp, (VALUE) le | 1),                 \
   rb_ary_push(tmp, iseqv),                          \
   rb_ary_push(tmp, (VALUE) lc | 1),                 \
   rb_ary_push(iseq->compile_data->catch_table_ary, tmp))

これを、ラベルの位置情報に変換します。

static int
set_exception_table(yarv_iseq_t *iseq)
  struct catch_table_entry *entry;

  tlen = RARRAY_LEN(iseq->compile_data->catch_table_ary);
  tptr = RARRAY_PTR(iseq->compile_data->catch_table_ary);

  iseq->catch_table = ALLOC_N(struct catch_table_entry, tlen);
  iseq->catch_table_size = tlen;

  for (i = 0; i < tlen; i++) {
    ptr = RARRAY_PTR(tptr[i]);
    entry = &iseq->catch_table[i];
    entry->type = ptr[0] & 0xffff;
    entry->start = label_get_position((LABEL *)(ptr[1] & ~1));
    entry->end = label_get_position((LABEL *)(ptr[2] & ~1));
    entry->iseq = ptr[3];

変換してます。

    /* stack depth */
    if (ptr[4]) {
      LABEL *lobj = (LABEL *)(ptr[4] & ~1);
      entry->cont = label_get_position(lobj);
      entry->sp = label_get_sp(lobj);

      /* TODO: Dirty Hack!  Fix me */
      if (entry->type == CATCH_TYPE_RESCUE ||
          entry->type == CATCH_TYPE_BREAK ||
          (((ptr[0] & 0x10000) == 0)
            && entry->type == CATCH_TYPE_NEXT)) {
        entry->sp--;
      }
    }

最後の「例外ハンドル後に行く先のラベル」については、単純な変換だけではなく、スタックの調整のための情報が計算されています。例外で飛んでくるときは、深くスタックを使った状態からジャンプしてくるので、さきほどset_sequenceで計算した「そのラベルに到達するときのスタックの深さ」とは違う状態になってます。なので、実行時にはentry->contに飛ぶ前にスタックの深さを強制的にentry->spに合わせるのではないかな、と思います。
/* Dirty Hack! */ と書いてある部分では、rescueハンドル後のジャンプ先, break, イテレータブロックからのnext先, の場合について、なぜかさきほど計算した値から 1 引いています。それぞれbegin節やwhileブロック、yield式の値として値を返す必要があって、そのコードがVM側で

  // vm.c  l.1645
  *th->cfp->sp++ = (GET_THROWOBJ_VAL(err));

となっているので、spには「そのラベルに到達するときの深さ」より1個下の値を格納しておくということ、かな。それだけならVM側のコードを th->cfp->sp[-1] = ... にする手がありそうですが、そうするとまた別の箇所と整合性がとれなくなったりするのかもしれません。あんまりちゃんと追ってないです(^-^;