2022-04-09

[php-src読書録]その9: whileとforeach

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

今回はwhileとforeach

while

<?php

$i = 0;
while($i < 1) {
  echo 123;
  $i++;
}

opcode

$_main:
     ; (lines=7, args=0, vars=1, tmps=3)
     ; (before optimizer)
     ; /path/to/9.php:1-8
     ; return  [] RANGE[0..0]
0000 ASSIGN CV0($i) int(0)
0001 JMP 0004
0002 ECHO int(123)
0003 PRE_INC CV0($i)
0004 T3 = IS_SMALLER CV0($i) int(1)
0005 JMPNZ T3 0002
0006 RETURN int(1)

見るとfor文とほぼ同じで、for文はinit/loopを書く場所が確保されているだけで本質的にはwhileと変わらないことがわかる。

JMPを生成
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5187:L5187

statementを生成
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5192

条件分岐前にJMP先を設定
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5195

条件判定とJMPを生成
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5196-L5198

foreach

foreachのスクリプト例

<?php

foreach([1, 2] as $a) {
  echo $a;
}

opcode

$_main:
     ; (lines=6, args=0, vars=1, tmps=1)
     ; (before optimizer)
     ; /path/to/9_1.php:1-6
     ; return  [] RANGE[0..0]
0000 V1 = FE_RESET_R array(...) 0004
0001 FE_FETCH_R V1 CV0($a) 0004
0002 ECHO CV0($a)
0003 JMP 0001
0004 FE_FREE V1
0005 RETURN int(1)
LIVE RANGES:
     1: 0001 - 0004 (loop)

FE_RESET_R FE_FETCH_R FE_FREE あたりが新出なのでこのあたりを見ていく

zend_compile_foreach()ZEND_FE_RESET_RZEND_FE_FETCH_R のopcodeをemit
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5323:L5323 https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5328

変数部分をコンパイル
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5333

ステートメント部分をコンパイル
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5354

FETCH部分にJMP
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5360

ZEND_FE_FREE をemit
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L5370

opcodeのハンドリング

ZEND_FE_RESET_RZEND_FE_RESET_R_SPEC_CONST_HANDLER を呼び出す

array_ptr = RT_CONSTANT(opline, opline->op1);
if (EXPECTED(Z_TYPE_P(array_ptr) == IS_ARRAY)) {
	result = EX_VAR(opline->result.var);
	ZVAL_COPY_VALUE(result, array_ptr);
	if (IS_CONST != IS_TMP_VAR && Z_OPT_REFCOUNTED_P(result)) {
		Z_ADDREF_P(array_ptr);
	}
	Z_FE_POS_P(result) = 0;
	ZEND_VM_NEXT_OPCODE();
// ...

resultにarrayのzvalをコピーし、 Z_FE_POS_P(array)result.u2.fe_pos )を0にリセットします。

ZEND_FE_FETCH_RZEND_FE_FETCH_R_SPEC_VAR_HANDLER を呼び出す

array = EX_VAR(opline->op1.var);
// ...
fe_ht = Z_ARRVAL_P(array);
pos = Z_FE_POS_P(array);
p = fe_ht->arData + pos;
while (1) {
	if (UNEXPECTED(pos >= fe_ht->nNumUsed)) {
		/* reached end of iteration */
		ZEND_VM_SET_RELATIVE_OPCODE(opline, opline->extended_value);
		ZEND_VM_CONTINUE();
	}
	pos++;
	value = &p->val;
	value_type = Z_TYPE_INFO_P(value);
	ZEND_ASSERT(value_type != IS_INDIRECT);
	if (EXPECTED(value_type != IS_UNDEF)) {
		break;
	}
	p++;
}
Z_FE_POS_P(array) = pos;
// ...
if (EXPECTED(opline->op2_type == IS_CV)) {
	zval *variable_ptr = EX_VAR(opline->op2.var);
	SAVE_OPLINE();
	zend_assign_to_variable(variable_ptr, value, IS_CV, EX_USES_STRICT_TYPES());
	ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
//...

arrayの現在のポジション(Z_FE_POS_P(array))からデータを取得し variable_ptr にセットします。 データ取得後はポジションをインクリメントし、arrayの現在のポジションにセットします。 このようにして ZEND_FE_FETCH_R が呼ばれるたびにデータを取得しつつポジションがインクリメントされることになります。

配列の要素数を超えた場合は ZEND_VM_SET_RELATIVE_OPCODE によってループを抜け出します。

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