摘要:結果和我們設想的一致。另外一個非常重要的工作是通過,設置對應的,代碼如下其中和之前的對應關系在中定義的。至此,整個抽象語法樹就編譯完成了,最終的結果為指令集,接下來就是在虛擬機上執行這些指令。參考資料源碼分析源碼研究之淺談虛擬機
grape
全部視頻:https://segmentfault.com/a/11...
原視頻地址:http://replay.xesv5.com/ll/24...
流程回顧上節課我們把$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。代碼:
我們來看一看到編譯ast生成op_array處的結果:
我們來看這個結果,vars是存我們的變量的,在這存的是a和b,并且last_Var=2只有兩個;T是temporary,T=2說明有兩個臨時變量。然后literals是存我們的字面量,再這里存的是2,3,last_literal=2表示現在有兩個字面量,接下來我們打印一下看是否和我們所解釋的一致。結果和我們設想的一致。另外,對于opcode的值又是如何呢?
return 1的做了什么?
我們發現,$a=2 op1是80,$b=3 op1為96,這是為什么呢?這之前我們說過這個問題,因為在棧中我們是分配一個大小為16的內存,所以需要增加16.第二個,我們知道result.constant的0和1代表字面量偏移量分別為0和1.
到這里都是之前學習過的內容,接下來繼續學習。繼續執行代碼:
我們發現在執行完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:
pass_two設置handler
我們發現,新增了一條指令,在代碼中就是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虛擬機上執行這些指令。參考資料:
【PHP7源碼分析】PHP7源碼研究之淺談Zend虛擬機
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/31534.html
摘要:此文用于匯總跟隨陳雷老師及團隊的視頻,學習源碼過程中的思考整理與心得體會,此文會不斷更新視頻傳送門每日學習記錄使用錄像設備記錄每天的學習源碼學習源碼學習內存管理筆記源碼學習內存管理筆記源碼學習內存管理筆記源碼學習基本變量筆記 此文用于匯總跟隨陳雷老師及團隊的視頻,學習源碼過程中的思考、整理與心得體會,此文會不斷更新 視頻傳送門:【每日學習記錄】使用錄像設備記錄每天的學習 PHP7...
摘要:在這里使用學而思網校的錄像設備,記錄每天學習的內容閆昌李樂階段李樂李樂李樂李樂李樂李樂馬運運李樂李樂李樂源碼集群閆昌源碼閆昌源碼主從復制李樂源碼施洪寶源碼施洪寶韓天 在這里使用學而思網校的錄像設備,記錄每天學習的內容: 2019-06-24 ~ 2019-06-28 06-27 nginx by 閆昌 06-26 nginx module by 李樂 06-25 nginx http ...
閱讀 2422·2021-11-11 11:01
閱讀 3296·2021-10-11 10:57
閱讀 2656·2021-09-30 09:46
閱讀 3497·2021-07-26 23:38
閱讀 1573·2019-08-29 12:22
閱讀 655·2019-08-29 11:28
閱讀 2359·2019-08-26 14:04
閱讀 3059·2019-08-23 18:34