2022-04-10

[php-src読書録]その10: if/else/switch

php-srcのコードリーディングした内容をコツコツ残すテスト。その10

今回はif/else/switch

まずはif/else

<?php

if (true) {
  echo 111;
} else {
  echo 222;
}

opcode

$_main:
     ; (lines=5, args=0, vars=0, tmps=0)
     ; (before optimizer)
     ; /path/to/10.php:1-8
     ; return  [] RANGE[0..0]
0000 JMPZ bool(true) 0003
0001 ECHO int(111)
0002 JMP 0004
0003 ECHO int(222)
0004 RETURN int(1)

zend_compile_if() でコンパイルされる

条件のコンパイル
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5392:L5392

JMPZの設定。ジャンプ先は後で設定。
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5393:L5393

if文のいずれかの処理にマッチした場合はif文を抜ける必要があるのでそのJMPの設定。JMP先は最後に設定
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5397-L5399

i != list->children - 1 の条件は最後のelseifはジャンプしないので分岐している感じっぽい

JMPZで条件に合致しなかったときのJMP先を次のelseif, elseの開始位置に設定
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5400

elseの場合は条件なしで実行する
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5404

if文のいずれかの処理にマッチしたときのif分を抜けるためのジャンプ先をここで設定
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5408-L5413

opcode

JMPZZEND_JMPZ_SPEC_CONST_HANDLER で処理される

val = RT_CONSTANT(opline, opline->op1);

if (Z_TYPE_INFO_P(val) == IS_TRUE) {
	ZEND_VM_NEXT_OPCODE();
} else if (EXPECTED(Z_TYPE_INFO_P(val) <= IS_TRUE)) {
	// ...
	ZEND_VM_JMP_EX(OP_JMP_ADDR(opline, opline->op2), 0);
}

SAVE_OPLINE();
op1_type = IS_CONST;
if (i_zend_is_true(val)) {
	opline++;
} else {
	opline = OP_JMP_ADDR(opline, opline->op2);
}
// ...
ZEND_VM_JMP(opline);

Z_TYPE_INFO_P(val) == IS_TRUE だったり、 i_zend_is_true(val) がtrueの場合は次の処理に以降し、 そうでない場合は OP_JMP_ADDR(opline, opline->op2) によって次の分岐部分(elseif か else)にジャンプします。

switch

今後はswitch文

<?php

switch(1) {
case 1:
  echo 111;
  break;
case 2:
  echo 222;
case 3:
  echo 333;
}

opcode

$_main:
     ; (lines=12, args=0, vars=0, tmps=1)
     ; (before optimizer)
     ; /path/to/10_1.php:1-12
     ; return  [] RANGE[0..0]
0000 T0 = IS_EQUAL int(1) int(1)
0001 JMPNZ T0 0007
0002 T0 = IS_EQUAL int(1) int(2)
0003 JMPNZ T0 0009
0004 T0 = IS_EQUAL int(1) int(3)
0005 JMPNZ T0 0010
0006 JMP 0011
0007 ECHO int(111)
0008 JMP 0011
0009 ECHO int(222)
0010 ECHO int(333)
0011 RETURN int(1)

expressionのコンパイル
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5487:L5487

loopしていないのにbeginしているのはbreakするため
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5489

条件判定結果のTMPVARを作成
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5492

条件のコンパイル
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5528

ZEND_IS_EQUAL をemit
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5537-L5543

条件に合致したときのJMPNZをemit。例のごとくジャンプ先は後で設定するスタイル https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5545

defaultのJMPをemit https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5549

順番に上記のJMP先(条件に合致したときにJMPNZやデフォルトのJMP)を設定してからstatementをコンパイル https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5557
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5573
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5582

defaultのケースがない場合はswitchを抜けた先がジャンプ先になるように調整
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5585-L5586

各caseにbreakがある場合はJMPのジャンプ先がswitchを抜けた先になる。 各caseにbreakがない場合はJMPがないので、フォールスルーして次のcaseのステートメントを実行する感じになる。

このエントリーをはてなブックマークに追加