PHP的编译与执行笔记 - Zend虚拟机

执行流程

opline

opline是ZendVM定义的执行指令
由编译器负责将PHP代码解释为ZendVM可识别的指令(即opline)
opline指令的结构为zend_op

1
2
3
4
5
6
7
8
9
10
11
12
struct _zend_op {
const void *handler; ////对应执行的C语言function,即每条opcode都有一个C function处理
znode_op op1; //操作数1
znode_op op2; //操作数2
znode_op result; //返回值
uint32_t extended_value;
uint32_t lineno;
zend_uchar opcode; //opcode指令
zend_uchar op1_type; //操作数1类型
zend_uchar op2_type; //操作数2类型
zend_uchar result_type; //返回值类型
};

简单概括一下opline指令组成对何数据,作何处理
前者称为操作数,后者及为opcode

opcode

目前PHP有173条opcode,在Zend/zend_vm_opcodes.h

操作数

从zend_op的结构可以看出操作数的结构为zende_op

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef union _znode_op {
uint32_t constant;
uint32_t var;
uint32_t num;
uint32_t opline_num; /* Needs to be signed */
#if ZEND_USE_ABS_JMP_ADDR
zend_op *jmp_addr;
#else
uint32_t jmp_offset;
#endif
#if ZEND_USE_ABS_CONST_ADDR
zval *zv;
#endif
} znode_op;

example
$a = 123;赋值操作

其中ZEND_ASSIGN为opcode指令
操作数有类别之分

1
2
3
4
5
6
#define IS_CONST	(1<<0)  //1
#define IS_TMP_VAR (1<<1) //2
#define IS_VAR (1<<2) //4
#define IS_UNUSED (1<<3) //8/* Unused variable */
#define IS_CV (1<<4) //16/* Compiled variable */
#define EXT_TYPE_UNUSED (1<<5) //32

IS_CONST为常量,比如$a=123,$a=array(),$a='hello'
中的123,hello,array()都为此类型,这种也称为字面量(literal)
IS_TMP_VAR为临时变量,比如$a='hello~'.time()'hello~'.time()为临时变量
IS_VAR为PHP变量,区别于IS_CV可以理解为没有显试的在PHP脚本中定义,比如$a=time()中的time()为PHP变量
IS_UNUSED表示没有操作数使用
IS_CV为CV变量,通过$声明的变量,比如$a
EXT_TYPE_UNUSED表示返回值没有使用

handler

统一opcode不同类型操作数的处理方式可能有些差异,因此每条opcode会根据操作数类型定义多个handler(最多5*5=25种)

zend_op_array

opline是编译生成的单条指令,所以指令集合成了zend_op_array
zend_op_array为编译器的输出也为执行器的输入

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
struct _zend_op_array {
//common是普通函数或类成员方法对应的opcodes快速访问时使用的字段
/* Common elements */
zend_uchar type;
zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */
uint32_t fn_flags;
zend_string *function_name;
zend_class_entry *scope;
zend_function *prototype;
uint32_t num_args;
uint32_t required_num_args;
zend_arg_info *arg_info;
/* END of common elements */

uint32_t *refcount;

uint32_t this_var;

uint32_t last;
zend_op *opcodes; //opcode指令数组

int last_var; //IS_CV的变量数,编译前为0,发现新变量加1
uint32_t T; //IS_TMP_VAR,IS_VAR的个数
zend_string **vars;//在ast编译期配合last_var来确定变量的编号

int last_brk_cont;
int last_try_catch;
zend_brk_cont_element *brk_cont_array;
zend_try_catch_element *try_catch_array;

/* static variables support */
HashTable *static_variables; //静态变量符号表

zend_string *filename;
uint32_t line_start;
uint32_t line_end;
zend_string *doc_comment;
uint32_t early_binding; /* the linked list of delayed declarations */

int last_literal; //字面量数量
zval *literals; //字面量数组

int cache_size; //运行使缓存数组大小
void **run_time_cache; //运行时缓存,用于快速获取数据

void *reserved[ZEND_MAX_RESERVED_RESOURCES];
};

zend_execute_data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct _zend_execute_data {
const zend_op *opline; /* executed opline */
zend_execute_data *call; /* current call */
zval *return_value;
zend_function *func; /* executed funcrion */
zval This; /* this + call_info + num_args */
zend_class_entry *called_scope;
zend_execute_data *prev_execute_data;
zend_array *symbol_table;
#if ZEND_EX_USE_RUN_TIME_CACHE
void **run_time_cache; /* cache op_array->run_time_cache */
#endif
#if ZEND_EX_USE_LITERALS
zval *literals; /* cache op_array->literals */
#endif
};

这里有个坑,这个动态变量区在_zend_execute_data结构体外,紧跟着结构体

zend_executor_globals