compile_branch_condition @ compile.c
「if文の条件を判定して、trueならthen側、falseならelse側にジャンプ」というマシン語を生成する処理は、関数が別にわかれています。
COMPILE_( nd_cond ) スタックから値をpopして、偽ならelse_labelにジャンプする命令
のようにすれば、その場で簡単にコードで生成もできそうですが…?
結論から言うと、そのようなやり方だと、&& や || を使った条件式を効率的なマシン語にコンパイルできません。
if b1 && b2 then body end
を律儀にコンパイルすると、たぶん
b1が真ならxxxxにジャンプする命令 put false jump yyy xxx: put b2 yyy: スタックから値をpopして、偽ならelse_labelにジャンプする命令 then_label: body else_label:
こんな感じになって、特にb1が偽だったときなど、いちいとfalseをスタックに入れてジャンプしてそれで条件判定をすることになって、あまり嬉しくありません。
b1が偽ならelse_labelにジャンプする命令 b2が偽ならelse_labelにジャンプする命令 then_label: body else_label:
こんな感じにコンパイルできれば、スッキリ綺麗です。これをやるのが compile_branch_condition 関数です。
static int compile_branch_condition(yarv_iseq_t *iseq, LINK_ANCHOR *ret, NODE * cond, LABEL *then_label, LABEL *else_label) switch (nd_type(cond)) {
なかではまたswitch文で分岐してます。簡単なところから見てみます。
case NODE_FALSE: case NODE_NIL: ADD_INSNL(ret, nd_line(cond), jump, else_label); break;
「偽」扱いになる式 false か nil が直接書いてあった場合は、無条件でelseに飛びます。「真」扱いの式では逆に無条件でthenに飛んでいます(略)。
case NODE_NOT: compile_branch_condition(iseq, ret, cond->nd_body, else_label, then_label); break;
"if ! cond then ..." という形の場合は、elseとthenを入れ替えてコード生成してやります。
case NODE_AND:{ LABEL *label = NEW_LABEL(nd_line(cond)); compile_branch_condition(iseq, ret, cond->nd_1st, label, else_label); ADD_LABEL(ret, label); compile_branch_condition(iseq, ret, cond->nd_2nd, then_label, else_label); break; }
nd_1st && nd_2nd の形の場合、偽なら即elseにジャンプという処理をつなげます。余計なpushやpopは要りません。さらに複雑な条件文だった場合を考えて、この分岐コード生成処理は、再帰的になっています。NODE_ORの場合は「真なら即thenにジャンプ」がつながります(略)。
default: COMPILE(ret, "branch condition", cond); ADD_INSNL(ret, nd_line(cond), branchunless, else_label); ADD_INSNL(ret, nd_line(cond), jump, then_label); break;
以上のどれでもない場合(if メソッド?呼び出し then .. など)は、どうしようもないので、普通に条件式をコンパイルしてやります。COMPILE()マクロでコンパイルすると、条件式の値がスタックに入った状態になるコードが生成されるのでした。その値で分岐するbranchunless命令がここでは使われています。