贪吃蛇php源代码,php代码编译的实现 Web程序 - 贪吃蛇学院-专业IT技术平台

1.php是解析型的高级语言,zend内核使用c语言实现,有main函数,php脚本就是输入,内核处理后输出结果,内核将php脚本翻译成c程序可识别的opcode就是php的编译。

c语言的编译将c代码编译成机器码,这些机器码就是操作指令,将指令写入二进制程序load相应的内存区(常量区 数据区 代码区),分配运行栈,开始从代码区依次执行。

php编译差不多,将php脚本解析成opcode,每条opcode就是c的stuct,对应着相应的机器指令,执行过程就是zend引擎执行这些opcode,编译过程包括词法分析、语法分析,就的php版本直接生成opcode,php7新增在语法分析阶段生成抽象语法树,然后生成opcode_array。

2.词法分析、语法分析 (PHP代码->抽象语法树(AST))

PHP使用re2c、bison完成这个阶段的工作

re2c: 词法分析器,将输入分割为一个个有意义的词块,称为token

bison: 语法分析器,确定词法分析器分割出的token是如何彼此关联的

3.opcode_array结构

zend引擎会把AST进一步编译为 zend_op_array ,它是编译阶段最终的产物,也是执行阶段的输入。 AST解析过程确定了当前脚本定义了哪些变量,并为这些变量 顺序编号 ,这些值在使用时都是按照这个编号获取的,另外也将变量的初始化值、调用的函数/类/常量名称等值(称之为字面量)保存到zend_op_array.literals中,这些字面量也有一个唯一的编号,所以执行的过程实际就是根据各指令调用不同的C函数,然后根据变量、字面量、临时变量的编号对这些值进行处理加工。

PHP主脚本会生成一个zend_op_array,每个function也会编译为独立的zend_op_array,所以从二进制程序的角度看zend_op_array包含着当前作用域下的所有堆栈信息,函数调用实际就是不同zend_op_array间的切换

20180528181716992798.png

opcode的结构

struct_zend_op_array {//common是普通函数或类成员方法对应的opcodes快速访问时使用的字段,后面分析PHP函数实现的时候会详细讲

...

uint32_t*refcount;

uint32_t this_var;

uint32_t last;//opcode指令数组

zend_op *opcodes;//PHP代码里定义的变量数:op_type为IS_CV的变量,不含IS_TMP_VAR、IS_VAR的//编译前此值为0,然后发现一个新变量这个值就加1

intlast_var;//临时变量数:op_type为IS_TMP_VAR、IS_VAR的变量

uint32_t T;//PHP变量名数组

zend_string **vars; //这个数组在ast编译期间配合last_var用来确定各个变量的编号,非常重要的一步操作

...//静态变量符号表:通过static声明的

HashTable *static_variables;

...//字面量数量

intlast_literal;//字面量(常量)数组,这些都是在PHP代码定义的一些值

zval *literals;//运行时缓存数组大小

intcache_size;//运行时缓存,主要用于缓存一些znode_op以便于快速获取数据,后面单独介绍这个机制

void **run_time_cache;void *reserved[ZEND_MAX_RESERVED_RESOURCES];

};

handler是每条opcode对应的C语言编写的 处理过程,所有hadler定义在zend_vm_def.h中,有三种不同的提供形式:CALL、SWITCH、GOTO,默认方式为CALL。

每条opcode都有两个操作数, 操作数记录着当前指令的关键信息(操作数类型实际就是个32位整形,它主要用于存储一些变量的索引位置、数值记录等等)。

每个操作都有5种不同的类型 IS_CONST:字面量,IS_TMP_VAR:临时变量, IS_VAR:PHP变量,  IS_CV:PHP脚本变量, IS_UNUSED:表示操作数没有用。

PHP代码不会直接编译为机器码,但编译、执行的设计跟C程序是一致的,也有常量区、变量也通过偏移量访问、也有虚拟的执行栈。

在编译时就可确定且不会改变的量称为字面量,也称作常量(IS_CONST),这些值在编译阶段就已经分配zval,保存在zend_op_array->literals数组中,访问时通过_zend_op_array->literals + 偏移量读取。

4.抽象语法树(AST)编译  (AST-> zend_op_array)

ZEND_API zend_op_array *compile_file(zend_file_handle *file_handle, inttype)

{

zend_op_array*op_array = NULL; //编译出的opcodes

...if (open_file_for_scanning(file_handle)==FAILURE) {//文件打开失败

...

}else{

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()) { //语法解析

zval retval_zv;

zend_file_context original_file_context;//保存原来的zend_file_context

zend_oparray_context original_oparray_context; //保存原来的zend_oparray_context,编译期间用于记录当前zend_op_array的opcodes、vars等数组的总大小

zend_op_array *original_active_op_array =CG(active_op_array);

op_array= emalloc(sizeof(zend_op_array)); //分配zend_op_array结构

init_op_array(op_array, ZEND_USER_FUNCTION, INITIAL_OP_ARRAY_SIZE);//初始化op_array

CG(active_op_array) = op_array; //将当前正在编译op_array指向当前

ZVAL_LONG(&retval_zv, 1);if(zend_ast_process) {

zend_ast_process(CG(ast));

}

zend_file_context_begin(&original_file_context); //初始化CG(file_context)

zend_oparray_context_begin(&original_oparray_context); //初始化CG(context)

zend_compile_top_stmt(CG(ast)); //AST->zend_op_array编译流程

zend_emit_final_return(&retval_zv); //设置最后的返回值

op_array->line_start = 1;

op_array->line_end =CG(zend_lineno);

pass_two(op_array);

zend_oparray_context_end(&original_oparray_context);

zend_file_context_end(&original_file_context);

CG(active_op_array)=original_active_op_array;

}

...

}

...returnop_array;

}

compile_file()操作中有几个保存原来值的操作,这是因为这个函数在PHP脚本执行中并不会只执行一次,主脚本执行时会第一次调用,而include、require也会调用,所以需要先保存当前值,然后执行完再还原回去。

AST->zend_op_array编译是在 zend_compile_top_stmt() 中完成,这个函数是总入口,会被多次递归调用:

//zend_compile.c

void zend_compile_top_stmt(zend_ast *ast)

{if (!ast) {return;

}if (ast->kind == ZEND_AST_STMT_LIST) { //第一次进来一定是这种类型

zend_ast_list *list =zend_ast_get_list(ast);

uint32_t i;for (i = 0; i < list->children; ++i) {

zend_compile_top_stmt(list->child[i]);//list各child语句相互独立,递归编译

}return;

}//各语句编译入口

zend_compile_stmt(ast);if (ast->kind != ZEND_AST_NAMESPACE && ast->kind !=ZEND_AST_HALT_COMPILER) {

zend_verify_namespace();

}//function、class两种情况的处理,非常关键的一步操作,后面分析函数、类实现的章节再详细分析

if (ast->kind == ZEND_AST_FUNC_DECL || ast->kind ==ZEND_AST_CLASS) {

CG(zend_lineno)= ((zend_ast_decl *) ast)->end_lineno;

zend_do_early_binding();//很重要!!!

}

}

首先从AST的根节点开始编译,根节点类型为ZEND_AST_STMT_LIST,这个类型表示当前节点下有多个独立的节点,各child都是独立的语句生成的节点,所以依次编译即可,直到到达有效节点位置(非ZEND_AST_STMT_LIST节点),然后调用zend_compile_stmt编译当前节点:

void zend_compile_stmt(zend_ast *ast)

{

CG(zend_lineno)= ast->lineno;switch (ast->kind) {casexxx:

...break;caseZEND_AST_ECHO:

zend_compile_echo(ast);break;

...default:

{

znode result;

zend_compile_expr(&result, ast);

zend_do_free(&result);

}

}if (FC(declarables).ticks && !zend_is_unticked_stmt(ast)) {

zend_emit_tick();

}

}

根据不同的节点类型(kind)作不同的处理。

最终编译的结果就是zend_op_array,其中最核心的操作就是AST的编译了,编译阶段很关键的一个操作就是确定了各个 变量、中间值、临时值、返回值、字面量 的 内存编号 ,这个地方非常重要,后面介绍执行流程时也会用到。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值