php-srcのコードリーディングした内容をコツコツ残すテスト。その6
今回はarray
定数配列
<?php
$i = [1, 2];
以下がopcodeで、arrayがそのままアサインされることになる
$_main:
; (lines=2, args=0, vars=1, tmps=1)
; (before optimizer)
; /path/to/6.php:1-4
; return [] RANGE[0..0]
0000 ASSIGN CV0($i) array(...)
0001 RETURN int(1)
arrayは zend_compile_array()
でコンパイルされる。
定数配列の場合、 zend_compile_array()
内の zend_try_ct_eval_array()
でarrayのzvalをコンパイルしている
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L9334
以下で全要素が定数であるかの確認
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L8454-L8483
array_init_size()
でarrayを初期化
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L8494:L8494
マクロなどを全部展開するとこんな感じなコードになり、ハッシュテーブルをinitしてzvalにセットしている。
HashTable *ht = emalloc(sizeof(HashTable));
_zend_hash_init_int(ht, list->children, ZVAL_PTR_DTOR, 0);
zend_array *__arr = ht;
zval *__z = result;
__z.value.arr = __arr;
__z.u1.type_info = IS_ARRAY_EX;
各要素に対して zend_ast_get_zval()
でzvalを取得する
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L8500
ここでresultにzvalをインサート
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L8559
ht->arData + h
に配列としてデータを格納している
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_hash.c#L1077-L1084
変数入りの配列
<?php
$i = 1;
$j = [$i, 2];
opcodes
$_main:
; (lines=5, args=0, vars=2, tmps=3)
; (before optimizer)
; /path/to/6_2.php:1-5
; return [] RANGE[0..0]
0000 ASSIGN CV0($i) int(1)
0001 T3 = INIT_ARRAY 2 (packed) CV0($i) NEXT
0002 T3 = ADD_ARRAY_ELEMENT int(2) NEXT
0003 ASSIGN CV1($j) T3
0004 RETURN int(1)
LIVE RANGES:
3: 0002 - 0003 (tmp/var)
INIT_ARRAYで配列を初期化し、2番目以降の要素をADD_ARRAY_ELEMENTで設定している
コンパイルの該当箇所はこちらで、一番目の要素で ZEND_INIT_ARRAY
して、2番目以降で ZEND_ADD_ARRAY_ELEMENT
のopcodeを作っている
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L9381-L9389
INIT_ARRAYは ZEND_INIT_ARRAY_SPEC_CV_UNUSED_HANDLER
でハンドリングする。
処理内容はこんな感じ。でarray_initして一番目の要素をinsertしている
size = opline->extended_value >> ZEND_ARRAY_SIZE_SHIFT;
ZVAL_ARR(array, zend_new_array(size));
// ...
zend_hash_next_index_insert(Z_ARRVAL_P(EX_VAR(opline->result.var)), expr_ptr)
ADD_ARRAY_ELEMENTは ZEND_ADD_ARRAY_ELEMENT_SPEC_CONST_UNUSED_HANDLER
でハンドリングされる。
こちらもINIT_ARRAYと同様に zend_hash_next_index_insert()
でinsertされている(詳細は割愛)
要素取得と要素設定
<?php
$i = [1, 2];
$i[1];
$i[0] = 3;
opcodes
$_main:
; (lines=6, args=0, vars=1, tmps=3)
; (before optimizer)
; /path/to/6_3.php:1-6
; return [] RANGE[0..0]
0000 ASSIGN CV0($i) array(...)
0001 T2 = FETCH_DIM_R CV0($i) int(1)
0002 FREE T2
0003 ASSIGN_DIM CV0($i) int(0)
0004 OP_DATA int(3)
0005 RETURN int(1)
ざっくり、FETCH_DIM_Rで配列要素の取得、ASSIGN_DIMで配列要素の設定をしている。
配列要素の取得のコンパイルでは zend_compile_dim()
が呼ばれる
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L10227
このへんでFETCH_DIM_Rにコンパイルされている
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L2851-L2857
ZEND_AST_DIMはこのへんでコンパイルされている
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L3229-L3239
expr_nodeはOP_DATAのところでemitされていることに注意。ASSIGN_DIMはASSIGN_DIMとOP_DATAの2つのopcodeを利用するため(後述
FETCH_DIM_Rは ZEND_FETCH_DIM_R_SPEC_CV_CONST_HANDLER
でハンドリングされる
value = zend_fetch_dimension_address_inner(Z_ARRVAL_P(container), dim, IS_CONST, BP_VAR_R EXECUTE_DATA_CC);
ZVAL_COPY_DEREF(EX_VAR(opline->result.var), value);
// ...
ZEND_HASH_INDEX_FIND(ht, hval, retval, num_undef);
ZEND_HASH_INDEX_FINDでハッシュテーブル(ht)からインデックス(hval)を指定して、retvalに値をセットしている。
ASSIGN_DIMは ZEND_ASSIGN_DIM_SPEC_CV_CONST_OP_DATA_CONST_HANDLER
でハンドリングされる
variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC);
value = RT_CONSTANT((opline+1), (opline+1)->op1);
/* assign_dim has two opcodes! */
ZEND_VM_NEXT_OPCODE_EX(1, 2);
zend_fetch_dimension_address_inner_W_CONST()
では最終的に ZEND_HASH_INDEX_LOOKUP()
が呼ばれて配列要素のポインタを取得する。
opline+1
というのは OP_DATA
のことを指していて、expr、つまり値のデータを取得している。
本来 opline+1
して次のopcodeに処理を移すのだが、OP_DATA分をスキップするので ZEND_VM_NEXT_OPCODE_EX
では opline+2
をしている。