php-srcのコードリーディングした内容をコツコツ残すテスト。その5
今回はfunction
<?php
function hoge($i) {}
hoge(123);
opcode
$_main:
; (lines=4, args=0, vars=0, tmps=1)
; (before optimizer)
; /path/to/5.php:1-6
; return [] RANGE[0..0]
0000 INIT_FCALL 1 96 string("hoge")
0001 SEND_VAL int(123) 1
0002 DO_UCALL
0003 RETURN int(1)
hoge:
; (lines=2, args=1, vars=1, tmps=0)
; (before optimizer)
; /path/to/5.php:3-3
; return [] RANGE[0..0]
0000 CV0($i) = RECV 1
0001 RETURN null
functionのコンパイル
zend_compile_top_stmt()
から zend_compile_func_decl()
が呼ばれる
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L9936
zend_compile_func_decl()
では関数用のop_arrayを作成する
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L7172-L7292
zend_compile_func_decl()
は zend_begin_func_decl()
を呼び出し
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L7214:L7214
top_levelの場合は zend_hash_add_ptr()
を呼び出し、 CG(function_table)
のHashの中にop_arrayをセットしている。
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L7150:L7150
さらに zend_compile_params()
を呼び出し関数のパラメータのコンパイルを行います
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L7256
パラメータはその関数のCVとしてコンパイルされる
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L6618-L6619
opcodeは ZEND_RECV
で
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L6673-L6675
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L6720:L6720
zend_compile_stmt()
で関数内のステートメントをコンパイル
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L7268
呼び出し部分のコンパイル
ノードのtypeとしては ZEND_AST_CALL
なので↓の部分を通る
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L10095
最終的に zend_compile_call()
が呼ばれてここでコンパイルされる
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L10234
zend_compile_call()
では zend_hash_find_ptr()
を使って CG(function_table)
から zend_function
を取得する
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L4458:L4458
ZEND_INIT_FCALL
のopcodeを生成
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L4490-L4491
op1.num
に zend_vm_calc_used_stack()
の値をセットする
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L3710:L3710
最後に ZEND_DO_UCALL
のopcodeがコンパイルされる
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L3667:L3667
OPCODE
INIT_FCALLでは ZEND_INIT_FCALL_SPEC_CONST_HANDLER
を呼び出します。
EX(run_time_cache)
にキャッシュされていたらそれを使い、キャッシュされていなければ EG(function_table)
からzend_functionを取得します。
fbc = CACHED_PTR(opline->result.num);
if (UNEXPECTED(fbc == NULL)) {
fname = (zval*)RT_CONSTANT(opline, opline->op2);
func = zend_hash_find_known_hash(EG(function_table), Z_STR_P(fname));
if (UNEXPECTED(func == NULL)) {
ZEND_VM_TAIL_CALL(zend_undefined_function_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));
}
fbc = Z_FUNC_P(func);
if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) {
init_func_run_time_cache(&fbc->op_array);
}
CACHE_PTR(opline->result.num, fbc);
}
その後、呼び出す関数のCall Frameをプッシュし、呼び出し元のcallをprev_execute_dataに格納します。
call = _zend_vm_stack_push_call_frame_ex(
opline->op1.num, ZEND_CALL_NESTED_FUNCTION,
fbc, opline->extended_value, NULL);
call->prev_execute_data = EX(call);
EX(call) = call;
フィールド | 意味 |
---|---|
op1.num | 確保するスタックのサイズ |
op2->constant | 関数名のliteral文字列 |
result.num | cache_slot(多分キャッシュ用の何か) |
SEND_VAL
のopcodeでは ZEND_SEND_VAL_SPEC_CONST_UNUSED_HANDLER
が呼ばれる。
arg = ZEND_CALL_VAR(EX(call), opline->result.var);
value = RT_CONSTANT(opline, opline->op1);
ZVAL_COPY_VALUE(arg, value);
op1の変数の値をresult.varに入れる。ZEND_CALL_VAR
の引数が EX(call)
になっていることで、
呼び出し先の関数のコールスタックのところに変数を代入する処理になっている。
DO_UCALL
のopcodeでは ZEND_DO_UCALL_SPEC_RETVAL_UNUSED_HANDLER
が呼ばれる。
call->prev_execute_data = execute_data;
execute_data = call;
i_init_func_execute_data(&fbc->op_array, ret, 0 EXECUTE_DATA_CC);
呼び出し先のコールスタックの prev_execute_data
に呼び出し元のコールスタックを紐付け、
execute_dataを呼び出し先のコールスタックに変更している。
型情報がない場合や引数が不足していない場合、RECVはスキップされる
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_execute.c#L3915-L3926
ZEND_RECV_SPEC_UNUSED_HANDLER
では呼び出し引数が少なくないかどうかや、型チェックをしている。
static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_RECV_SPEC_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
uint32_t arg_num = opline->op1.num;
zval *param;
if (UNEXPECTED(arg_num > EX_NUM_ARGS())) {
ZEND_VM_TAIL_CALL(zend_missing_arg_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));
}
param = EX_VAR(opline->result.var);
if (UNEXPECTED(!(opline->op2.num & (1u << Z_TYPE_P(param))))) {
ZEND_VM_TAIL_CALL(zend_verify_recv_arg_type_helper_SPEC(param ZEND_OPCODE_HANDLER_ARGS_PASSTHRU_CC));
}
ZEND_VM_NEXT_OPCODE();
}
RETURNでは zend_leave_helper_SPEC
が呼ばれ、以下の処理が実行される。
EG(current_execute_data) = EX(prev_execute_data);
EG(vm_stack_top) = (zval*)execute_data;
current_execute_dataを呼び出し元のCall Frameに戻し、vm_stack_topをexecute_dataに戻す
(設定前は execute_data + 関数のCall Frameのサイズ
のアドレスを指している)
ちなみに return
文が入る場合は、i_init_func_execute_data()
で EX(return_value)
にreturn用の変数アドレスをセットし、
ZVAL_COPY_VALUE(return_value, retval_ptr);
で呼び出し先からreturn用変数にセットしている。
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_execute.c#L3910:L3910