认识PHP 7虚拟机(2)

### OPArray OPArray是指一个包含许多要被顺序执行的OPCode的数组,如下图: ![](/uploads/image/2016/11/08/20161108012913_21400.png) OPArray由结构体_zend_op_array表示: struct _zend_op_array { /* Common elements */ /* 省略 */ /* END of common elements */ /* 省略 */ zend_op *opcodes; //<------ 存储着OPCode的数组 /* 省略 */ }; 在PHP中,每个PHP用户函数或者PHP脚本、传递给eval()的参数,会被编译为一个OPArray。 OPArray中包含了许多静态的信息,能够帮助执行引擎更高效地执行PHP代码。部分重要的信息如下: 当前脚本的文件名,OPArray对应的PHP代码在脚本中起始和终止的行号 /**的代码注释信息 refcount引用计数,OPArray是可共享的 try-catch-finally的跳转信息 break-continue的跳转信息 当前作用域所有PHP变量的名称 函数中用到的静态变量 literals(字面量),编译阶段已知的值,例如字符串“foo”,或者整数42 运行时缓存槽,引擎会缓存一些后续执行需要用到的东西 #### 一个简单的例子: $a = 8; $b = 'foo'; echo $a + $b; OPArray中的部分成员其内容如下: ![](/uploads/image/2016/11/08/20161108013028_71421.png) OPArray包含的信息越多,即在编译期间尽量的将已知的信息计算好存储到OPArray中,执行引擎就能够更高效地执行。我们可以看到每个字面量都已经被编译为zval并存储到literals数组中(你可能发现这里多了一个整型值1,其实这是用于ZEND_RETURN OPCode的,PHP文件的OPArray默认会返回1,但函数的OPArray默认返回null)。OPArray所使用到的PHP变量的名字信息也被编译为zend_string存储到vars数组中,编译后的OPCode则存储到opcodes数组中。 ### OPCode的执行 OPCode的执行是通过一个while循环去做的: //删除了预处理语句 ZEND_API void execute_ex(zend_execute_data *ex) { DCL_OPLINE const zend_op *orig_opline = opline; zend_execute_data *orig_execute_data = execute_data; execute_data = ex; LOAD_OPLINE(); while (1) { ((opcode_handler_t)OPLINE->handler)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); //执行OPCode对应的C函数 if (UNEXPECTED(!OPLINE)) { //当前OPArray执行完 execute_data = orig_execute_data; opline = orig_opline; return; } } zend_error_noreturn(E_CORE_ERROR, "Arrived at end of main loop which shouldn't happen"); } 那么是如何切换到下一个OPCode去执行的呢?每个OPCode的Handler中都会调用到一个宏: #define ZEND_VM_NEXT_OPCODE_EX(check_exception, skip) \ CHECK_SYMBOL_TABLES() \ if (check_exception) { \ OPLINE = EX(opline) + (skip); \ } else { \ OPLINE = opline + (skip); \ } \ ZEND_VM_CONTINUE() 该宏会把当前的opline+skip(skip通常是1),将opline指向下一条OPCode。opline是一个全局变量,指向当前执行的OPCode。 编译器优化 在Zend/zend_vm_execute.h中,会看到如下奇怪的代码: static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_ARRAY_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { /* 省略 */ if (IS_CONST == IS_UNUSED) { ZEND_VM_NEXT_OPCODE(); #if 0 || (IS_CONST != IS_UNUSED) } else { ZEND_VM_TAIL_CALL(ZEND_ADD_ARRAY_ELEMENT_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); #endif } } 你可能会对if (IS_CONST == IS_UNUSED)和#if 0 || (IS_CONST != IS_UNUSED)感到奇怪。看下其对应的模板代码: ZEND_VM_HANDLER(71, ZEND_INIT_ARRAY, CONST|TMP|VAR|UNUSED|CV, CONST|TMPVAR|UNUSED|CV) { zval *array; uint32_t size; USE_OPLINE array = EX_VAR(opline->result.var); if (OP1_TYPE != IS_UNUSED) { size = opline->extended_value >> ZEND_ARRAY_SIZE_SHIFT; } else { size = 0; } ZVAL_NEW_ARR(array); zend_hash_init(Z_ARRVAL_P(array), size, NULL, ZVAL_PTR_DTOR, 0); if (OP1_TYPE != IS_UNUSED) { /* Explicitly initialize array as not-packed if flag is set */ if (opline->extended_value & ZEND_ARRAY_NOT_PACKED) { zend_hash_real_init(Z_ARRVAL_P(array), 0); } } if (OP1_TYPE == IS_UNUSED) { ZEND_VM_NEXT_OPCODE(); if !defined(ZEND_VM_SPEC) || (OP1_TYPE != IS_UNUSED) } else { ZEND_VM_DISPATCH_TO_HANDLER(ZEND_ADD_ARRAY_ELEMENT); #endif } } php zend_vm_gen.php在生成zend_vm_execute.h时,会把OP1_TYPE替换为op1的类型,从而生成这样子的代码:if (IS_CONST == IS_UNUSED),但C编译器会把这些代码优化掉。
联系我们

邮箱 626512443@qq.com
电话 18611320371(微信)
QQ群 235681453

Copyright © 2015-2022

备案号:京ICP备15003423号-3