天天看點

十五、PHP核心探索:操作碼OpCode ☞ 計算機最終是執行這些OpCode

運作一段PHP代碼主要有兩個階段:編譯和執行。 當然編譯過程中還包括詞法分析文法分析不同階段和細節,這裡我們将其作為一個整體。在這兩個階段之間,PHP代碼會被編譯成op code,可以将其認為是引擎的一個中間語言,編輯階段把PHP源碼生成op code,然後在執行階段執行這些op code。這篇文章将簡單的介紹op code。

PHP代碼編譯之後會生成許多的op,每一個op都是一個zend_op類型的c變量。相關的定義可以在{PHPSRC}/Zend/zend_compile.h中看到:

struct _zend_op {  
    opcode_handler_t handler;  
    znode result;  
    znode op1;  
    znode op2;  
    ulong extended_value;  
    uint lineno;  
    zend_uchar opcode;  
};  
  
typedef struct _zend_op zend_op;      

簡單的說說這幾個字段:

1. result,op1,op2

這三個字段都是znode類型,它們是op的操作數和操作結果載體,當然并不是每個op都需要使用這三個字段,根據op的功能不同,會使用其中某些字段。比如類型為ZEND_ECHO的op值需要使用op1,功能就是将op1中的相應的值輸出。一會再單獨介紹znode類型。

2. opcode

opcode的類型為zend_uchar,zend_uchar實際上就是unsigned char,此字段儲存的整形值即為op的編号,用來區分不同的op類型,opcode的可取值都被定義成了宏,可以在{PHPSRC}/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  
//......      

3. handler

op的執行句柄,其類型為opcode_handler_t,opcode_handler_t的類型定義為typedef int (ZEND_FASTCALL *opcode_handler_t) (ZEND_OPCODE_HANDLER_ARGS); 這個函數指針為op定義了執行方式,每一種opcode字段都對應一個種類的handler,比如opcode= 38 (ZEND_ASSIGN), 那麼其對應的handler對應的就是static int ZEND_FASTCALL  ZEND_ASSIGN_**種類的handler,根據op操作數類型的不同,可以确定到這個種類中的某一個具體的函數,比如如果$a = 1;這樣的代碼生成的op,操作數為const和cv,最後就能确定handler為函數ZEND_ASSIGN_SPEC_CV_CONST_HANDLER,這些handler函數都定義在{PHPSRC}/Zend/zend_vm_execute.h中,此檔案可以由一個PHP腳本生成,其中也定義了通過op來映射得到其hander的算法。

4. lineno

op對應源代碼檔案中的行号。

5. extended_value

擴充字段暫時不介紹

操作數znode簡介 

操作數字段是這個類型中比較重要的部分了,其中op1,op2,result三個操作數定義為znode類型,znode相關定義在此檔案中:

typedef struct _znode {  
    int op_type;  
    union {  
        zval constant;  
  
        zend_uint var;  
        zend_uint opline_num; /*  Needs to be signed */  
        zend_op_array *op_array;  
        zend_op *jmp_addr;  
        struct {  
            zend_uint var;  /* dummy */  
            zend_uint type;  
        } EA;  
    } u;  
} znode;      

znode類型中定義了兩個字段:

1. op_type

這個int類型的字段定義znode操作數的類型,這些類型的可取值的宏定義在此檔案中

#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 = 123; $b = "hello";這些代碼生成OP後,123和"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相當于一個索引号,通過索引号從緩存中取得相應的值。