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;
}
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);
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);
}
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();
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)) {
| 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
}
|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 {
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,