PHP Opcode內核實現 - [ PHP內核學習 ]

catalogue

1. Opcode簡介
2. PHP中的Opcode
3. 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保存在這裏,這裏需要明白的是,並非全部的PHP用戶態代碼都能在編譯compile時期被翻譯過來,因爲本質上PHP是一種即時編譯執行的腳本語言,有很多代碼是在運行時run-time時期被動態地進行翻譯併產生對應的opcode,例如lambda-function、var變量函數。所以本質上來說,PHP的opcode翻譯可能是一個遞歸的過程
在執行的時候由下面的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/1325
http://php.net/manual/zh/internals2.opcodes.list.php
http://www.nowamagic.net/librarys/veda/detail/1543
http://www.nowamagic.net/librarys/veda/detail/1324
http://www.nowamagic.net/librarys/veda/detail/1543 
http://www.laruence.com/2008/06/18/221.html
http://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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章