]> granicus.if.org Git - php/commitdiff
JIT for INIT_METHOD_CALL
authorDmitry Stogov <dmitry@zend.com>
Wed, 16 Sep 2020 07:25:36 +0000 (10:25 +0300)
committerDmitry Stogov <dmitry@zend.com>
Wed, 16 Sep 2020 07:25:36 +0000 (10:25 +0300)
ext/opcache/jit/zend_jit.c
ext/opcache/jit/zend_jit_disasm_x86.c
ext/opcache/jit/zend_jit_helpers.c
ext/opcache/jit/zend_jit_trace.c
ext/opcache/jit/zend_jit_x86.dasc

index b5c6c20e8d3028b844a1d3304e28695ddefe1448..0ad3d3883eea7641b8d78947f8b178fa8d62cc16 100644 (file)
@@ -3176,6 +3176,41 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
                                                        goto jit_failure;
                                                }
                                                goto done;
+                                       case ZEND_INIT_METHOD_CALL:
+                                               if (opline->op2_type != IS_CONST
+                                                || Z_TYPE_P(RT_CONSTANT(opline, opline->op2)) != IS_STRING) {
+                                                       break;
+                                               }
+                                               ce = NULL;
+                                               ce_is_instanceof = 0;
+                                               if (opline->op1_type == IS_UNUSED) {
+                                                       op1_info = MAY_BE_OBJECT|MAY_BE_RC1|MAY_BE_RCN;
+                                                       op1_addr = 0;
+                                                       ce = op_array->scope;
+                                                       ce_is_instanceof = (ce->ce_flags & ZEND_ACC_FINAL) != 0;
+                                               } else {
+                                                       op1_info = OP1_INFO();
+                                                       if (!(op1_info & MAY_BE_OBJECT)) {
+                                                               break;
+                                                       }
+                                                       op1_addr = OP1_REG_ADDR();
+                                                       if (ssa->var_info && ssa->ops) {
+                                                               zend_ssa_op *ssa_op = &ssa->ops[opline - op_array->opcodes];
+                                                               if (ssa_op->op1_use >= 0) {
+                                                                       zend_ssa_var_info *op1_ssa = ssa->var_info + ssa_op->op1_use;
+                                                                       if (op1_ssa->ce && !op1_ssa->ce->create_object) {
+                                                                               ce = op1_ssa->ce;
+                                                                               ce_is_instanceof = op1_ssa->is_instanceof;
+                                                                       }
+                                                               }
+                                                       }
+                                               }
+                                               if (!zend_jit_init_method_call(&dasm_state, opline, b, op_array, ssa, ssa_op, call_level,
+                                                               op1_info, op1_addr, ce, ce_is_instanceof, 0, NULL,
+                                                               NULL)) {
+                                                       goto jit_failure;
+                                               }
+                                               goto done;
                                        default:
                                                break;
                                }
index 0146d2a1ecb4b3ef671aedf73e89e73b65886963..07884ad03a52c4d3a8c8884cdc1c173323ed1e64 100644 (file)
@@ -398,6 +398,11 @@ static int zend_jit_disasm_init(void)
        REGISTER_HELPER(zend_jit_init_func_run_time_cache_helper);
        REGISTER_HELPER(zend_jit_find_func_helper);
        REGISTER_HELPER(zend_jit_find_ns_func_helper);
+       REGISTER_HELPER(zend_jit_find_method_helper);
+       REGISTER_HELPER(zend_jit_push_static_metod_call_frame);
+       REGISTER_HELPER(zend_jit_push_static_metod_call_frame_tmp);
+       REGISTER_HELPER(zend_jit_invalid_method_call);
+       REGISTER_HELPER(zend_jit_unref_helper);
        REGISTER_HELPER(zend_jit_extend_stack_helper);
        REGISTER_HELPER(zend_jit_int_extend_stack_helper);
        REGISTER_HELPER(zend_jit_leave_nested_func_helper);
index 89f55b96440fffb9944c3a3e693775f7ccb1dab4..03e4041f0aab1c01e077e8544698721ecbf910ef 100644 (file)
@@ -90,6 +90,109 @@ static zend_function* ZEND_FASTCALL zend_jit_find_ns_func_helper(zval *func_name
        return fbc;
 }
 
+static ZEND_COLD void ZEND_FASTCALL zend_jit_invalid_method_call(zval *object)
+{
+       zend_execute_data *execute_data = EG(current_execute_data);
+       const zend_op *opline = EX(opline);
+       zval *function_name = function_name = RT_CONSTANT(opline, opline->op2);;
+
+       if (Z_TYPE_P(object) == IS_UNDEF && opline->op1_type == IS_CV) {
+               zend_string *cv = EX(func)->op_array.vars[EX_VAR_TO_NUM(opline->op1.var)];
+
+               zend_error(E_WARNING, "Undefined variable $%s", ZSTR_VAL(cv));
+               if (UNEXPECTED(EG(exception) != NULL)) {
+                       return;
+               }
+               object = &EG(uninitialized_zval);
+       }
+       zend_throw_error(NULL, "Call to a member function %s() on %s",
+               Z_STRVAL_P(function_name), zend_zval_type_name(object));
+       if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) {
+               zval_ptr_dtor_nogc(EX_VAR(opline->op1.var));
+       }
+}
+
+static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_undefined_method(const zend_class_entry *ce, const zend_string *method)
+{
+       zend_throw_error(NULL, "Call to undefined method %s::%s()", ZSTR_VAL(ce->name), ZSTR_VAL(method));
+}
+
+static void ZEND_FASTCALL zend_jit_unref_helper(zval *zv)
+{
+       zend_reference *ref;
+
+       ZEND_ASSERT(Z_ISREF_P(zv));
+       ref = Z_REF_P(zv);
+       ZVAL_COPY_VALUE(zv, &ref->val);
+       if (GC_DELREF(ref) == 0) {
+               efree_size(ref, sizeof(zend_reference));
+       } else {
+               Z_TRY_ADDREF_P(zv);
+       }
+}
+
+static zend_function* ZEND_FASTCALL zend_jit_find_method_helper(zend_object *obj, zval *function_name, zend_object **obj_ptr)
+{
+       zend_execute_data *execute_data = EG(current_execute_data);
+       const zend_op *opline = EX(opline);
+       zend_class_entry *called_scope = obj->ce;
+       zend_object *orig_obj = obj;
+       zend_function *fbc;
+
+       fbc = obj->handlers->get_method(&obj, Z_STR_P(function_name), function_name + 1);
+       if (UNEXPECTED(fbc == NULL)) {
+               if (EXPECTED(!EG(exception))) {
+                       zend_undefined_method(called_scope, Z_STR_P(function_name));
+               }
+               if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) && GC_DELREF(orig_obj) == 0) {
+                       zend_objects_store_del(orig_obj);
+               }
+               return NULL;
+       }
+
+       if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) {
+               zend_init_func_run_time_cache(&fbc->op_array);
+       }
+
+       if (UNEXPECTED(obj != orig_obj)) {
+               if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) {
+                       GC_ADDREF(obj);
+                       if (GC_DELREF(orig_obj) == 0) {
+                               zend_objects_store_del(orig_obj);
+                       }
+               }
+               *obj_ptr = obj;
+               return fbc;
+       }
+
+       if (EXPECTED(!(fbc->common.fn_flags & (ZEND_ACC_CALL_VIA_TRAMPOLINE|ZEND_ACC_NEVER_CACHE)))) {
+               CACHE_POLYMORPHIC_PTR(opline->result.num, called_scope, fbc);
+       }
+
+       return fbc;
+}
+
+static zend_execute_data* ZEND_FASTCALL zend_jit_push_static_metod_call_frame(zend_object *obj, zend_function *fbc, uint32_t num_args)
+{
+       zend_class_entry *scope = obj->ce;
+
+       return zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION, fbc, num_args, scope);
+}
+
+static zend_execute_data* ZEND_FASTCALL zend_jit_push_static_metod_call_frame_tmp(zend_object *obj, zend_function *fbc, uint32_t num_args)
+{
+       zend_class_entry *scope = obj->ce;
+
+       if (GC_DELREF(obj) == 0) {
+               zend_objects_store_del(obj);
+               if (UNEXPECTED(EG(exception))) {
+                       return NULL;
+               }
+       }
+
+       return zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION, fbc, num_args, scope);
+}
+
 static zend_execute_data* ZEND_FASTCALL zend_jit_extend_stack_helper(uint32_t used_stack, zend_function *fbc)
 {
        zend_execute_data *call = (zend_execute_data*)zend_vm_stack_extend(used_stack);
index 15bc71a691016c8156d246e86af229c69c503c9b..e8cc51cd7f3a7b14bf5f2fbf022d5a3addf9b881 100644 (file)
@@ -1703,6 +1703,13 @@ propagate_arg:
                                        }
                                        ADD_OP1_TRACE_GUARD();
                                        break;
+                               case ZEND_INIT_METHOD_CALL:
+                                       if (opline->op2_type != IS_CONST
+                                        || Z_TYPE_P(RT_CONSTANT(opline, opline->op2)) != IS_STRING) {
+                                               break;
+                                       }
+                                       ADD_OP1_TRACE_GUARD();
+                                       break;
                                case ZEND_INIT_DYNAMIC_CALL:
                                        if (orig_op2_type == IS_OBJECT && op2_ce == zend_ce_closure) {
                                                ADD_OP2_TRACE_GUARD();
@@ -4979,31 +4986,76 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par
                                                        goto jit_failure;
                                                }
                                                goto done;
-                                       case ZEND_INIT_DYNAMIC_CALL:
-                                               if (orig_op2_type == IS_OBJECT && op2_ce == zend_ce_closure) {
-                                                       op2_info = OP2_INFO();
-                                                       CHECK_OP2_TRACE_TYPE();
-                                                       if (!zend_jit_init_closure_call(&dasm_state, opline, op_array_ssa->cfg.map ? op_array_ssa->cfg.map[opline - op_array->opcodes] : -1, op_array, ssa, ssa_op, frame->call_level, p + 1)) {
-                                                               goto jit_failure;
+                                       case ZEND_INIT_METHOD_CALL:
+                                               if (opline->op2_type != IS_CONST
+                                                || Z_TYPE_P(RT_CONSTANT(opline, opline->op2)) != IS_STRING) {
+                                                       goto generic_dynamic_call;
+                                               }
+                                               delayed_fetch_this = 0;
+                                               ce = NULL;
+                                               ce_is_instanceof = 0;
+                                               if (opline->op1_type == IS_UNUSED) {
+                                                       op1_info = MAY_BE_OBJECT|MAY_BE_RC1|MAY_BE_RCN;
+                                                       ce = op_array->scope;
+                                                       ce_is_instanceof = (ce->ce_flags & ZEND_ACC_FINAL) != 0;
+                                                       op1_addr = 0;
+                                               } else {
+                                                       op1_info = OP1_INFO();
+                                                       if (!(op1_info & MAY_BE_OBJECT)) {
+                                                                       goto generic_dynamic_call;
+                                                       }
+                                                       op1_addr = OP1_REG_ADDR();
+                                                       if (orig_op1_type != IS_UNKNOWN
+                                                        && (orig_op1_type & IS_TRACE_REFERENCE)) {
+                                                               if (!zend_jit_fetch_reference(&dasm_state, opline, orig_op1_type, &op1_info, &op1_addr,
+                                                                               !ssa->var_info[ssa_op->op1_use].guarded_reference, 1)) {
+                                                                       goto jit_failure;
+                                                               }
+                                                               if (opline->op1_type == IS_CV
+                                                                && zend_jit_var_may_alias(op_array, op_array_ssa, EX_VAR_TO_NUM(opline->op1.var)) == NO_ALIAS) {
+                                                                       ssa->var_info[ssa_op->op1_use].guarded_reference = 1;
+                                                               }
+                                                       } else {
+                                                               CHECK_OP1_TRACE_TYPE();
+                                                       }
+                                                       if (ssa->var_info && ssa->ops) {
+                                                               if (ssa_op->op1_use >= 0) {
+                                                                       zend_ssa_var_info *op1_ssa = ssa->var_info + ssa_op->op1_use;
+                                                                       if (op1_ssa->ce && !op1_ssa->ce->create_object) {
+                                                                               ce = op1_ssa->ce;
+                                                                               ce_is_instanceof = op1_ssa->is_instanceof;
+                                                                       }
+                                                               }
+                                                       }
+                                                       if (ssa_op->op1_use >= 0) {
+                                                               delayed_fetch_this = ssa->var_info[ssa_op->op1_use].delayed_fetch_this;
                                                        }
-                                                       goto done;
                                                }
-                                               /* break missing intentionally */
-                                       case ZEND_INIT_METHOD_CALL:
-                                               if (!zend_jit_trace_handler(&dasm_state, op_array, opline, zend_may_throw(opline, ssa_op, op_array, ssa), p + 1)) {
+                                               if (!zend_jit_init_method_call(&dasm_state, opline,
+                                                               op_array_ssa->cfg.map ? op_array_ssa->cfg.map[opline - op_array->opcodes] : -1,
+                                                               op_array, ssa, ssa_op, frame->call_level,
+                                                               op1_info, op1_addr, ce, ce_is_instanceof, delayed_fetch_this, op1_ce,
+                                                               p + 1)) {
                                                        goto jit_failure;
                                                }
-                                               if ((p+1)->op == ZEND_JIT_TRACE_INIT_CALL && (p+1)->func) {
-                                                       if (!zend_jit_init_fcall_guard(&dasm_state, 0, (p+1)->func, opline+1)) {
-                                                               goto jit_failure;
-                                                       }
+                                               goto done;
+                                       case ZEND_INIT_DYNAMIC_CALL:
+                                               if (orig_op2_type != IS_OBJECT || op2_ce != zend_ce_closure) {
+                                                       goto generic_dynamic_call;
+                                               }
+                                               op2_info = OP2_INFO();
+                                               CHECK_OP2_TRACE_TYPE();
+                                               if (!zend_jit_init_closure_call(&dasm_state, opline, op_array_ssa->cfg.map ? op_array_ssa->cfg.map[opline - op_array->opcodes] : -1, op_array, ssa, ssa_op, frame->call_level, p + 1)) {
+                                                       goto jit_failure;
                                                }
                                                goto done;
                                        case ZEND_INIT_STATIC_METHOD_CALL:
+generic_dynamic_call:
                                                if (!zend_jit_trace_handler(&dasm_state, op_array, opline, zend_may_throw(opline, ssa_op, op_array, ssa), p + 1)) {
                                                        goto jit_failure;
                                                }
-                                               if ((opline->op1_type != IS_CONST
+                                               if ((opline->opcode != ZEND_INIT_STATIC_METHOD_CALL
+                                                 || opline->op1_type != IS_CONST
                                                  || opline->op2_type != IS_CONST)
                                                 && (p+1)->op == ZEND_JIT_TRACE_INIT_CALL && (p+1)->func) {
                                                        if (!zend_jit_init_fcall_guard(&dasm_state, 0, (p+1)->func, opline+1)) {
index d4e40c431fb4f71a7529b86ed2736d74a97439bf..6a1fcb9591e08a5860c28e31e64e17c1b42e31ee 100644 (file)
@@ -8572,8 +8572,10 @@ static int zend_jit_push_call_frame(dasm_State **Dst, const zend_op *opline, zen
                |       MEM_OP2_1_ZTS add, aword, executor_globals, vm_stack_top, FCARG1a, r2
        }
        |       // zend_vm_init_call_frame(call, call_info, func, num_args, called_scope, object);
-       |       // ZEND_SET_CALL_INFO(call, 0, call_info);
-       |       mov dword EX:RX->This.u1.type_info, (IS_UNDEF | ZEND_CALL_NESTED_FUNCTION)
+       if (JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE || opline->opcode != ZEND_INIT_METHOD_CALL) {
+               |       // ZEND_SET_CALL_INFO(call, 0, call_info);
+               |       mov dword EX:RX->This.u1.type_info, (IS_UNDEF | ZEND_CALL_NESTED_FUNCTION)
+       }
 #ifdef _WIN32
        if (0) {
 #else
@@ -8593,7 +8595,30 @@ static int zend_jit_push_call_frame(dasm_State **Dst, const zend_op *opline, zen
                }
                |1:
        }
-       if (!is_closure) {
+       if (opline->opcode == ZEND_INIT_METHOD_CALL) {
+               |       // Z_PTR(call->This) = obj;
+               |       mov r1, aword T1
+               |       mov aword EX:RX->This.value.ptr, r1
+           if (opline->op1_type == IS_UNUSED) {
+                       |       // call->call_info |= ZEND_CALL_HAS_THIS;
+                       if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
+                               |       mov dword EX:RX->This.u1.type_info, ZEND_CALL_HAS_THIS
+                       } else {
+                               |       or dword EX:RX->This.u1.type_info, ZEND_CALL_HAS_THIS
+                       }
+           } else {
+                       if (opline->op1_type == IS_CV) {
+                               |       // GC_ADDREF(obj);
+                               |       add dword [r1], 1
+                       }
+                       |       // call->call_info |= ZEND_CALL_HAS_THIS | ZEND_CALL_RELEASE_THIS;
+                       if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
+                               |       mov dword EX:RX->This.u1.type_info, (ZEND_CALL_HAS_THIS | ZEND_CALL_RELEASE_THIS)
+                       } else {
+                               |       or dword EX:RX->This.u1.type_info, (ZEND_CALL_HAS_THIS | ZEND_CALL_RELEASE_THIS)
+                       }
+           }
+       } else if (!is_closure) {
                |       // Z_CE(call->This) = called_scope;
                |       mov aword EX:RX->This.value.ptr, 0
        } else {
@@ -9029,6 +9054,242 @@ static int zend_jit_init_fcall(dasm_State **Dst, const zend_op *opline, uint32_t
        return 1;
 }
 
+static int zend_jit_init_method_call(dasm_State          **Dst,
+                                     const zend_op        *opline,
+                                     uint32_t              b,
+                                     const zend_op_array  *op_array,
+                                     zend_ssa             *ssa,
+                                     const zend_ssa_op    *ssa_op,
+                                     int                   call_level,
+                                     uint32_t              op1_info,
+                                     zend_jit_addr         op1_addr,
+                                     zend_class_entry     *ce,
+                                     zend_bool             ce_is_instanceof,
+                                     zend_bool             use_this,
+                                     zend_class_entry     *trace_ce,
+                                     zend_jit_trace_rec   *trace)
+{
+       zend_func_info *info = ZEND_FUNC_INFO(op_array);
+       zend_call_info *call_info = NULL;
+       zend_function *func = NULL;
+       zval *function_name;
+
+       ZEND_ASSERT(opline->op2_type == IS_CONST);
+       ZEND_ASSERT(op1_info & MAY_BE_OBJECT);
+
+       function_name = RT_CONSTANT(opline, opline->op2);
+
+       if (opline->op1_type == IS_UNUSED || use_this) {
+               zend_jit_addr this_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, offsetof(zend_execute_data, This));
+
+               |       GET_ZVAL_PTR FCARG1a, this_addr
+       } else {
+           if (op1_info & MAY_BE_REF) {
+                       if (opline->op1_type == IS_CV) {
+                               if (Z_REG(op1_addr) != ZREG_FCARG1a || Z_OFFSET(op1_addr) != 0) {
+                                       |       LOAD_ZVAL_ADDR FCARG1a, op1_addr
+                               }
+                               |       ZVAL_DEREF FCARG1a, op1_info
+                               op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
+                       } else {
+                               ZEND_ASSERT(Z_REG(op1_addr) == ZREG_FP);
+                               |       IF_NOT_ZVAL_TYPE op1_addr, IS_REFERENCE, >1
+                               |       LOAD_ZVAL_ADDR FCARG1a, op1_addr
+                               |       EXT_CALL zend_jit_unref_helper, r0
+                               |1:
+                       }
+               }
+               if (op1_info & ((MAY_BE_UNDEF|MAY_BE_ANY)- MAY_BE_OBJECT)) {
+                       if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
+                               int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
+                               const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+
+                               if (!exit_addr) {
+                                       return 0;
+                               }
+                               |       IF_NOT_ZVAL_TYPE op1_addr, IS_OBJECT, &exit_addr
+                       } else {
+                               |       IF_NOT_ZVAL_TYPE op1_addr, IS_OBJECT, >1
+                               |.cold_code
+                               |1:
+                               if (Z_REG(op1_addr) != ZREG_FCARG1a || Z_OFFSET(op1_addr) != 0) {
+                                       |       LOAD_ZVAL_ADDR FCARG1a, op1_addr
+                               }
+                               |       SET_EX_OPLINE opline, r0
+                               |       EXT_CALL zend_jit_invalid_method_call, r0
+                               |       jmp ->exception_handler
+                               |.code
+                       }
+               }
+               |       GET_ZVAL_PTR FCARG1a, op1_addr
+       }
+
+       if (delayed_call_chain) {
+               if (!zend_jit_save_call_chain(Dst, delayed_call_level)) {
+                       return 0;
+               }
+       }
+
+       if (info) {
+               call_info = info->callee_info;
+               while (call_info && call_info->caller_init_opline != opline) {
+                       call_info = call_info->next_callee;
+               }
+               if (call_info && call_info->callee_func) {
+                       func = call_info->callee_func;
+               }
+       }
+
+       |       mov aword T1, FCARG1a // save
+
+       if (func) {
+               |       // fbc = CACHED_PTR(opline->result.num + sizeof(void*));
+               |       mov r0, EX->run_time_cache
+               |       mov r0, aword [r0 + opline->result.num + sizeof(void*)]
+               |       test r0, r0
+               |       jz >1
+       } else {
+               |       // if (CACHED_PTR(opline->result.num) == obj->ce)) {
+               |       mov r0, EX->run_time_cache
+               |       mov r2, aword [r0 + opline->result.num]
+               |       cmp r2, [FCARG1a + offsetof(zend_object, ce)]
+               |       jnz >1
+               |       // fbc = CACHED_PTR(opline->result.num + sizeof(void*));
+               |       mov r0, aword [r0 + opline->result.num + sizeof(void*)]
+       }
+
+       |.cold_code
+       |1:
+       |       LOAD_ADDR FCARG2a, function_name
+       |.if X64
+       |       lea CARG3, aword T1
+       |.else
+       |       lea r0, aword T1
+       |       sub r4, 12
+       |       push r0
+       |.endif
+       |       SET_EX_OPLINE opline, r0
+       |       EXT_CALL zend_jit_find_method_helper, r0
+       |       test r0, r0
+       |       jz ->exception_handler
+       |.if not(X64)
+       |       add r4, 12
+       |.endif
+       |       jmp >2
+       |.code
+       |2:
+
+       if (!func
+        && trace
+        && trace->op == ZEND_JIT_TRACE_INIT_CALL
+        && trace->func
+#ifdef _WIN32
+        && trace->func->type != ZEND_INTERNAL_FUNCTION
+#endif
+       ) {
+               int32_t exit_point;
+               const void *exit_addr;
+
+               exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_DYNAMIC_CALL);
+               exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+               if (!exit_addr) {
+                       return 0;
+               }
+
+               func = (zend_function*)trace->func;
+
+               if (func->type == ZEND_USER_FUNCTION &&
+                   (!(func->common.fn_flags & ZEND_ACC_IMMUTABLE) ||
+                    (func->common.fn_flags & ZEND_ACC_CLOSURE) ||
+                    !func->common.function_name)) {
+                       const zend_op *opcodes = func->op_array.opcodes;
+
+                       |   .if X64
+                       ||              if (!IS_SIGNED_32BIT(opcodes)) {
+                       |                       mov64 r1, ((ptrdiff_t)opcodes)
+                       |                       cmp aword [r0 + offsetof(zend_op_array, opcodes)], r1
+                       ||              } else {
+                       |                       cmp aword [r0 + offsetof(zend_op_array, opcodes)], opcodes
+                       ||              }
+                       |       .else
+                       |               cmp aword [r0 + offsetof(zend_op_array, opcodes)], opcodes
+                       |       .endif
+                       |       jne &exit_addr
+               } else {
+                       |   .if X64
+                       ||              if (!IS_SIGNED_32BIT(func)) {
+                       |                       mov64 r1, ((ptrdiff_t)func)
+                       |                       cmp r0, r1
+                       ||              } else {
+                       |                       cmp r0, func
+                       ||              }
+                       |       .else
+                       |               cmp r0, func
+                       |       .endif
+                       |       jne &exit_addr
+               }
+       }
+
+       if (!func) {
+               |       // if (fbc->common.fn_flags & ZEND_ACC_STATIC) {
+               |       test dword [r0 + offsetof(zend_function, common.fn_flags)], ZEND_ACC_STATIC
+               |       jnz >1
+               |.cold_code
+               |1:
+       }
+
+       if (!func || (func->common.fn_flags & ZEND_ACC_STATIC) != 0) {
+               |       mov FCARG1a, aword T1 // restore
+               |       mov FCARG2a, r0
+               |.if X64
+               |       mov CARG3d, opline->extended_value
+               |.else
+               |       sub r4, 12
+               |       push opline->extended_value
+               |.endif
+               if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) {
+                       |       EXT_CALL zend_jit_push_static_metod_call_frame_tmp, r0
+               } else {
+                       |       EXT_CALL zend_jit_push_static_metod_call_frame, r0
+               }
+               |.if not(X64)
+               |       add r4, 12
+               |.endif
+               if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) {
+                       |       test r0, r0
+                       |       jz ->exception_handler
+               }
+               |       mov RX, r0
+       }
+
+       if (!func) {
+               |       jmp >9
+               |.code
+       }
+
+       if (!func || (func->common.fn_flags & ZEND_ACC_STATIC) == 0) {
+               if (!zend_jit_push_call_frame(Dst, opline, func, 0)) {
+                       return 0;
+               }
+       }
+
+       if (!func) {
+               |9:
+       }
+       zend_jit_start_reuse_ip();
+
+       if (zend_jit_needs_call_chain(call_info, b, op_array, ssa, ssa_op, opline, trace)) {
+               if (!zend_jit_save_call_chain(Dst, call_level)) {
+                       return 0;
+               }
+       } else {
+               delayed_call_chain = 1;
+               delayed_call_level = call_level;
+       }
+
+       return 1;
+}
+
 static int zend_jit_init_closure_call(dasm_State          **Dst,
                                       const zend_op        *opline,
                                       uint32_t              b,