关于PHP中的opcode

简介

1、当Zend engine解释器完成对脚本代码的分析后,便将它们生成可以直接运行的中间代码,也称为操作码(Operate Code,opcode),opcode是一个四元组,(opcode, op1, op2, result),它们分别代表操作码,第一操作数,第二操作数,结果

2、因为PHP是构建在Zend虚拟机(Zend VM)之上的,所以PHP的opcode就是Zend虚拟机中的指令

opcode结构

struct _zend_op {
        opcode_handler_t handler;
        znode_op op1;
        znode_op op2;
        znode_op result;
        ulong extended_value;
        uint lineno;
        zend_uchar opcode;
        zend_uchar op1_type;
        zend_uchar op2_type;
        zend_uchar result_type;
};

1、 opcode_handler_t  opcode的函数指针 参考地址 opcode handler

2、result

我们看一下两个输出函数 ,echo 和 print

void zend_do_print(znode *result, const znode *arg TSRMLS_DC) /* {{{ */
{       
        zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);
        
        opline->result_type = IS_TMP_VAR;
        opline->result.var = get_temporary_variable(CG(active_op_array));
        opline->opcode = ZEND_PRINT;
        SET_NODE(opline->op1, arg);
        SET_UNUSED(opline->op2);
        GET_NODE(result, opline->result);
}
/* }}} */

void zend_do_echo(const znode *arg TSRMLS_DC) /* {{{ */
{
        zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);

        opline->opcode = ZEND_ECHO;
        SET_NODE(opline->op1, arg);
        SET_UNUSED(opline->op2);
}
/* }}} */

 我们可以看到print有result的设置 op2均为使用,从这里我们也能看出print和echo的区别来,print有返回值,而echo没有,这里的没有和返回null是不同的, 如果尝试将echo的值赋值给某个变量或者传递给函数都会出现语法错误

3、op1,op2 记录参数

4、lineno 对应的行号

5、opcode 对应相应的操作

6、extended_value   和CPU的指令类似,有一个标示指令的opcode字段,以及这个opcode所操作的操作数,PHP不像汇编那么底层, 在脚本实际执行的时候可能还需要其他更多的信息,extended_value字段就保存了这类信息

关于result op1 op2的结构znode

typedef union _znode_op {
        zend_uint      constant;
        zend_uint      var;
        zend_uint      num;
        zend_ulong     hash;
        zend_uint      opline_num; /*  Needs to be signed */
        zend_op       *jmp_addr;
        zval          *zv;
        zend_literal  *literal;
        void          *ptr;        /* Used for passing pointers from the compile to execution phase, currently used for traits */
} znode_op;

typedef struct _znode { /* used only during compilation */
        int op_type;
        union {
                znode_op op;
                zval constant; /* replaced by literal/zv */
                zend_op_array *op_array;
                zend_ast *ast;
        } u;
        zend_uint EA;      /* extended attributes */
} znode;

 op_type

#define IS_CONST    (1<<0)  
#define IS_TMP_VAR  (1<<1)  
#define IS_VAR      (1<<2)  
#define IS_UNUSED   (1<<3)    /* Unused variable */  
#define IS_CV       (1<<4)    /* Compiled variable */

IS_CONST:表示常量,例如$a = 1; $b = "hello";这些代码生成OP后,1和"hello"都是以常量类型操作数存在。
IS_TMP_VAR:表示临时变量,临时变量一般在前面加~来表示,这是一些OP执行过程中需要用到的中间变量,例如初始化一个数组的时候,就需要一个临时变量来暂时存储数组zval,然后将数组赋值给变量。
IS_VAR: 一般意义上的变量,以$开发表示
IS_UNUSED : 暂时不介绍,从名字来看应该是标识为不使用
IS_CV:这种类型的操作数比较重要,此类型是在PHP后来的版本中(大概5.1)中才出现,CV的意思是compiled variable,即编译后的变量,变量都是保存在一个符号表中,这个符号表是一个哈希表,试想如果每次读写变量的时候都需要到哈希表中去检索,势必会对效率有一定的影响,因此在执行上下文环境中,会将一些编译期间生成的变量缓存起来,此过程以后再详细介绍。此类型操作数一般以!开头表示,比如变量$a=123;$b="hello"这段代码,$a和$b对应的操作数可能就是!0和!1, 0和1相当于一个索引号,通过索引号从缓存中取得相应的值。

u

此字段为一个联合体,根据op_type的不同,u取不同的值。比如op_type=IS_CONST的时候,u中的constant保存的就是操作数对 应的zval结构。例如$a=123时,123这个操作数中,u中的constant是一个IS_LONG类型的zval,其值lval为123

原文地址:https://www.cnblogs.com/chenpingzhao/p/4822672.html