资讯专栏INFORMATION COLUMN

【PHP源码学习】2019-03-27 pass_two函数详解笔记

PumpkinDylan / 2959人阅读

摘要:结果和我们设想的一致。另外一个非常重要的工作是通过,设置对应的,代码如下其中和之前的对应关系在中定义的。至此,整个抽象语法树就编译完成了,最终的结果为指令集,接下来就是在虚拟机上执行这些指令。参考资料源码分析源码研究之浅谈虚拟机

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。代码:


我们来看一看到编译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_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虚拟机上执行这些指令。

参考资料:
【PHP7源码分析】PHP7源码研究之浅谈Zend虚拟机

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/31534.html

相关文章

  • 【LNMPR源码学习笔记汇总

    摘要:此文用于汇总跟随陈雷老师及团队的视频,学习源码过程中的思考整理与心得体会,此文会不断更新视频传送门每日学习记录使用录像设备记录每天的学习源码学习源码学习内存管理笔记源码学习内存管理笔记源码学习内存管理笔记源码学习基本变量笔记 此文用于汇总跟随陈雷老师及团队的视频,学习源码过程中的思考、整理与心得体会,此文会不断更新 视频传送门:【每日学习记录】使用录像设备记录每天的学习 PHP7...

    Barrior 评论0 收藏0
  • 【每日学习记录】使用录像设备记录每天的学习

    摘要:在这里使用学而思网校的录像设备,记录每天学习的内容闫昌李乐阶段李乐李乐李乐李乐李乐李乐马运运李乐李乐李乐源码集群闫昌源码闫昌源码主从复制李乐源码施洪宝源码施洪宝韩天 在这里使用学而思网校的录像设备,记录每天学习的内容: 2019-06-24 ~ 2019-06-28 06-27 nginx by 闫昌 06-26 nginx module by 李乐 06-25 nginx http ...

    szysky 评论0 收藏0

发表评论

0条评论

PumpkinDylan

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<