前言
本文从函数定义的语法规则开始,简要介绍 PHP 解释器如何 "编译" 函数定义
函数对应的 AST 节点
为了看起来清楚一些,我们将 语法规则定义 与 语法动作分开:
// 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 的命名也是很 "范化" 的
编译 AST
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;
}
}
这里有几个地方比较有意思:
函数参数 ast 的类型是 zend_ast,但是被强制转换成了 zend_ast_decl,这个 zend_ast_decl 结构体和 zend_ast 结构体在内存布局上有者相同的 "头部",C 语言经常使用这种技巧类实现类似 面向对象里面 继承 的概念
这里又遇到了 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;
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。