【PHP源码学习】关于$a=1整个过程的gdb过程与相关验证

栏目: C · 发布时间: 6年前

内容简介:grape代码:我们来gdb一下整个过程,首先,在zend_compile_top_stmt入口处打断点:

关于$a=1整个过程的gdb过程与相关验证

grape

代码:

<?php
    $a =1;

基本流程:

-   zend_compile_top_stmt(CG(ast))
-   zend_compile_stmt(ast)
-   zend_compile_expr(&result, ast);
-   zend_compile_assign(result, ast);
-   zend_delayed_compile_begin();
-   zend_delayed_compile_var(&var_node, var_ast, BP_VAR_W);
-   zend_compile_expr(&expr_node, expr_ast);
-   zend_delayed_compile_end(offset);
-   zend_emit_op(result, ZEND_ASSIGN, &var_node, &expr_node);

GDB过程

我们来gdb一下整个过程,首先,在zend_compile_top_stmt入口处打断点:

【PHP源码学习】关于$a=1整个过程的gdb过程与相关验证

gdb下来我们进入

【PHP源码学习】关于$a=1整个过程的gdb过程与相关验证

在zend_compile_stmt我们进入default的zend_compile_expr函数:

【PHP源码学习】关于$a=1整个过程的gdb过程与相关验证

【PHP源码学习】关于$a=1整个过程的gdb过程与相关验证

因为我们是赋值运算,我们此时走到了assign函数,接下来就是整个编译过程中的重点部分。

【PHP源码学习】关于$a=1整个过程的gdb过程与相关验证

打印var_ast->kind

【PHP源码学习】关于$a=1整个过程的gdb过程与相关验证

可以看出我们接下来要走的就是上图的几个函数,那么,这几个函数又是怎么走的呢?我们接着看,首先,进入zend_delayed_compile_begin():

【PHP源码学习】关于$a=1整个过程的gdb过程与相关验证

这个函数的作用是取栈顶,然后在函数结束后赋值给offest,那我们看看这个offest是什么?

$a的处理

接下来进入$a的处理函数:

【PHP源码学习】关于$a=1整个过程的gdb过程与相关验证

在这里记录下我们处理$a过程中调用的函数:

zend_delayed_compile_var (result=0x7fffffffa580, ast=0x7ffff5e7f060, type=1)
zend_compile_simple_var (result=0x7fffffffa580, ast=0x7ffff5e7f060, type=1, delayed=1)
zend_try_compile_cv (result=0x7fffffffa580, ast=0x7ffff5e7f060)
lookup_cv (op_array=0x7ffff5e75460, name=0x7ffff5e5e4e0)

lookup_cv函数的作用是什么呢?首先我们先看lookcv的源代码:

static int lookup_cv(zend_op_array *op_array, zend_string* name) /* {{{ */{
    int i = 0;
    zend_ulong hash_value = zend_string_hash_val(name);

    while (i < op_array->last_var) {
        if (ZSTR_VAL(op_array->vars[i]) == ZSTR_VAL(name) ||
            (ZSTR_H(op_array->vars[i]) == hash_value &&
             ZSTR_LEN(op_array->vars[i]) == ZSTR_LEN(name) &&
             memcmp(ZSTR_VAL(op_array->vars[i]), ZSTR_VAL(name), ZSTR_LEN(name)) == 0)) {
            zend_string_release(name);
            return (int)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL, i);
        }
        i++;
    }
    i = op_array->last_var;
    op_array->last_var++;
    if (op_array->last_var > CG(context).vars_size) {
        CG(context).vars_size += 16; /* FIXME */
        op_array->vars = erealloc(op_array->vars, CG(context).vars_size * sizeof(zend_string*));
    }

    op_array->vars[i] = zend_new_interned_string(name);
    return (int)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL, i);
}

我们发现,lookup_cv()函数它返回一个int类型的地址,是sizeof(zval)的整数倍,通过它可以得到每个变量的偏移量(80(后面会讲) + 16 * i),i是变量的编号。这样就规定了运行时在栈上相对于zend_execute_data的偏移量,从而在栈上方便地存储了$a这个变量(下一篇笔记会详细讲)。而$a在zend_op_array的vars数组上也冗余存了一份,这样如果后面又用到了$a的话,直接去zend_op_array的vars数组中查找找,如果存在,那么直接使用之前的编号i,如果不存在则按序分配一个编号,然后再插入zend_op_array的vars数组,节省了分配编号的时间。

另外,在zend_try_compile_cv这个函数中对于result进行赋值,那么我们打印下result的值:

【PHP源码学习】关于$a=1整个过程的gdb过程与相关验证

我们发现,op_type=16(IS_CV),u.op.var=80

到此我们总结一下$a这个过程,核心函数lookupcv,在lookupcv中我们将变量存储在op_array->vars中,并且返回一个int型整数,代表着偏移量。随后在zend_try_compile_cv中将op_type和u.op.var赋值给znode *result,具体编译示例图如下图所示:

【PHP源码学习】关于$a=1整个过程的gdb过程与相关验证

到此,$a即左子节点就结束了。

1的处理

接下来我们来进行右子树的处理,gdb如图:

【PHP源码学习】关于$a=1整个过程的gdb过程与相关验证

右子树的处理比较简单,其调用函数为:

zend_compile_expr
ZVAL_COPY(z, v)

重点函数在于ZVAL_COPY这个宏,首先我们看gdb中到达了这个宏

【PHP源码学习】关于$a=1整个过程的gdb过程与相关验证

然后我们继续分析这个宏的作用,老规矩,先贴源码:

#define ZVAL_COPY(z, v)                                    \
    do {                                                \
        zval *_z1 = (z);                                \
        const zval *_z2 = (v);                            \
        zend_refcounted *_gc = Z_COUNTED_P(_z2);        \
        uint32_t _t = Z_TYPE_INFO_P(_z2);                \
        ZVAL_COPY_VALUE_EX(_z1, _z2, _gc, _t);            \
        if ((_t & (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT)) != 0) { \
            GC_REFCOUNT(_gc)++;                            \
        }                                                \
    } while (0)

它的功能是把一个zval(v)拷贝到另外一个zval(z)中,具体的一些分析请查看上一篇文章: https://segmentfault.com/a/11...

在进行复制完之后我们对于result进行打印

【PHP源码学习】关于$a=1整个过程的gdb过程与相关验证

我们可以看到已经完成了赋值。

最后进行了 result->op_type = IS_CONST,op_type的赋值

【PHP源码学习】关于$a=1整个过程的gdb过程与相关验证

重新打印result即最终的结果:

【PHP源码学习】关于$a=1整个过程的gdb过程与相关验证

至此,$a和1都分别存在了两个znode中。下边开始生成指令。

我们进入到zend_emit_op函数中:

【PHP源码学习】关于$a=1整个过程的gdb过程与相关验证

首先我们看这个函数所执行的所有指令:

【PHP源码学习】关于$a=1整个过程的gdb过程与相关验证

其中,set_node出现的频次很高,我们来看一下它究竟有什么用:

#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)

从代码中可以看出,对于操作数1,会将编译过程中临时的结构znode传递给zend_op中,对于操作数2,因为是常量(IS_CONST),会调用zend_add_literal将其插入到op_array->literals中。

接下来我们进行返回值的设置,此时会调用zend_make_var_result这个函数:

static inline void zend_make_var_result(znode *result, zend_op *opline) /* {{{ */
{
    opline->result_type = IS_VAR; //返回值的类型设置为IS_VAR
    opline->result.var = get_temporary_variable(CG(active_op_array));  //这个是返回值的编号,对应T位置
    GET_NODE(result, opline->result);
}
static uint32_t get_temporary_variable(zend_op_array *op_array) /* {{{ */
{
      return (uint32_t)op_array->T++;
}

返回值的类型为IS_VAR,result.var为T的值

最后打印opline看一下最终的结果:

【PHP源码学习】关于$a=1整个过程的gdb过程与相关验证

,下面我们给出Assign操作对应的指令图,如图所示:

【PHP源码学习】关于$a=1整个过程的gdb过程与相关验证

从图中可以看出,生成的opline中opcode等于38;op1的类型为IS_CV,op1.var对应的是vm_stack上的偏移量;op2的类型为IS_CONST,op2.constant对应的是op_array中literals数组的下标;result的类型为IS_VAR,result.var对应的是T的值;此时handler的值为空。

到此,编译阶段告一段落。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Big Java Late Objects

Big Java Late Objects

Horstmann, Cay S. / 2012-2 / 896.00元

The introductory programming course is difficult. Many students fail to succeed or have trouble in the course because they don't understand the material and do not practice programming sufficiently. ......一起来看看 《Big Java Late Objects》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具