当前位置: 首页 > 后端技术 > PHP

【PHP7源码学习】2019-03-27pass_two函数详解

时间:2023-03-29 17:26:52 PHP

葡萄全部视频:https://segmentfault.com/a/11...原视频地址:http://replay.xesv5.com/ll/24...流程回顾上节课我们整理整理了$a=1的流程。我们了解了op1、op2、result、opcode的生成过程。下面我们来回顾一下整个过程。staticzend_op_array*zend_compile(inttype){zend_op_array*op_array=NULL;zend_booloriginal_in_compilation=CG(in_compilation);CG(in_compilation)=1;CG(ast)=NULL;CG(ast_arena)=zend_arena_create(1024*32);/首先会分配内存if(!zendparse()){//zendparse(即yyparse)(zend_language_parse.y)==>通过parser调用lexer,生成抽象语法树ast_list,保存到CG(ast);yyparse是通过bison编译zend_language_parser.y生成intlast_lineno=CG(zend_lineno);zend_file_contextoriginal_file_context;zend_oparray_contextoriginal_oparray_context;zend_op_array*original_active_op_array=CG(active_op_array);op_array=emalloc(sizeof(zend_op_array));init_op_array(op_array,类型,INITIAL_OP_ARRAY_SIZE);//初始化oparrayCG(active_op_array)=op_array;如果(zend_ast_process){zend_ast_process(CG(ast));}zend_file_context_begin(&original_file_context);zend_oparray_context_begin(&original_oparray_context);zend_compile_top_stmt(CG(ast));//编译ast生成oparrayCG(zend_lineno)=last_lineno;zend_emit_final_return(类型==ZEND_USER_FUNCTION);//return1会在PHP中加入并在这里处理op_array->line_start=1;op_array->line_end=last_lineno;pass_two(op_array);//针对handler的处理zend_oparray_context_end(&original_oparray_context);zend_file_context_end(&original_file_context);CG(active_op_array)=original_active_op_array;}zend_ast_destroy(CG(ast));zend_arena_destroy(CG(ast_arena));CG(in_compilation)=original_in_compilation;returnop_array;}大致流程是:词法分析->语法分析->编译ast生成op_array->处理return1->对return1链接前的handler做上面的处理,我们在文章中已经提到了。不明白的请看前面的文章。接下来,我们的gdb程序转到链接返回1。代码:fn_flags&ZEND_ACC_RETURN_REFERENCE)!=0;如果(CG(active_op_array)->fn_flags&ZEND_ACC_HAS_RETURN_TYPE&&!(CG(active_op_array)->fn_flags&ZEND_ACC_GENERATOR)){zend_emit_return_type_check(NULL,CG(active_op_array)->arg_info-1,1);(&zn.u.constant,1);//这一步在gdb过程中会到达,给zn.u.constant赋1}else{ZVAL_NULL(&zn.u.constant);}ret=zend_emit_op(NULL,returns_reference?ZEND_RETURN_BY_REF:ZEND_RETURN,&zn,NULL);//这里的字面值会增加一个新的元素1ret->extended_value=-1;}staticzend_op*zend_emit_op(znode*result,zend_uchar操作码,znode*op1,znode*op2)/*{{{*/{zend_op*opline=get_next_op(CG(active_op_array));opline->操作码=操作码;如果(op1==NULL){SET_UNUSED(opline->op1);}else{SET_NODE(opline->op1,op1);}if(op2==NULL){SET_UNUSED(opline->op2);}else{SET_NODE(opline->op2,op2);}zend_check_live_ranges(opline);如果(结果){zend_make_var_result(结果,opline);}returnopline;}#defineSET_NODE(target,src)do{\target##_type=(src)->op_type;\if((src)->op_type==IS_CONST){\target.constant=zend_add_literal(CG(active_op_array),&(src)->u.constant);\//增加元素}else{\target=(src)->u.op;\}\}while(0)我们发现gdb进程在这个函数中添加了1likeliteralselement,我们打印操作码:我们发现添加了一条新的指令,在代码中是return1。此时我们发现有3条指令,2个变量,3个字面量。$a和$b的位置已经存在,字面值也是如此。我们发现处理程序仍然是一个空指针。接下来,让我们看看处理程序的生成。pass_two设置处理程序。让我们继续并转到pass_two函数。在这个函数中,进一步处理了opline指令集。主要工作是设置指令处理程序。源码如下:ZEND_APIintpass_two(zend_op_array*op_array){/**代码省略**/while(oplineop1_type==IS_CONST){ZEND_PASS_TWO_UPDATE_CONSTANT(op_array,opline->op1);}elseif(opline->op1_type&(IS_VAR|IS_TMP_VAR)){opline->op1.var=(uint32_t)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL,op_array->last_var+opline->op1.var);}if(opline->op2_type==IS_CONST){ZEND_PASS_TWO_UPDATE_CONSTANT(op_array,opline->op2);}elseif(opline->op2_type&(IS_VAR|IS_TMP_VAR)){opline->op2.var=(uint32_t)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL,op_array->last_var+opline->op2.var);}if(opline->result_type&(IS_VAR|IS_TMP_VAR)){opline->result.var=(uint32_t)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL,op_array->last_var+opline->result.var);}ZEND_VM_SET_OPCODE_HANDLER(opline);/**代码省略**/}观察代码,这个函数会遍历opline指令数组,他会处理opline之前产生的每一条指令,我们以IS_CONST为例,如果op1和op2的类型都是IS_CONST,那么ZEND_PASS_TWO_UPDATE_CONSTANT会被调用,代码如下:/*convertconstantfromcompile-timetorun-time*/#defineZEND_PASS_TWO_UPDATE_CONSTANT(op_array,node)do{\(node).zv=CT_CONSTANT_EX(op_array,(node).持续的);\}while(0)#defineCT_CONSTANT_EX(op_array,num)\((op_array)->literals+(num))我们知道对于IS_CONST变量的字面值存在于字面量中,constant是相对下标,所以我们可以通过偏移第一个地址常量将其转换为实际偏移量。对于IS_VAR|IS_TMP_VAR类型的变量,它会通过ZEND_CALL_VAR_NUM计算偏移量。还有一个很重要的工作就是通过ZEND_VM_SET_OPCODE_HANDLER(opline)设置opline对应的hanlder,代码如下:;}staticconstvoid*zend_vm_get_opcode_handler(zend_ucharopcode,constzend_op*op){returnzend_vm_get_opcode_handler_ex(zend_spec_handlers[opcode],op);}opcode和handler的对应关系定义在Zend/zend_vm_execute.h中。opline数组遍历一次后,设置handler。设置好的opline数组如图所示:最后我们打印handler生成后的op_array:我们发现handler已经被赋值了。至此,整个抽象语法树编译完成,最终的结果就是opline指令集,接下来就是在Zend虚拟机上执行这些指令了。参考资料:【PHP7源码解析】Zend虚拟机PHP7源码研究