Skip to content

Commit 5acabed

Browse files
committed
add defer
1 parent e09183e commit 5acabed

File tree

1 file changed

+96
-3
lines changed

1 file changed

+96
-3
lines changed

try/defer.md

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ statement:
4848
修改完这两个文件后需要分别调用re2c、yacc生成对应的C文件,具体的生成命令可以在Makefile.frag中看到:
4949
```c
5050
$ re2c --no-generation-date --case-inverted -cbdFt Zend/zend_language_scanner_defs.h -oZend/zend_language_scanner.c Zend/zend_language_scanner.l
51-
$ yacc -p ini_ -v -d Zend/zend_language_parser.y -oZend/zend_language_parser.c
51+
$ yacc -p zend -v -d Zend/zend_language_parser.y -oZend/zend_language_parser.c
5252
```
5353
执行完以后将在Zend目录下重新生成zend_language_scanner.c、zend_language_parser.c两个文件。到这一步已经完成生成抽象语法树的工作了,重新编译PHP后已经能够解析defer语法了,将会生成以下节点:
5454

@@ -140,7 +140,7 @@ void zend_emit_final_return(zval *zv)
140140
ret = zend_emit_op(NULL, returns_reference ? ZEND_RETURN_BY_REF : ZEND_RETURN, &zn, NULL);
141141
ret->extended_value = -1;
142142

143-
//编译defer call
143+
//编译推迟执行的函数调用
144144
zend_emit_defer_call();
145145
}
146146
```
@@ -172,7 +172,7 @@ void zend_emit_defer_call()
172172
//编译函数调用
173173
zend_compile_call(&result, call_ast, BP_VAR_R);
174174
}
175-
//compile ZEND_DEFER_CALL
175+
//compile ZEND_DEFER_CALL_END
176176
zend_emit_op(NULL, ZEND_DEFER_CALL_END, NULL, NULL);
177177
}
178178
```
@@ -201,4 +201,97 @@ func main(){
201201

202202
__(3)编译return__
203203

204+
编译return时需要插入一条指令用于跳转到推迟执行的函数调用指令处,因此这里需要再定义一条opcode:ZEND_DEFER_CALL,在编译过程中defer call还未编译,因此此时还无法知道具体的跳转值。
205+
```c
206+
//zend_vm_opcodes.h
207+
#define ZEND_DEFER_CALL 173
208+
#define ZEND_DEFER_CALL_END 174
209+
```
210+
PHP脚本中声明的return语句由zend_compile_return()方法完成编译,在编译生成ZEND_DEFER_CALL指令时还需要将当前已定义的defer数(即在return前声明的defer)记录下来,用于计算具体的跳转值。
211+
```c
212+
void zend_compile_return(zend_ast *ast)
213+
{
214+
...
215+
//在return前编译ZEND_DEFER_CALL:用于在执行retur前跳转到defer call
216+
if (CG(active_op_array)->defer_call_array) {
217+
defer_zn.op_type = IS_UNUSED;
218+
defer_zn.u.op.num = CG(active_op_array)->last_defer;
219+
zend_emit_op(NULL, ZEND_DEFER_CALL, NULL, &defer_zn);
220+
}
221+
222+
//编译正常返回的指令
223+
opline = zend_emit_op(NULL, by_ref ? ZEND_RETURN_BY_REF : ZEND_RETURN,
224+
&expr_node, NULL);
225+
...
226+
}
227+
```
228+
除了这种return外还有一种我们上面已经提过的return,即PHP内核编译的return指令,当PHP脚本中没有声明return语句时将执行内核添加的那条指令,因此也需要在zend_emit_final_return()加上上面的逻辑。
229+
```c
230+
void zend_emit_final_return(zval *zv)
231+
{
232+
...
233+
//在return前编译ZEND_DEFER_CALL:用于在执行retur前跳转到defer call
234+
if (CG(active_op_array)->defer_call_array) {
235+
//当前return之前定义的defer数
236+
defer_zn.op_type = IS_UNUSED;
237+
defer_zn.u.op.num = CG(active_op_array)->last_defer;
238+
zend_emit_op(NULL, ZEND_DEFER_CALL, NULL, &defer_zn);
239+
}
240+
241+
//编译返回指令
242+
ret = zend_emit_op(NULL, returns_reference ? ZEND_RETURN_BY_REF : ZEND_RETURN, &zn, NULL);
243+
ret->extended_value = -1;
244+
245+
//编译推迟执行的函数调用
246+
zend_emit_defer_call();
247+
}
248+
```
249+
__(4)计算ZEND_DEFER_CALL指令的跳转位置__
250+
251+
前面我们已经完成了推迟调用函数以及return编译过程的改造,在编译完成后ZEND_DEFER_CALL指令已经能够知道具体的跳转位置了,因为推迟调用的函数已经编译完成了,所以下一步就是为全部的ZEND_DEFER_CALL指令计算跳转值。前面曾介绍过,在编译完成有一个pass_two()的环节,我们就在这里完成具体跳转位置的计算,并把跳转位置保存到ZEND_DEFER_CALL指令的操作数中,在执行阶段直接跳转到对应位置。
252+
253+
```c
254+
ZEND_API int pass_two(zend_op_array *op_array)
255+
{
256+
zend_op *opline, *end;
257+
...
258+
//遍历opcode
259+
opline = op_array->opcodes;
260+
end = opline + op_array->last;
261+
while (opline < end) {
262+
switch (opline->opcode) {
263+
...
264+
case ZEND_DEFER_CALL: //设置jmp
265+
{
266+
uint32_t defer_start = op_array->defer_start_op;
267+
//skip_defer为当前return之后声明的defer数,也就是不需要执行的defer
268+
uint32_t skip_defer = op_array->last_defer - opline->op2.num;
269+
//defer_opline为推迟的函数调用起始位置
270+
zend_op *defer_opline = op_array->opcodes + defer_start;
271+
uint32_t n = 0;
272+
273+
while(n <= skip_defer){
274+
if (defer_opline->opcode == ZEND_NOP && defer_opline->op1.var == -2) {
275+
n++;
276+
}
277+
defer_opline++;
278+
defer_start++;
279+
}
280+
281+
//defer_start为opcode在op_array->opcodes数组中的位置
282+
opline->op1.opline_num = defer_start;
283+
//将跳转位置保存到操作数op1中
284+
ZEND_PASS_TWO_UPDATE_JMP_TARGET(op_array, opline, opline->op1);
285+
}
286+
break;
287+
}
288+
...
289+
}
290+
...
291+
}
292+
```
293+
这里我们并没有直接编译为ZEND_JMP跳转指令,虽然ZEND_JMP可以跳转到后面的指令位置,但是最后的那条跳回return位置的指令(即:ZEND_DEFER_CALL_END)由于可能存在多个return的原因无法在编译期间确定具体的跳转值,只能在运行期间执行ZEND_DEFER_CALL时才能确定,所以需要在ZEND_DEFER_CALL指令的handler中将return的位置记录下来,执行ZEND_DEFER_CALL_END时根据这个值跳回。
294+
295+
__(5)定义ZEND_DEFER_CALL、ZEND_DEFER_CALL_END指令的handler__
296+
204297

0 commit comments

Comments
 (0)