PHP的编译与执行笔记 - PHP的执行

ZendVM执行器由以下两个组成

  • handler
  • 调度器

handler

一条opcode对于不同的操作数类型会有不同的handler
最多可以有25种handler
定义在Zend/zend_vm_def.hcf
但编译时不会用到,修改后需要在Zend目录下执行zend_vm_gen.php
脚本生成实际的handler文件:zend_vm_execute.h
ZEND_ECHO为echo操作的opcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
ZEND_VM_HANDLER(40, ZEND_ECHO, CONST|TMPVAR|CV, ANY)
{
USE_OPLINE
zend_free_op free_op1;
zval *z;

SAVE_OPLINE();
z = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);

if (Z_TYPE_P(z) == IS_STRING) {
zend_string *str = Z_STR_P(z);

if (ZSTR_LEN(str) != 0) {
zend_write(ZSTR_VAL(str), ZSTR_LEN(str));
}
} else {
zend_string *str = _zval_get_string_func(z);

if (ZSTR_LEN(str) != 0) {
zend_write(ZSTR_VAL(str), ZSTR_LEN(str));
} else if (OP1_TYPE == IS_CV && UNEXPECTED(Z_TYPE_P(z) == IS_UNDEF)) {
GET_OP1_UNDEF_CV(z, BP_VAR_R);
}
zend_string_release(str);
}

FREE_OP1();
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}

调度方式

zend_vm_gen.php带有3钟模式

1
2
3
CALL
SWITCH
GOTO

默认为CALL
若有其他模式则需要跟参数
--with-vm-kind=CALL|SWITCH|GOTO

执行流程

zend_execute_data结构的两个作用

  1. 记录运行时信息
  2. 分配动态变量内存

执行的入口函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ZEND_API void zend_execute(zend_op_array *op_array, zval *return_value)
{
zend_execute_data *execute_data;

if (EG(exception) != NULL) {
return;
}
//分配zend_execute_data
execute_data = zend_vm_stack_push_call_frame(ZEND_CALL_TOP_CODE,
(zend_function*)op_array, 0, zend_get_called_scope(EG(current_execute_data)), zend_get_this_object(EG(current_execute_data)));
if (EG(current_execute_data)) {
execute_data->symbol_table = zend_rebuild_symbol_table();
} else {
execute_data->symbol_table = &EG(symbol_table);
}
EX(prev_execute_data) = EG(current_execute_data);
//execute_data 初始化
i_init_execute_data(execute_data, op_array, return_value);
//执行opcode
zend_execute_ex(execute_data);
zend_vm_stack_free_call_frame(execute_data);
}

分配zend_execute_data

其中zend_vm_stack_push_call_frame()
会根据op_array.last_varop_array.T
分配zend_execute_data以及动态变量区大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static zend_always_inline zend_execute_data *zend_vm_stack_push_call_frame(uint32_t call_info, zend_function *func, uint32_t num_args, zend_class_entry *called_scope, zend_object *object)
{
uint32_t used_stack = zend_vm_calc_used_stack(num_args, func);

return zend_vm_stack_push_call_frame_ex(used_stack, call_info,
func, num_args, called_scope, object);
}

static zend_always_inline uint32_t zend_vm_calc_used_stack(uint32_t num_args, zend_function *func)
{
uint32_t used_stack = ZEND_CALL_FRAME_SLOT + num_args;

if (EXPECTED(ZEND_USER_CODE(func->type))) {
used_stack += func->op_array.last_var + func->op_array.T - MIN(func->op_array.num_args, num_args);
}
return used_stack * sizeof(zval);
}

zend_execute_data动态变量内存分布
与此同时zend_vm_stack_push_call_frame()也传入了ZEND_CALL_TOP_CODE

1
2
3
4
5
6
typedef enum _zend_call_kind {
ZEND_CALL_NESTED_FUNCTION, /* stackless VM call to function */
ZEND_CALL_NESTED_CODE, /* stackless VM call to include/require/eval */
ZEND_CALL_TOP_FUNCTION, /* direct VM call to function from external C code */
ZEND_CALL_TOP_CODE /* direct VM call to "main" code from external C code */
} zend_call_kind;

可以看到是调用主代码的调用类型

初始化 zend_execute_data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static zend_always_inline void i_init_execute_data(zend_execute_data *execute_data, zend_op_array *op_array, zval *return_value) /* {{{ */
{
ZEND_ASSERT(EX(func) == (zend_function*)op_array);
//zend_execute_data->opline指向第一条指令
EX(opline) = op_array->opcodes;
EX(call) = NULL;
EX(return_value) = return_value;

if (UNEXPECTED(EX(symbol_table) != NULL)) {
...
//添加全局变量
zend_attach_symbol_table(execute_data);
} else {
...
}
EX_LOAD_RUN_TIME_CACHE(op_array);
//zend_execute_data->execute_literals指向op_array->literals,便于快速访问
EX_LOAD_LITERALS(op_array);
//EG(current_execute_data) 指向 execute_data
EG(current_execute_data) = execute_data;
ZEND_VM_INTERRUPT_CHECK();
}

其中zend_attach_symbol_table()是一个hash表,用于存储全局变量
到此步结束,执行前的准备工作已经全部完成
初始化结束后状态图

执行

执行调度器为zend_execute_ex,这是函数指针,默认为execute_ex()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
ZEND_API void execute_ex(zend_execute_data *ex)
{
DCL_OPLINE

#ifdef ZEND_VM_IP_GLOBAL_REG
const zend_op *orig_opline = opline;
#endif
#ifdef ZEND_VM_FP_GLOBAL_REG
zend_execute_data *orig_execute_data = execute_data;
execute_data = ex;
#else
zend_execute_data *execute_data = ex;
#endif


LOAD_OPLINE();

while (1) {
#if !defined(ZEND_VM_FP_GLOBAL_REG) || !defined(ZEND_VM_IP_GLOBAL_REG)
int ret;
#endif
#if defined(ZEND_VM_FP_GLOBAL_REG) && defined(ZEND_VM_IP_GLOBAL_REG)
((opcode_handler_t)OPLINE->handler)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);
if (UNEXPECTED(!OPLINE)) {
#else
if (UNEXPECTED((ret = ((opcode_handler_t)OPLINE->handler)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)) != 0)) {
#endif
#ifdef ZEND_VM_FP_GLOBAL_REG
execute_data = orig_execute_data;
# ifdef ZEND_VM_IP_GLOBAL_REG
opline = orig_opline;
# endif
return;
#else
if (EXPECTED(ret > 0)) {
execute_data = EG(current_execute_data);
} else {
# ifdef ZEND_VM_IP_GLOBAL_REG
opline = orig_opline;
# endif
return;
}
#endif
}

}
zend_error_noreturn(E_CORE_ERROR, "Arrived at end of main loop which shouldn't happen");
}

在GCC低于4.8的情况下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ZEND_API void execute_ex(zend_execute_data *ex)
{
zend_execute_data *execute_data = ex;

while (1) {
int ret;
//执行当前指令
if (UNEXPECTED((ret = ((opcode_handler_t)OPLINE->handler)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)) != 0)) {
if (EXPECTED(ret > 0)) {
execute_data = EG(current_execute_data);
} else {
return;
}
}
}
zend_error_noreturn(E_CORE_ERROR, "Arrived at end of main loop which shouldn't happen");
}

$a = 123;会进入ZEND_ASSIGN_SPEC_CV_CONST_HANDLER()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_SPEC_CV_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE

zval *value;
zval *variable_ptr;

SAVE_OPLINE();
//取值
value = EX_CONSTANT(opline->op2);
variable_ptr = _get_zval_ptr_cv_undef_BP_VAR_W(execute_data, opline->op1.var);

if (IS_CV == IS_VAR && UNEXPECTED(variable_ptr == &EG(error_zval))) {

if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
ZVAL_NULL(EX_VAR(opline->result.var));
}
} else {
//赋值复制
value = zend_assign_to_variable(variable_ptr, value, IS_CONST);
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
ZVAL_COPY(EX_VAR(opline->result.var), value);
}

/* zend_assign_to_variable() always takes care of op2, never free it! */
}
//跟新opline,指向下一条命令
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}

这里会出现不同的返回值
调度器会根据不同的返回值决定下一步动作

1
2
3
4
#define ZEND_VM_CONTINUE()  return 0
#define ZEND_VM_ENTER() return 1
#define ZEND_VM_LEAVE() return 2
#define ZEND_VM_RETURN() return -1

ZEND_VM_CONTINUE()表示执行下一条opcode
ZEND_VM_ENTER()/ZEND_VM_LEAVE()是调用函数时的动作
ZEND_VM_ENTER(),execute_data()中会将execute_data切换到被调函数的结构上
ZEND_VM_LEAVE(),再将execute_data切换到原来结构
ZEND_VM_RETURN()execute_ex()退出执行

释放zend_execute_data

调用zend_vm_stack_free_call_frame()释放zend_execute_data