運行一段PHP代碼主要有兩個階段:編譯和執行。 當然編譯過程中還包括詞法分析語法分析不同階段和細節,這裏我們將其作爲一個整體。在這兩個階段之間,PHP代碼會被編譯成op code,可以將其認爲是引擎的一箇中間語言,編輯階段把PHP源碼生成op code,然後在執行階段執行這些op code。這篇文章將簡單的介紹op code。
PHP代碼編譯之後會生成許多的op,每一個op都是一個zend_op類型的c變量。相關的定義可以在{PHPSRC}/Zend/zend_compile.h中看到:
02 |
opcode_handler_t handler;
|
11 |
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中看到這些宏的定義,類似如下:
11 |
#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相關定義在此文件中:
01 |
typedef
struct _znode { |
08 |
zend_op_array *op_array;
|
znode類型中定義了兩個字段:
1. op_type
這個int類型的字段定義znode操作數的類型,這些類型的可取值的宏定義在此文件中
1 |
#define IS_CONST (1<<0)
|
2 |
#define IS_TMP_VAR (1<<1)
|
4 |
#define IS_UNUSED (1<<3) /* Unused variable */
|
5 |
#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相當於一個索引號,通過索引號從緩存中取得相應的值。
2. u
此字段爲一個聯合體,根據op_type的不同,u取不同的值。比如op_type=IS_CONST的時候,u中的constant保存的就是操作數對應的zval結構。例如$a=123時,123這個操作數中,u中的constant是一個IS_LONG類型的zval,其值lval爲123。