# zend執行過程
## EG變量
executor_globals是一個全局變量,存儲著許多信息(當前上下文、符號表、函數/類/常量表、堆棧等),EG宏就是用于訪問executor_globals的某個成員.
```c
//zend_global.h/_zend_executor_globals
struct _zend_executor_globals {
...
zend_array symbol_table; /* PHP全局變量表:$_GET,$_POST等 main symbol table */
HashTable included_files; /* 已經引入的腳本 files already included */
JMP_BUF *bailout; /* try-catch保存的catch跳轉位置 */
int error_reporting;
int exit_status;
HashTable *function_table; /* 全部已編譯的function哈希表,包括內部函數,用戶自定義函數,函數調用將從這里查找 function symbol table */
HashTable *class_table; /* 全部已編譯的class哈希表,new class 時從此查找 class table */
HashTable *zend_constants; /* 常量符號表 constants table */
zval *vm_stack_top;//棧內存池剩余可用內存起始位置
zval *vm_stack_end;//棧內存池結束位置
zend_vm_stack vm_stack; /* 運行棧內存池,一塊空白的內存,用于分配PHP執行期間的一些數據結構(zend_execute),局部變量從這里分配 */
struct _zend_execute_data *current_execute_data; /* 指向當前正在執行的運行棧,函數調用就是分配一個新的zend_execute_data,然后將EG(current_execute_data)指向新的結構繼續執行,調用完畢再還原回去,類似匯編call,ret指令的作用 */
zend_class_entry *fake_scope; /* used to avoid checks accessing properties */
...
HashTable *in_autoload; /* 在類的自動加載過程中會使用到 */
zend_function *autoload_func; /* 自動加載回調函數:__autoload() */
zend_bool full_tables_cleanup;
...
HashTable regular_list;
HashTable persistent_list; /* 持久化符號表,request請求結束后不釋放可以跨request共享,在php_module_shutdown()階段清理,*/
...
};
//zend/zend_globals_macros.h
# define EG(v) (executor_globals.v)
```
# zend_compile.h/_zend_execute_data
zend_execute_data是執行過程中最核心的一個結構,每次函數的調用、include/require、eval等都會生成一個新的結構,它表示當前的作用域、代碼的執行位置以及局部變量的分配等等,等同于機器碼執行過程中stack的角色.
```c
//64位機器上,占80個字節
struct _zend_execute_data {
const zend_op *opline;/* 指向當前執行的opcode,初始時指向zend_op_array起始位置executed opline */
zend_execute_data *call;/* 當前正在調用的子函數 current call */
zval *return_value;//返回值指針
zend_function *func;/* 當前執行的函數自身(非函數調用時為空)executed function */
zval This;/* 這個值并不僅僅是面向對象的this,還有另外兩個值也通過這個記錄:call_info + num_args,分別存在zval.u1.reserved、zval.u2.num_args */
zend_execute_data *prev_execute_data;//調用時指向父級調用位置作用空間
zend_array *symbol_table;//全局變量符號表
#if ZEND_EX_USE_RUN_TIME_CACHE
void **run_time_cache;/* cache op_array->run_time_cache */
#endif
#if ZEND_EX_USE_LITERALS
zval *literals;/* 字面量數組,與func.op_array->literals相同 //cache op_array->literals*/
#endif
};
```

### 運行棧幀布局:
#### linux程序運行內存分配

#### C程序運行棧

### ZEND分配運行棧
```c
//Zend/zend_execute.c : 2094
/*
* 棧幀布局,所有運行棧空間是同時分配的 / Stack Frame Layout (the whole stack frame is allocated at once)
* ==================
*
* +=========================================================+
* EG(current_execute_data) -> | zend_execute_data |
* +---------------------------------------------------------+
* EX_CV_NUM(0) ---------> | VAR[0] = ARG[1] | 參數(arguments)
* | ... |
* | VAR[op_array->num_args-1] = ARG[N] |
* | VAR[num_args] = CV[num_args] | 局部變量(remaining CVs)
* | ... |
* | VAR[op_array->last_var-1] = CV[last_var-1] |
* | VAR[op_array->last_var] = TMP[0] | 臨時變量(TMP/VARs)
* | ... |
* | VAR[op_array->last_var+op_array->T-1] = TMP[T] |
* | ARG[N+1] (extra_args) | 其余參數(extra arguments)
* | ... |
* +---------------------------------------------------------+
*/
```
#### 變量類型:
```c
// Zend/zend_compile.h
#define IS_CONST (1<<0) //1
#define IS_TMP_VAR (1<<1) //2
#define IS_VAR (1<<2) //4
#define IS_UNUSED (1<<3) //8
#define IS_CV (1<<4) //16
```
* IS_CONST:字面量,編譯時就可確定且不會改變的值,比如:$a = "hello~",其中字符串"hello~"就是常量
* IS_TMP_VAR:臨時變量,比如:$a = "hello~" . time(),其中"hello~" . time()的值類型就是IS_TMP_VAR,再比如:$a = "123" + $b,"123" + $b的結果類型也是IS_TMP_VAR,從這兩個例子可以猜測,臨時變量多是執行期間其它類型組合現生成的一個中間值,由于它是現生成的,所以把IS_TMP_VAR賦值給IS_CV變量時不會增加其引用計數
* IS_VAR:PHP變量,這個很容易認為是PHP腳本里的變量,其實不是,這里PHP變量的含義可以這樣理解:PHP變量是沒有顯式的在PHP腳本中定義的,不是直接在代碼通過$var_name定義的.這個類型最常見的例子是PHP函數的返回值,再如$a[0]數組這種,它取出的值也是IS_VAR,再比如$$a這種
* IS_UNUSED:表示操作數沒有用
* IS_CV:PHP腳本變量,即腳本里通過$var_name定義的變量,這些變量是編譯階段確定的,所以是compile variable,
除了分配execute_data的存儲空間外,還分配了CV(compiled variable,即PHP變量)、TMP_VAR(臨時變量,例如執行if (!$a) echo 'a';,就需要一個臨時變量來存儲!$a的結果)的存儲空間.
EX宏是用于訪問execute_data的成員
堆棧的結構體,堆棧的設計跟PHP 5類似:
struct _zend_vm_stack {
zval *top; /* 指向堆棧的頂端 */
zval *end; /* 指向堆棧的底端 */
zend_vm_stack prev; /* 指向上一個堆棧,當前堆棧剩余空間不足時,會向內存管理器申請新的內存創建新的堆棧 */
};
#### opcode執行時使用的寄存器優化
```c
define ZEND_VM_FP_GLOBAL_REG "%r14" //棧幀指針(frame pointer)寄存器
define ZEND_VM_IP_GLOBAL_REG "%r15" //指令指針寄存器
register const zend_op* volatile opline __asm__(ZEND_VM_IP_GLOBAL_REG);
register zend_execute_data* volatile execute_data __asm__(ZEND_VM_FP_GLOBAL_REG);
```
## 參考資料:
https://yangxikun.github.io/php/2016/11/04/php-7-func-call.html
https://github.com/pangudashu/php7-internal/
https://nikic.github.io/2017/04/14/PHP-7-Virtual-machine.html