认识PHP 7虚拟机(2)
认识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; //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编译器会把这些代码优化掉。