1.PHP代碼的編譯
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處理函數
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.抽象文法樹->Opcodes
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、1、2、3...依次遞增的,但是實際使用中并不是直接用的這個下标,而是轉化成了記憶體偏移量offset,這個是ZEND_CALL_VAR_NUM宏處理的,是以變量偏移量實際是96、112、128...遞增的
pass_two()主要有兩個重要操作:
1.将IS_CONST、IS_VAR、IS_TMP_VAR類型的操作數、傳回值轉化為記憶體偏移量
2.另外一個重要操作就是設定各指令的處理handler