OPCODE

什么样是 OPCODE?它是风流倜傥种设想机能够分辨并拍卖的通令。Zend
虚构机满含了意气风发体系的 OPCODE,通过 OPCODE 设想机能够做过多业务,列举几个OPCODE 的事例:

  • ZEND_ADD 将几个操作数相加。
  • ZEND_NEW 创设一个 PHP 对象。
  • ZEND_ECHO 将内容输出到专门的学问输出中。
  • ZEND_EXIT 退出 PHP。

如此那般的操作,PHP 定义了1九十多个(随着 PHP 的更新,断定会补助更加多花色的
OPCODE),全部的 OPCODE
的定义和达成都能够在源码的 zend/zend_vm_def.h 文件(这几个文件的开始和结果并非原生的
C 代码,而是四个模板,后边会表达原委)中查看见。

我们来看下 PHP 是怎么布置 OPCODE 数据布局:

struct _zend_op {
    const void *handler;
    znode_op op1;
    znode_op op2;
    znode_op result;
    uint32_t extended_value;
    uint32_t lineno;
    zend_uchar opcode;
    zend_uchar op1_type;
    zend_uchar op2_type;
    zend_uchar result_type;
};

周全察看 OPCODE 的数据布局,是或不是能找到汇编语言的感觉。每三个 OPCODE
都带有三个操作数,op1和 op2handler 指针则指向了奉行该 OPCODE
操作的函数,函数处理后的结果,会被保存在 result 中。

我们举一个简便的事例:

<?php
$b = 1;
$a = $b + 2;

大家经过 vld 扩展看见,经过编写翻译的后,上面的代码生成了 ZEND_ADD 指令的
OPCODE。

compiled vars:  !0 = $b, !1 = $a
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   ASSIGN                                                   !0, 1
   3     1        ADD                                              ~3      !0, 2
         2        ASSIGN                                                   !1, ~3
   8     3      > RETURN                                                   1

其间,第二行是 ZEND_ADD 指令的
OPCODE。大家见到,它采用2个操作数,op1 是变量 $bop2 是数字常量1,重临的结果存入了临时变量中。在 zend/zend_vm_def.h 文件中,大家得以找到
ZEND_ADD 指令对应的函数完成:

ZEND_VM_HANDLER(1, ZEND_ADD, CONST|TMPVAR|CV, CONST|TMPVAR|CV)
{
    USE_OPLINE
    zend_free_op free_op1, free_op2;
    zval *op1, *op2, *result;

    op1 = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
    op2 = GET_OP2_ZVAL_PTR_UNDEF(BP_VAR_R);
    if (EXPECTED(Z_TYPE_INFO_P(op1) == IS_LONG)) {
        if (EXPECTED(Z_TYPE_INFO_P(op2) == IS_LONG)) {
            result = EX_VAR(opline->result.var);
            fast_long_add_function(result, op1, op2);
            ZEND_VM_NEXT_OPCODE();
        } else if (EXPECTED(Z_TYPE_INFO_P(op2) == IS_DOUBLE)) {
            result = EX_VAR(opline->result.var);
            ZVAL_DOUBLE(result, ((double)Z_LVAL_P(op1)) + Z_DVAL_P(op2));
            ZEND_VM_NEXT_OPCODE();
        }
    } else if (EXPECTED(Z_TYPE_INFO_P(op1) == IS_DOUBLE)) {

    ...
}

上边的代码并非原生的 C 代码,而是后生可畏种模板。

缘何那样做?因为 PHP 是弱类型语言,而其完结的 C
则是强类型语言。弱类型语言援救活动类型相称,而活动类型相配的落真实情状势,就好像上述代码同样,通过决断来拍卖不相同品类的参数。试想一下,假诺每三个OPCODE
管理的时候都急需判断传入的参数类型,那么品质势必成为巨大的难点(三次呼吁须求管理的
OPCODE 只怕能达成数不清个)。

哪有何办法呢?我们发将来编写翻译的时候,已经能够明确每一种操作数的种类(大概是常量依旧变量)。所以,PHP
真正执行时的 C
代码,分歧档案的次序操作数将分成差异的函数,供设想机直接调用。那黄金年代部分代码放在了 zend/zend_vm_execute.h 中,张开后的文书非常的大,而且我们注意到还恐怕有那样的代码:

if (IS_CONST == IS_CV) {

一起未有啥样含义是啊?然而并未有涉及,C
的编写翻译器会自动优化那样决断。大许多情况,大家盼望理解某些 OPCODE
管理的逻辑,依然经过翻阅模板文件 zend/zend_vm_def.h 比较简单。顺便说一下,遵照模板生成
C 代码的顺序正是用 PHP 完毕的。

19             _UNUSED_CODE, /* 15             */

4.

讲到这里,大家明白opcode怎么和handler对应了。可是在全体上还也有二个历程,就是语法分析,深入分析后有所的opcode是怎么串联起来的呢?

语法拆解解析的细节就背着了,解析过后,会有个包涵全部opcode的大数组(说链表恐怕更确切),从地点代码大家得以看见,种种handler试行完后,都会调用
ZEND_VM_NEXT_OPCODE(卡塔尔国,抽取下二个opcode,继续试行,直到最终退出,循环的代码
vim Zend/zend_vm_execute.h +337:

ZEND_API void execute_ex(zend_execute_data *execute_data TSRMLS_DC)
{
›   DCL_OPLINE
›   zend_bool original_in_execution;



›   original_in_execution = EG(in_execution);
›   EG(in_execution) = 1;

›   if (0) {
zend_vm_enter:
›   ›   execute_data = i_create_execute_data_from_op_array(EG(active_op_array), 1 TSRMLS_CC);
›   }

›   LOAD_REGS();
›   LOAD_OPLINE();

›   while (1) {
    ›   int ret;
#ifdef ZEND_WIN32
›   ›   if (EG(timed_out)) {
›   ›   ›   zend_timeout(0);
›   ›   }
#endif

›   ›   if ((ret = OPLINE->handler(execute_data TSRMLS_CC)) > 0) {
›   ›   ›   switch (ret) {
›   ›   ›   ›   case 1:
›   ›   ›   ›   ›   EG(in_execution) = original_in_execution;
›   ›   ›   ›   ›   return;
›   ›   ›   ›   case 2:
›   ›   ›   ›   ›   goto zend_vm_enter;
›   ›   ›   ›   ›   break;
›   ›   ›   ›   case 3:
›   ›   ›   ›   ›   execute_data = EG(current_execute_data);
›   ›   ›   ›   ›   break;
›   ›   ›   ›   default:
›   ›   ›   ›   ›   break;
›   ›   ›   }
›   ›   }

›   }
›   zend_error_noreturn(E_ERROR, "Arrived at end of main loop which shouldn't happen");
}

宏定义, vim Zend/zend_execute.c +1772

1772 #define ZEND_VM_NEXT_OPCODE() \
1773 ›   CHECK_SYMBOL_TABLES() \
1774 ›   ZEND_VM_INC_OPCODE(); \
1775 ›   ZEND_VM_CONTINUE()

329 #define ZEND_VM_CONTINUE()         return 0
330 #define ZEND_VM_RETURN()           return 1
331 #define ZEND_VM_ENTER()            return 2
332 #define ZEND_VM_LEAVE()            return 3

while是一个死循环,施行贰个handler函数,除个别境况,超多handler函数末尾都调用ZEND_VM_NEXT_OPCODE()
-> ZEND_VM_CONTINUE(State of Qatar,return 0,继续循环。

注:例如 yield
协程是个不相同,它会回去1,直接return出循环。今后有空子大家再独自对yield做解析。

但愿你看完下面内容,对PHP Zend
引擎的剖析进程有个详细的打听,下边大家根据原理的拆解解析,再简单聊聊PHP的优化。

和CPU的一声令下相仿,有贰个标识指令的opcode字段,甚至那些opcode所操作的操作数,PHP不像汇编那么底层,
在本子实际施行的时候可能还亟需别的更加的多的音讯,extended_value字段就封存了那类音信,
当中的result域则是保留该指令实行到位后的结果

PHP 是一门解释型的言语。诸如 Java、Python、Ruby、Javascript
等解释型语言,我们编辑的代码不会被编写翻译成机器码运营,而是会被编写翻译中间码运转在设想机(VM)上。运维PHP 的设想机,称之为 Zend 虚构机,今天我们将浓郁内核,商讨 Zend
设想机运行的原理。

08  

1.

先说个PHP5.3+ 的语法糖,平日大家这么写:

<?php
    $a = 0;
    $b = $a ? $a : 1;

语法糖可以这么写:

<?php
    $a = 0;
    $b = $a ?: 1;

实践结果$b =
1,前面写法更简练,但平淡无奇不太提议用太多语法糖,特别是便于明白混淆的,举个例子PHP
7 新扩充??如下:

<?php
    $b = $a ?? 1;

相当于:

<?php
    $b = isset($a) ? $a : 1;

?: 和 ??
你是或不是便于搞混,假若这么,笔者提议宁可不要,代码可读性强,易维护更珍视。

语法糖不是本文的严重性,咱们的指标是从语法糖动手聊聊Zend VM的深入分析原理。

0x1: 数据构造

逻辑跳转

咱俩了解指令都以逐生机勃勃执行的,而小编辈的主次,平时都包蕴众多的逻辑推断和循环,那有的又是哪些通过
OPCODE 完毕的啊?

<?php
$a = 10;
if ($a == 10) {
    echo 'success';
} else {
    echo 'failure';
}

咱俩依旧经过 vld 查看 OPCODE(一定要说 vld 扩大是剖判 PHP 的神器)。

compiled vars:  !0 = $a
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   ASSIGN                                                   !0, 10
   3     1        IS_EQUAL                                         ~2      !0, 10
         2      > JMPZ                                                     ~2, ->5
   4     3    >   ECHO                                                     'success'
         4      > JMP                                                      ->6
   6     5    >   ECHO                                                     'failure'
   7     6    > > RETURN                                                   1

咱俩见到,JMPZ 和 JMP 调节了试行流程。JMP 的逻辑特简单,将目前的
OPCODE 指针指向内需跳转的 OPCODE。

ZEND_VM_HANDLER(42, ZEND_JMP, JMP_ADDR, ANY)
{
    USE_OPLINE

    ZEND_VM_SET_OPCODE(OP_JMP_ADDR(opline, opline->op1));
    ZEND_VM_CONTINUE();
}

JMPZ 仅仅是多了一回决断,遵照结果选择是或不是跳转,这里就不再另行列举了。而管理循环的主意与判别基本上是近乎的。

<?php
$a = [1, 2, 3];
foreach ($a as $n) {
    echo $n;
}

compiled vars:  !0 = $a, !1 = $n
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   ASSIGN                                                   !0, <array>
   3     1      > FE_RESET_R                                       $3      !0, ->5
         2    > > FE_FETCH_R                                               $3, !1, ->5
   4     3    >   ECHO                                                     !1
         4      > JMP                                                      ->2
         5    >   FE_FREE                                                  $3
   5     6      > RETURN                                                   1

巡回只需求 JMP 指令就可以到位,通过 FE_FETCH_R 指令判定是还是不是曾经达到数组的最终,要是达到则脱离循环。

15             _UNUSED_CODE, /* 11             */

5.3 动态函数的代价

<?php
    function foo() { }
    foo();

对应opcode:

number of ops:  3
compiled vars:  none
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   NOP
   3     1        DO_FCALL                                      0          'foo'
   4     2      > RETURN                                                   1

动态调用的代码:

<?php
    function foo() { }
    $a = 'foo';
    $a();

opcode:

number of ops:  5
compiled vars:  !0 = $a
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   NOP
   3     1        ASSIGN                                                   !0, 'foo'
   4     2        INIT_FCALL_BY_NAME                                       !0
         3        DO_FCALL_BY_NAME                              0
   5     4      > RETURN                                                   1

可以 vim Zend/zend_vm_def.h
+2630,看看INIT_FCALL_BY_NAME做的政工,代码太长,这里不列出来了。动态天性纵然低价,但必然会牺牲质量,所以利用前要平衡利弊。

比对汇编语言的定义,每种opcode都对应于三个品种,申明该opcpde的”操作指令”,opcode的品类为zend_uchar,zend_uchar实际上正是unsigned
char,此字段保存的整形值即为op的编号,用来区分分歧的op类型,opcode的可取值都被定义成了宏

结语

透过摸底 Zend 设想机,相信您对 PHP
是什么样运作的,会有更加深厚的掌握。想到大家写的黄金年代行行代码,最终机器实行的时候会化为成千上万的下令,每一种指令再次创下设在复杂的拍卖逻辑之上。那一个过去随机写下的代码,今后会不会在脑公里不自觉的转换到OPCODE 再尝试风流浪漫番啊?

07 #endif

5.1 echo 输出

<?php
    $foo = 'foo';
    $bar = 'bar';
    echo $foo . $bar;

vld 查看opcode:

number of ops:  5
compiled vars:  !0 = $foo, !1 = $bar
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   ASSIGN                                                   !0, 'foo'
   3     1        ASSIGN                                                   !1, 'bar'
   4     2        CONCAT                                           ~2      !0, !1
         3        ECHO                                                     ~2
   5     4      > RETURN                                                   1

branch: #  0; line:     2-    5; sop:     0; eop:     4; out1:  -2
path #1: 0,

ZEND_CONCAT 连接 $a和$b的值,保存到有的时候变量~第22中学,然后echo
出来。那些历程中关系要分配一块内部存款和储蓄器,用于不常变量,用完后还要释放,还索要调用拼接函数,奉行拼接进程。

万生龙活虎换来这么写:

<?php
    $foo = 'foo';
    $bar = 'bar';
    echo $foo, $bar;

对应的opcode:

number of ops:  5
compiled vars:  !0 = $foo, !1 = $bar
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   ASSIGN                                                   !0, 'foo'
   3     1        ASSIGN                                                   !1, 'bar'
   4     2        ECHO                                                     !0
         3        ECHO                                                     !1
   5     4      > RETURN                                                   1

branch: #  0; line:     2-    5; sop:     0; eop:     4; out1:  -2
path #1: 0,

没有必要分配内部存款和储蓄器,也不须要实践拼接函数,是还是不是功用越来越好呢!想驾驭拼接进程,能够依照本文讲的内容,自行检索
ZEND_CONCAT 那些opcode对应的handler,做了许多政工啊。

推行进度

准确的来讲,PHP
的施行分成了两大片段:编写翻译和推行。这里小编将不会详细展开编写翻译的片段,而是把抢手放在实行的进程。

经过语法、词法深入分析等意气风发密密层层的编写翻译进程后,我们赢得了四个名称为 OPArray
的数据,其组织如下:

struct _zend_op_array {
    /* Common elements */
    zend_uchar type;
    zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */
    uint32_t fn_flags;
    zend_string *function_name;
    zend_class_entry *scope;
    zend_function *prototype;
    uint32_t num_args;
    uint32_t required_num_args;
    zend_arg_info *arg_info;
    /* END of common elements */

    uint32_t *refcount;

    uint32_t last;
    zend_op *opcodes;

    int last_var;
    uint32_t T;
    zend_string **vars;

    int last_live_range;
    int last_try_catch;
    zend_live_range *live_range;
    zend_try_catch_element *try_catch_array;

    /* static variables support */
    HashTable *static_variables;

    zend_string *filename;
    uint32_t line_start;
    uint32_t line_end;
    zend_string *doc_comment;
    uint32_t early_binding; /* the linked list of delayed declarations */

    int last_literal;
    zval *literals;

    int  cache_size;
    void **run_time_cache;

    void *reserved[ZEND_MAX_RESERVED_RESOURCES];
};

内容比比较多对啊?简单的敞亮,其本质便是三个 OPCODE
数组外加试行进程中所需求的境况数据的聚众。介绍多少个相对来说比较关键的字段:

  • opcodes 存放 OPCODE 的数组。
  • filename 当前实践的本子的文件名。
  • function_name 当前实行的主意名称。
  • static_variables 静态变量列表。
  • last_try_catch try_catch_array 当前上下文中,假如现身十分try-catch-finally 跳转所需的新闻。
  • literals 全体诸如字符串 foo 恐怕数字23,那样的常量字面量集结。

干什么必要调换那样庞大的数量?因为编译时期生成的新闻更多,实践时代所须要的岁月就越少。

接下去,大家看下 PHP 是如何实行 OPCODE。OPCODE
的举行被放在叁个周而复始中,这么些轮回坐落于 zend/zend_vm_execute.h 中的 execute_ex 函数:

ZEND_API void execute_ex(zend_execute_data *ex)
{
    DCL_OPLINE

    zend_execute_data *execute_data = ex;

    LOAD_OPLINE();
    ZEND_VM_LOOP_INTERRUPT_CHECK();

    while (1) {
        if (UNEXPECTED((ret = ((opcode_handler_t)OPLINE->handler)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)) != 0)) {
            if (EXPECTED(ret > 0)) {
                execute_data = EG(current_execute_data);
                ZEND_VM_LOOP_INTERRUPT_CHECK();
            } else {
                return;
            }
        }
    }

    zend_error_noreturn(E_CORE_ERROR, "Arrived at end of main loop which shouldn't happen");
}

此地,笔者去掉了大器晚成部分情形变量剖断分支,保留了运营的主流程。能够观看,在三个Infiniti循环中,虚构时机不断调用
OPCODE
钦命的 handler 函数处理指令集,直到某次指令管理的结果 ret 小于0。注意到,在主流程中并未运动
OPCODE
数组的脚下指针,而是把那么些历程置于指令履行的切实可行函数的最终。所以,大家在大多OPCODE 的兑现函数的最后,都能看见调用这些宏:

ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();

在事情发生从前极度轻易例子中,我们来看 vld 打字与印刷出的施行 OPCODE
数组中,最后有生龙活虎项命令为 ZEND_RETURN 的 OPCODE。但咱们编辑的 PHP
代码中并不曾如此的言语。在编写翻译时代,虚构机遇自动将以此命令加到 OPCODE
数组的最终。ZEND_RETURN 指令对应的函数会返回-1,决断推行的结果小于0时,就能退出循环,从而甘休程序的运作。

05             _CONST_CODE,  /* 1 = IS_CONST   */

3.

珍视四个opcode,ZEND_JMP_SET_VAR 和
ZEND_QM_ASSIGN_VA奥迪Q5,怎么接着读代码呢?上边说下PHP的opcode。

PHP5.6有1六二十一个opcode,意味着能够进行167种不相同的精兵简政操作,官方文书档案看这里

PHP内部使用_zend_op 那么些构造体来代表opcode, vim Zend/zend_compile.h
+111

111 struct _zend_op {
112 ›   opcode_handler_t handler;
113 ›   znode_op op1;
114 ›   znode_op op2;
115 ›   znode_op result;
116 ›   ulong extended_value;
117 ›   uint lineno;
118 ›   zend_uchar opcode;
119 ›   zend_uchar op1_type;
120 ›   zend_uchar op2_type;
121 ›   zend_uchar result_type;
122 }

PHP 7.0略有区别,首要不一致在针对陆12个人系统
uint换来uint32_t,显明钦命字节数。

您把opcode当成三个总括器,只接纳多个操作数(op1,
op2State of Qatar,奉行三个操作(handler,
比方加减乘除State of Qatar,然后它回到三个结实(result卡塔尔国给你,再稍加拍卖算术溢出的气象(extended_value)。

Zend的VM对每种opcode的工作措施完全雷同,都有叁个handler(函数指针),指向管理函数的地址。那是二个C函数,包涵了推行opcode对应的代码,使用op1,op2做为参数,施行到位后,会再次回到二个结实(result),临时也会增大学一年级段音讯(extended_value)。

用大家例子中的操作数 ZEND_JMP_SET_VAR 说明,vim Zend/zend_vm_def.h
+4995

4942 ZEND_VM_HANDLER(158, ZEND_JMP_SET_VAR, CONST|TMP|VAR|CV, ANY)
4943 {
4944 ›   USE_OPLINE
4945 ›   zend_free_op free_op1;
4946 ›   zval *value, *ret;
4947
4948 ›   SAVE_OPLINE();
4949 ›   value = GET_OP1_ZVAL_PTR(BP_VAR_R);
4950
4951 ›   if (i_zend_is_true(value)) {
4952 ›   ›   if (OP1_TYPE == IS_VAR || OP1_TYPE == IS_CV) {
4953 ›   ›   ›   Z_ADDREF_P(value);
4954 ›   ›   ›   EX_T(opline->result.var).var.ptr = value;
4955 ›   ›   ›   EX_T(opline->result.var).var.ptr_ptr = &EX_T(opline->result.var).var.ptr;
4956 ›   ›   } else {
4957 ›   ›   ›   ALLOC_ZVAL(ret);
4958 ›   ›   ›   INIT_PZVAL_COPY(ret, value);
4959 ›   ›   ›   EX_T(opline->result.var).var.ptr = ret;
4960 ›   ›   ›   EX_T(opline->result.var).var.ptr_ptr = &EX_T(opline->result.var).var.ptr;
4961 ›   ›   ›   if (!IS_OP1_TMP_FREE()) {
4962 ›   ›   ›   ›   zval_copy_ctor(EX_T(opline->result.var).var.ptr);
4963 ›   ›   ›   }
4964 ›   ›   }
4965 ›   ›   FREE_OP1_IF_VAR();
4966 #if DEBUG_ZEND>=2
4967 ›   ›   printf("Conditional jmp to %d\n", opline->op2.opline_num);
4968 #endif
4969 ›   ›   ZEND_VM_JMP(opline->op2.jmp_addr);
4970 ›   }
4971
4972 ›   FREE_OP1();
4973 ›   CHECK_EXCEPTION();
4974 ›   ZEND_VM_NEXT_OPCODE();
4975 }

i_zend_is_true
来决断操作数是还是不是为true,所以ZEND_JMP_SET_VARAV4是生机勃勃种规格赋值,相信大家都能看掌握,下边讲珍视。

注意zend_vm_def.h那并不是二个得以平素编写翻译的C的头文件,只好算得一个模板,具体可编写翻译的头为zend_vm_execute.h(那么些文件可有45000多行哦),它实际不是手动生成,而是由zend_vm_gen.php其生龙活虎PHP脚本解析zend_vm_def.h后转移(风趣啊,先有鸡依旧先有蛋,未有PHP
哪来的这几个剧本?),推测那么些是早先时期产品,早期php版本应该不会用这些。

上面ZEND_JMP_SET_VAHighlander的代码,依据差异参数 CONST|TMP|VAR|CV
末了会变动差异门类的,但功效相通的handler函数:

static int ZEND_FASTCALL  ZEND_JMP_SET_VAR_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
static int ZEND_FASTCALL  ZEND_JMP_SET_VAR_SPEC_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
static int ZEND_FASTCALL  ZEND_JMP_SET_VAR_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
static int ZEND_FASTCALL  ZEND_JMP_SET_VAR_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)

诸如此比做的目标是为了在编写翻译期显著handler,升高运转期的品质。不那样做,在运维期依照参数类型选用,也足以实现,但品质不佳。当然如此做有的时候也会扭转一些污源代码(看似无用),不用顾忌,C的编写翻译器会更加的优化管理。

zend_vm_gen.php 也足以担当一些参数,细节在PHP源码中的README文件
Zend/README.ZEND_VM 有详实表达。

本条函数指针为op定义了实行办法,每少年老成种opcode字段都对应多少个项指标handler,例如假若$a

金沙糖果派对网站app,1;那样的代码生成的op,操作数为const和cv,最终就能够明确handler为函数ZEND_ASSIGN_SPEC_CV_CONST_HANDLER

/Zend/zend_vm_execute.h

void zend_init_opcodes_handlers{static const opcode_handler_t labels[] = {..ZEND_ASSIGN_SPEC_CV_CONST_HANDLER,..}}

0x4: opcpde操作数znode

操作数字段是_zend_op类型中比较关键的部分了,在那之中op1,op2,result八个操作数定义为znode类型

\php-5.6.17\Zend\zend_compile.h

typedef struct _znode { /* used only during compilation *//*这个int类型的字段定义znode操作数的类型#define IS_CONST  //表示常量,例如$a = 123; $b = "hello";这些代码生成OP后,123和"hello"都是以常量类型操作数存在#define IS_TMP_VAR  //表示临时变量,临时变量一般在前面加~来表示,这是一些OP执行过程中需要用到的中间变量,例如初始化一个数组的时候,就需要一个临时变量来暂时存储数组zval,然后将数组赋值给变量#define IS_VAR  //一般意义上的变量,以$开发表示#define IS_UNUSED  // Unused variable #define IS_CV  // Compiled variable,这种类型的操作数比较重要,此类型是在PHP后来的版本中中才出现,CV的意思是compiled variable,即编译后的变量,变量都是保存在一个符号表中,这个符号表是一个哈希表,如果每次读写变量的时候都需要到哈希表中去检索,会对效率有一定的影响,因此在执行上下文环境中,会将一些编译期间生成的变量缓存起来。此类型操作数一般以!开头表示,比如变量$a=123;$b="hello"这段代码,$a和$b对应的操作数可能就是!0和!1, 0和1相当于一个索引号,通过索引号从缓存中取得相应的值*/int op_type;/*此字段为一个联合体,根据op_type的不同,u取不同的值1. op_type=IS_CONST的时候,u中的constant保存的就是操作数对应的zval结构2. 例如$a=123时,123这个操作数中,u中的constant是一个IS_LONG类型的zval,其值lval为123 */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; 

0x5: opcode编写翻译后数组op_array

在zend_do_print函数中的第大器晚成行,大家注意到上边这行代码

zend_op *opline = get_next_op TSRMLS_CC); 

PHP脚本代码被编译后发出的opcode保存在op_array中,其内部存款和储蓄的组织如下

\php-5.6.17\Zend\zend_compile.h

struct _zend_op_array {/* Common elements */zend_uchar type;const char *function_name; // 如果是用户定义的函数则,这里将保存函数的名字zend_class_entry *scope;zend_uint fn_flags;union _zend_function *prototype;zend_uint num_args;zend_uint required_num_args;zend_arg_info *arg_info;/* END of common elements */zend_uint *refcount;zend_op *opcodes; // opcode数组zend_uint last;zend_compiled_variable *vars;int last_var;zend_uint T;zend_uint nested_calls;zend_uint used_stack;zend_brk_cont_element *brk_cont_array;int last_brk_cont;zend_try_catch_element *try_catch_array;int last_try_catch;zend_bool has_finally_block;/* static variables support */HashTable *static_variables;zend_uint this_var;const char *filename;zend_uint line_start;zend_uint line_end;const char *doc_comment;zend_uint doc_comment_len;zend_uint early_binding; /* the linked list of delayed declarations */zend_literal *literals;int last_literal;void **run_time_cache;int last_cache_slot;void *reserved[ZEND_MAX_RESERVED_RESOURCES];}; 

整整PHP脚本代码被编写翻译后的opcodes保存在这里地,在进行的时候由上边包车型大巴execute函数奉行

ZEND_API void execute(zend_op_array *op_array TSRMLS_DC){// ... 循环执行op_array中的opcode或者执行其他op_array中的opcode}

每条opcode都有二个opcode_handler_t的函数指针字段,用于执行该opcode,PHP有二种艺术来进展opcode的管理

  1. CALL: PHP暗许使用CALL的点子,也正是函数调用的主意2. SWITCH:
    由于opcode施行是各样PHP程序往往要求张开的操作,还行SWITCH也许GOTO的措施来散发3.
    GOTO: 日常GOTO的功效绝对会高级中学一年级些,可是效用是还是不是升高信赖于不相同的CPU
    实际上我们会发觉,在/zend/zend_language_parser.c中正是Zend的opcode翻译解释施行进度,个中蕴含了call、switch、goto三种opcode奉行格局

那就是PHP为何称之为解释型语言的基石原理,PHP在成就Lex词法解析后,在语法深入分析即生成发生式的时候,直接通过call、switch、goto的主意调用zend
api实行固然解释推行

Relevant Link:

http://www.nowamagic.net/librarys/veda/detail/1325http://php.net/manual/zh/internals2.opcodes.list.phphttp://www.nowamagic.net/librarys/veda/detail/1543http://www.nowamagic.net/librarys/veda/detail/1324http://www.nowamagic.net/librarys/veda/detail/1543 http://www.laruence.com/2008/06/18/221.htmlhttp://www.php-internals.com/book/?p=chapt02/02-03-02-opcode 

3. opcode翻译实施

Relevant Link:

http://www.php-internals.com/book/?p=chapt02/02-03-03-from-opcode-to-handler

如上所述本文给我们介绍的PHP内核学习课程之php
opcode内查验现的有关知识,希望对我们有所匡助。

办法调用

只要大家调用一个自定义的函数,设想时机怎么管理啊?

<?php
function foo() {
    echo 'test';
}

foo();

咱俩由此 vld 查看生成的 OPCODE。出现了多个 OPCODE
指令实施栈,是因为咱们自定义了一个 PHP
函数。在第一个施行栈上,调用自定义函数会履行七个 OPCODE
指令:INIT_FCALL 和 DO_FCALL

compiled vars:  none
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   NOP
   6     1        INIT_FCALL                                               'foo'
         2        DO_FCALL                                      0
         3      > RETURN                                                   1

compiled vars:  none
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   3     0  E >   ECHO                                                     'test'
   4     1      > RETURN                                                   null

其中,INIT_FCALL 计划了实行函数时所急需的上下文数据。DO_FCALL 担任施行函数。DO_FCALL 的管理函数根据不一致的调用情形处理了多量逻辑,小编选用了内部实行顾客定义的函数的逻辑部分:

ZEND_VM_HANDLER(60, ZEND_DO_FCALL, ANY, ANY, SPEC(RETVAL))
{
    USE_OPLINE
    zend_execute_data *call = EX(call);
    zend_function *fbc = call->func;
    zend_object *object;
    zval *ret;

    ...

    if (EXPECTED(fbc->type == ZEND_USER_FUNCTION)) {
        ret = NULL;
        if (RETURN_VALUE_USED(opline)) {
            ret = EX_VAR(opline->result.var);
            ZVAL_NULL(ret);
        }

        call->prev_execute_data = execute_data;
        i_init_func_execute_data(call, &fbc->op_array, ret);

        if (EXPECTED(zend_execute_ex == execute_ex)) {
            ZEND_VM_ENTER();
        } else {
            ZEND_ADD_CALL_FLAG(call, ZEND_CALL_TOP);
            zend_execute_ex(call);
        }
    }

    ...

    ZEND_VM_SET_OPCODE(opline + 1);
    ZEND_VM_CONTINUE();
}

能够见见,DO_FCALL 首先将调用函数前的上下文数据保存到 call->prev_execute_data,然后调用 i_init_func_execute_data 函数,将自定义函数对象中的 op_array(各样自定义函数会在编写翻译的时候生成对应的多少,其数据布局中蕴藏了函数的
OPCODE 数组) 赋值给新的实践上下文对象。

然后,调用 zend_execute_ex 函数,最初试行自定义的函数。zend_execute_ex 实际上正是前方提到的 execute_ex 函数(暗许是这么,但扩展或者重写 zend_execute_ex 指针,那一个API 让 PHP
扩大开辟者能够由此覆写函数到达扩展效率的指标,不是本篇的主旨,不酌量深切讨论),只是上下文数据被替换到当前函数所在的上下文数据。

作者们能够这么敞亮,最外层的代码就是一个默许存在的函数(肖似 C
语言中的 main()函数),和顾客自定义的函数本质上是未曾分别的。

20             _CV_CODE      /* 16 = IS_CV     */

5. PHP优化注意事项

/Zend/zend_vm_opcodes.h

02 {

2.

浅析的PHP源码分支 =>
remotes/origin/PHP-5.6.14,关于怎样通过vld查看opcode,请看自身事前写的那篇文章:

<?php
    $a = 0;
    $b = $a ?: 1;

对应的opcdoe如下:

number of ops:  5
compiled vars:  !0 = $a, !1 = $b
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   ASSIGN                                                   !0, 0
   3     1        JMP_SET_VAR                                      $1      !0
         2        QM_ASSIGN_VAR                                    $1      1
         3        ASSIGN                                                   !1, $1
   4     4      > RETURN                                                   1

branch: #  0; line:     2-    4; sop:     0; eop:     4; out1:  -2
path #1: 0,

vim Zend/zend_language_parser.y +834

834 ›   |›  expr '?' ':' { zend_do_jmp_set(&$1, &$2, &$3 TSRMLS_CC); }
835 ›   ›   expr     { zend_do_jmp_set_else(&$$, &$5, &$2, &$3 TSRMLS_CC); }

若是你心爱,能够慈爱入手,重新定义 ?:
的语法糖。服从BNF文法则则,使用bison解析,风野趣能够活动Google相关知识,继续深刻摸底。

从vld的opcode能够清楚,推行了 zend_do_jmp_set_else,代码在
Zend/zend_compile.c 中:

void zend_do_jmp_set_else(znode *result, const znode *false_value, const znode *jmp_token, const znode *colon_token TSRMLS_DC)
{
›   zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);

›   SET_NODE(opline->result, colon_token);
›   if (colon_token->op_type == IS_TMP_VAR) {
›   ›   if (false_value->op_type == IS_VAR || false_value->op_type == IS_CV) {
›   ›   ›   CG(active_op_array)->opcodes[jmp_token->u.op.opline_num].opcode = ZEND_JMP_SET_VAR;
›   ›   ›   CG(active_op_array)->opcodes[jmp_token->u.op.opline_num].result_type = IS_VAR;
›   ›   ›   opline->opcode = ZEND_QM_ASSIGN_VAR;
›   ›   ›   opline->result_type = IS_VAR;
›   ›   } else {
›   ›   ›   opline->opcode = ZEND_QM_ASSIGN;
›   ›   }
›   } else {
›   ›   opline->opcode = ZEND_QM_ASSIGN_VAR;
›   }
›   opline->extended_value = 0;
›   SET_NODE(opline->op1, false_value);
›   SET_UNUSED(opline->op2);

›   GET_NODE(result, opline->result);

›   CG(active_op_array)->opcodes[jmp_token->u.op.opline_num].op2.opline_num = get_next_op_number(CG(active_op_array));

›   DEC_BPC(CG(active_op_array));
}

\php-5.6.17\Zend\zend_compile.c

6 }

5.4 类的延期阐明的代价

抑或先看代码:

<?php
    class Bar { }
    class Foo extends Bar { }

对应opcode:

number of ops:  4
compiled vars:  none
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   NOP
   3     1        NOP
         2        NOP
   4     3      > RETURN                                                   1

轮番注解顺序:

<?php
    class Foo extends Bar { }
    class Bar { }

对应opcode:

number of ops:  4
compiled vars:  none
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   FETCH_CLASS                                   0  :0      'Bar'
         1        DECLARE_INHERITED_CLASS                                  '%00foo%2FUsers%2Fqisen%2Ftmp%2Fvld.php0x103d58020', 'foo'
   3     2        NOP
   4     3      > RETURN                                                   1

比方在强语言中,前面包车型地铁写法会发生编写翻译错误,但PHP这种动态语言,会把类的宣示推迟到运营时,如若您不当心,就很恐怕踩到这么些雷。

因而在大家询问Zend
VM原理后,就更应当当心少用动态天性,腹背之毛的时候,就必然不要用。

转自:

比如如下代码是在编写翻译器遭遇print语句的时候举行编写翻译的函数

13                     return;

5.2 define()和const

const关键字是从5.3开头引进的,和define有不小间隔,和C语言的#define倒是含义差不离。

  • define(卡塔尔(قطر‎ 是函数调用,有函数调用开支。
  • const
    是重视字,直接生成opcode,归属编写翻译期能鲜明的,无需动态在履行期分配。

const 的值是死的,运转时不得以转移,所以说雷同C语言的
#define,归于编写翻译时期就鲜明的内容,况兼对数值类型有节制。

直白看代码,相比较opcode:

define例子:

<?php
    define('FOO', 'foo');
    echo FOO;

define opcode:

number of ops:  6
compiled vars:  none
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   SEND_VAL                                                 'FOO'
         1        SEND_VAL                                                 'foo'
         2        DO_FCALL                                      2          'define'
   3     3        FETCH_CONSTANT                                   ~1      'FOO'
         4        ECHO                                                     ~1
   4     5      > RETURN                                                   1

const例子:

<?php
    const FOO = 'foo';
    echo FOO;

const opcode:

number of ops:  4
compiled vars:  none
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   DECLARE_CONST                                            'FOO', 'foo'
   3     1        FETCH_CONSTANT                                   ~0      'FOO'
         2        ECHO                                                     ~0
   4     3      > RETURN                                                   1

Relevant Link:

28 {
#define ZEND_NOP 0#define ZEND_ADD 1#define ZEND_SUB 2#define ZEND_MUL 3#define ZEND_DIV 4#define ZEND_MOD 5#define ZEND_SL 6#define ZEND_SR 7#define ZEND_CONCAT 8#define ZEND_BW_OR 9#define ZEND_BW_AND 10#define ZEND_BW_XOR 11#define ZEND_BW_NOT 12#define ZEND_BOOL_NOT 13#define ZEND_BOOL_XOR 14#define ZEND_IS_IDENTICAL 15#define ZEND_IS_NOT_IDENTICAL 16#define ZEND_IS_EQUAL 17#define ZEND_IS_NOT_EQUAL 18#define ZEND_IS_SMALLER 19#define ZEND_IS_SMALLER_OR_EQUAL 20#define ZEND_CAST 21#define ZEND_QM_ASSIGN 22#define ZEND_ASSIGN_ADD 23#define ZEND_ASSIGN_SUB 24#define ZEND_ASSIGN_MUL 25#define ZEND_ASSIGN_DIV 26#define ZEND_ASSIGN_MOD 27#define ZEND_ASSIGN_SL 28#define ZEND_ASSIGN_SR 29#define ZEND_ASSIGN_CONCAT 30#define ZEND_ASSIGN_BW_OR 31#define ZEND_ASSIGN_BW_AND 32#define ZEND_ASSIGN_BW_XOR 33#define ZEND_PRE_INC 34#define ZEND_PRE_DEC 35#define ZEND_POST_INC 36#define ZEND_POST_DEC 37#define ZEND_ASSIGN 38#define ZEND_ASSIGN_REF 39#define ZEND_ECHO 40#define ZEND_PRINT 41#define ZEND_JMP 42#define ZEND_JMPZ 43#define ZEND_JMPNZ 44#define ZEND_JMPZNZ 45#define ZEND_JMPZ_EX 46#define ZEND_JMPNZ_EX 47#define ZEND_CASE 48#define ZEND_SWITCH_FREE 49#define ZEND_BRK 50#define ZEND_CONT 51#define ZEND_BOOL 52#define ZEND_INIT_STRING 53#define ZEND_ADD_CHAR 54#define ZEND_ADD_STRING 55#define ZEND_ADD_VAR 56#define ZEND_BEGIN_SILENCE 57#define ZEND_END_SILENCE 58#define ZEND_INIT_FCALL_BY_NAME 59#define ZEND_DO_FCALL 60#define ZEND_DO_FCALL_BY_NAME 61#define ZEND_RETURN 62#define ZEND_RECV 63#define ZEND_RECV_INIT 64#define ZEND_SEND_VAL 65#define ZEND_SEND_VAR 66#define ZEND_SEND_REF 67#define ZEND_NEW 68#define ZEND_INIT_NS_FCALL_BY_NAME 69#define ZEND_FREE 70#define ZEND_INIT_ARRAY 71#define ZEND_ADD_ARRAY_ELEMENT 72#define ZEND_INCLUDE_OR_EVAL 73#define ZEND_UNSET_VAR 74#define ZEND_UNSET_DIM 75#define ZEND_UNSET_OBJ 76#define ZEND_FE_RESET 77#define ZEND_FE_FETCH 78#define ZEND_EXIT 79#define ZEND_FETCH_R 80#define ZEND_FETCH_DIM_R 81#define ZEND_FETCH_OBJ_R 82#define ZEND_FETCH_W 83#define ZEND_FETCH_DIM_W 84#define ZEND_FETCH_OBJ_W 85#define ZEND_FETCH_RW 86#define ZEND_FETCH_DIM_RW 87#define ZEND_FETCH_OBJ_RW 88#define ZEND_FETCH_IS 89#define ZEND_FETCH_DIM_IS 90#define ZEND_FETCH_OBJ_IS 91#define ZEND_FETCH_FUNC_ARG 92#define ZEND_FETCH_DIM_FUNC_ARG 93#define ZEND_FETCH_OBJ_FUNC_ARG 94#define ZEND_FETCH_UNSET 95#define ZEND_FETCH_DIM_UNSET 96#define ZEND_FETCH_OBJ_UNSET 97#define ZEND_FETCH_DIM_TMP_VAR 98#define ZEND_FETCH_CONSTANT 99#define ZEND_GOTO 100#define ZEND_EXT_STMT 101#define ZEND_EXT_FCALL_BEGIN 102#define ZEND_EXT_FCALL_END 103#define ZEND_EXT_NOP 104#define ZEND_TICKS 105#define ZEND_SEND_VAR_NO_REF 106#define ZEND_CATCH 107#define ZEND_THROW 108#define ZEND_FETCH_CLASS 109#define ZEND_CLONE 110#define ZEND_RETURN_BY_REF 111#define ZEND_INIT_METHOD_CALL 112#define ZEND_INIT_STATIC_METHOD_CALL 113#define ZEND_ISSET_ISEMPTY_VAR 114#define ZEND_ISSET_ISEMPTY_DIM_OBJ 115#define ZEND_PRE_INC_OBJ 132#define ZEND_PRE_DEC_OBJ 133#define ZEND_POST_INC_OBJ 134#define ZEND_POST_DEC_OBJ 135#define ZEND_ASSIGN_OBJ 136#define ZEND_INSTANCEOF 138#define ZEND_DECLARE_CLASS 139#define ZEND_DECLARE_INHERITED_CLASS 140#define ZEND_DECLARE_FUNCTION 141#define ZEND_RAISE_ABSTRACT_ERROR 142#define ZEND_DECLARE_CONST 143#define ZEND_ADD_INTERFACE 144#define ZEND_DECLARE_INHERITED_CLASS_DELAYED 145#define ZEND_VERIFY_ABSTRACT_CLASS 146#define ZEND_ASSIGN_DIM 147#define ZEND_ISSET_ISEMPTY_PROP_OBJ 148#define ZEND_HANDLE_EXCEPTION 149#define ZEND_USER_OPCODE 150#define ZEND_JMP_SET 152#define ZEND_DECLARE_LAMBDA_FUNCTION 153#define ZEND_ADD_TRAIT 154#define ZEND_BIND_TRAITS 155#define ZEND_SEPARATE 156#define ZEND_QM_ASSIGN_VAR 157#define ZEND_JMP_SET_VAR 158#define ZEND_DISCARD_EXCEPTION 159#define ZEND_YIELD 160#define ZEND_GENERATOR_RETURN 161#define ZEND_FAST_CALL 162#define ZEND_FAST_RET 163#define ZEND_RECV_VARIADIC 164#define ZEND_SEND_UNPACK 165#define ZEND_POW 166#define ZEND_ASSIGN_POW 167 
09         if ((ret = EX(opline)->handler(execute_data TSRMLS_CC)) > 0) {

平时opcode还会有另风流倜傥种称谓:字节码。
比方Java设想机,.NET的通用中间语言(CIL: Common Intermeditate
Language卡塔尔国等等。

近日谈起各当中间代码在奉行完后都会将中间代码的指针指向下一条指令,何况重临0。
当再次来到0时,while
循环中的if语句都不知足条件,进而使得中间代码能够继续推行下去。
正是那些while(1卡塔尔的巡回使得PHP内核中的opcode可以从第一条推行到结尾一条,
当然那中档也是有一点函数的跳转或类措施的实践等。

在PHP达成内部,opcode由如下的构造体表示

1 #define ZEND_VM_NEXT_OPCODE() \

op的实行句柄,其品种为opcode_handler_t

16                     goto zend_vm_enter;

\php-5.6.17\Zend\zend_compile.h

5     }

0x2: opcode类型: zend_op->zend_uchar opcode

那多少个全局的函数指针均只调用了系统默许落成的多少个函数,比方compile_file和compile_string函数,
他们都以以大局函数指针存在,这种达成情势在PHP内核中数不尽,其优势在于更低的耦合度,甚至足以定制那么些函数。
举个例子在APC等opcode优化扩大中便是通过轮番系统默许的zend_compile_file函数指针为自个儿的函数指针my_compile_file,
并且在my_compile_file中追加缓存等功能。

opcode是计算机指令中的黄金时代部分,用于内定要施行的操作,
指令的格式和行业内部由微管理机的一声令下标准钦定。
除了命令自身以外平常还大概有指令所急需的操作数,只怕有的指令没有必要显式的操作数。
那个操作数或然是寄放器中的值,旅社中的值,某块内部存款和储蓄器的值可能IO端口中的值等等

4 zend_execute_internal = NULL;

1. Opcode简介

14             _UNUSED_CODE, /* 10             */

2. PHP中的Opcode

19                 default:

PHP中的opcode则归于前面介绍中的后着,PHP是营造在Zend设想机之上的。PHP的opcode正是Zend虚构机中的指令

3     if ($c > 2) {

typedef int (ZEND_FASTCALL *opcode_handler_t)
(ZEND_OPCODE_HANDLER_ARGS);

3 zend_execute = execute;
struct _zend_op {opcode_handler_t handler; // 执行该opcode时调用的处理函数znode_op op1; // opcode所操作的操作数znode_op op2; // opcode所操作的操作数znode_op result;ulong extended_value;uint lineno;zend_uchar opcode; // opcode代码zend_uchar op1_type;zend_uchar op2_type;zend_uchar result_type;}; 
21             }

opcode是Computer指令中的生机勃勃有些,用于钦定要试行的操作,
指令的格式和正式由微电脑的一声令下规范钦赐。
除了指令本人以外平时还应该有指令所必要的操作数,大概某个指令无需显式的操作数。
那些操作数可能是存放器中的值,仓库中的值,某块内部存款和储蓄器的值或许IO端口中的值等等。

设若你是利用VS查看源码的话,将光标移到zend_execute并直接按F12,
你会发掘zend_execute的概念跳转到了三个指针函数的注解(Zend/zend_execute_API.c)。

平日opcode还应该有另大器晚成种称谓: 字节码。
举个例子Java设想机,.NET的通用中间语言(CIL: Common Intermeditate
LanguageState of Qatar等等

如上是一条中间代码的实践,那么对于函数的递归调用,PHP内核是如何管理的呢?
看如下后生可畏段PHP代码:

void zend_do_print(znode *result, const znode *arg TSRMLS_DC) /* {{{ */{ //新创建一条zend_op zend_op *opline = get_next_op TSRMLS_CC);//将新建的zend_op的返回值类型设置为临时变量,因为print中的内存仅仅为了临时输出,并不需要保存opline->result_type = IS_TMP_VAR;//为临时变量申请空间opline->result.var = get_temporary_variable;//指定opcode为ZEND_PRINTopline->opcode = ZEND_PRINT;//将传递进来的参数赋值给这条opcode的第一个操作数SET_NODE;SET_UNUSED;GET_NODE(result, opline->result);}
04             _UNUSED_CODE, /* 0              */

0x3: opcode推行句柄: zend_op->handler

09             _UNUSED_CODE, /* 5              */

上风度翩翩段代码中的ZEND_VM_ENTER()定义在Zend/zend_vm_execute.h的开头,如下:

admin

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注