摘要:前言本文從函數(shù)定義的語法規(guī)則開始,簡要介紹解釋器如何編譯函數(shù)定義函數(shù)對應(yīng)的節(jié)點(diǎn)為了看起來清楚一些,我們將語法規(guī)則定義與語法動作分開根據(jù)語法動作,這條函數(shù)定義規(guī)則會創(chuàng)建一個類型的結(jié)點(diǎn),我們來看看方法是一個通用的方法,通
前言
本文從函數(shù)定義的語法規(guī)則開始,簡要介紹 PHP 解釋器如何 "編譯" 函數(shù)定義
函數(shù)對應(yīng)的 AST 節(jié)點(diǎn)為了看起來清楚一些,我們將 語法規(guī)則定義 與 語法動作分開:
// zend_language_parser.y top_statement: function_declaration_statement function_declaration_statement: function returns_ref T_STRING backup_doc_comment "(" parameter_list ")" return_type backup_fn_flags "{" inner_statement_list "}" backup_fn_flags { $$ = zend_ast_create_decl(ZEND_AST_FUNC_DECL, $2 | $13, $1, $4, zend_ast_get_str($3), $6, NULL, $11, $8); CG(extra_fn_flags) = $9; }
根據(jù)語法動作,這條函數(shù)定義規(guī)則會創(chuàng)建一個 ZEND_AST_FUNC_DECL 類型的 AST 結(jié)點(diǎn),我們來看看 zend_ast_create_create_decl 方法:
// zend_ast.c ZEND_API zend_ast *zend_ast_create_decl( zend_ast_kind kind, uint32_t flags, uint32_t start_lineno, zend_string *doc_comment, zend_string *name, zend_ast *child0, zend_ast *child1, zend_ast *child2, zend_ast *child3) { zend_ast_decl *ast; ast = zend_ast_alloc(sizeof(zend_ast_decl)); ast->kind = kind; ast->attr = 0; ast->start_lineno = start_lineno; ast->end_lineno = CG(zend_lineno); ast->flags = flags; ast->lex_pos = LANG_SCNG(yy_text); ast->doc_comment = doc_comment; ast->name = name; ast->child[0] = child0; ast->child[1] = child1; ast->child[2] = child2; ast->child[3] = child3; return (zend_ast *) ast; }
zend_ast_create_decl 是一個通用的方法,通過 kind 參數(shù)區(qū)分不同類型的定義
同理,參數(shù) child0, child1 .etc 的命名也是很 "范化" 的
編譯 ASTzend_compile_func_decl 用于編譯函數(shù)定義 AST,由于函數(shù)代碼相對比較長,我們分塊開分析
// zend_compile.c void zend_compile_func_decl(znode *result, zend_ast *ast) { // 獲取 AST 子節(jié)點(diǎn) zend_ast_decl *decl = (zend_ast_decl *) ast; zend_ast *params_ast = decl->child[0]; zend_ast *uses_ast = decl->child[1]; zend_ast *stmt_ast = decl->child[2]; zend_ast *return_type_ast = decl->child[3]; zend_bool is_method = decl->kind == ZEND_AST_METHOD; // 將 CG 的 active_op_array(字節(jié)碼數(shù)組) 保存在 orig_op_array 中,因?yàn)槊總€函數(shù)會有自己的 op_array zend_op_array *orig_op_array = CG(active_op_array); // 新建 op_array zend_op_array *op_array = zend_arena_alloc(&CG(arena), sizeof(zend_op_array)); zend_oparray_context orig_oparray_context; // 初始化新建的 op_array init_op_array(op_array, ZEND_USER_FUNCTION, INITIAL_OP_ARRAY_SIZE); op_array->fn_flags |= (orig_op_array->fn_flags & ZEND_ACC_STRICT_TYPES); op_array->fn_flags |= decl->flags; op_array->line_start = decl->start_lineno; op_array->line_end = decl->end_lineno; if (decl->doc_comment) { op_array->doc_comment = zend_string_copy(decl->doc_comment); } if (decl->kind == ZEND_AST_CLOSURE) { op_array->fn_flags |= ZEND_ACC_CLOSURE; } }
這里有幾個地方比較有意思:
函數(shù)參數(shù) ast 的類型是 zend_ast,但是被強(qiáng)制轉(zhuǎn)換成了 zend_ast_decl,這個 zend_ast_decl 結(jié)構(gòu)體和 zend_ast 結(jié)構(gòu)體在內(nèi)存布局上有者相同的 "頭部",C 語言經(jīng)常使用這種技巧類實(shí)現(xiàn)類似 面向?qū)ο罄锩?繼承 的概念
這里又遇到了 CG,參考之前的系列文章,CG 是解釋器在 編譯代碼 過程中用于保存編譯上下文的一個 "對象",當(dāng)遇到函數(shù)定義時,解釋器會把當(dāng)前已經(jīng)生成的 active_op_array 保存起來,為函數(shù)定義新建一個 op_array,至于這個新建的 op_array 保存在哪?請見下文分解
我們接著看源代碼:
// zend_compile_func_decl @ zend_compile.c if (is_method) { zend_bool has_body = stmt_ast != NULL; zend_begin_method_decl(op_array, decl->name, has_body); } else { zend_begin_func_decl(result, op_array, decl); if (uses_ast) { zend_compile_closure_binding(result, uses_ast); } } CG(active_op_array) = op_array; zend_oparray_context_begin(&orig_oparray_context);
函數(shù)定義有兩種,全局函數(shù)以及類里面的"方法",is_method 標(biāo)志區(qū)分這兩種情況,如果是方法定義就調(diào)用 zend_begin_method_decl,這里先略過不表
zend_begin_func_decl 函數(shù)用于在編譯之前做一些準(zhǔn)備工作,注意到這里傳入了新建的 op_array
下面是 zend_begin_func_decl 函數(shù)的實(shí)現(xiàn),我們只保留和函數(shù) op_array 相關(guān)的代碼
static void zend_begin_func_decl(...) { ... key = zend_build_runtime_definition_key(lcname, decl->lex_pos); // 將 函數(shù) key,op_array 存儲在 CG 的 function_table 中 ?。?! zend_hash_update_ptr(CG(function_table), key, op_array); if (op_array->fn_flags & ZEND_ACC_CLOSURE) { ... } else { // 在當(dāng)前 active_op_array 中生成一條函數(shù)定義指令 !??! opline = get_next_op(CG(active_op_array)); opline->opcode = ZEND_DECLARE_FUNCTION; opline->op1_type = IS_CONST; ... } }
現(xiàn)在明白了,原來函數(shù)的 op_array 是保存在 CG 的 function_table 中,這里還有一個有意思的地方,php 生成了一條函數(shù)定義指令,這一點(diǎn)正是 動態(tài)腳本 語言和 靜態(tài)類型語言(Java)非常不同的地方!靜態(tài)類型的語言不需要執(zhí)行代碼來添加函數(shù) or 方法,因?yàn)樗鼈冊诖a編譯階段就已經(jīng)確定了,當(dāng)然也就缺少了一點(diǎn)靈活性
我們回歸主線,接著看 zend_compile_func_decl 代碼
// 上面已經(jīng)將 CG(active_op_array)暫存起來了,所以這里將 CG(active_op_array) 設(shè)置成 函數(shù)的 op_array // 函數(shù)內(nèi)部的語句的字節(jié)碼都會保存在 CG(active_op_array) 中 ?。?! CG(active_op_array) = op_array; zend_oparray_context_begin(&orig_oparray_context); if (CG(compiler_options) & ZEND_COMPILE_EXTENDED_INFO) { zend_op *opline_ext = zend_emit_op(NULL, ZEND_EXT_NOP, NULL, NULL); opline_ext->lineno = decl->start_lineno; } { /* Push a separator to the loop variable stack */ zend_loop_var dummy_var; dummy_var.opcode = ZEND_RETURN; zend_stack_push(&CG(loop_var_stack), (void *) &dummy_var); } // 編譯參數(shù) zend_compile_params(params_ast, return_type_ast); if (CG(active_op_array)->fn_flags & ZEND_ACC_GENERATOR) { zend_mark_function_as_generator(); zend_emit_op(NULL, ZEND_GENERATOR_CREATE, NULL, NULL); } if (uses_ast) { zend_compile_closure_uses(uses_ast); } // 編譯函數(shù)內(nèi)部語句 zend_compile_stmt(stmt_ast); if (is_method) { zend_check_magic_method_implementation( CG(active_class_entry), (zend_function *) op_array, E_COMPILE_ERROR); } /* put the implicit return on the really last line */ CG(zend_lineno) = decl->end_lineno; zend_do_extended_info(); zend_emit_final_return(0); pass_two(CG(active_op_array)); zend_oparray_context_end(&orig_oparray_context); /* Pop the loop variable stack separator */ zend_stack_del_top(&CG(loop_var_stack)); CG(active_op_array) = orig_op_array;總結(jié)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/22338.html
摘要:前言本文簡要介紹虛擬機(jī)解釋執(zhí)行字節(jié)碼的基本邏輯以及相關(guān)的數(shù)據(jù)結(jié)構(gòu),關(guān)于源代碼的下載,編譯,調(diào)試可以參考之前的系列文章我們來看看執(zhí)行一個簡單的腳本的調(diào)用棧由于是執(zhí)行腳本文件,所以調(diào)用了函數(shù),最終調(diào)用函數(shù)和其它語言編寫的系統(tǒng)軟件類似,函數(shù)中 前言 本文簡要介紹 zend 虛擬機(jī)解釋執(zhí)行字節(jié)碼的基本邏輯以及相關(guān)的數(shù)據(jù)結(jié)構(gòu),關(guān)于 PHP 源代碼的下載,編譯,調(diào)試可以參考之前的系列文章 exec...
摘要:前言字節(jié)碼生成編譯的代碼主要集中在,文件中包含大量的函數(shù),基本上一個函數(shù)對應(yīng)語法規(guī)則文件一個非終結(jié)符,函數(shù)是所有函數(shù)的入口數(shù)據(jù)結(jié)構(gòu)結(jié)構(gòu)體是字節(jié)碼抽象結(jié)構(gòu)體并沒有像名字那樣簡單,它包含了大量的字段供虛擬機(jī)在運(yùn)行時使用一如既往的簡單,直觀,相比 前言 字節(jié)碼生成(編譯)的代碼主要集中在 zend_compile.c ,文件中包含大量的 zend_compile_xxx 函數(shù),基本上一個函數(shù)...
摘要:前言使用和進(jìn)行語法分析和詞法分析,本文以語法定義文件為起點(diǎn),使用等命令行工具搜索相關(guān)源碼,以此來展示探索語法分析源碼思路語法定義文件在源代碼根目錄下通過命令查找文件我們找到了文件,里面定義了腳本的語法語法分析樹節(jié)點(diǎn)類型在查看具體的語法規(guī)則 前言 php 使用 lex 和 bison 進(jìn)行語法分析和詞法分析,本文以 bison 語法定義文件為起點(diǎn),使用 find, grep 等命令行工具...
摘要:前言本文通過分析這個語句的編譯和執(zhí)行來窺探解釋執(zhí)行邏輯準(zhǔn)備參考之前的系列文章,在環(huán)境下下載,編譯源代碼將代碼導(dǎo)入中編輯運(yùn)行選項(xiàng),增加運(yùn)行參數(shù)設(shè)置斷點(diǎn)開始調(diào)試是一個測試腳本,放在目錄下,中只包含一條簡單的賦值語句調(diào)用堆棧參考之前的系列文章 前言 本文通過分析 $a=1 這個 PHP 語句的編譯和執(zhí)行來窺探 php-cli 解釋執(zhí)行邏輯 準(zhǔn)備 參考之前的系列文章,在 ubuntu 環(huán)境下...
摘要:前言函數(shù)默認(rèn)構(gòu)建目標(biāo)為,相關(guān)代碼在目錄下,文件中能夠找到入口函數(shù),大概流程如下命令行參數(shù)處理初始化清理工作語言系統(tǒng)編程常用手法,通過中聲明函數(shù)指針類型的字段來實(shí)現(xiàn)類似面向?qū)ο笾谐橄箢惖母拍?,在文件中可以找到該結(jié)構(gòu)體的定義,這里只列出部分 前言 php cli main 函數(shù) configure & make 默認(rèn)構(gòu)建目標(biāo)為 php-cli,相關(guān)代碼在 sapi/cli 目錄下,php_...
閱讀 2821·2021-11-24 09:39
閱讀 3392·2021-11-19 09:40
閱讀 2263·2021-11-17 09:33
閱讀 3752·2021-10-08 10:04
閱讀 3042·2021-09-26 09:55
閱讀 1668·2021-09-22 15:26
閱讀 931·2021-09-10 10:51
閱讀 3130·2019-08-30 15:44