本文從函數定義的語法規則開始,簡要介紹 PHP 解釋器如何 "編譯" 函數定義php
爲了看起來清楚一些,咱們將 語法規則定義 與 語法動做分開:node
// 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; }
根據語法動做,這條函數定義規則會建立一個 ZEND_AST_FUNC_DECL 類型的 AST 結點,咱們來看看 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 參數區分不一樣類型的定義函數
同理,參數 child0, child1 .etc 的命名也是很 "範化" 的oop
zend_compile_func_decl 用於編譯函數定義 AST,因爲函數代碼相對比較長,咱們分塊開分析佈局
// zend_compile.c void zend_compile_func_decl(znode *result, zend_ast *ast) { // 獲取 AST 子節點 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(字節碼數組) 保存在 orig_op_array 中,由於每一個函數會有本身的 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; } }
這裏有幾個地方比較有意思:ui
函數參數 ast 的類型是 zend_ast,可是被強制轉換成了 zend_ast_decl,這個 zend_ast_decl 結構體和 zend_ast 結構體在內存佈局上有者相同的 "頭部",C 語言常常使用這種技巧類實現相似 面向對象裏面 繼承 的概念code
這裏又遇到了 CG,參考以前的系列文章,CG 是解釋器在 編譯代碼 過程當中用於保存編譯上下文的一個 "對象",當遇到函數定義時,解釋器會把當前已經生成的 active_op_array 保存起來,爲函數定義新建一個 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);
函數定義有兩種,全局函數以及類裏面的"方法",is_method 標誌區分這兩種狀況,若是是方法定義就調用 zend_begin_method_decl,這裏先略過不表
zend_begin_func_decl 函數用於在編譯以前作一些準備工做,注意到這裏傳入了新建的 op_array
下面是 zend_begin_func_decl 函數的實現,咱們只保留和函數 op_array 相關的代碼
static void zend_begin_func_decl(...) { ... key = zend_build_runtime_definition_key(lcname, decl->lex_pos); // 將 函數 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 { // 在當前 active_op_array 中生成一條函數定義指令 !!! opline = get_next_op(CG(active_op_array)); opline->opcode = ZEND_DECLARE_FUNCTION; opline->op1_type = IS_CONST; ... } }
如今明白了,原來函數的 op_array 是保存在 CG 的 function_table 中,這裏還有一個有意思的地方,php 生成了一條函數定義指令,這一點正是 動態腳本 語言和 靜態類型語言(Java)很是不一樣的地方!靜態類型的語言不須要執行代碼來添加函數 or 方法,由於它們在代碼編譯階段就已經肯定了,固然也就缺乏了一點靈活性
咱們迴歸主線,接着看 zend_compile_func_decl 代碼
// 上面已經將 CG(active_op_array)暫存起來了,因此這裏將 CG(active_op_array) 設置成 函數的 op_array // 函數內部的語句的字節碼都會保存在 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); } // 編譯參數 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); } // 編譯函數內部語句 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;