PHP内核的op_array由编译时转化为运行时

PHP内核在pass_two这个函数里面,会对op_array进行一个编译时到运行时的转化。

主要体现在以下几个地方:

重新分配literals

literalsopcodes由原来分散存储的内存合并为连续的一块内存。这么做除了内存连续带来的性能提升之外,另一个好处是,在执行opline的时候,直接通过偏移量就可以拿到对应的字面量了,不需要传递op_array,相当于少传递了一个参数(之前需要通过op_array->literals的方式来获取)。

重新设置常量的constant值

znode_op::constant最终是要存储这个常量相对这条opline的偏移量

在编译完AST生成完opcode之后,znode_op::constant存储的是这个常量在literals数组的索引。

znode_op::constant在从编译期转运行期之后,变成了相对这条opline的偏移量。

重新设置临时变量的var值

znode_op::var最终是要存储这个变量相对execute_data的偏移量

我们知道,IS_CV变量它相对execute_data的偏移量在编译这个变量的时候就已经通过EX_NUM_TO_VAR确定了。但是,IS_TMP类型的变量,它的znode_op::var里面只存了这个临时变量是第几个,还没有确定这个临时变量相对execute_data的偏移量。所以,在编译时转化为运行时的阶段,需要确定好。

那么为什么只有IS_TMP需要做转化呢?而IS_CV不需要呢?这是和PHP栈帧的设计有关的,PHP的栈帧结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
* Stack Frame Layout (the whole stack frame is allocated at once)
* ==================
*
* +========================================+
* EG(current_execute_data) -> | zend_execute_data |
* +----------------------------------------+
* EX_VAR_NUM(0) --------> | VAR[0] = ARG[1] |
* | ... |
* | VAR[op_array->num_args-1] = ARG[N] |
* | ... |
* | VAR[op_array->last_var-1] |
* | VAR[op_array->last_var] = TMP[0] |
* | ... |
* | VAR[op_array->last_var+op_array->T-1] |
* | ARG[N+1] (extra_args) |
* | ... |
* +----------------------------------------+
*/

可以发现,前面是IS_CV类型的变量,IS_TMP类型的变量在IS_CV变量的后面。所以,我们在编译出IS_TMP的时候,还无法确定IS_CV变量的个数,所以,也就无法确定IS_TMP相对于execute_data的偏移量。所以,得把IS_TMP的转化放在后面进行。