運作一段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相當于一個索引号,通過索引号從緩存中取得相應的值。