1.PHP代碼的編譯node
PHP的解析過程任務就是將PHP代碼轉化爲opcode數組,代碼裏的全部信息都保存在opcode數組中,而後將opcode數組交給zend引擎執行,opcode就是內核具體執行的命令,好比賦值、加減操做、函數調用等,每一條opcode都對應一個處理handle,這些handler是提早定義好的C函數。
2.PHP代碼->抽象語法樹(AST)數組
PHP使用re2c、bison完成這個階段的工做: re2c: 詞法分析器,將輸入分割爲一個個有意義的詞塊,稱爲token bison: 語法分析器,肯定詞法分析器分割出的token是如何彼此關聯的
詞法、語法解析過程 1.yyparse(zendparse)調用yylex(zendlex),當讀取到一個合法的token時,返回值爲token類型 2.yylex調用lex_scan讀取合法的token值 3.yyparse將token類型與token值構造抽象語法樹,最後將根節點保存到CG(compiler_globals ,Zend編譯器相關的全局變量)的ast中
3.AST節點結構緩存
typedef struct _zend_ast zend_ast; //普通節點類型 struct _zend_ast { zend_ast_kind kind; //節點類型 zend_ast_attr attr; //節點附加屬性 uint32_t lineno; //行號 zend_ast *child[1]; //子節點數組 }; //普通節點類型,但有子節點的個數 typedef struct _zend_ast_list { zend_ast_kind kind; //節點類型 zend_ast_attr attr; //節點附加屬性 uint32_t lineno; //行號 uint32_t children; //子節點數量 zend_ast *child[1];//子節點數組 } zend_ast_list; //函數、類的ast節點結構 typedef struct _zend_ast_decl { zend_ast_kind kind; //節點類型 zend_ast_attr attr; //節點附加屬性 uint32_t start_lineno; //開始行號 uint32_t end_lineno; //結束行號 uint32_t flags; unsigned char *lex_pos; zend_string *doc_comment; zend_string *name; zend_ast *child[4]; //類中會將繼承的父類、實現的接口以及類中的語句解析保存在child中 } zend_ast_decl;
實例: $a = 123; $b = "hi~"; echo $a,$b;
4.zend_op_array函數
struct _zend_op_array { zend_op *opcodes; //opcode指令數組 int last_var; //編譯前此值爲0,而後發現一個新變量這個值就加1(op_type爲IS_CV的變量) uint32_t T;//臨時變量數:op_type爲IS_TMP_VAR、IS_VAR的變量 int last_literal; //字面量數量 zval *literals; //字面量(常量)數組,這些都是在PHP代碼定義的一些值 zend_string **vars; //PHP變量名數組,這個數組在ast編譯期間配合last_var用來肯定各個變量的編號 HashTable *static_variables;//靜態變量符號表:經過static聲明的 int cache_size; //運行時緩存數組大小 void **run_time_cache; //運行時緩存,主要用於緩存一些znode_op以便於快速獲取數據 };
//opcode指令結構 struct _zend_op { const void *handler; //指令執行handler 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; //返回值類型 }; //操做數類型 #define IS_CONST (1<<0) //1:字面量,編譯時就可肯定且不會改變的值,好比:$a = "hello~",其中字符串"hello~"就是常量 #define IS_TMP_VAR (1<<1) //2:臨時變量,好比:$a = "hello~" . time(),其中"hello~" . time()的值類型就是IS_TMP_VAR #define IS_VAR (1<<2) //4:PHP變量是沒有顯式的在PHP腳本中定義的,不是直接在代碼經過$var_name定義的。這個類型最多見的例子是PHP函數的返回值 #define IS_UNUSED (1<<3) //8:表示操做數沒有用 #define IS_CV (1<<4) //16:PHP腳本變量,即腳本里經過$var_name定義的變量,這些變量是編譯階段肯定的
5.handler處理函數ui
handler爲每條opcode對應的C語言編寫的處理過程,全部opcode對應的處理過程定義在zend_vm_def.h中,opcode的處理過程有三種不一樣的提供形式:CALL、SWITCH、GOTO,默認方式爲CALL
CALL:把每種opcode負責的工做封裝成不一樣的function,而後執行器循環調用執行 SWITCH:把全部的處理方式寫到一個switch下,而後經過case不一樣的opcode執行具體的操做 GOTO:把全部opcode的處理方式經過C語言裏面的label標籤區分開,而後執行器執行的時候goto到相應的位置處理
6.抽象語法樹->Opcodesspa
void zend_compile_top_stmt(zend_ast *ast){ .... if (ast->kind == ZEND_AST_STMT_LIST) { //第一次進來必定是這種類型 zend_ast_list *list = zend_ast_get_list(ast); uint32_t i; for (i = 0; i < list->children; ++i) { zend_compile_top_stmt(list->child[i]);//list各child語句相互獨立,遞歸編譯 } return; } //各語句編譯入口 zend_compile_stmt(ast); .... } 1.zend_compile_top_stmt接收語法樹,首先判斷節點類型是否爲ZEND_AST_STMT_LIST(表示當前節點下 有多個獨立的節點),如果則進行遞歸 2.當遞歸結束後,調用zend_compile_stmt進行編譯成opcodes
實例: $a = 123; $b = "hi~"; echo $a,$b;
注意:這裏變量的編號從0、一、二、3...依次遞增的,可是實際使用中並非直接用的這個下標,而是轉化成了內存偏移量offset,這個是ZEND_CALL_VAR_NUM宏處理的,因此變量偏移量實際是9六、1十二、128...遞增的
pass_two()主要有兩個重要操做: 1.將IS_CONST、IS_VAR、IS_TMP_VAR類型的操做數、返回值轉化爲內存偏移量 2.另一個重要操做就是設置各指令的處理handler