ひとり勉強会

ひとり楽しく勉強会

NODE_BREAK

break文のコンパイルでやるべきことのおさらいです。

  • ジャンプ脱出コードを生成するか、例外脱出コードを生成するか
  • ジャンプ脱出の場合、ensureの扱い
case NODE_BREAK:{
  unsigned long level = 0;

  if (iseq->compile_data->redo_label != 0) {
      /* while/until */
      add_ensure_iseq(ret, iseq);
      COMPILE_(ret, "break val (while/until)", node->nd_stts,
               iseq->compile_data->loopval_popped);
      ADD_INSNL(ret, nd_line(node), jump,
                iseq->compile_data->end_label);
  }

まずジャンプ脱出の場合。ブロック等の別命令列の中ではなく、whileループの中なら、redo_labelのようなループ脱出先ラベルが記憶されているので、それで判断してます。end_labelの有無で判断する方が好みなような(どうでもいい)。
で、次に、add_ensure_iseqでensureの命令列をここに展開します。思いっきり簡略化すると、この関数は次のようにひたすらensureの命令列をコンパイルしてつなげていく関数です。begin〜ensureは何重にもネストしている可能性があるので、ループで全てのensure節をたどる必要があります。

static void
add_ensure_iseq(LINK_ANCHOR *ret, yarv_iseq_t *iseq) #超簡略化
{
    struct iseq_compile_data_ensure_node_stack *enlp =
        iseq->compile_data->ensure_node_stack;
    while (enlp) {
        COMPILE_POPED(ensure_part, "ensure part",
                      enlp->ensure_node);
        enlp = enlp->prev;
    }
}

続けて、break に渡す式をコンパイル。最後に、end_labelへのジャンプ命令を付け加えます。

  else if (iseq->type == ISEQ_TYPE_BLOCK) {
    break_by_insn:
      /* escape from block */
      COMPILE(ret, "break val (block)", node->nd_stts);
      ADD_INSN1(ret, nd_line(node), throw,
                INT2FIX(level | 0x02) /* TAG_BREAK */ );
  }

お次は、例外で脱出するコードの場合。ブロックの中のbreakなら例外で脱出です。まずbreakの値式をコンパイルして、throw命令を最後に一個出力します。level変数については後述。

  else if (iseq->type == ISEQ_TYPE_EVAL) {
      COMPILE_ERROR(("Can't escape from eval with break"));
  }

eval("break") はコンパイルエラーになるそうです。YARVだとこれは難しいのかな。
最後に、それ以外の場合。命令列の親をたどっていって、ループかブロックが見つかるまで探します。以下概要。見つかったら、goto break_by_insnで、さっきの例外で脱出する場合のコードへとコンパイルします。

  else {
    yarv_iseq_t *ip = iseq->parent_iseq;
    while (ip) {
      level++;
      if (ip->compile_data->redo_label != 0) {
        ... (略) ...
        goto break_by_insn;
      }
      else if (ip->type == ISEQ_TYPE_BLOCK) {
        ... (略) ...
        goto break_by_insn;
      }
      ip = ip->parent_iseq;
    }
    COMPILE_ERROR(("Illegal break"));
  }

(... (略) ...)のところではlevelを命令に埋め込むためのビット演算がいろいろありましたが省略。level変数は、「何個親の命令列まで戻って例外を飛ばすか」をカウントしています。levelを数える必要がある理由はちょっとまだ把握できてません。ユーザーの書いたbegin〜rescueがbreakの例外などを捕まえたりしないように、かな?