grapephp
所有視頻:https://segmentfault.com/a/11...node
原視頻地址:http://replay.xesv5.com/ll/24...segmentfault
上節課咱們把$a=1這個過程編譯梳理了一遍,咱們瞭解到op1,op2,result,opcode的生成過程,下面咱們把整個過程來回顧一下。數組
static zend_op_array *zend_compile(int type) { zend_op_array *op_array = NULL; zend_bool original_in_compilation = CG(in_compilation); CG(in_compilation) = 1; CG(ast) = NULL; CG(ast_arena) = zend_arena_create(1024 * 32); //首先會分配內存 if (!zendparse()) { //zendparse(就是yyparse)(zend_language_parse.y) ==> 經過parser調用lexer,生成抽象語法樹ast_list,存到CG(ast);yyparse是經過bison編譯zend_language_parser.y生成 int last_lineno = CG(zend_lineno); zend_file_context original_file_context; zend_oparray_context original_oparray_context; zend_op_array *original_active_op_array = CG(active_op_array); op_array = emalloc(sizeof(zend_op_array)); init_op_array(op_array, type, INITIAL_OP_ARRAY_SIZE); //初始化oparray CG(active_op_array) = op_array; if (zend_ast_process) { zend_ast_process(CG(ast)); } zend_file_context_begin(&original_file_context); zend_oparray_context_begin(&original_oparray_context); zend_compile_top_stmt(CG(ast)); //編譯ast生成oparray CG(zend_lineno) = last_lineno; zend_emit_final_return(type == ZEND_USER_FUNCTION); //PHP中會加return 1,在此進行處理 op_array->line_start = 1; op_array->line_end = last_lineno; pass_two(op_array); //對於handler的處理 zend_oparray_context_end(&original_oparray_context); zend_file_context_end(&original_file_context); CG(active_op_array) = original_active_op_array; } zend_ast_destroy(CG(ast)); zend_arena_destroy(CG(ast_arena)); CG(in_compilation) = original_in_compilation; return op_array; }
大致流程爲:詞法分析->語法分析->編譯ast生成op_array->處理return 1->對於handler作處理
以上處理return 1 環節以前的文章中咱們都已經提到過,若是有不太理解的請翻閱以前的文章。接下來咱們gdb程序到環節return 1。代碼:函數
<?php $a = 2; $b = 3;
咱們來看一看到編譯ast生成op_array處的結果:源碼分析
咱們來看這個結果,vars是存咱們的變量的,在這存的是a和b,而且last_Var=2只有兩個;T是temporary,T=2說明有兩個臨時變量。而後literals是存咱們的字面量,再這裏存的是2,3,last_literal=2表示如今有兩個字面量,接下來咱們打印一下看是否和咱們所解釋的一致。學習
結果和咱們設想的一致。另外,對於opcode的值又是如何呢?ui
咱們發現,$a=2 op1是80,$b=3 op1爲96,這是爲何呢?這以前咱們說過這個問題,由於在棧中咱們是分配一個大小爲16的內存,因此須要增長16.第二個,咱們知道result.constant的0和1表明字面量偏移量分別爲0和1.
到這裏都是以前學習過的內容,接下來繼續學習。spa
繼續執行代碼:3d
咱們發如今執行完zend_emit_final_return這句以後咱們的op_array發生了變化。那麼爲何會發生這樣的變化呢?咱們在文章開頭有些到這個函數的做用是增長return 1結尾,那麼具體其中是怎麼來操做呢?咱們來看代碼:
void zend_emit_final_return(int return_one) /* {{{ */ { znode zn; zend_op *ret; zend_bool returns_reference = (CG(active_op_array)->fn_flags & ZEND_ACC_RETURN_REFERENCE) != 0; if (CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE && !(CG(active_op_array)->fn_flags & ZEND_ACC_GENERATOR)) { zend_emit_return_type_check(NULL, CG(active_op_array)->arg_info - 1, 1); } zn.op_type = IS_CONST; if (return_one) { ZVAL_LONG(&zn.u.constant, 1); //在gdb過程當中會走到這一步,把1賦值給zn.u.constant } else { ZVAL_NULL(&zn.u.constant); } ret = zend_emit_op(NULL, returns_reference ? ZEND_RETURN_BY_REF : ZEND_RETURN, &zn, NULL);//在此會像字面量中添加一個新的元素1 ret->extended_value = -1; } static zend_op *zend_emit_op(znode *result, zend_uchar opcode, znode *op1, znode *op2) /* {{{ */ { zend_op *opline = get_next_op(CG(active_op_array)); opline->opcode = opcode; if (op1 == NULL) { SET_UNUSED(opline->op1); } else { SET_NODE(opline->op1, op1); } if (op2 == NULL) { SET_UNUSED(opline->op2); } else { SET_NODE(opline->op2, op2); } zend_check_live_ranges(opline); if (result) { zend_make_var_result(result, opline); } return opline; } #define SET_NODE(target, src) do { \ target ## _type = (src)->op_type; \ if ((src)->op_type == IS_CONST) { \ target.constant = zend_add_literal(CG(active_op_array), &(src)->u.constant); \ //增長元素 } else { \ target = (src)->u.op; \ } \ } while (0)
咱們發現,gdb過程在這個函數中像literals裏邊又新增1個元素,咱們打印opcodes:
咱們發現,新增了一條指令,在代碼中就是return 1。
好的,到此,咱們發現,有三條指令,兩個變量,三個字面量。$a和$b的位置已經有了,字面量也有了,咱們發現handler仍是個空指針,接下來咱們看handler的生成。
咱們接着走,會走到pass_two這個函數,這個函數中,對opline指令集作了進一步的加工,最主要的工做是設置指令的handler,源碼以下:
ZEND_API int pass_two(zend_op_array *op_array) { /**代碼省略**/ while (opline < end) {//遍歷opline數組 if (opline->op1_type == IS_CONST) { ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, opline->op1); } else if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { opline->op1.var = (uint32_t)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL, op_array->last_var + opline->op1.var); } if (opline->op2_type == IS_CONST) { ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, opline->op2); } else if (opline->op2_type & (IS_VAR|IS_TMP_VAR)) { opline->op2.var = (uint32_t)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL, op_array->last_var + opline->op2.var); } if (opline->result_type & (IS_VAR|IS_TMP_VAR)) { opline->result.var = (uint32_t)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL, op_array->last_var + opline->result.var); } ZEND_VM_SET_OPCODE_HANDLER(opline); /**代碼省略**/ }
觀察代碼,該函數會對opline指令數組進行遍歷,他會處理以前生成的每一條opline,咱們拿IS_CONST來舉例,若是op1,op2的type爲IS_CONST,那麼將會調用ZEND_PASS_TWO_UPDATE_CONSTANT,代碼以下:
/* convert constant from compile-time to run-time */ # define ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, node) do { \ (node).zv = CT_CONSTANT_EX(op_array, (node).constant); \ } while (0) # define CT_CONSTANT_EX(op_array, num) \ ((op_array)->literals + (num))
咱們知道,對於IS_CONST的變量的字面量是存在與literals裏邊的,而constant是相對的下標,所以咱們能夠經過對於首地址偏移constant來進行轉換爲真實的偏移量。對於IS_VAR|IS_TMP_VAR類型的變量,會經過ZEND_CALL_VAR_NUM計算偏移量。
另一個很是重要的工做是經過ZEND_VM_SET_OPCODE_HANDLER(opline),設置opline對應的hanlder,代碼以下:
ZEND_API void zend_vm_set_opcode_handler(zend_op* op) { op->handler = zend_vm_get_opcode_handler(zend_user_opcodes[op->opcode], op); } static const void *zend_vm_get_opcode_handler(zend_uchar opcode, const zend_op* op) { return zend_vm_get_opcode_handler_ex(zend_spec_handlers[opcode], op); }
其中opcode和handler以前的對應關係在Zend/zend_vm_execute.h中定義的。opline數組通過一次遍歷後,handler也就設置完畢,設置後的opline數組如圖所示:
最後咱們打印下生成handler後的op_array:
咱們發現,handler已經被賦值。
至此,整個抽象語法樹就編譯完成了,最終的結果爲opline指令集,接下來就是在Zend虛擬機上執行這些指令。