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
JMPZ
は ZEND_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のステートメントを実行する感じになる。