资讯专栏INFORMATION COLUMN

PHP-7.1 源代码学习:字节码生成 之 "$a = 1"

ConardLi / 3409人阅读

摘要:前言本文通过分析这个语句的编译和执行来窥探解释执行逻辑准备参考之前的系列文章,在环境下下载,编译源代码将代码导入中编辑运行选项,增加运行参数设置断点开始调试是一个测试脚本,放在目录下,中只包含一条简单的赋值语句调用堆栈参考之前的系列文章

前言

本文通过分析 "$a=1" 这个 PHP 语句的编译和执行来窥探 php-cli 解释执行逻辑

准备

参考之前的系列文章,在 ubuntu 环境下下载,编译 PHP 源代码

将代码导入 idea clion IDE 中

编辑运行选项,增加运行参数:-f test.php

设置断点开始调试

test.php 是一个测试脚本,放在 sapi/cli/ 目录下,test.php 中只包含一条简单的赋值语句:


调用堆栈

参考之前的系列文章来了解 php-cli 启动过程以及语法分析和字节码生成的基本概念,这里直接给出调用堆栈:

我们尝试从 zend_compile_expr 函数说起

zend_compile_expr

赋值语句 is-a 表达式,zend_compile_expr 函数根据 ast 类型选择调用 zend_compile_assign:

// zend_compile.c

void zend_compile_expr(znode *result, zend_ast *ast) {
    ...
    switch (ast->kind) {
        ...
        case ZEND_AST_ASSIGN:
            zend_compile_assign(result, ast);
            break;
    }
}
zend_compile_assign

赋值语句的 ast 包含两个 child ast,即 left hand side var(ast->child[0]) 和 right hand side expr(ast->child[1]),var_node 和 expr_node 两个 znode 类型的变量是生成字节码过程使用的中间变量

// zend_compile.c

void zend_compile_assign(znode *result, zend_ast *ast) /* {{{ */
{
    zend_ast *var_ast = ast->child[0];
    zend_ast *expr_ast = ast->child[1];

    znode var_node, expr_node;
    zend_op *opline;
    uint32_t offset;

    if (is_this_fetch(var_ast)) {
        zend_error_noreturn(E_COMPILE_ERROR, "Cannot re-assign $this");
    }

    zend_ensure_writable_variable(var_ast);

然后我们来看看 switch case 语句

// zend_compile.c

switch (var_ast->kind) {
    case ZEND_AST_VAR:
    case ZEND_AST_STATIC_PROP:
        offset = 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);
        return;
}

刚看到这段代码可能会觉得挺绕的:zend_delayed_xxx 函数是干啥的?最终生成的字节码又保存在哪呢?

zend_emit_op

emit 有 "发射,散播"的意思,所以 zend_emit_op 可能和字节码保存相关:

// zend_compile.c

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;
}

这里我们又遇到了全局变量 CG(compile globals),zend_emit_op 先调用 get_next_op 获取可用的 zend_op(虚拟机指令),然后设置 op1, op2 为 opline 的两个操作数

现在我们知道生成的字节码保存在 CG 的 active_op_array 数组里

总结

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

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

相关文章

  • PHP-7.1 源代学习字节生成 概述

    摘要:前言字节码生成编译的代码主要集中在,文件中包含大量的函数,基本上一个函数对应语法规则文件一个非终结符,函数是所有函数的入口数据结构结构体是字节码抽象结构体并没有像名字那样简单,它包含了大量的字段供虚拟机在运行时使用一如既往的简单,直观,相比 前言 字节码生成(编译)的代码主要集中在 zend_compile.c ,文件中包含大量的 zend_compile_xxx 函数,基本上一个函数...

    1fe1se 评论0 收藏0
  • PHP-7.1 源代学习字节在 Zend 虚拟机中的解释执行 概述

    摘要:前言本文简要介绍虚拟机解释执行字节码的基本逻辑以及相关的数据结构,关于源代码的下载,编译,调试可以参考之前的系列文章我们来看看执行一个简单的脚本的调用栈由于是执行脚本文件,所以调用了函数,最终调用函数和其它语言编写的系统软件类似,函数中 前言 本文简要介绍 zend 虚拟机解释执行字节码的基本逻辑以及相关的数据结构,关于 PHP 源代码的下载,编译,调试可以参考之前的系列文章 exec...

    Tamic 评论0 收藏0
  • 重学计算机组成原理(五)- "旋转跳跃"的指令实现

    摘要:在中央处理器的控制部件中,包含的寄存器有指令寄存器和程序计数器。这条指令的第一个操作数,代表累加寄存器在中央处理器中,累加器是一种寄存器,用来储存计算产生的中间结果。第二个操作数则是进制的的表示。 showImg(https://ask.qcloudimg.com/http-save/1752328/57mlmnq3i5.png); CPU执行的也不只是一条指令,一般一个程序包含很多条...

    siberiawolf 评论0 收藏0
  • 从指令的角度看"abc"和new String("abc")

    摘要:但是有一个的指令,可以把字节码翻译成人类能看懂的东西。是文件分解器,可以反编译即对编译的文件进行反编译,也可以查看编译器生成的字节码。现在有一个类,定义入下先用编译成字节码,再使用进行反编译。 概要 Java工程师面试官偏爱的问题之一,就是abc和 new String(abc)的区别是什么?回答的比较好的会带出Java堆,栈,常量池,引用等概念。但今天不止如此,我们从指令的角度,去看...

    doodlewind 评论0 收藏0
  • PHP-7.1 源代学习:代生成 函数定义

    摘要:前言本文从函数定义的语法规则开始,简要介绍解释器如何编译函数定义函数对应的节点为了看起来清楚一些,我们将语法规则定义与语法动作分开根据语法动作,这条函数定义规则会创建一个类型的结点,我们来看看方法是一个通用的方法,通 前言 本文从函数定义的语法规则开始,简要介绍 PHP 解释器如何 编译 函数定义 函数对应的 AST 节点 为了看起来清楚一些,我们将 语法规则定义 与 语法动作分开: ...

    aisuhua 评论0 收藏0

发表评论

0条评论

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