NODE_WHILE 再び
case NODE_OPT_N: case NODE_WHILE: case NODE_UNTIL:{ LABEL *prev_start_label = iseq->compile_data->start_label; LABEL *prev_end_label = iseq->compile_data->end_label; LABEL *prev_redo_label = iseq->compile_data->redo_label; VALUE prev_loopval_popped = iseq->compile_data->loopval_popped; struct iseq_compile_data_ensure_node_stack *enlp = iseq->compile_data->ensure_node_stack;
さっきまとめたところによると、whileで管理しておきたいのは
- break, redo, next のジャンプ先
- whileの時点でのpopedフラグ
- ensureで囲まれてる場合はそのensureの命令列
でした。まさにその5項目がiseq構造体のメンバとして保存されています。ここは、これからこの5項目を書き換えるので、whileループのコンパイルが終わったら戻すために一時退避しているコードです。
続いて、その5項目を新しくセットアップするコードです。
LABEL *next_label = iseq->compile_data->start_label = NEW_LABEL(nd_line(node)); /* next */ LABEL *redo_label = iseq->compile_data->redo_label = NEW_LABEL(nd_line(node)); /* redo */ LABEL *break_label = iseq->compile_data->end_label = NEW_LABEL(nd_line(node)); /* break */ LABEL *end_label = NEW_LABEL(nd_line(node)); iseq->compile_data->loopval_popped = 0; iseq->compile_data->ensure_node_stack = 0;
break, redo, next のジャンプ先ラベルは新しく作っています。新しくwhileで囲ったことで、「この中でbreakなどしてもensureを実行する必要はない状態」にいったん戻るので、ensure_node_stack は 0 クリアされています。loopval_popped は... なんで 0 なんでしょ?
前回読んだ部分をもいちど見直してみると...
jmp next_label redo_label: 本体 next_label: 条件判定 end_label: putnil break_label: if(poped) pop
breakでは常に値がpushされることを前提にして、要らないならwhile側でpopする、というコードに現在はコンパイルされてるようでした。なので、とりあえずbreakには常にpushさせるloopval_popped = 0 になっているのかな、と思います。もしかしたら将来的に最適化される予定なのかも。
この下には、前回やったwhile文のコード生成が続きます。中に出てくるbreakなどは、ここで再帰的にコンパイルされるはずです。で、それが終わると後処理です。
ADD_CATCH_ENTRY(CATCH_TYPE_BREAK, redo_label, break_label, 0, break_label); ADD_CATCH_ENTRY(CATCH_TYPE_NEXT | 0x10000, redo_label, break_label, 0, iseq->compile_data->start_label); ADD_CATCH_ENTRY(CATCH_TYPE_REDO, redo_label, break_label, 0, iseq->compile_data->redo_label);
「break while 例外」で説明したように、ブロック内での break, next, redo を例外として捕まえるための、ハンドラ登録をしています。読み方はだいたい、たとえば
ADD_CATCH_ENTRY(CATCH_TYPE_BREAK, redo_label, break_label,
0, break_label);
なら「redo_labelからbreak_labelの範囲でCATCH_TYPE_BREAKタイプの例外が発生したら、break_labelにジャンプせよ」という意味で登録されてるようです。CATCH_TYPE_NEXT に or されている 0x10000 がとてもマジカルな感じですが、whileのnextとブロックのnextで処理を変えなきゃいけないせいで、その識別用、かな。あとでset_exception_table (compile step 4.2)を読むときに真剣に調べるかもしれません。とりあえずスルーで。
あと、ここの iseq->compile_data->start_label は簡単に redo_label でいいんじゃないかと思うのですが、どうなんだろ。
iseq->compile_data->start_label = prev_start_label;
iseq->compile_data->end_label = prev_end_label;
iseq->compile_data->redo_label = prev_redo_label;
iseq->compile_data->loopval_popped = prev_loopval_popped;
iseq->compile_data->ensure_node_stack = enlp;
break;
最後に、最初で保存した変数を戻しています。これでネストもばっちりです。