ひとり勉強会

ひとり楽しく勉強会

th_eval_body:例外処理

改めて話を戻して、例外処理について。raise〜rescueやthrow〜catchの他にも、break/redo/next, returnなども同じ仕組みを使って実装されています。この辺りの処理は th_eval_body にまとまっています。基本的な流れとしては…

exception_handler:
  cont_pc = cont_sp = catch_iseqval = 0;

  cfp = th->cfp;                            // 現在のCFP
  epc = cfp->pc - cfp->iseq->iseq_encoded;  // 例外の発生したアドレス

  // 現在のスコープで例外をcatchすべきかどうかチェック
  //  (iseq->catch_tableを調べる)

  // catch すべきなら例外タイプに応じて処理

  // そうでない場合、もう一つ上のスコープを調べる
  else {
    th->cfp++;
    if (th->cfp->pc != &yarv_finish_insn_seq[0]) {
      goto exception_handler;
    }
    else {
      pop_frame(th);
      th->errinfo = err;
      TH_POP_TAG2();
      JUMP_TAG(state);
    }
  }

&yarv_finish_insn_seq[0] は前回読んだfinish命令が入ったスコープですね。そこまで言っても例外ハンドラが見あたらなかったら、VMレベルでもっと上にエラーを投げてます。
「現在のスコープで例外をcatchすべきかどうかチェック」は以下のような単純なforループで探索。ensureハンドラが見つかった場合、そこに処理を移します。

for (i = 0; i < cfp->iseq->catch_table_size; i++) {
  entry = &cfp->iseq->catch_table[i];
  if (entry->start < epc && entry->end >= epc) {
    if (entry->type == CATCH_TYPE_ENSURE) {
      catch_iseqval = entry->iseq;
      cont_pc = entry->cont;
      cont_sp = entry->sp;
      break;
    }
  }
}

ensureに処理を移すコードはこんな感じに、新しく制御フレームを作ってvm_loop_startに戻ってます。

if (catch_iseqval != 0) {
  /* found catch table */
  rb_iseq_t *catch_iseq;

  /* enter catch scope */
  GetISeqPtr(catch_iseqval, catch_iseq);
  cfp->sp = cfp->bp + cont_sp;
  cfp->pc = cfp->iseq->iseq_encoded + cont_pc;

  /* push block frame */
  cfp->sp[0] = err;
  push_frame(th, catch_iseq, FRAME_MAGIC_BLOCK,
    cfp->self, (VALUE)cfp->dfp, catch_iseq->iseq_encoded,
    cfp->sp + 1, cfp->lfp, catch_iseq->local_size - 1);

  state = 0;
  th->errinfo = Qnil;
  goto vm_loop_start;
}

catch_tableでCATCH_TYPE_ENSURE以外の適切なタイプにひっかかった場合、たとえばbreak中にCATCH_TYPE_BREAKの範囲に当たった場合は、単純にその位置にpcを戻せば良いので、新しいフレームを積んだりせずに(ほぼ即)goto vm_loop_startで戻ってました。