@@ -54,4 +54,151 @@ $ yacc -p ini_ -v -d Zend/zend_language_parser.y -oZend/zend_language_parser.c
5454
5555![ ] ( ../img/defer_ast.png )
5656
57+ __ (2)编译ZEND_AST_DEFER_CALL__
58+
59+ 生成抽象语法树后接下来就是编译生成opcodes的操作,即从AST->Opcodes。编译ZEND_AST_DEFER_CALL节点时不能立即进行编译,需要等到当前脚本或函数全部编译完以后再进行编译,所以在编译过程需要把ZEND_AST_DEFER_CALL节点先缓存下来,参考循环结构编译时生成的zend_brk_cont_element的存储位置,我们也把ZEND_AST_DEFER_CALL节点保存在zend_op_array中,通过数组进行存储,将ZEND_AST_DEFER_CALL节点依次存入该数组,zend_op_array中加入以下几个成员:
60+
61+ * __ last_defer:__ 整形,记录当前编译的defer数
62+ * __ defer_start_op:__ 整形,用于记录defer编译生成opcode指令的起始位置
63+ * __ defer_call_array:__ 保存ZEND_AST_DEFER_CALL节点的数组,用于保存ast节点的地址
64+
65+ ``` c
66+ struct _zend_op_array {
67+ ...
68+ int last_defer;
69+ uint32_t defer_start_op;
70+ zend_ast **defer_call_array;
71+ }
72+ ```
73+ 修改完数据结构后接着对应修改zend_op_array初始化的过程:
74+ ``` c
75+ // zend_opcode.c
76+ void init_op_array (zend_op_array * op_array, zend_uchar type, int initial_ops_size)
77+ {
78+ ...
79+ op_array->last_defer = 0;
80+ op_array->defer_start_op = 0;
81+ op_array->defer_call_array = NULL;
82+ ...
83+ }
84+ ```
85+ 完成依赖的这些数据结构的改造后接下来开始编写具体的编译逻辑,也就是编译ZEND_AST_DEFER_CALL的处理。抽象语法树的编译入口函数为zend_compile_top_stmt(),然后根据不同节点的类型进行相应的编译,我们在zend_compile_stmt()函数中对ZEND_AST_DEFER_CALL节点进行编译:
86+ ```c
87+ void zend_compile_stmt(zend_ast *ast)
88+ {
89+ ...
90+ switch (ast->kind) {
91+ ...
92+ case ZEND_AST_DEFER_CALL:
93+ zend_compile_defer_call(ast);
94+ break
95+ ...
96+ }
97+ }
98+ ```
99+ 编译过程只是将ZEND_AST_DEFER_CALL的子节点(即:ZEND_AST_CALL)保存到zend_op_array->defer_call_array数组中,注意这里defer_call_array数组还没有分配内存,参考循环结构的实现,这里我们定义了一个函数用于数组的分配:
100+ ``` c
101+ // zend_compile.c
102+ void zend_compile_defer_call (zend_ast * ast)
103+ {
104+ if(!ast){
105+ return;
106+ }
107+
108+ zend_ast **call_ast = NULL;
109+ //将普通函数调用的ast节点保存到defer_call_array数组中
110+ call_ast = get_next_defer_call(CG(active_op_array));
111+ *call_ast = ast->child[0];
112+ }
113+
114+ //zend_opcode.c
115+ zend_ast ** get_next_defer_call(zend_op_array * op_array)
116+ {
117+ op_array->last_defer++;
118+ op_array->defer_call_array = erealloc(op_array->defer_call_array, sizeof(zend_ast* )* op_array->last_defer);
119+ return &op_array->defer_call_array[ op_array->last_defer-1] ;
120+ }
121+ ```
122+ 既然分配了defer_call_array数组的内存就需要在zend_op_array销毁时释放:
123+ ```c
124+ //zend_opcode.c
125+ ZEND_API void destroy_op_array(zend_op_array *op_array)
126+ {
127+ ...
128+ if (op_array->defer_call_array) {
129+ efree(op_array->defer_call_array);
130+ }
131+ ...
132+ }
133+ ```
134+ 编译完整个脚本或函数后,最后还会编译一条ZEND_RETURN,也就是返回指令,相当于ret指令,注意:这条opcode并不是我们在脚本中定义的return语句的,而是PHP内核为我们加的一条指令,这就是为什么有些函数我们没有写return也能返回的原因,任何函数或脚本都会生成这样一条指令。我们缓存在zend_op_array->defer_call_array数组中defer就是要在这时进行编译,也就是把defer的指令编译在最后。内核最后编译返回的这条指令由zend_emit_final_return()方法完成,我们把defer的编译放在此方法的末尾:
135+ ``` c
136+ // zend_compile.c
137+ void zend_emit_final_return (zval * zv)
138+ {
139+ ...
140+ ret = zend_emit_op(NULL, returns_reference ? ZEND_RETURN_BY_REF : ZEND_RETURN, &zn, NULL);
141+ ret->extended_value = -1;
142+
143+ //编译defer call
144+ zend_emit_defer_call();
145+ }
146+ ```
147+ 前面已经说过,defer本质上就是函数调用,所以编译的过程直接复用普通函数调用的即可。另外,在编译时把起始位置记录到zend_op_array->defer_start_op中,因为在执行return前需要知道跳转到什么位置,这个值就是在那时使用的,具体的用法稍后再作说明。编译时按照倒序的顺序进行编译:
148+ ```c
149+ //zend_compile.c
150+ void zend_emit_defer_call()
151+ {
152+ if (!CG(active_op_array)->defer_call_array) {
153+ return;
154+ }
155+
156+ zend_ast *call_ast;
157+ zend_op *nop;
158+ znode result;
159+ uint32_t opnum = get_next_op_number(CG(active_op_array));
160+ int defer_num = CG(active_op_array)->last_defer;
161+
162+ //记录推迟的函数调用指令开始位置
163+ CG(active_op_array)->defer_start_op = opnum;
164+
165+ while(--defer_num >= 0){
166+ call_ast = CG(active_op_array)->defer_call_array[defer_num];
167+ if (call_ast == NULL) {
168+ continue;
169+ }
170+ nop = zend_emit_op(NULL, ZEND_NOP, NULL, NULL);
171+ nop->op1.var = -2;
172+ //编译函数调用
173+ zend_compile_call(&result, call_ast, BP_VAR_R);
174+ }
175+ //compile ZEND_DEFER_CALL
176+ zend_emit_op(NULL, ZEND_DEFER_CALL_END, NULL, NULL);
177+ }
178+ ```
179+ 编译完推迟的函数调用之后,编译一条ZEND_DEFER_CALL_END指令,该指令用于执行完推迟的函数后跳回return的位置进行返回,opcode定义在zend_vm_opcodes.h中:
180+ ``` c
181+ // zend_vm_opcodes.h
182+ #define ZEND_DEFER_CALL_END 174
183+ ```
184+ 还有一个地方你可能已经注意到,在逐个编译defer的函数调用前都生成了一条ZEND_NOP的指令,这个的目的是什么呢?开始的时候已经介绍过defer语法的特点,函数中定义的defer并不是全部执行,在return之后定义的defer是不会执行的,比如:
185+ ``` go
186+ func main (){
187+ defer fmt.Println (" A" )
188+
189+ if 1 == 1 {
190+ return
191+ }
192+
193+ defer fmt.Println (" B" )
194+ }
195+ ```
196+ 这种情况下第2个defer就不会生效,因此在return前跳转的位置就不一定是zend_op_array->defer_start_op,有可能会跳过几个函数的调用,所以这里我们通过ZEND_NOP这条空指令对多个defer call进行隔离,同时为避免与其它ZEND_NOP指令混淆,增加一个判断条件:op1.var=-2。这样在return前跳转时就根据此前定义的defer数跳过部分函数的调用,如下图所示。
197+
198+ ![ ] ( ../img/defer_call.png )
199+
200+ 到这一步我们已经完成defer函数调用的编译,此时重新编译PHP后可以看到通过defer推迟的函数调用已经被编译在最后了,只不过这个时候它们不能被执行。
201+
202+ __ (3)编译return__
203+
57204
0 commit comments