使用ZEND_VM_REPEATABLE_OPCODE减少Zend虚拟机函数调用
    
  
      
      
     
    
      
        
本文基于PHP8.0.1
我们先来看看对应的宏:
1 2 3 4 5 6
   | #define ZEND_VM_REPEATABLE_OPCODE \     do { #define ZEND_VM_REPEAT_OPCODE(_opcode) \     } while (UNEXPECTED((++opline)->opcode == _opcode)); \     OPLINE = opline; \     ZEND_VM_CONTINUE()
   | 
 
可以看到,在ZEND_VM_REPEATABLE_OPCODE和ZEND_VM_REPEAT_OPCODE两个宏之间,会判断下一个opcode是否和当前的opcode一样,如果一样,那么再次进入循环。这可以运用在opline的handler里面,比如说如下脚本:
1 2 3 4 5 6 7 8
   | <?php
  function foo($a = 1, $b = 2) {     var_dump($a, $b); }
  foo();
   | 
 
函数foo生成的opcodes如下:
1 2 3 4 5 6 7 8
   | L3-6 foo() /Users/codinghuang/.phpbrew/build/php-8.0.1/test5.php - 0x10e85f3c0 + 7 ops  L3      L3      L5      L5      L5      L5      L6    
   | 
 
可以看到,当函数有默认参数的时候,会通过这个ZEND_RECV_INIT来接收参数的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
   | static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_RECV_INIT_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) {     USE_OPLINE     uint32_t arg_num;     zval *param;
      ZEND_VM_REPEATABLE_OPCODE
      arg_num = opline->op1.num;     param = EX_VAR(opline->result.var);     if (arg_num > EX_NUM_ARGS()) {         zval *default_value = RT_CONSTANT(opline, opline->op2);
          if (Z_OPT_TYPE_P(default_value) == IS_CONSTANT_AST) {             zval *cache_val = (zval*)CACHE_ADDR(Z_CACHE_SLOT_P(default_value));
                           if (Z_TYPE_P(cache_val) != IS_UNDEF) {                 ZVAL_COPY_VALUE(param, cache_val);             } else {                 SAVE_OPLINE();                 ZVAL_COPY(param, default_value);                 if (UNEXPECTED(zval_update_constant_ex(param, EX(func)->op_array.scope) != SUCCESS)) {                     zval_ptr_dtor_nogc(param);                     ZVAL_UNDEF(param);                     HANDLE_EXCEPTION();                 }                 if (!Z_REFCOUNTED_P(param)) {                     ZVAL_COPY_VALUE(cache_val, param);                 }             }             goto recv_init_check_type;         } else {             ZVAL_COPY(param, default_value);         }     } else { recv_init_check_type:         if (UNEXPECTED((EX(func)->op_array.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) != 0)) {             SAVE_OPLINE();             if (UNEXPECTED(!zend_verify_recv_arg_type(EX(func), arg_num, param, CACHE_ADDR(opline->extended_value)))) {                 HANDLE_EXCEPTION();             }         }     }
      ZEND_VM_REPEAT_OPCODE(ZEND_RECV_INIT);     ZEND_VM_NEXT_OPCODE(); }
   | 
 
这里就用到了这个优化,连续的两个opcode是一样的,所以在第一条ZEND_RECV_INIT执行完之后,不会退出这个函数,而是回到了ZEND_VM_REPEATABLE_OPCODE处,继续执行下一条opline。