php是解析型高級語言,事實上從zend內核的角度來看php就是一個普通的c程序,它有main函數,我們寫的php代碼是這個程序的輸入,然后經過內核的處理輸出結果,內核將php代碼”翻譯”為c程序可識別的過程就是php的編譯。
c程序在編譯時將一行行代碼編譯為機器碼,每一個操作都認為是一條機器指令,這些指令寫入到編譯后的二進制程序中,執(zhí)行的時候將二進制程序load進相應的內存區(qū)域(常量區(qū)、數據區(qū)、代碼區(qū))、分配運行棧,然后從代碼區(qū)起始位置開始執(zhí)行,這是c程序編譯、執(zhí)行的簡單過程。
同樣,php的編譯與普通的c程序類似,只是php代碼沒有編譯成機器碼,而是解析成了若干條opcode數組,每條opcode就是c里面普通的struct,含義對應c程序的機器指令,執(zhí)行的過程就是引擎依次執(zhí)行opcode,比如我們在php里定義一個變量:$a = 123;,最終到內核里執(zhí)行就是malloc一塊內存,然后把值寫進去。
在zend_compile.h文件中,opcode結構:
struct _zend_op {
const void *handler; //對應執(zhí)行的c語言function,即每條opcode都有一個c function處理
znode_op op1; //操作數1
znode_op op2; //操作數2
znode_op result; //返回值
uint32_t extended_value;
uint32_t lineno;
zend_uchar opcode; //opcode指令
zend_uchar op1_type; //操作數1類型
zend_uchar op2_type; //操作數2類型
zend_uchar result_type; //返回值類型
};所以php的解析過程任務就是將php代碼(通過詞法分析re2c,語法分析bison)轉化為opcode數組,代碼里的所有信息都保存在opcode中,然后將opcode數組交給zend引擎執(zhí)行,opcode就是內核具體執(zhí)行的命令,比如賦值、加減操作、函數調用等,每一條opcode都對應一個處理handle,這些handler是提前定義好的c函數。
struct _zend_op_array {
//common是普通函數或類成員方法對應的opcodes快速訪問時使用的字段
/* common elements */
zend_uchar type;
zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */
uint32_t fn_flags;
zend_string *function_name;
zend_class_entry *scope;
zend_function *prototype;
uint32_t num_args;
uint32_t required_num_args;
zend_arg_info *arg_info;
/* end of common elements */
uint32_t *refcount;
uint32_t last;
//opcode指令數組
zend_op *opcodes;
//php代碼里定義的變量數:op_type為is_cv的變量,不含is_tmp_var、is_var的
//編譯前此值為0,然后發(fā)現一個新變量這個值就加1
int last_var;
//臨時變量數:op_type為is_tmp_var、is_var的變量
uint32_t t;
//php變量名數組
zend_string vars;//這個數組在ast編譯期間配合last_var用來確定各個變量的編號,非常重要的一步操作
int last_live_range;
int last_try_catch;
zend_live_range *live_range;
zend_try_catch_element *try_catch_array;
//靜態(tài)變量符號表:通過static聲明的
/* static variables support */
hashtable *static_variables;
zend_string *filename;
uint32_t line_start;
uint32_t line_end;
zend_string *doc_comment;
uint32_t early_binding; /* the linked list of delayed declarations */
//字面量數量
int last_literal;
//字面量(常量)數組,這些都是在php代碼定義的一些值
zval *literals;
//運行時緩存數組大小
int cache_size;
//運行時緩存,主要用于緩存一些znode_op以便于快速獲取數據,后面單獨介紹這個機制
void run_time_cache;
void *reserved[zend_max_reserved_resources];
};opcode指令:即php代碼具體對應的處理動作,與二進制程序中的代碼段對應
字面量存儲:php代碼中定義的一些變量初始值、調用的函數名稱、類名稱、常量名稱等等稱之為字面量,這些值用于執(zhí)行時初始化變量、函數調用等等
變量分配情況:與字面量類似,這里指的是當前opcodes定義了多少變量、臨時變量,每個變量都有一個對應的編號,執(zhí)行初始化按照總的數目一次性分配zval,使用時也完全按照編號索引,而不是根據變量名索引
從php代碼到opcode是怎么實現的?
最容易想到的方式就是正則匹配,當然過程沒有這么簡單。php編譯過程包括詞法分析、語法分析,使用re2c、bison完成,舊的php版本直接生成了opcode,php7新增了抽象語法樹(ast),在語法分析階段生成ast,然后再生成opcode數組