PHP Opcode核心實現

來源:互聯網
上載者:User

catalogue

1. Opcode簡介2. PHP中的Opcode3. opcode翻譯執行(即時解釋執行)

1. Opcode簡介

opcode是電腦指令中的一部分,用於指定要執行的操作, 指令的格式和規範由處理器的指令規範指定。 除了指令本身以外通常還有指令所需要的運算元,可能有的指令不需要顯式的運算元。 這些運算元可能是寄存器中的值,堆棧中的值,某塊記憶體的值或者IO連接埠中的值等等

通常opcode還有另一種稱謂: 位元組碼(byte codes)。 例如Java虛擬機器(JVM),.NET的通用中繼語言(CIL: Common Intermeditate Language)等等

PHP中的opcode則屬於前面介紹中的後著,PHP是構建在Zend虛擬機器(Zend VM)之上的。PHP的opcode就是Zend虛擬機器中的指令(基於Zend的中間代碼)

Relevant Link:

http://www.luocong.com/learningopcode/doc/1._%E4%BB%80%E4%B9%88%E6%98%AFOpCode%EF%BC%9F.htm

2. PHP中的Opcode

0x1: 資料結構

在PHP實現內部,opcode由如下的結構體表示\php-5.6.17\Zend\zend_compile.h

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

和CPU的指令類似,有一個標示指令的opcode欄位,以及這個opcode所操作的運算元,PHP不像彙編那麼底層, 在指令碼實際執行的時候可能還需要其他更多的資訊,extended_value欄位就儲存了這類資訊, 其中的result域則是儲存該指令執行完成後的結果

例如如下代碼是在編譯器遇到print語句的時候進行編譯的函數

\php-5.6.17\Zend\zend_compile.c

void zend_do_print(znode *result, const znode *arg TSRMLS_DC) /* {{{ */{        //新建立一條zend_op     zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);    //將建立的zend_op的傳回值類型設定為臨時變數(IS_TMP_VAR),因為print中的記憶體僅僅為了臨時輸出,並不需要儲存    opline->result_type = IS_TMP_VAR;    //為臨時變數申請空間    opline->result.var = get_temporary_variable(CG(active_op_array));    //指定opcode為ZEND_PRINT    opline->opcode = ZEND_PRINT;    //將傳遞進來的參數賦值給這條opcode的第一個運算元    SET_NODE(opline->op1, arg);    SET_UNUSED(opline->op2);    GET_NODE(result, opline->result);}

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

比對組合語言的概念,每個opcode都對應於一個類型,表明該opcpde的"操作指令",opcode的類型為zend_uchar,zend_uchar實際上就是unsigned char,此欄位儲存的整形值即為op的編號,用來區分不同的op類型,opcode的可取值都被定義成了宏/Zend/zend_vm_opcodes.h

#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

0x3: opcode執行控制代碼: zend_op->handler

op的執行控制代碼,其類型為opcode_handler_t

typedef int (ZEND_FASTCALL *opcode_handler_t) (ZEND_OPCODE_HANDLER_ARGS);

這個函數指標為op定義了執行方式,每一種opcode欄位都對應一個種類的handler,比如如果$a = 1;這樣的代碼產生的op,運算元為const和cv,最後就能確定handler為函數ZEND_ASSIGN_SPEC_CV_CONST_HANDLER/Zend/zend_vm_execute.h

void zend_init_opcodes_handlers(void){  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    (1<<0)    //表示常量,例如$a = 123; $b = "hello";這些代碼產生OP後,123和"hello"都是以常量類型運算元存在    #define IS_TMP_VAR    (1<<1)    //表示臨時變數,臨時變數一般在前面加~來表示,這是一些OP執行過程中需要用到的中間變數,例如初始化一個數組的時候,就需要一個臨時變數來暫時儲存數組zval,然後將數組賦值給變數    #define IS_VAR        (1<<2)    //一般意義上的變數,以$開發表示    #define IS_UNUSED    (1<<3)    // Unused variable      #define IS_CV        (1<<4)    // Compiled variable,這種類型的運算元比較重要,此類型是在PHP後來的版本中(大概5.1)中才出現,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(CG(active_op_array) 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

Copyright (c) 2016 Little5ann All rights reserved

  • 相關文章

    聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.