内容简介:grape全部视频:原视频地址:
grape
全部视频: https://segmentfault.com/a/11...
原视频地址: http://replay.xesv5.com/ll/24...
流程回顾
上节课我们把$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的值又是如何呢?
我们发现,$a=2 op1是80,$b=3 op1为96,这是为什么呢?这之前我们说过这个问题,因为在栈中我们是分配一个大小为16的内存,所以需要增加16.第二个,我们知道result.constant的0和1代表字面量偏移量分别为0和1.
到这里都是之前学习过的内容,接下来继续学习。
return 1的做了什么?
继续执行代码:
我们发现在执行完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设置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_constr的变量的字面量是存在与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虚拟机上执行这些指令。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Oracle函数 - 日期函数详解
- Java构造函数与普通函数用法详解
- golang init()函数详解
- SQL SERVER 2012新增函数之逻辑函数CHOOSE详解
- Oracle中的translate函数和replace函数的用法详解
- golang中的append函数详解
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Writing Apache Modules with Perl and C
Lincoln Stein、Doug MacEachern / O'Reilly Media, Inc. / 1999-03 / USD 39.95
Apache is the most popular Web server on the Internet because it is free, reliable, and extensible. The availability of the source code and the modular design of Apache makes it possible to extend Web......一起来看看 《Writing Apache Modules with Perl and C》 这本书的介绍吧!