以下の内容はhttps://yui-knk.hatenablog.com/entry/2026/01/15/113728より取得しました。


Ruby Parser開発日誌 (24-20) - parse.yが生成するノードを変える ー rescueとensureのコンパイル

20日目: rescueとensureをコンパイルする

前回はrescueとensureのノードを書き換えたので、今回はそれらをコンパイルできるようにしていきましょう。

バイトコードを眺める

compile.cをいじるまえに、rescueやensureがどのようなバイトコードになるのかを確認しておきましょう。

begin
  a
rescue Ex1 => e1
  e1
rescue Ex2, expr2, Ex2_2 => e2
  e2
else
  b
ensure
  c
end

このコードに対応するバイトコードは次の通りです。

== disasm: #<ISeq:<main>@test.rb:1 (1,0)-(11,3)>
== catch table
# 複数のrescueで1つのcatch tableとISeqになる
| catch type: rescue st: 0000 ed: 0003 sp: 0000 cont: 0008
| == disasm: #<ISeq:rescue in <main>@test.rb:3 (3,0)-(6,4)>
| local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
| [ 1] "$!"@0 # 発生した例外を表す特殊変数

# 1つめのrescue
# `rescue Ex1`
| 0000 getlocal_WC_0                          "$!"@0                    (   3)
| 0002 opt_getconstant_path                   <ic:0 Ex1>
| 0004 checkmatch                             3
| 0006 branchunless                           15
# `=> e1`による代入
| 0008 getlocal_WC_0                          "$!"@0[Rs]
| 0010 setlocal_WC_1                          e1@0
# rescueのbodyである`e1`
| 0012 getlocal_WC_1                          e1@0                      (   4)[Li]
| 0014 leave                                                            (   3)

# 2つめのrescue
# `rescue Ex2`
| 0015 getlocal_WC_0                          "$!"@0
| 0017 opt_getconstant_path                   <ic:1 Ex2>                (   5)
| 0019 checkmatch                             3                         (   3)
| 0021 branchif                               40
# `rescue expr2`
| 0023 getlocal_WC_0                          "$!"@0
| 0025 putself                                                          (   5)
| 0026 opt_send_without_block                 <calldata!mid:expr2, argc:0, FCALL|VCALL|ARGS_SIMPLE>
| 0028 checkmatch                             3                         (   3)
| 0030 branchif                               40
# `rescue Ex2_2`
| 0032 getlocal_WC_0                          "$!"@0
| 0034 opt_getconstant_path                   <ic:2 Ex2_2>              (   5)
| 0036 checkmatch                             3                         (   3)
| 0038 branchunless                           47
# `=> e2`による代入
| 0040 getlocal_WC_0                          "$!"@0                    (   5)[Rs]
| 0042 setlocal_WC_1                          e2@1
# rescueのbodyである`e2`
| 0044 getlocal_WC_1                          e2@1                      (   6)[Li]
| 0046 leave                                                            (   3)

# いずれのrescueでもハンドルできなかった場合は例外を投げる
| 0047 getlocal_WC_0                          "$!"@0
| 0049 throw                                  0

# retryようのcatch table
| catch type: retry  st: 0003 ed: 0008 sp: 0000 cont: 0000

# ensureに対応するcatch tableとISeq
| catch type: ensure st: 0000 ed: 0008 sp: 0001 cont: 0012
| == disasm: #<ISeq:ensure in <main>@test.rb:10 (10,2)-(10,3)>
| local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
| [ 1] "$!"@0
# rescueのbodyである`c`
| 0000 putself                                                          (  10)[Li]
| 0001 opt_send_without_block                 <calldata!mid:c, argc:0, FCALL|VCALL|ARGS_SIMPLE>
| 0003 pop

# 最後に例外を投げる
| 0004 getlocal_WC_0                          "$!"@0
| 0006 throw                                  0
|------------------------------------------------------------------------

# begin ... end全体に対応する
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 2] e1@0       [ 1] e2@1
0000 putself                                                          (   2)[Li]
0001 opt_send_without_block                 <calldata!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0003 pop                                                              (   3)
0004 putself                                                          (   8)[Li]
0005 opt_send_without_block                 <calldata!mid:b, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0007 nop                                                              (   3)
0008 putself                                                          (  10)[Li]
0009 opt_send_without_block                 <calldata!mid:c, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0011 pop
0012 leave

今までと雰囲気がだいぶ違いますね。 もとのコードと各ISeqの対応をまとめると以下のようになります。

  • メインとなるISeqの他にrescue, retry, ensure用のcatch tableとISeqが生成される
  • メインとなるISeq:
    • 本体に当たる部分(a)とelseの部分(b)とensureの部分(c)に対応する命令がその順番で並ぶ
    • elseの部分は例外が発生しないときに実行するので、メインのISeqにだけ存在している
    • ensureの部分は例外が発生してもしなくても実行するので、メインのISeqにも存在する必要がある
  • rescueに対応するISeq:
    • 複数のrescueがある場合でも1つのISeqに命令がまとめられる
    • これは上から順番にチェックして、最初にマッチするrescue句が実行されるため
    • rescue Ex1, Ex2のように1つのrescueに例外が列挙されている場合には左から順に命令列にコンパイルしてISeqに並ぶ
  • ensureに対応するISeq:
    • ensureの部分(c)に対応する命令が入っている
    • ensureの部分は例外が発生してもしなくても実行するので、例外発生時のensureのためにメインのISeqとは別にensureのISeqを用意している
    • メインのISeqとは異なり最後にthrowする

ここでcatch tableについてすこし触れておきましょう。 catch tableには4つの数値が設定されています。

  • st(start): そのcatch tableがカバーするprogram counterの最小値
  • ed(end): そのcatch tableがカバーするprogram counterの最大値
  • sp(stack pointer?): catch tableを抜けたあとのstack pointerの値(だとおもう)
  • cont(continue?): catch tableを抜けたあとのprogram counterの値(のはず)

VMはthrow命令が呼ばれたときに対応するcatch tableを探します。 ここで対応するというのは、現在のpc(program counter)がそのcatch tableのstedの間に収まっているかどうかで判断します。

さきほどのコードでいえばrescueのcatch tableはpc 0000 ~ 0003をカバーしています。

| catch type: rescue st: 0000 ed: 0003 sp: 0000 cont: 0008

本体のISeqでいうとちょうどaメソッドの呼び出しの部分がrescueのcatch tableでカバーされていることがわかります。

-- ここから
0000 putself                                                          (   2)[Li]
0001 opt_send_without_block                 <calldata!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0003 pop                                                              (   3)
-- ここまで
0004 putself                                                          (   8)[Li]
0005 opt_send_without_block                 <calldata!mid:b, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0007 nop                                                              (   3)
0008 putself                                                          (  10)[Li]
0009 opt_send_without_block                 <calldata!mid:c, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0011 pop
0012 leave

バイトコードを生成する

これまでは複数の異なるノードを逆順に木構造にすることでbegin ... endを表してきました。

  • NODE_ENSURE
    • NODE_RESCUE
      • NODE_RESBODY

書き換えによってBeginNodeのフィールドとしてrescue, else, ensureが並ぶことになります。 書き換え前はそれぞれのノードに対応する関数が用意されています。

case NODE_BEGIN:{
  CHECK(COMPILE_(ret, "NODE_BEGIN", RNODE_BEGIN(node)->nd_body, popped));
  break;
}
case NODE_RESCUE:
  CHECK(compile_rescue(iseq, ret, node, popped));
  break;
case NODE_RESBODY:
  CHECK(compile_resbody(iseq, ret, node, popped));
  break;
case NODE_ENSURE:
  CHECK(compile_ensure(iseq, ret, node, popped));
  break;

まずはrescueから対応していきましょう。 再度書き換え前のノードを確認しておきます。

begin
  :begin
rescue Ex => ex
  :rescue
else
  :else
end

#  @ NODE_RESCUE (id: 8, line: 1, location: (2,2)-(6,7))*
#  +- nd_head:
#  |   @ NODE_SYM (id: 0, line: 2, location: (2,2)-(2,8))*
#  |   +- string: :begin
#  +- nd_resq:
#  |   @ NODE_RESBODY (id: 6, line: 3, location: (3,0)-(4,9))
#  |   +- nd_args:
#  |   |   @ NODE_LIST (id: 2, line: 3, location: (3,7)-(3,9))
#  |   |   +- as.nd_alen: 1
#  |   |   +- nd_head:
#  |   |   |   @ NODE_CONST (id: 1, line: 3, location: (3,7)-(3,9))
#  |   |   |   +- nd_vid: :Ex
#  |   |   +- nd_next:
#  |   |       (null node)
#  |   +- nd_exc_var:
#  |   |   @ NODE_LASGN (id: 3, line: 3, location: (3,10)-(3,15))
#  |   |   +- nd_vid: :ex
#  |   |   +- nd_value:
#  |   |       @ NODE_ERRINFO (id: 5, line: 3, location: (3,10)-(3,15))
#  |   +- nd_body:
#  |   |   @ NODE_SYM (id: 4, line: 4, location: (4,2)-(4,9))*
#  |   |   +- string: :rescue
#  |   +- nd_next:
#  |       (null node)
#  +- nd_else:
#      @ NODE_SYM (id: 7, line: 6, location: (6,2)-(6,7))*
#      +- string: :else

既存のコンパイル処理をおおまかに説明すると以下のようになっています。

  • compile_rescue関数
  • compile_resbody関数1
    • rescueの対象となるNODE_RESBODYnd_headコンパイルしてcheckmatch命令とbranchif命令をそれぞれの例外ごとに付与する
    • 例外を代入する変数であるNODE_RESBODYnd_exc_varコンパイルしてsetlocal命令を生成する
    • NODE_RESBODYnd_bodyコンパイルする
    • これをnd_nextがなくなるまで繰り返す
static int
compile_rescue(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int popped)
{
    // `rescue`以下の部分をコンパイルする
    const rb_iseq_t *rescue = NEW_CHILD_ISEQ(RNODE_RESCUE(node)->nd_resq,
                                             rb_str_concat(rb_str_new2("rescue in "),
                                                           ISEQ_BODY(iseq)->location.label),
                                             ISEQ_TYPE_RESCUE, line);

    bool prev_in_rescue = ISEQ_COMPILE_DATA(iseq)->in_rescue;
    ISEQ_COMPILE_DATA(iseq)->in_rescue = true;
    {
        // `begin`の本体部分をコンパイルする
        CHECK(COMPILE(ret, "rescue head", RNODE_RESCUE(node)->nd_head));
    }
    ISEQ_COMPILE_DATA(iseq)->in_rescue = prev_in_rescue;

    // `else`があればコンパイルする
    if (RNODE_RESCUE(node)->nd_else) {
        ADD_INSN(ret, line_node, pop);
        CHECK(COMPILE(ret, "rescue else", RNODE_RESCUE(node)->nd_else));
    }
    ADD_INSN(ret, line_node, nop);
    ADD_LABEL(ret, lcont);

    if (popped) {
        ADD_INSN(ret, line_node, pop);
    }

    /* register catch entry */
    ADD_CATCH_ENTRY(CATCH_TYPE_RESCUE, lstart, lend, rescue, lcont);
    ADD_CATCH_ENTRY(CATCH_TYPE_RETRY, lend, lcont, NULL, lstart);
    return COMPILE_OK;
}

static int
compile_resbody(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int popped)
{
    while (resq) {
        label_miss = NEW_LABEL(line);
        label_hit = NEW_LABEL(line);

        // `Ex =>` の部分をコンパイルする
        narg = RNODE_RESBODY(resq)->nd_args;
        if (narg) {
            switch (nd_type(narg)) {
              case NODE_LIST:
                while (narg) {
                    ADD_GETLOCAL(ret, line_node, LVAR_ERRINFO, 0);
                    CHECK(COMPILE(ret, "rescue arg", RNODE_LIST(narg)->nd_head));
                    ADD_INSN1(ret, line_node, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_RESCUE));
                    ADD_INSNL(ret, line_node, branchif, label_hit);
                    narg = RNODE_LIST(narg)->nd_next;
                }
                break;
              case NODE_SPLAT:
              case NODE_ARGSCAT:
              case NODE_ARGSPUSH:
                ADD_GETLOCAL(ret, line_node, LVAR_ERRINFO, 0);
                CHECK(COMPILE(ret, "rescue/cond splat", narg));
                ADD_INSN1(ret, line_node, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_RESCUE | VM_CHECKMATCH_ARRAY));
                ADD_INSNL(ret, line_node, branchif, label_hit);
                break;
              default:
                UNKNOWN_NODE("NODE_RESBODY", narg, COMPILE_NG);
            }
        }
        else {
            ADD_GETLOCAL(ret, line_node, LVAR_ERRINFO, 0);
            ADD_INSN1(ret, line_node, putobject, rb_eStandardError);
            ADD_INSN1(ret, line_node, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_RESCUE));
            ADD_INSNL(ret, line_node, branchif, label_hit);
        }
        ADD_INSNL(ret, line_node, jump, label_miss);
        ADD_LABEL(ret, label_hit);
        ADD_TRACE(ret, RUBY_EVENT_RESCUE);

        // `=> ex` の部分をコンパイルする
        if (RNODE_RESBODY(resq)->nd_exc_var) {
            CHECK(COMPILE_POPPED(ret, "resbody exc_var", RNODE_RESBODY(resq)->nd_exc_var));
        }

        // `rescue`のbodyをコンパイルする
        if (nd_type(RNODE_RESBODY(resq)->nd_body) == NODE_BEGIN && RNODE_BEGIN(RNODE_RESBODY(resq)->nd_body)->nd_body == NULL && !RNODE_RESBODY(resq)->nd_exc_var) {
            // empty body
            ADD_SYNTHETIC_INSN(ret, nd_line(RNODE_RESBODY(resq)->nd_body), -1, putnil);
        }
        else {
            CHECK(COMPILE(ret, "resbody body", RNODE_RESBODY(resq)->nd_body));
        }

        if (ISEQ_COMPILE_DATA(iseq)->option->tailcall_optimization) {
            ADD_INSN(ret, line_node, nop);
        }
        ADD_INSN(ret, line_node, leave);
        ADD_LABEL(ret, label_miss);
        resq = RNODE_RESBODY(resq)->nd_next;
    }
    return COMPILE_OK;
}

ノードの書き換え後はBeginNodeにrescueやensureがぶら下がることになるので、BeginNodeを処理するcompile_begin関数を用意しておきます。 compile_begin関数はrescueの有無を確認して、rescueがあればcompile_begin_rescue関数を、そうでなければiseq_compile_each0関数を呼び出します。

static int
compile_begin(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int popped)
{
    const int line = nd_line(node);
    const NODE *line_node = node;
    LABEL *lstart = NEW_LABEL(line);
    LABEL *lend = NEW_LABEL(line);
    LABEL *lcont = NEW_LABEL(line);

    if (RB_NODE_BEGIN(node)->rescue_clause) {
        compile_begin_rescue(iseq, ret, RB_NODE_BEGIN(node), popped);
    }
    else {
        CHECK(COMPILE_(ret, "begin body", RB_NODE_BEGIN(node)->statements, popped));
    }

    return COMPILE_OK;
}

compile_begin_rescue関数はというと、引数がBeginNodeになったこと以外はcompile_rescue関数と変わりません。 引数が変わったのはbeginのbodyにあたる部分やelseにあたる部分がBeginNodeに移動したためです。

static int
compile_begin_rescue(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const rb_begin_node_t *const node, int popped)
{
    const int line = nd_line(node);
    const NODE *line_node = node;
    LABEL *lstart = NEW_LABEL(line);
    LABEL *lend = NEW_LABEL(line);
    LABEL *lcont = NEW_LABEL(line);
    const rb_iseq_t *rescue = NEW_CHILD_ISEQ(node->rescue_clause,
                                             rb_str_concat(rb_str_new2("rescue in "),
                                                           ISEQ_BODY(iseq)->location.label),
                                             ISEQ_TYPE_RESCUE, line);

    lstart->rescued = LABEL_RESCUE_BEG;
    lend->rescued = LABEL_RESCUE_END;
    ADD_LABEL(ret, lstart);

    bool prev_in_rescue = ISEQ_COMPILE_DATA(iseq)->in_rescue;
    ISEQ_COMPILE_DATA(iseq)->in_rescue = true;
    {
        CHECK(COMPILE(ret, "rescue head", node->statements));
    }
    ISEQ_COMPILE_DATA(iseq)->in_rescue = prev_in_rescue;

    ADD_LABEL(ret, lend);
    if (node->else_clause) {
        ADD_INSN(ret, line_node, pop);
        CHECK(COMPILE(ret, "rescue else", node->else_clause));
    }
    ADD_INSN(ret, line_node, nop);
    ADD_LABEL(ret, lcont);

    if (popped) {
        ADD_INSN(ret, line_node, pop);
    }

    /* register catch entry */
    ADD_CATCH_ENTRY(CATCH_TYPE_RESCUE, lstart, lend, rescue, lcont);
    ADD_CATCH_ENTRY(CATCH_TYPE_RETRY, lend, lcont, NULL, lstart);
    return COMPILE_OK;
}

rescue EX => ex ...の部分にあたるRescueNodeコンパイルcompile_resbody関数とほとんど変わらないので割愛します。

ensure部分のコンパイル

次にensureがある場合のコンパイル処理についてです。

  • compile_ensure関数
    • NODE_ENSUREnd_ensr、つまりensureのbodyを別のISeqとしてコンパイルしてensure用のcatch tableのためのバイトコードをつくる。実際のコンパイル処理はcompile_ensure関数が行う
    • 別のanchorをつくり本体のISeq用にNODE_ENSUREnd_ensrコンパイルする
    • NODE_ENSUREnd_ensrpush_ensure_entryする
    • NODE_ENSUREnd_head、つまりbegin ... rescue ...の部分をコンパイルする
    • 別のanchorにコンパイルしたnd_ensrの結果を本体のISeqにくっつける
    • ensure用のcatch tableをつくる
static int
compile_ensure(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int popped)
{
    // ensureのcatch table用に`ensure ...`の部分をコンパイルする
    const rb_iseq_t *ensure = NEW_CHILD_ISEQ(RNODE_ENSURE(node)->nd_ensr,
                                             rb_str_concat(rb_str_new2 ("ensure in "), ISEQ_BODY(iseq)->location.label),
                                             ISEQ_TYPE_ENSURE, line);

    // 本体用に`ensure ...`の部分をコンパイルする
    CHECK(COMPILE_POPPED(ensr, "ensure ensr", RNODE_ENSURE(node)->nd_ensr));

    er.begin = lstart;
    er.end = lend;
    er.next = 0;
    // これについては今回は触れない
    push_ensure_entry(iseq, &enl, &er, RNODE_ENSURE(node)->nd_ensr);

    // `ensure`以外の部分をコンパイルする
    CHECK(COMPILE_(ret, "ensure head", RNODE_ENSURE(node)->nd_head, (popped | last_leave)));
    ADD_SEQ(ret, ensr);
    if (!popped && last_leave) ADD_INSN(ret, line_node, putnil);
    if (last_leave) ADD_INSN(ret, line_node, pop);

    // ensureのcatch tableを生成する
    erange = ISEQ_COMPILE_DATA(iseq)->ensure_node_stack->erange;
    if (lstart->link.next != &lend->link) {
        while (erange) {
            ADD_CATCH_ENTRY(CATCH_TYPE_ENSURE, erange->begin, erange->end,
                            ensure, lcont);
            erange = erange->next;
        }
    }

    ISEQ_COMPILE_DATA(iseq)->ensure_node_stack = enl.prev;
    return COMPILE_OK;
}

ensureBeginNodeの配下に移動したので、ensureがある場合のエントリーポイントもcompile_begin関数になります。 compile_begin関数に分岐を追加して

  • ensureがあるとき
  • ensureはないけどrescueがあるとき
  • どちらもないとき

の3パターンで処理を変えます。

static int
compile_begin(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int popped)
{
    if (RB_NODE_BEGIN(node)->ensure_clause){
        compile_begin_ensure(iseq, ret, RB_NODE_BEGIN(node), popped);
    }
    else if (RB_NODE_BEGIN(node)->rescue_clause) {
        compile_begin_rescue(iseq, ret, RB_NODE_BEGIN(node), popped);
    }
    else {
        CHECK(COMPILE_(ret, "begin body", RB_NODE_BEGIN(node)->statements, popped));
    }

    return COMPILE_OK;
}

生成されるバイトコードを確認します。

begin
  p :begin
rescue Ex => ex
  p :rescue
else
  p :else
ensure
  p :ensure
end
== disasm: #<ISeq:<main>@../../test.rb:1 (1,0)-(9,3)>
== catch table
| catch type: rescue st: 0000 ed: 0005 sp: 0000 cont: 0012
| == disasm: #<ISeq:rescue in <main>@../../test.rb:3 (3,0)-(4,11)>
| local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
| [ 1] "$!"@0
| 0000 getlocal_WC_0                          "$!"@0                    (   3)
| 0002 opt_getconstant_path                   <ic:0 Ex>
| 0004 checkmatch                             3
| 0006 branchunless                           18
| 0008 getlocal_WC_0                          "$!"@0[Rs]
| 0010 setlocal_WC_1                          ex@0
| 0012 putself                                                          (   4)[Li]
| 0013 putobject                              :rescue
| 0015 opt_send_without_block                 <calldata!mid:p, argc:1, FCALL|ARGS_SIMPLE>
| 0017 leave                                                            (   3)
| 0018 getlocal_WC_0                          "$!"@0
| 0020 throw                                  0
| catch type: retry  st: 0005 ed: 0012 sp: 0000 cont: 0000
| catch type: ensure st: 0000 ed: 0012 sp: 0001 cont: 0018
| == disasm: #<ISeq:ensure in <main>@../../test.rb:7 (7,0)-(8,11)>
| local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
| [ 1] "$!"@0
| 0000 putself                                                          (   8)[Li]
| 0001 putobject                              :ensure
| 0003 opt_send_without_block                 <calldata!mid:p, argc:1, FCALL|ARGS_SIMPLE>
| 0005 pop
| 0006 getlocal_WC_0                          "$!"@0
| 0008 throw                                  0
|------------------------------------------------------------------------
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] ex@0
0000 putself                                                          (   2)[Li]
0001 putobject                              :begin
0003 opt_send_without_block                 <calldata!mid:p, argc:1, FCALL|ARGS_SIMPLE>
0005 pop                                                              (   1)
0006 putself                                                          (   6)[Li]
0007 putobject                              :else
0009 opt_send_without_block                 <calldata!mid:p, argc:1, FCALL|ARGS_SIMPLE>
0011 nop                                                              (   1)
0012 putself                                                          (   8)[Li]
0013 putobject                              :ensure
0015 opt_send_without_block                 <calldata!mid:p, argc:1, FCALL|ARGS_SIMPLE>
0017 pop
0018 leave                                                            (   6)

良さそうですね。

後置rescueをコンパイルする

ノードの書き換えによってbegin ... resuceと後置rescueとで違うノードを使うようになりました。 いままではどちらのケースもNODE_RESCUEというノードを使い、そのコンパイル処理はcompile_rescue関数が行っていました。 compile_rescue関数はcompile_begin_rescue関数になったので、後置rescueについてもcompile_begin_rescue関数に処理を任せたいところです。

static int
compile_begin_rescue(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int popped)
{
    const int line = nd_line(node);
    const NODE *line_node = node;
    LABEL *lstart = NEW_LABEL(line);
    LABEL *lend = NEW_LABEL(line);
    LABEL *lcont = NEW_LABEL(line);
    NODE *nd_stmts, *nd_rescue, *nd_else;

    switch (nd_type(node)) {
      case RB_BEGIN_NODE:
        nd_stmts = RB_NODE_BEGIN(node)->statements;
        nd_rescue = RB_NODE_BEGIN(node)->rescue_clause;
        nd_else = RB_NODE_BEGIN(node)->else_clause;
        break;
      case RB_RESCUE_MODIFIER_NODE:
        nd_stmts = RB_NODE_RESCUE_MODIFIER(node)->expression;
        nd_rescue = RB_NODE_RESCUE_MODIFIER(node)->rescue_expression;
        nd_else = NULL;
        break;
      default:
    }
    ...
}

static int
iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int popped)
{
      ...
      case RB_RESCUE_MODIFIER_NODE: {
        CHECK(compile_begin_rescue(iseq, ret, node, popped));
        break;
      }
      ...
}

:begin rescue :rescueコンパイルしてみます。

== disasm: #<ISeq:<main>@../../test.rb:1 (1,0)-(1,21)>
== catch table
| catch type: rescue st: 0000 ed: 0002 sp: 0000 cont: 0003
| == disasm: #<ISeq:rescue in <main>@../../test.rb:1 (1,7)-(1,21)>
| local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
| [ 1] "$!"@0
| 0000 getlocal_WC_0                          "$!"@0                    (   1)
| 0002 putobject                              StandardError
| 0004 checkmatch                             3
| 0006 branchunless                           11
| 0008 putobject                              :rescue[Rs]
| 0010 leave
| 0011 getlocal_WC_0                          "$!"@0
| 0013 throw                                  0
| catch type: retry  st: 0002 ed: 0003 sp: 0000 cont: 0000
|------------------------------------------------------------------------
0000 putobject                              :begin                    (   1)[Li]
0002 nop
0003 leave

良さそうですね。

まとめ

今日の成果です。

次回はretryやnextといった制御構文に取り組みたいと思います。


  1. resuceのコンパイル結果の最後に、いずれのrescueにもヒットしなかった場合のためにgetlocal "$!"throwの2つの命令を追加する必要がありますが、これはrb_iseq_compile_node関数で行うのでここでは触れません。



以上の内容はhttps://yui-knk.hatenablog.com/entry/2026/01/15/113728より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14