摘要:引入閱讀前請先查看源碼學習的遍歷了解基本概念。此篇文章是針對于進行了實戰調試,以驗證源碼學習的遍歷中的一些論證。到此,編譯階段告一段落。參考資料源碼分析源碼研究之淺談虛擬機
grape
引入閱讀前請先查看【PHP源碼學習】2019-03-22 AST的遍歷了解基本概念。
此篇文章是針對于$a=1進行了gdb實戰調試,以驗證 【PHP源碼學習】2019-03-22 AST的遍歷 中的一些論證。
實驗代碼:
基本流程:- zend_compile_top_stmt(CG(ast)) - zend_compile_stmt(ast) - zend_compile_expr(&result, ast); - zend_compile_assign(result, ast); - zend_delayed_compile_begin(); - zend_delayed_compile_var(&var_node, var_ast, BP_VAR_W); - zend_compile_expr(&expr_node, expr_ast); - zend_delayed_compile_end(offset); - zend_emit_op(result, ZEND_ASSIGN, &var_node, &expr_node);GDB過程我們來gdb一下整個過程,首先,在zend_compile_top_stmt入口處打斷點:
gdb下來我們進入:
在zend_compile_stmt我們進入default的zend_compile_expr函數:
因為我們是賦值運算,我們此時走到了assign函數,接下來就是整個編譯過程中的重點部分:
打印var_ast->kind:
可以看出我們接下來要走的就是上圖的幾個函數,那么,這幾個函數又是怎么走的呢?我們接著看,首先,進入zend_delayed_compile_begin():
這個函數的作用是取棧頂,然后在函數結束后賦值給offest,那我們看看這個offest是什么?
等號左邊$a的編譯接下來進入$a的處理函數:
在這里記錄下我們處理$a過程中調用的函數:
zend_delayed_compile_var (result=0x7fffffffa580, ast=0x7ffff5e7f060, type=1) zend_compile_simple_var (result=0x7fffffffa580, ast=0x7ffff5e7f060, type=1, delayed=1) zend_try_compile_cv (result=0x7fffffffa580, ast=0x7ffff5e7f060) lookup_cv (op_array=0x7ffff5e75460, name=0x7ffff5e5e4e0)lookup_cv函數的作用是什么呢?首先我們先看lookcv的源代碼:
static int lookup_cv(zend_op_array *op_array, zend_string* name) /* {{{ */{ int i = 0; zend_ulong hash_value = zend_string_hash_val(name); while (i < op_array->last_var) { if (ZSTR_VAL(op_array->vars[i]) == ZSTR_VAL(name) || (ZSTR_H(op_array->vars[i]) == hash_value && ZSTR_LEN(op_array->vars[i]) == ZSTR_LEN(name) && memcmp(ZSTR_VAL(op_array->vars[i]), ZSTR_VAL(name), ZSTR_LEN(name)) == 0)) { zend_string_release(name); return (int)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL, i); } i++; } i = op_array->last_var; op_array->last_var++; if (op_array->last_var > CG(context).vars_size) { CG(context).vars_size += 16; /* FIXME */ op_array->vars = erealloc(op_array->vars, CG(context).vars_size * sizeof(zend_string*)); } op_array->vars[i] = zend_new_interned_string(name); return (int)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL, i); }我們發現,lookup_cv()函數它返回一個int類型的地址,是sizeof(zval)的整數倍,通過它可以得到每個變量的偏移量(80 + 16 * i),i是變量的編號。這樣就規定了運行時在棧上相對于zend_execute_data的偏移量,從而在棧上方便地存儲了$a這個變量。而$a在zend_op_array的vars數組上也存了一份,這樣如果后面又用到了$a的話,直接去zend_op_array的vars數組中查找找,如果存在,那么直接使用之前的編號i,如果不存在則按序分配一個編號,然后再插入zend_op_array的vars數組,節省了分配編號的時間。
另外,在zend_try_compile_cv這個函數中對于result進行賦值,那么我們打印下result的值:我們發現,op_type=16(IS_CV),u.op.var=80
到此我們總結一下$a這個過程,核心函數lookupcv,在lookupcv中我們將變量存儲在op_array->vars中,并且返回一個int型整數,代表著偏移量。隨后在zend_try_compile_cv中將op_type和u.op.var賦值給znode *result,具體編譯示例圖如下圖所示:到此,$a即左子節點就結束了。
等號右邊1的編譯接下來我們來進行右子樹的處理,gdb如圖:
右子樹的處理比較簡單,其調用函數為:
zend_compile_expr ZVAL_COPY(z, v)重點函數在于ZVAL_COPY這個宏,首先我們看gdb中到達了這個宏:
然后我們繼續分析這個宏的作用,老規矩,先貼源碼:
#define ZVAL_COPY(z, v) do { zval *_z1 = (z); const zval *_z2 = (v); zend_refcounted *_gc = Z_COUNTED_P(_z2); uint32_t _t = Z_TYPE_INFO_P(_z2); ZVAL_COPY_VALUE_EX(_z1, _z2, _gc, _t); if ((_t & (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT)) != 0) { GC_REFCOUNT(_gc)++; } } while (0)它的功能是把一個zval(v)拷貝到另外一個zval(z)中,具體的一些分析請查看上一篇文章:【PHP源碼學習】2019-03-22 AST的遍歷-1
在進行復制完之后我們對于result進行打印:我們可以看到已經完成了賦值。
最后進行了 result->op_type = IS_CONST,op_type的賦值:重新打印result即最終的結果:
根據assign以及op1,op2生成opline
至此,$a和1都分別存在了兩個znode中。下邊開始生成指令。我們進入到zend_emit_op函數中,這個函數中會生成opcode:
首先我們看這個函數所執行的所有指令:
其中,set_node出現的頻次很高,我們來看一下它究竟有什么用:
#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)從代碼中可以看出,對于操作數1,會將編譯過程中臨時的結構znode傳遞給zend_op中,對于操作數2,因為是常量(IS_CONST),會調用zend_add_literal將其插入到op_array->literals中。
接下來我們進行返回值的設置,此時會調用zend_make_var_result這個函數:static inline void zend_make_var_result(znode *result, zend_op *opline) /* {{{ */ { opline->result_type = IS_VAR; //返回值的類型設置為IS_VAR opline->result.var = get_temporary_variable(CG(active_op_array)); //這個是返回值的編號,對應T位置 GET_NODE(result, opline->result); } static uint32_t get_temporary_variable(zend_op_array *op_array) /* {{{ */ { return (uint32_t)op_array->T++; }返回值的類型為IS_VAR,result.var為T的值
最后打印opline看一下最終的結果:,下面我們給出Assign操作對應的指令圖,如圖所示:
從圖中可以看出,生成的opline中opcode等于38;op1的類型為IS_CV,op1.var對應的是vm_stack上的偏移量;op2的類型為IS_CONST,op2.constant對應的是op_array中literals數組的下標;result的類型為IS_VAR,result.var對應的是T的值;此時handler的值為空。
到此,編譯階段告一段落。參考資料:
【PHP7源碼分析】PHP7源碼研究之淺談Zend虛擬機
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/31488.html
摘要:在中,源代碼首先將進行詞法分析,將源代碼切割為多個字符串單元,分割后的字符串稱之為。圖以為例解釋型語言的執行示意圖第步源碼通過詞法分析得到第步基于語法分析器生成抽象語法樹第步抽象語法樹轉換為指令集合,解釋執行。 順風車運營研發團隊 李志 發表在程序人生 公眾號我們常用的高級語言有很多種,比較出名的有CC++、Python、 PHP、Go、Pascal等。而這些語言根據運行的方式不同,...
摘要:中詞法語法分析,生成抽象語法樹,然后編譯成及被執行均由虛擬機完成。通常情況下這部分是可選部分,主要為便于程序的讀寫方便而使用。指令虛擬機的指令稱為,每條指令對應一個。 作者 陳雷編程語言的虛擬機是一種可以運行中間語言的程序。中間語言是抽象出的指令集,由原生語言編譯而成,作為虛擬機執行階段的輸入。很多語言都實現了自己的虛擬機,比如Java、C#和Lua。PHP語言也有自己的虛擬機,稱為Z...
摘要:此文用于匯總跟隨陳雷老師及團隊的視頻,學習源碼過程中的思考整理與心得體會,此文會不斷更新視頻傳送門每日學習記錄使用錄像設備記錄每天的學習源碼學習源碼學習內存管理筆記源碼學習內存管理筆記源碼學習內存管理筆記源碼學習基本變量筆記 此文用于匯總跟隨陳雷老師及團隊的視頻,學習源碼過程中的思考、整理與心得體會,此文會不斷更新 視頻傳送門:【每日學習記錄】使用錄像設備記錄每天的學習 PHP7...
閱讀 1136·2019-08-30 12:44
閱讀 642·2019-08-29 13:03
閱讀 2551·2019-08-28 18:15
閱讀 2419·2019-08-26 10:41
閱讀 3082·2019-08-26 10:28
閱讀 3029·2019-08-23 16:54
閱讀 1983·2019-08-23 15:16
閱讀 802·2019-08-23 14:55