grapephp
所有视频:https://segmentfault.com/a/11...node
原视频地址:http://replay.xesv5.com/ll/24...segmentfault
上节课咱们把$a=1这个过程编译梳理了一遍,咱们了解到op1,op2,result,opcode的生成过程,下面咱们把整个过程来回顾一下。数组
static zend_op_array *zend_compile(int type) { zend_op_array *op_array = NULL; zend_bool original_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生成 int last_lineno = CG(zend_lineno); zend_file_context original_file_context; zend_oparray_context original_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, type, INITIAL_OP_ARRAY_SIZE); //初始化oparray CG(active_op_array) = op_array; if (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生成oparray CG(zend_lineno) = last_lineno; zend_emit_final_return(type == ZEND_USER_FUNCTION); //PHP中会加return 1,在此进行处理 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; return op_array; }
大致流程为:词法分析->语法分析->编译ast生成op_array->处理return 1->对于handler作处理
以上处理return 1 环节以前的文章中咱们都已经提到过,若是有不太理解的请翻阅以前的文章。接下来咱们gdb程序到环节return 1。代码:函数
<?php $a = 2; $b = 3;
咱们来看一看到编译ast生成op_array处的结果:源码分析
咱们来看这个结果,vars是存咱们的变量的,在这存的是a和b,而且last_Var=2只有两个;T是temporary,T=2说明有两个临时变量。而后literals是存咱们的字面量,再这里存的是2,3,last_literal=2表示如今有两个字面量,接下来咱们打印一下看是否和咱们所解释的一致。学习
结果和咱们设想的一致。另外,对于opcode的值又是如何呢?ui
咱们发现,$a=2 op1是80,$b=3 op1为96,这是为何呢?这以前咱们说过这个问题,由于在栈中咱们是分配一个大小为16的内存,因此须要增长16.第二个,咱们知道result.constant的0和1表明字面量偏移量分别为0和1.
到这里都是以前学习过的内容,接下来继续学习。spa
继续执行代码:3d
咱们发如今执行完zend_emit_final_return这句以后咱们的op_array发生了变化。那么为何会发生这样的变化呢?咱们在文章开头有些到这个函数的做用是增长return 1结尾,那么具体其中是怎么来操做呢?咱们来看代码:
void zend_emit_final_return(int return_one) /* {{{ */ { znode zn; zend_op *ret; zend_bool returns_reference = (CG(active_op_array)->fn_flags & ZEND_ACC_RETURN_REFERENCE) != 0; if (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.op_type = IS_CONST; if (return_one) { ZVAL_LONG(&zn.u.constant, 1); //在gdb过程当中会走到这一步,把1赋值给zn.u.constant } else { ZVAL_NULL(&zn.u.constant); } ret = zend_emit_op(NULL, returns_reference ? ZEND_RETURN_BY_REF : ZEND_RETURN, &zn, NULL);//在此会像字面量中添加一个新的元素1 ret->extended_value = -1; } static zend_op *zend_emit_op(znode *result, zend_uchar opcode, znode *op1, znode *op2) /* {{{ */ { zend_op *opline = get_next_op(CG(active_op_array)); opline->opcode = opcode; if (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); if (result) { zend_make_var_result(result, opline); } return opline; } #define SET_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过程在这个函数中像literals里边又新增1个元素,咱们打印opcodes:
咱们发现,新增了一条指令,在代码中就是return 1。
好的,到此,咱们发现,有三条指令,两个变量,三个字面量。$a和$b的位置已经有了,字面量也有了,咱们发现handler仍是个空指针,接下来咱们看handler的生成。
咱们接着走,会走到pass_two这个函数,这个函数中,对opline指令集作了进一步的加工,最主要的工做是设置指令的handler,源码以下:
ZEND_API int pass_two(zend_op_array *op_array) { /**代码省略**/ while (opline < end) {//遍历opline数组 if (opline->op1_type == IS_CONST) { ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, opline->op1); } else if (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); } else if (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的type为IS_CONST,那么将会调用ZEND_PASS_TWO_UPDATE_CONSTANT,代码以下:
/* convert constant from compile-time to run-time */ # define ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, node) do { \ (node).zv = CT_CONSTANT_EX(op_array, (node).constant); \ } while (0) # define CT_CONSTANT_EX(op_array, num) \ ((op_array)->literals + (num))
咱们知道,对于IS_CONST的变量的字面量是存在与literals里边的,而constant是相对的下标,所以咱们能够经过对于首地址偏移constant来进行转换为真实的偏移量。对于IS_VAR|IS_TMP_VAR类型的变量,会经过ZEND_CALL_VAR_NUM计算偏移量。
另一个很是重要的工做是经过ZEND_VM_SET_OPCODE_HANDLER(opline),设置opline对应的hanlder,代码以下:
ZEND_API void zend_vm_set_opcode_handler(zend_op* op) { op->handler = zend_vm_get_opcode_handler(zend_user_opcodes[op->opcode], op); } static const void *zend_vm_get_opcode_handler(zend_uchar opcode, const zend_op* op) { return zend_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虚拟机上执行这些指令。