/* +----------------------------------------------------------------------+ | Zend JIT | +----------------------------------------------------------------------+ | Copyright (c) The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | https://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Authors: Dmitry Stogov | +----------------------------------------------------------------------+ */ #include "main/php.h" #include "main/SAPI.h" #include "php_version.h" #include #include "zend_shared_alloc.h" #include "Zend/zend_execute.h" #include "Zend/zend_vm.h" #include "Zend/zend_exceptions.h" #include "Zend/zend_constants.h" #include "Zend/zend_closures.h" #include "Zend/zend_ini.h" #include "Zend/zend_observer.h" #include "zend_smart_str.h" #include "jit/zend_jit.h" #ifdef HAVE_JIT #include "Optimizer/zend_func_info.h" #include "Optimizer/zend_ssa.h" #include "Optimizer/zend_inference.h" #include "Optimizer/zend_call_graph.h" #include "Optimizer/zend_dump.h" #if ZEND_JIT_TARGET_X86 # include "jit/zend_jit_x86.h" #elif ZEND_JIT_TARGET_ARM64 # include "jit/zend_jit_arm64.h" #endif #include "jit/zend_jit_internal.h" #ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP #include #endif #ifdef ZTS int jit_globals_id; #else zend_jit_globals jit_globals; #endif //#define CONTEXT_THREADED_JIT #define ZEND_JIT_USE_RC_INFERENCE #ifdef ZEND_JIT_USE_RC_INFERENCE # define ZEND_SSA_RC_INFERENCE_FLAG ZEND_SSA_RC_INFERENCE # define RC_MAY_BE_1(info) (((info) & (MAY_BE_RC1|MAY_BE_REF)) != 0) # define RC_MAY_BE_N(info) (((info) & (MAY_BE_RCN|MAY_BE_REF)) != 0) #else # define ZEND_SSA_RC_INFERENCE_FLAG 0 # define RC_MAY_BE_1(info) 1 # define RC_MAY_BE_N(info) 1 #endif #define JIT_PREFIX "JIT$" #define JIT_STUB_PREFIX "JIT$$" #define TRACE_PREFIX "TRACE-" #define DASM_M_GROW(ctx, t, p, sz, need) \ do { \ size_t _sz = (sz), _need = (need); \ if (_sz < _need) { \ if (_sz < 16) _sz = 16; \ while (_sz < _need) _sz += _sz; \ (p) = (t *)erealloc((p), _sz); \ (sz) = _sz; \ } \ } while(0) #define DASM_M_FREE(ctx, p, sz) efree(p) #if ZEND_DEBUG # define DASM_CHECKS 1 #endif #include "dynasm/dasm_proto.h" typedef struct _zend_jit_stub { const char *name; int (*stub)(dasm_State **Dst); uint32_t offset; uint32_t adjustment; } zend_jit_stub; #define JIT_STUB(name, offset, adjustment) \ {JIT_STUB_PREFIX #name, zend_jit_ ## name ## _stub, offset, adjustment} zend_ulong zend_jit_profile_counter = 0; int zend_jit_profile_counter_rid = -1; int16_t zend_jit_hot_counters[ZEND_HOT_COUNTERS_COUNT]; const zend_op *zend_jit_halt_op = NULL; static int zend_jit_vm_kind = 0; #ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP static int zend_write_protect = 1; #endif static void *dasm_buf = NULL; static void *dasm_end = NULL; static void **dasm_ptr = NULL; static size_t dasm_size = 0; static zend_long jit_bisect_pos = 0; static const void *zend_jit_runtime_jit_handler = NULL; static const void *zend_jit_profile_jit_handler = NULL; static const void *zend_jit_func_hot_counter_handler = NULL; static const void *zend_jit_loop_hot_counter_handler = NULL; static const void *zend_jit_func_trace_counter_handler = NULL; static const void *zend_jit_ret_trace_counter_handler = NULL; static const void *zend_jit_loop_trace_counter_handler = NULL; static int ZEND_FASTCALL zend_runtime_jit(void); static int zend_jit_trace_op_len(const zend_op *opline); static int zend_jit_trace_may_exit(const zend_op_array *op_array, const zend_op *opline); static uint32_t zend_jit_trace_get_exit_point(const zend_op *to_opline, uint32_t flags); static const void *zend_jit_trace_get_exit_addr(uint32_t n); static void zend_jit_trace_add_code(const void *start, uint32_t size); static bool zend_jit_needs_arg_dtor(const zend_function *func, uint32_t arg_num, zend_call_info *call_info); #if ZEND_JIT_TARGET_ARM64 static zend_jit_trace_info *zend_jit_get_current_trace_info(void); static uint32_t zend_jit_trace_find_exit_point(const void* addr); #endif static int zend_jit_assign_to_variable(dasm_State **Dst, const zend_op *opline, zend_jit_addr var_use_addr, zend_jit_addr var_addr, uint32_t var_info, uint32_t var_def_info, zend_uchar val_type, zend_jit_addr val_addr, uint32_t val_info, zend_jit_addr res_addr, bool check_exception); static bool dominates(const zend_basic_block *blocks, int a, int b) { while (blocks[b].level > blocks[a].level) { b = blocks[b].idom; } return a == b; } static bool zend_ssa_is_last_use(const zend_op_array *op_array, const zend_ssa *ssa, int var, int use) { int next_use; if (ssa->vars[var].phi_use_chain) { zend_ssa_phi *phi = ssa->vars[var].phi_use_chain; do { if (!ssa->vars[phi->ssa_var].no_val) { return 0; } phi = zend_ssa_next_use_phi(ssa, var, phi); } while (phi); } if (ssa->cfg.blocks[ssa->cfg.map[use]].loop_header > 0 || (ssa->cfg.blocks[ssa->cfg.map[use]].flags & ZEND_BB_LOOP_HEADER)) { int b = ssa->cfg.map[use]; int prev_use = ssa->vars[var].use_chain; while (prev_use >= 0 && prev_use != use) { if (b != ssa->cfg.map[prev_use] && dominates(ssa->cfg.blocks, b, ssa->cfg.map[prev_use]) && !zend_ssa_is_no_val_use(op_array->opcodes + prev_use, ssa->ops + prev_use, var)) { return 0; } prev_use = zend_ssa_next_use(ssa->ops, var, prev_use); } } next_use = zend_ssa_next_use(ssa->ops, var, use); if (next_use < 0) { return 1; } else if (zend_ssa_is_no_val_use(op_array->opcodes + next_use, ssa->ops + next_use, var)) { return 1; } return 0; } static bool zend_ival_is_last_use(const zend_lifetime_interval *ival, int use) { if (ival->flags & ZREG_LAST_USE) { const zend_life_range *range = &ival->range; while (range->next) { range = range->next; } return range->end == use; } return 0; } static bool zend_is_commutative(zend_uchar opcode) { return opcode == ZEND_ADD || opcode == ZEND_MUL || opcode == ZEND_BW_OR || opcode == ZEND_BW_AND || opcode == ZEND_BW_XOR; } static int zend_jit_is_constant_cmp_long_long(const zend_op *opline, zend_ssa_range *op1_range, zend_jit_addr op1_addr, zend_ssa_range *op2_range, zend_jit_addr op2_addr, bool *result) { zend_long op1_min; zend_long op1_max; zend_long op2_min; zend_long op2_max; if (op1_range) { op1_min = op1_range->min; op1_max = op1_range->max; } else if (Z_MODE(op1_addr) == IS_CONST_ZVAL) { ZEND_ASSERT(Z_TYPE_P(Z_ZV(op1_addr)) == IS_LONG); op1_min = op1_max = Z_LVAL_P(Z_ZV(op1_addr)); } else { return 0; } if (op2_range) { op2_min = op2_range->min; op2_max = op2_range->max; } else if (Z_MODE(op2_addr) == IS_CONST_ZVAL) { ZEND_ASSERT(Z_TYPE_P(Z_ZV(op2_addr)) == IS_LONG); op2_min = op2_max = Z_LVAL_P(Z_ZV(op2_addr)); } else { return 0; } switch (opline->opcode) { case ZEND_IS_EQUAL: case ZEND_IS_IDENTICAL: case ZEND_CASE: case ZEND_CASE_STRICT: if (op1_min == op1_max && op2_min == op2_max && op1_min == op2_min) { *result = 1; return 1; } else if (op1_max < op2_min || op1_min > op2_max) { *result = 0; return 1; } return 0; case ZEND_IS_NOT_EQUAL: case ZEND_IS_NOT_IDENTICAL: if (op1_min == op1_max && op2_min == op2_max && op1_min == op2_min) { *result = 0; return 1; } else if (op1_max < op2_min || op1_min > op2_max) { *result = 1; return 1; } return 0; case ZEND_IS_SMALLER: if (op1_max < op2_min) { *result = 1; return 1; } else if (op1_min >= op2_max) { *result = 0; return 1; } return 0; case ZEND_IS_SMALLER_OR_EQUAL: if (op1_max <= op2_min) { *result = 1; return 1; } else if (op1_min > op2_max) { *result = 0; return 1; } return 0; default: ZEND_UNREACHABLE(); } return 0; } static int zend_jit_needs_call_chain(zend_call_info *call_info, uint32_t b, const zend_op_array *op_array, zend_ssa *ssa, const zend_ssa_op *ssa_op, const zend_op *opline, int call_level, zend_jit_trace_rec *trace) { int skip; if (trace) { zend_jit_trace_rec *p = trace; ssa_op++; while (1) { if (p->op == ZEND_JIT_TRACE_VM) { switch (p->opline->opcode) { case ZEND_SEND_ARRAY: case ZEND_SEND_USER: case ZEND_SEND_UNPACK: case ZEND_INIT_FCALL: case ZEND_INIT_METHOD_CALL: case ZEND_INIT_STATIC_METHOD_CALL: case ZEND_INIT_FCALL_BY_NAME: case ZEND_INIT_NS_FCALL_BY_NAME: case ZEND_INIT_DYNAMIC_CALL: case ZEND_NEW: case ZEND_INIT_USER_CALL: case ZEND_FAST_CALL: case ZEND_JMP: case ZEND_JMPZ: case ZEND_JMPNZ: case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: case ZEND_JMP_SET: case ZEND_COALESCE: case ZEND_JMP_NULL: case ZEND_ASSERT_CHECK: case ZEND_CATCH: case ZEND_DECLARE_ANON_CLASS: case ZEND_FE_FETCH_R: case ZEND_FE_FETCH_RW: return 1; case ZEND_DO_ICALL: case ZEND_DO_UCALL: case ZEND_DO_FCALL_BY_NAME: case ZEND_DO_FCALL: case ZEND_CALLABLE_CONVERT: return 0; case ZEND_SEND_VAL: case ZEND_SEND_VAR: case ZEND_SEND_VAL_EX: case ZEND_SEND_VAR_EX: case ZEND_SEND_FUNC_ARG: case ZEND_SEND_REF: case ZEND_SEND_VAR_NO_REF: case ZEND_SEND_VAR_NO_REF_EX: /* skip */ break; default: if (zend_may_throw(opline, ssa_op, op_array, ssa)) { return 1; } } ssa_op += zend_jit_trace_op_len(opline); } else if (p->op == ZEND_JIT_TRACE_ENTER || p->op == ZEND_JIT_TRACE_BACK || p->op == ZEND_JIT_TRACE_END) { return 1; } p++; } } if (!call_info) { const zend_op *end = op_array->opcodes + op_array->last; opline++; ssa_op++; skip = (call_level == 1); while (opline != end) { if (!skip) { if (zend_may_throw(opline, ssa_op, op_array, ssa)) { return 1; } } switch (opline->opcode) { case ZEND_SEND_VAL: case ZEND_SEND_VAR: case ZEND_SEND_VAL_EX: case ZEND_SEND_VAR_EX: case ZEND_SEND_FUNC_ARG: case ZEND_SEND_REF: case ZEND_SEND_VAR_NO_REF: case ZEND_SEND_VAR_NO_REF_EX: skip = 0; break; case ZEND_SEND_ARRAY: case ZEND_SEND_USER: case ZEND_SEND_UNPACK: case ZEND_INIT_FCALL: case ZEND_INIT_METHOD_CALL: case ZEND_INIT_STATIC_METHOD_CALL: case ZEND_INIT_FCALL_BY_NAME: case ZEND_INIT_NS_FCALL_BY_NAME: case ZEND_INIT_DYNAMIC_CALL: case ZEND_NEW: case ZEND_INIT_USER_CALL: case ZEND_FAST_CALL: case ZEND_JMP: case ZEND_JMPZ: case ZEND_JMPNZ: case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: case ZEND_JMP_SET: case ZEND_COALESCE: case ZEND_JMP_NULL: case ZEND_ASSERT_CHECK: case ZEND_CATCH: case ZEND_DECLARE_ANON_CLASS: case ZEND_FE_FETCH_R: case ZEND_FE_FETCH_RW: return 1; case ZEND_DO_ICALL: case ZEND_DO_UCALL: case ZEND_DO_FCALL_BY_NAME: case ZEND_DO_FCALL: case ZEND_CALLABLE_CONVERT: end = opline; if (end - op_array->opcodes >= ssa->cfg.blocks[b].start + ssa->cfg.blocks[b].len) { /* INIT_FCALL and DO_FCALL in different BasicBlocks */ return 1; } return 0; } opline++; ssa_op++; } return 1; } else { const zend_op *end = call_info->caller_call_opline; /* end may be null if an opcode like EXIT is part of the argument list. */ if (!end || end - op_array->opcodes >= ssa->cfg.blocks[b].start + ssa->cfg.blocks[b].len) { /* INIT_FCALL and DO_FCALL in different BasicBlocks */ return 1; } opline++; ssa_op++; skip = (call_level == 1); while (opline != end) { if (skip) { switch (opline->opcode) { case ZEND_SEND_VAL: case ZEND_SEND_VAR: case ZEND_SEND_VAL_EX: case ZEND_SEND_VAR_EX: case ZEND_SEND_FUNC_ARG: case ZEND_SEND_REF: case ZEND_SEND_VAR_NO_REF: case ZEND_SEND_VAR_NO_REF_EX: skip = 0; break; case ZEND_SEND_ARRAY: case ZEND_SEND_USER: case ZEND_SEND_UNPACK: return 1; } } else { if (zend_may_throw(opline, ssa_op, op_array, ssa)) { return 1; } } opline++; ssa_op++; } return 0; } } static uint32_t skip_valid_arguments(const zend_op_array *op_array, zend_ssa *ssa, const zend_call_info *call_info) { uint32_t num_args = 0; zend_function *func = call_info->callee_func; /* It's okay to handle prototypes here, because they can only increase the accepted arguments. * Anything legal for the parent method is also legal for the parent method. */ while (num_args < call_info->num_args) { zend_arg_info *arg_info = func->op_array.arg_info + num_args; if (ZEND_TYPE_IS_SET(arg_info->type)) { if (ZEND_TYPE_IS_ONLY_MASK(arg_info->type)) { zend_op *opline = call_info->arg_info[num_args].opline; zend_ssa_op *ssa_op = &ssa->ops[opline - op_array->opcodes]; uint32_t type_mask = ZEND_TYPE_PURE_MASK(arg_info->type); if ((OP1_INFO() & (MAY_BE_ANY|MAY_BE_UNDEF)) & ~type_mask) { break; } } else { break; } } num_args++; } return num_args; } static uint32_t zend_ssa_cv_info(const zend_op_array *op_array, zend_ssa *ssa, uint32_t var) { uint32_t j, info; if (ssa->vars && ssa->var_info) { info = ssa->var_info[var].type; for (j = op_array->last_var; j < ssa->vars_count; j++) { if (ssa->vars[j].var == var) { info |= ssa->var_info[j].type; } } } else { info = MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY | MAY_BE_UNDEF | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; } #ifdef ZEND_JIT_USE_RC_INFERENCE /* Refcount may be increased by RETURN opcode */ if ((info & MAY_BE_RC1) && !(info & MAY_BE_RCN)) { for (j = 0; j < ssa->cfg.blocks_count; j++) { if ((ssa->cfg.blocks[j].flags & ZEND_BB_REACHABLE) && ssa->cfg.blocks[j].len > 0) { const zend_op *opline = op_array->opcodes + ssa->cfg.blocks[j].start + ssa->cfg.blocks[j].len - 1; if (opline->opcode == ZEND_RETURN) { if (opline->op1_type == IS_CV && opline->op1.var == EX_NUM_TO_VAR(var)) { info |= MAY_BE_RCN; break; } } } } } #endif return info; } static bool zend_jit_may_avoid_refcounting(const zend_op *opline, uint32_t op1_info) { switch (opline->opcode) { case ZEND_FETCH_OBJ_FUNC_ARG: if (!JIT_G(current_frame) || !JIT_G(current_frame)->call->func || !TRACE_FRAME_IS_LAST_SEND_BY_VAL(JIT_G(current_frame)->call)) { return 0; } /* break missing intentionally */ case ZEND_FETCH_OBJ_R: case ZEND_FETCH_OBJ_IS: if ((op1_info & MAY_BE_OBJECT) && opline->op2_type == IS_CONST && Z_TYPE_P(RT_CONSTANT(opline, opline->op2)) == IS_STRING && Z_STRVAL_P(RT_CONSTANT(opline, opline->op2))[0] != '\0') { return 1; } break; case ZEND_FETCH_DIM_FUNC_ARG: if (!JIT_G(current_frame) || !JIT_G(current_frame)->call->func || !TRACE_FRAME_IS_LAST_SEND_BY_VAL(JIT_G(current_frame)->call)) { return 0; } /* break missing intentionally */ case ZEND_FETCH_DIM_R: case ZEND_FETCH_DIM_IS: return 1; case ZEND_ISSET_ISEMPTY_DIM_OBJ: if (!(opline->extended_value & ZEND_ISEMPTY)) { return 1; } break; } return 0; } static bool zend_jit_is_persistent_constant(zval *key, uint32_t flags) { zval *zv; zend_constant *c = NULL; /* null/true/false are resolved during compilation, so don't check for them here. */ zv = zend_hash_find_known_hash(EG(zend_constants), Z_STR_P(key)); if (zv) { c = (zend_constant*)Z_PTR_P(zv); } else if (flags & IS_CONSTANT_UNQUALIFIED_IN_NAMESPACE) { key++; zv = zend_hash_find_known_hash(EG(zend_constants), Z_STR_P(key)); if (zv) { c = (zend_constant*)Z_PTR_P(zv); } } return c && (ZEND_CONSTANT_FLAGS(c) & CONST_PERSISTENT); } static zend_property_info* zend_get_known_property_info(const zend_op_array *op_array, zend_class_entry *ce, zend_string *member, bool on_this, zend_string *filename) { zend_property_info *info = NULL; if ((on_this && (op_array->fn_flags & ZEND_ACC_TRAIT_CLONE)) || !ce || !(ce->ce_flags & ZEND_ACC_LINKED) || (ce->ce_flags & ZEND_ACC_TRAIT) || ce->create_object) { return NULL; } if (!(ce->ce_flags & ZEND_ACC_IMMUTABLE)) { if (ce->info.user.filename != filename) { /* class declaration might be changed independently */ return NULL; } if (ce->parent) { zend_class_entry *parent = ce->parent; do { if (parent->type == ZEND_INTERNAL_CLASS) { break; } else if (parent->info.user.filename != filename) { /* some of parents class declarations might be changed independently */ /* TODO: this check may be not enough, because even * in the same it's possible to conditionally define * few classes with the same name, and "parent" may * change from request to request. */ return NULL; } parent = parent->parent; } while (parent); } } info = (zend_property_info*)zend_hash_find_ptr(&ce->properties_info, member); if (info == NULL || !IS_VALID_PROPERTY_OFFSET(info->offset) || (info->flags & ZEND_ACC_STATIC)) { return NULL; } if (info->flags & ZEND_ACC_PUBLIC) { return info; } else if (on_this) { if (ce == info->ce) { if (ce == op_array->scope) { return info; } else { return NULL; } } else if ((info->flags & ZEND_ACC_PROTECTED) && instanceof_function_slow(ce, info->ce)) { return info; } } return NULL; } static bool zend_may_be_dynamic_property(zend_class_entry *ce, zend_string *member, bool on_this, const zend_op_array *op_array) { zend_property_info *info; if (!ce || (ce->ce_flags & ZEND_ACC_TRAIT) || (op_array->fn_flags & ZEND_ACC_TRAIT_CLONE)) { return 1; } if (!(ce->ce_flags & ZEND_ACC_IMMUTABLE)) { if (ce->info.user.filename != op_array->filename) { /* class declaration might be changed independently */ return 1; } } info = (zend_property_info*)zend_hash_find_ptr(&ce->properties_info, member); if (info == NULL || !IS_VALID_PROPERTY_OFFSET(info->offset) || (info->flags & ZEND_ACC_STATIC)) { return 1; } if (!(info->flags & ZEND_ACC_PUBLIC) && (!on_this || info->ce != ce)) { return 1; } return 0; } #define OP_RANGE(ssa_op, opN) \ (((opline->opN##_type & (IS_TMP_VAR|IS_VAR|IS_CV)) && \ ssa->var_info && \ (ssa_op)->opN##_use >= 0 && \ ssa->var_info[(ssa_op)->opN##_use].has_range) ? \ &ssa->var_info[(ssa_op)->opN##_use].range : NULL) #define OP1_RANGE() OP_RANGE(ssa_op, op1) #define OP2_RANGE() OP_RANGE(ssa_op, op2) #define OP1_DATA_RANGE() OP_RANGE(ssa_op + 1, op1) #if ZEND_JIT_TARGET_X86 # include "dynasm/dasm_x86.h" #elif ZEND_JIT_TARGET_ARM64 static int zend_jit_add_veneer(dasm_State *Dst, void *buffer, uint32_t ins, int *b, uint32_t *cp, ptrdiff_t offset); # define DASM_ADD_VENEER zend_jit_add_veneer # include "dynasm/dasm_arm64.h" #endif #include "jit/zend_jit_helpers.c" #include "jit/zend_jit_disasm.c" #ifndef _WIN32 # include "jit/zend_jit_gdb.h" # include "jit/zend_jit_perf_dump.c" #endif #ifdef HAVE_OPROFILE # include "jit/zend_jit_oprofile.c" #endif #include "Zend/zend_cpuinfo.h" #ifdef HAVE_VALGRIND # include #endif #ifdef HAVE_GCC_GLOBAL_REGS # define GCC_GLOBAL_REGS 1 #else # define GCC_GLOBAL_REGS 0 #endif /* By default avoid JITing inline handlers if it does not seem profitable due to lack of * type information. Disabling this option allows testing some JIT handlers in the * presence of try/catch blocks, which prevent SSA construction. */ #ifndef PROFITABILITY_CHECKS # define PROFITABILITY_CHECKS 1 #endif #define BP_JIT_IS 6 /* Used for ISSET_ISEMPTY_DIM_OBJ. see BP_VAR_*defines in Zend/zend_compile.h */ typedef enum _sp_adj_kind { SP_ADJ_NONE, SP_ADJ_RET, SP_ADJ_VM, SP_ADJ_JIT, SP_ADJ_ASSIGN, SP_ADJ_LAST } sp_adj_kind; static int sp_adj[SP_ADJ_LAST]; /* The generated code may contain tautological comparisons, ignore them. */ #if defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wtautological-compare" # pragma clang diagnostic ignored "-Wstring-compare" #endif #if ZEND_JIT_TARGET_X86 # include "jit/zend_jit_vtune.c" # include "jit/zend_jit_x86.c" #elif ZEND_JIT_TARGET_ARM64 # include "jit/zend_jit_arm64.c" #endif #if defined(__clang__) # pragma clang diagnostic pop #endif #if _WIN32 # include #else # include # if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) # define MAP_ANONYMOUS MAP_ANON # endif #endif ZEND_EXT_API void zend_jit_status(zval *ret) { zval stats; array_init(&stats); add_assoc_bool(&stats, "enabled", JIT_G(enabled)); add_assoc_bool(&stats, "on", JIT_G(on)); add_assoc_long(&stats, "kind", JIT_G(trigger)); add_assoc_long(&stats, "opt_level", JIT_G(opt_level)); add_assoc_long(&stats, "opt_flags", JIT_G(opt_flags)); if (dasm_buf) { add_assoc_long(&stats, "buffer_size", (char*)dasm_end - (char*)dasm_buf); add_assoc_long(&stats, "buffer_free", (char*)dasm_end - (char*)*dasm_ptr); } else { add_assoc_long(&stats, "buffer_size", 0); add_assoc_long(&stats, "buffer_free", 0); } add_assoc_zval(ret, "jit", &stats); } static zend_string *zend_jit_func_name(const zend_op_array *op_array) { smart_str buf = {0}; if (op_array->function_name) { if (op_array->scope) { smart_str_appends(&buf, JIT_PREFIX); smart_str_appendl(&buf, ZSTR_VAL(op_array->scope->name), ZSTR_LEN(op_array->scope->name)); smart_str_appends(&buf, "::"); smart_str_appendl(&buf, ZSTR_VAL(op_array->function_name), ZSTR_LEN(op_array->function_name)); smart_str_0(&buf); return buf.s; } else { smart_str_appends(&buf, JIT_PREFIX); smart_str_appendl(&buf, ZSTR_VAL(op_array->function_name), ZSTR_LEN(op_array->function_name)); smart_str_0(&buf); return buf.s; } } else if (op_array->filename) { smart_str_appends(&buf, JIT_PREFIX); smart_str_appendl(&buf, ZSTR_VAL(op_array->filename), ZSTR_LEN(op_array->filename)); smart_str_0(&buf); return buf.s; } else { return NULL; } } #if ZEND_DEBUG static void handle_dasm_error(int ret) { switch (ret & 0xff000000u) { case DASM_S_NOMEM: fprintf(stderr, "DASM_S_NOMEM\n"); break; case DASM_S_PHASE: fprintf(stderr, "DASM_S_PHASE\n"); break; case DASM_S_MATCH_SEC: fprintf(stderr, "DASM_S_MATCH_SEC\n"); break; case DASM_S_RANGE_I: fprintf(stderr, "DASM_S_RANGE_I\n"); break; case DASM_S_RANGE_SEC: fprintf(stderr, "DASM_S_RANGE_SEC\n"); break; case DASM_S_RANGE_LG: fprintf(stderr, "DASM_S_RANGE_LG\n"); break; case DASM_S_RANGE_PC: fprintf(stderr, "DASM_S_RANGE_PC %d\n", ret & 0xffffffu); break; #ifdef DASM_S_RANGE_VREG case DASM_S_RANGE_VREG: fprintf(stderr, "DASM_S_RANGE_VREG\n"); break; #endif #ifdef DASM_S_UNDEF_L case DASM_S_UNDEF_L: fprintf(stderr, "DASM_S_UNDEF_L\n"); break; #endif #ifdef DASM_S_UNDEF_LG case DASM_S_UNDEF_LG: fprintf(stderr, "DASM_S_UNDEF_LG\n"); break; #endif #ifdef DASM_S_RANGE_REL case DASM_S_RANGE_REL: fprintf(stderr, "DASM_S_RANGE_REL\n"); break; #endif case DASM_S_UNDEF_PC: fprintf(stderr, "DASM_S_UNDEF_PC %d\n", ret & 0xffffffu); break; default: fprintf(stderr, "DASM_S_%0x\n", ret & 0xff000000u); break; } ZEND_UNREACHABLE(); } #endif static void *dasm_link_and_encode(dasm_State **dasm_state, const zend_op_array *op_array, zend_ssa *ssa, const zend_op *rt_opline, zend_lifetime_interval **ra, const char *name, uint32_t trace_num, uint32_t sp_offset, uint32_t sp_adjustment) { size_t size; int ret; void *entry; #if defined(HAVE_DISASM) || defined(HAVE_GDB) || defined(HAVE_OPROFILE) || defined(HAVE_PERFTOOLS) || defined(HAVE_VTUNE) zend_string *str = NULL; #endif if (rt_opline && ssa && ssa->cfg.map) { /* Create additional entry point, to switch from interpreter to JIT-ed * code at run-time. */ int b = ssa->cfg.map[rt_opline - op_array->opcodes]; //#ifdef CONTEXT_THREADED_JIT // if (!(ssa->cfg.blocks[b].flags & (ZEND_BB_START|ZEND_BB_RECV_ENTRY))) { //#else if (!(ssa->cfg.blocks[b].flags & (ZEND_BB_START|ZEND_BB_ENTRY|ZEND_BB_RECV_ENTRY))) { //#endif zend_jit_label(dasm_state, ssa->cfg.blocks_count + b); zend_jit_prologue(dasm_state); if (ra) { int i; zend_lifetime_interval *ival; zend_life_range *range; uint32_t pos = rt_opline - op_array->opcodes; for (i = 0; i < ssa->vars_count; i++) { ival = ra[i]; if (ival && ival->reg != ZREG_NONE) { range = &ival->range; if (pos >= range->start && pos <= range->end) { if (!zend_jit_load_var(dasm_state, ssa->var_info[i].type, ssa->vars[i].var, ival->reg)) { return NULL; } break; } range = range->next; } } } zend_jit_jmp(dasm_state, b); } } ret = dasm_link(dasm_state, &size); if (ret != DASM_S_OK) { #if ZEND_DEBUG handle_dasm_error(ret); #endif return NULL; } if ((void*)((char*)*dasm_ptr + size) > dasm_end) { *dasm_ptr = dasm_end; //prevent further try // TODO: jit_buffer_size overflow ??? return NULL; } #if ZEND_JIT_TARGET_ARM64 dasm_venners_size = 0; #endif ret = dasm_encode(dasm_state, *dasm_ptr); if (ret != DASM_S_OK) { #if ZEND_DEBUG handle_dasm_error(ret); #endif return NULL; } #if ZEND_JIT_TARGET_ARM64 size += dasm_venners_size; #endif entry = *dasm_ptr; *dasm_ptr = (void*)((char*)*dasm_ptr + ZEND_MM_ALIGNED_SIZE_EX(size, DASM_ALIGNMENT)); /* flush the hardware I-cache */ JIT_CACHE_FLUSH(entry, entry + size); if (trace_num) { zend_jit_trace_add_code(entry, dasm_getpclabel(dasm_state, 1)); } if (op_array && ssa) { int b; for (b = 0; b < ssa->cfg.blocks_count; b++) { //#ifdef CONTEXT_THREADED_JIT // if (ssa->cfg.blocks[b].flags & (ZEND_BB_START|ZEND_BB_RECV_ENTRY)) { //#else if (ssa->cfg.blocks[b].flags & (ZEND_BB_START|ZEND_BB_ENTRY|ZEND_BB_RECV_ENTRY)) { //#endif zend_op *opline = op_array->opcodes + ssa->cfg.blocks[b].start; int offset = dasm_getpclabel(dasm_state, ssa->cfg.blocks_count + b); if (offset >= 0) { opline->handler = (void*)(((char*)entry) + offset); } } } if (rt_opline && ssa && ssa->cfg.map) { int b = ssa->cfg.map[rt_opline - op_array->opcodes]; zend_op *opline = (zend_op*)rt_opline; int offset = dasm_getpclabel(dasm_state, ssa->cfg.blocks_count + b); if (offset >= 0) { opline->handler = (void*)(((char*)entry) + offset); } } } #if defined(HAVE_DISASM) || defined(HAVE_GDB) || defined(HAVE_OPROFILE) || defined(HAVE_PERFTOOLS) || defined(HAVE_VTUNE) if (!name) { if (JIT_G(debug) & (ZEND_JIT_DEBUG_ASM|ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_OPROFILE|ZEND_JIT_DEBUG_PERF|ZEND_JIT_DEBUG_VTUNE|ZEND_JIT_DEBUG_PERF_DUMP)) { str = zend_jit_func_name(op_array); if (str) { name = ZSTR_VAL(str); } } #ifdef HAVE_DISASM if (JIT_G(debug) & ZEND_JIT_DEBUG_ASM) { zend_jit_disasm_add_symbol(name, (uintptr_t)entry, size); zend_jit_disasm( name, (op_array && op_array->filename) ? ZSTR_VAL(op_array->filename) : NULL, op_array, &ssa->cfg, entry, size); } } else { if (JIT_G(debug) & (ZEND_JIT_DEBUG_ASM_STUBS|ZEND_JIT_DEBUG_ASM)) { zend_jit_disasm_add_symbol(name, (uintptr_t)entry, size); if ((JIT_G(debug) & (trace_num ? ZEND_JIT_DEBUG_ASM : ZEND_JIT_DEBUG_ASM_STUBS)) != 0) { zend_jit_disasm( name, (op_array && op_array->filename) ? ZSTR_VAL(op_array->filename) : NULL, op_array, ssa ? &ssa->cfg : NULL, entry, size); } } # endif } #endif #ifdef HAVE_GDB if (JIT_G(debug) & ZEND_JIT_DEBUG_GDB) { if (name) { zend_jit_gdb_register( name, op_array, entry, size, sp_adj[sp_offset], sp_adj[sp_adjustment]); } } #endif #ifdef HAVE_OPROFILE if (JIT_G(debug) & ZEND_JIT_DEBUG_OPROFILE) { zend_jit_oprofile_register( name, entry, size); } #endif #ifdef HAVE_PERFTOOLS if (JIT_G(debug) & (ZEND_JIT_DEBUG_PERF|ZEND_JIT_DEBUG_PERF_DUMP)) { if (name) { zend_jit_perf_map_register( name, entry, size); if (JIT_G(debug) & ZEND_JIT_DEBUG_PERF_DUMP) { zend_jit_perf_jitdump_register( name, entry, size); } } } #endif #ifdef HAVE_VTUNE if (JIT_G(debug) & ZEND_JIT_DEBUG_VTUNE) { if (name) { zend_jit_vtune_register( name, entry, size); } } #endif #if defined(HAVE_DISASM) || defined(HAVE_GDB) || defined(HAVE_OPROFILE) || defined(HAVE_PERFTOOLS) || defined(HAVE_VTUNE) if (str) { zend_string_release(str); } #endif return entry; } static int zend_may_overflow(const zend_op *opline, const zend_ssa_op *ssa_op, const zend_op_array *op_array, zend_ssa *ssa) { int res; zend_long op1_min, op1_max, op2_min, op2_max; if (!ssa->ops || !ssa->var_info) { return 1; } switch (opline->opcode) { case ZEND_PRE_INC: case ZEND_POST_INC: res = ssa_op->op1_def; if (res < 0 || !ssa->var_info[res].has_range || ssa->var_info[res].range.overflow) { if (!OP1_HAS_RANGE()) { return 1; } op1_max = OP1_MAX_RANGE(); if (op1_max == ZEND_LONG_MAX) { return 1; } } return 0; case ZEND_PRE_DEC: case ZEND_POST_DEC: res = ssa_op->op1_def; if (res < 0 || !ssa->var_info[res].has_range || ssa->var_info[res].range.underflow) { if (!OP1_HAS_RANGE()) { return 1; } op1_min = OP1_MIN_RANGE(); if (op1_min == ZEND_LONG_MIN) { return 1; } } return 0; case ZEND_ADD: res = ssa_op->result_def; if (res < 0 || !ssa->var_info[res].has_range || ssa->var_info[res].range.underflow) { if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) { return 1; } op1_min = OP1_MIN_RANGE(); op2_min = OP2_MIN_RANGE(); if (zend_add_will_overflow(op1_min, op2_min)) { return 1; } } if (res < 0 || !ssa->var_info[res].has_range || ssa->var_info[res].range.overflow) { if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) { return 1; } op1_max = OP1_MAX_RANGE(); op2_max = OP2_MAX_RANGE(); if (zend_add_will_overflow(op1_max, op2_max)) { return 1; } } return 0; case ZEND_SUB: res = ssa_op->result_def; if (res < 0 || !ssa->var_info[res].has_range || ssa->var_info[res].range.underflow) { if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) { return 1; } op1_min = OP1_MIN_RANGE(); op2_max = OP2_MAX_RANGE(); if (zend_sub_will_overflow(op1_min, op2_max)) { return 1; } } if (res < 0 || !ssa->var_info[res].has_range || ssa->var_info[res].range.overflow) { if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) { return 1; } op1_max = OP1_MAX_RANGE(); op2_min = OP2_MIN_RANGE(); if (zend_sub_will_overflow(op1_max, op2_min)) { return 1; } } return 0; case ZEND_MUL: res = ssa_op->result_def; return (res < 0 || !ssa->var_info[res].has_range || ssa->var_info[res].range.underflow || ssa->var_info[res].range.overflow); case ZEND_ASSIGN_OP: if (opline->extended_value == ZEND_ADD) { res = ssa_op->op1_def; if (res < 0 || !ssa->var_info[res].has_range || ssa->var_info[res].range.underflow) { if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) { return 1; } op1_min = OP1_MIN_RANGE(); op2_min = OP2_MIN_RANGE(); if (zend_add_will_overflow(op1_min, op2_min)) { return 1; } } if (res < 0 || !ssa->var_info[res].has_range || ssa->var_info[res].range.overflow) { if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) { return 1; } op1_max = OP1_MAX_RANGE(); op2_max = OP2_MAX_RANGE(); if (zend_add_will_overflow(op1_max, op2_max)) { return 1; } } return 0; } else if (opline->extended_value == ZEND_SUB) { res = ssa_op->op1_def; if (res < 0 || !ssa->var_info[res].has_range || ssa->var_info[res].range.underflow) { if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) { return 1; } op1_min = OP1_MIN_RANGE(); op2_max = OP2_MAX_RANGE(); if (zend_sub_will_overflow(op1_min, op2_max)) { return 1; } } if (res < 0 || !ssa->var_info[res].has_range || ssa->var_info[res].range.overflow) { if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) { return 1; } op1_max = OP1_MAX_RANGE(); op2_min = OP2_MIN_RANGE(); if (zend_sub_will_overflow(op1_max, op2_min)) { return 1; } } return 0; } else if (opline->extended_value == ZEND_MUL) { res = ssa_op->op1_def; return (res < 0 || !ssa->var_info[res].has_range || ssa->var_info[res].range.underflow || ssa->var_info[res].range.overflow); } ZEND_FALLTHROUGH; default: return 1; } } static int zend_jit_build_cfg(const zend_op_array *op_array, zend_cfg *cfg) { uint32_t flags; flags = ZEND_CFG_STACKLESS | ZEND_CFG_NO_ENTRY_PREDECESSORS | ZEND_SSA_RC_INFERENCE_FLAG | ZEND_SSA_USE_CV_RESULTS | ZEND_CFG_RECV_ENTRY; zend_build_cfg(&CG(arena), op_array, flags, cfg); /* Don't JIT huge functions. Apart from likely being detrimental due to the amount of * generated code, some of our analysis is recursive and will stack overflow with many * blocks. */ if (cfg->blocks_count > 100000) { return FAILURE; } zend_cfg_build_predecessors(&CG(arena), cfg); /* Compute Dominators Tree */ zend_cfg_compute_dominators_tree(op_array, cfg); /* Identify reducible and irreducible loops */ zend_cfg_identify_loops(op_array, cfg); return SUCCESS; } static int zend_jit_op_array_analyze1(const zend_op_array *op_array, zend_script *script, zend_ssa *ssa) { if (zend_jit_build_cfg(op_array, &ssa->cfg) != SUCCESS) { return FAILURE; } #if 0 /* TODO: debugger and profiler supports? */ if ((ssa->cfg.flags & ZEND_FUNC_HAS_EXTENDED_INFO)) { return FAILURE; } #endif /* TODO: move this to zend_cfg.c ? */ if (!op_array->function_name) { ssa->cfg.flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } if ((JIT_G(opt_level) >= ZEND_JIT_LEVEL_OPT_FUNC) && ssa->cfg.blocks && op_array->last_try_catch == 0 && !(op_array->fn_flags & ZEND_ACC_GENERATOR) && !(ssa->cfg.flags & ZEND_FUNC_INDIRECT_VAR_ACCESS)) { if (zend_build_ssa(&CG(arena), script, op_array, ZEND_SSA_RC_INFERENCE | ZEND_SSA_USE_CV_RESULTS, ssa) != SUCCESS) { return FAILURE; } zend_ssa_compute_use_def_chains(&CG(arena), op_array, ssa); zend_ssa_find_false_dependencies(op_array, ssa); zend_ssa_find_sccs(op_array, ssa); } return SUCCESS; } static int zend_jit_op_array_analyze2(const zend_op_array *op_array, zend_script *script, zend_ssa *ssa, uint32_t optimization_level) { if ((JIT_G(opt_level) >= ZEND_JIT_LEVEL_OPT_FUNC) && ssa->cfg.blocks && op_array->last_try_catch == 0 && !(op_array->fn_flags & ZEND_ACC_GENERATOR) && !(ssa->cfg.flags & ZEND_FUNC_INDIRECT_VAR_ACCESS)) { if (zend_ssa_inference(&CG(arena), op_array, script, ssa, optimization_level & ~ZEND_OPTIMIZER_NARROW_TO_DOUBLE) != SUCCESS) { return FAILURE; } } return SUCCESS; } static int zend_jit_add_range(zend_lifetime_interval **intervals, int var, uint32_t from, uint32_t to) { zend_lifetime_interval *ival = intervals[var]; if (!ival) { ival = zend_arena_alloc(&CG(arena), sizeof(zend_lifetime_interval)); if (!ival) { return FAILURE; } ival->ssa_var = var; ival->reg = ZREG_NONE; ival->flags = 0; ival->range.start = from; ival->range.end = to; ival->range.next = NULL; ival->hint = NULL; ival->used_as_hint = NULL; intervals[var] = ival; } else if (ival->range.start > to + 1) { zend_life_range *range = zend_arena_alloc(&CG(arena), sizeof(zend_life_range)); if (!range) { return FAILURE; } range->start = ival->range.start; range->end = ival->range.end; range->next = ival->range.next; ival->range.start = from; ival->range.end = to; ival->range.next = range; } else if (ival->range.start == to + 1) { ival->range.start = from; } else { zend_life_range *range = &ival->range; zend_life_range *last = NULL; do { if (range->start > to + 1) { break; } else if (range->end + 1 >= from) { if (range->start > from) { range->start = from; } last = range; range = range->next; while (range) { if (range->start > to + 1) { break; } last->end = range->end; range = range->next; last->next = range; } if (to > last->end) { last->end = to; } return SUCCESS; } last = range; range = range->next; } while (range); range = zend_arena_alloc(&CG(arena), sizeof(zend_life_range)); if (!range) { return FAILURE; } range->start = from; range->end = to; range->next = last->next; last->next = range; } return SUCCESS; } static int zend_jit_begin_range(zend_lifetime_interval **intervals, int var, uint32_t block_start, uint32_t from) { if (block_start != from && intervals[var]) { zend_life_range *range = &intervals[var]->range; do { if (from >= range->start && from <= range->end) { if (range->start == block_start) { range->start = from; } else { zend_life_range *r = zend_arena_alloc(&CG(arena), sizeof(zend_life_range)); if (!r) { return FAILURE; } r->start = from; r->end = range->end; r->next = range->next; range->end = block_start - 1; range->next = r; } return SUCCESS; } range = range->next; } while (range); } // dead store return zend_jit_add_range(intervals, var, from, from); } static void zend_jit_insert_interval(zend_lifetime_interval **list, zend_lifetime_interval *ival) { while (1) { if (*list == NULL) { *list = ival; ival->list_next = NULL; return; } else if (ival->range.start < (*list)->range.start) { ival->list_next = *list; *list = ival; return; } list = &(*list)->list_next; } } static int zend_jit_split_interval(zend_lifetime_interval *current, uint32_t pos, zend_lifetime_interval **list, zend_lifetime_interval **free) { zend_lifetime_interval *ival; zend_life_range *range = ¤t->range; zend_life_range *prev = NULL; if (*free) { ival = *free; *free = ival->list_next; } else { ival = zend_arena_alloc(&CG(arena), sizeof(zend_lifetime_interval)); if (!ival) { return FAILURE; } } current->flags |= ZREG_STORE; ival->ssa_var = current->ssa_var; ival->reg = ZREG_NONE; ival->flags |= ZREG_SPLIT | ZREG_LOAD; ival->flags &= ~ZREG_STORE; ival->hint = NULL; do { if (pos >= range->start && pos <= range->end) { break; } prev = range; range = range->next; } while(range); ZEND_ASSERT(range != NULL); ival->range.start = pos; ival->range.end = range->end; ival->range.next = range->next; if (pos == range->start) { ZEND_ASSERT(prev != NULL); prev->next = NULL; } else { range->end = pos - 1; } zend_jit_insert_interval(list, ival); return SUCCESS; } static zend_lifetime_interval *zend_jit_sort_intervals(zend_lifetime_interval **intervals, int count) { zend_lifetime_interval *list, *last; int i; list = NULL; i = 0; while (i < count) { list = intervals[i]; i++; if (list) { last = list; last->list_next = NULL; break; } } while (i < count) { zend_lifetime_interval *ival = intervals[i]; i++; if (ival) { if ((ival->range.start > last->range.start) || (ival->range.start == last->range.start && ((!ival->hint && last->hint && last->hint != ival) || ival->range.end > last->range.end))) { last->list_next = ival; last = ival; ival->list_next = NULL; } else { zend_lifetime_interval **p = &list; while (1) { if (*p == NULL) { *p = last = ival; ival->list_next = NULL; break; } else if ((ival->range.start < (*p)->range.start) || (ival->range.start == (*p)->range.start && ((ival->hint && !(*p)->hint && ival->hint != *p) || ival->range.end < (*p)->range.end))) { ival->list_next = *p; *p = ival; break; } p = &(*p)->list_next; } } } } return list; } static ZEND_ATTRIBUTE_UNUSED void zend_jit_print_regset(zend_regset regset) { zend_reg reg; int first = 1; ZEND_REGSET_FOREACH(regset, reg) { if (first) { first = 0; fprintf(stderr, "%s", zend_reg_name[reg]); } else { fprintf(stderr, ", %s", zend_reg_name[reg]); } } ZEND_REGSET_FOREACH_END(); } static int *zend_jit_compute_block_order_int(zend_ssa *ssa, int n, int *block_order) { zend_basic_block *b = ssa->cfg.blocks + n; tail_call: *block_order = n; block_order++; n = b->children; while (n >= 0) { b = ssa->cfg.blocks + n; if (b->next_child < 0) { goto tail_call; } block_order = zend_jit_compute_block_order_int(ssa, n, block_order); n = b->next_child; } return block_order; } static int zend_jit_compute_block_order(zend_ssa *ssa, int *block_order) { int *end = zend_jit_compute_block_order_int(ssa, 0, block_order); return end - block_order; } static bool zend_jit_in_loop(zend_ssa *ssa, int header, zend_basic_block *b) { while (b->loop_header >= 0) { if (b->loop_header == header) { return 1; } b = ssa->cfg.blocks + b->loop_header; } return 0; } static void zend_jit_compute_loop_body(zend_ssa *ssa, int header, int n, zend_bitset loop_body) { zend_basic_block *b = ssa->cfg.blocks + n; uint32_t i; tail_call: if (b->len) { for (i = b->start; i < b->start + b->len; i++) { zend_bitset_incl(loop_body, i); } } n = b->children; while (n >= 0) { b = ssa->cfg.blocks + n; if (zend_jit_in_loop(ssa, header, b)) { if (b->next_child < 0) { goto tail_call; } zend_jit_compute_loop_body(ssa, header, n, loop_body); } n = b->next_child; } } static void zend_jit_add_hint(zend_lifetime_interval **intervals, int dst, int src) { if (intervals[dst]->range.start < intervals[src]->range.start) { int tmp = src; src = dst; dst = tmp; } while (dst != src && intervals[dst]->hint) { if (intervals[dst]->hint->range.start < intervals[src]->range.start) { int tmp = src; src = intervals[dst]->hint->ssa_var; dst = tmp; } else { dst = intervals[dst]->hint->ssa_var; } } if (dst != src) { intervals[dst]->hint = intervals[src]; } } /* See "Linear Scan Register Allocation on SSA Form", Christian Wimmer and Michael Franz, CGO'10 (2010), Figure 4. */ static int zend_jit_compute_liveness(const zend_op_array *op_array, zend_ssa *ssa, zend_bitset candidates, zend_lifetime_interval **list) { int set_size, i, j, k, l; uint32_t n; zend_bitset live, live_in, pi_vars, loop_body; int *block_order; zend_ssa_phi *phi; zend_lifetime_interval **intervals; size_t mem_size; ALLOCA_FLAG(use_heap); set_size = zend_bitset_len(ssa->vars_count); mem_size = ZEND_MM_ALIGNED_SIZE(ssa->vars_count * sizeof(zend_lifetime_interval*)) + ZEND_MM_ALIGNED_SIZE((set_size * ssa->cfg.blocks_count) * ZEND_BITSET_ELM_SIZE) + ZEND_MM_ALIGNED_SIZE(set_size * ZEND_BITSET_ELM_SIZE) + ZEND_MM_ALIGNED_SIZE(set_size * ZEND_BITSET_ELM_SIZE) + ZEND_MM_ALIGNED_SIZE(zend_bitset_len(op_array->last) * ZEND_BITSET_ELM_SIZE) + ZEND_MM_ALIGNED_SIZE(ssa->cfg.blocks_count * sizeof(int)); intervals = do_alloca(mem_size, use_heap); if (!intervals) { *list = NULL; return FAILURE; } live_in = (zend_bitset)((char*)intervals + ZEND_MM_ALIGNED_SIZE(ssa->vars_count * sizeof(zend_lifetime_interval*))); live = (zend_bitset)((char*)live_in + ZEND_MM_ALIGNED_SIZE((set_size * ssa->cfg.blocks_count) * ZEND_BITSET_ELM_SIZE)); pi_vars = (zend_bitset)((char*)live + ZEND_MM_ALIGNED_SIZE(set_size * ZEND_BITSET_ELM_SIZE)); loop_body = (zend_bitset)((char*)pi_vars + ZEND_MM_ALIGNED_SIZE(set_size * ZEND_BITSET_ELM_SIZE)); block_order = (int*)((char*)loop_body + ZEND_MM_ALIGNED_SIZE(zend_bitset_len(op_array->last) * ZEND_BITSET_ELM_SIZE)); memset(intervals, 0, ssa->vars_count * sizeof(zend_lifetime_interval*)); zend_bitset_clear(live_in, set_size * ssa->cfg.blocks_count); /* TODO: Provide a linear block order where all dominators of a block * are before this block, and where all blocks belonging to the same loop * are contiguous ??? */ for (l = zend_jit_compute_block_order(ssa, block_order) - 1; l >= 0; l--) { zend_basic_block *b; i = block_order[l]; b = ssa->cfg.blocks + i; /* live = UNION of successor.liveIn for each successor of b */ /* live.add(phi.inputOf(b)) for each phi of successors of b */ zend_bitset_clear(live, set_size); for (j = 0; j < b->successors_count; j++) { int succ = b->successors[j]; zend_bitset_union(live, live_in + set_size * succ, set_size); zend_bitset_clear(pi_vars, set_size); for (phi = ssa->blocks[succ].phis; phi; phi = phi->next) { if (ssa->vars[phi->ssa_var].no_val) { /* skip */ } else if (phi->pi >= 0) { if (phi->pi == i && phi->sources[0] >= 0) { if (zend_bitset_in(candidates, phi->sources[0])) { zend_bitset_incl(live, phi->sources[0]); } zend_bitset_incl(pi_vars, phi->var); } } else if (!zend_bitset_in(pi_vars, phi->var)) { for (k = 0; k < ssa->cfg.blocks[succ].predecessors_count; k++) { if (ssa->cfg.predecessors[ssa->cfg.blocks[succ].predecessor_offset + k] == i) { if (phi->sources[k] >= 0 && zend_bitset_in(candidates, phi->sources[k])) { zend_bitset_incl(live, phi->sources[k]); } break; } } } } } /* addRange(var, b.from, b.to) for each var in live */ ZEND_BITSET_FOREACH(live, set_size, j) { if (zend_bitset_in(candidates, j)) { if (zend_jit_add_range(intervals, j, b->start, b->start + b->len - 1) != SUCCESS) { goto failure; } } } ZEND_BITSET_FOREACH_END(); /* for each operation op of b in reverse order */ for (n = b->start + b->len; n > b->start;) { zend_ssa_op *op; const zend_op *opline; uint32_t num; n--; op = ssa->ops + n; opline = op_array->opcodes + n; if (UNEXPECTED(opline->opcode == ZEND_OP_DATA)) { num = n - 1; } else { num = n; } /* for each output operand opd of op do */ /* setFrom(opd, op) */ /* live.remove(opd) */ if (op->op1_def >= 0 && zend_bitset_in(candidates, op->op1_def)) { if (zend_jit_begin_range(intervals, op->op1_def, b->start, num) != SUCCESS) { goto failure; } zend_bitset_excl(live, op->op1_def); } if (op->op2_def >= 0 && zend_bitset_in(candidates, op->op2_def)) { if (zend_jit_begin_range(intervals, op->op2_def, b->start, num) != SUCCESS) { goto failure; } zend_bitset_excl(live, op->op2_def); } if (op->result_def >= 0 && zend_bitset_in(candidates, op->result_def)) { if (zend_jit_begin_range(intervals, op->result_def, b->start, num) != SUCCESS) { goto failure; } zend_bitset_excl(live, op->result_def); } /* for each input operand opd of op do */ /* live.add(opd) */ /* addRange(opd, b.from, op) */ if (op->op1_use >= 0 && zend_bitset_in(candidates, op->op1_use) && !zend_ssa_is_no_val_use(opline, op, op->op1_use)) { zend_bitset_incl(live, op->op1_use); if (zend_jit_add_range(intervals, op->op1_use, b->start, num) != SUCCESS) { goto failure; } } if (op->op2_use >= 0 && zend_bitset_in(candidates, op->op2_use) && !zend_ssa_is_no_val_use(opline, op, op->op2_use)) { zend_bitset_incl(live, op->op2_use); if (zend_jit_add_range(intervals, op->op2_use, b->start, num) != SUCCESS) { goto failure; } } if (op->result_use >= 0 && zend_bitset_in(candidates, op->result_use) && !zend_ssa_is_no_val_use(opline, op, op->result_use)) { zend_bitset_incl(live, op->result_use); if (zend_jit_add_range(intervals, op->result_use, b->start, num) != SUCCESS) { goto failure; } } } /* live.remove(phi.output) for each phi of b */ for (phi = ssa->blocks[i].phis; phi; phi = phi->next) { zend_bitset_excl(live, phi->ssa_var); } /* b.liveIn = live */ zend_bitset_copy(live_in + set_size * i, live, set_size); } for (i = ssa->cfg.blocks_count - 1; i >= 0; i--) { zend_basic_block *b = ssa->cfg.blocks + i; /* if b is loop header */ if ((b->flags & ZEND_BB_LOOP_HEADER)) { live = live_in + set_size * i; if (!zend_bitset_empty(live, set_size)) { uint32_t set_size2 = zend_bitset_len(op_array->last); zend_bitset_clear(loop_body, set_size2); zend_jit_compute_loop_body(ssa, i, i, loop_body); while (!zend_bitset_empty(loop_body, set_size2)) { uint32_t from = zend_bitset_first(loop_body, set_size2); uint32_t to = from; do { zend_bitset_excl(loop_body, to); to++; } while (zend_bitset_in(loop_body, to)); to--; ZEND_BITSET_FOREACH(live, set_size, j) { if (zend_jit_add_range(intervals, j, from, to) != SUCCESS) { goto failure; } } ZEND_BITSET_FOREACH_END(); } } } } if (JIT_G(opt_flags) & ZEND_JIT_REG_ALLOC_GLOBAL) { /* Register hinting (a cheap way for register coalescing) */ for (i = 0; i < ssa->vars_count; i++) { if (intervals[i]) { int src; if (ssa->vars[i].definition_phi) { zend_ssa_phi *phi = ssa->vars[i].definition_phi; if (phi->pi >= 0) { src = phi->sources[0]; if (intervals[src]) { zend_jit_add_hint(intervals, i, src); } } else { for (k = 0; k < ssa->cfg.blocks[phi->block].predecessors_count; k++) { src = phi->sources[k]; if (src >= 0) { if (ssa->vars[src].definition_phi && ssa->vars[src].definition_phi->pi >= 0 && phi->block == ssa->vars[src].definition_phi->block) { /* Skip zero-length interval for Pi variable */ src = ssa->vars[src].definition_phi->sources[0]; } if (intervals[src]) { zend_jit_add_hint(intervals, i, src); } } } } } } } for (i = 0; i < ssa->vars_count; i++) { if (intervals[i] && !intervals[i]->hint) { if (ssa->vars[i].definition >= 0) { uint32_t line = ssa->vars[i].definition; const zend_op *opline = op_array->opcodes + line; switch (opline->opcode) { case ZEND_QM_ASSIGN: case ZEND_POST_INC: case ZEND_POST_DEC: if (ssa->ops[line].op1_use >= 0 && intervals[ssa->ops[line].op1_use] && (i == ssa->ops[line].op1_def || (i == ssa->ops[line].result_def && (ssa->ops[line].op1_def < 0 || !intervals[ssa->ops[line].op1_def])))) { zend_jit_add_hint(intervals, i, ssa->ops[line].op1_use); } break; case ZEND_SEND_VAR: case ZEND_PRE_INC: case ZEND_PRE_DEC: if (i == ssa->ops[line].op1_def && ssa->ops[line].op1_use >= 0 && intervals[ssa->ops[line].op1_use]) { zend_jit_add_hint(intervals, i, ssa->ops[line].op1_use); } break; case ZEND_ASSIGN: if (ssa->ops[line].op2_use >= 0 && intervals[ssa->ops[line].op2_use] && (i == ssa->ops[line].op2_def || (i == ssa->ops[line].op1_def && (ssa->ops[line].op2_def < 0 || !intervals[ssa->ops[line].op2_def])) || (i == ssa->ops[line].result_def && (ssa->ops[line].op2_def < 0 || !intervals[ssa->ops[line].op2_def]) && (ssa->ops[line].op1_def < 0 || !intervals[ssa->ops[line].op1_def])))) { zend_jit_add_hint(intervals, i, ssa->ops[line].op2_use); } break; case ZEND_SUB: case ZEND_ADD: case ZEND_MUL: case ZEND_BW_OR: case ZEND_BW_AND: case ZEND_BW_XOR: if (i == ssa->ops[line].result_def) { if (ssa->ops[line].op1_use >= 0 && intervals[ssa->ops[line].op1_use] && ssa->ops[line].op1_use_chain < 0 && !ssa->vars[ssa->ops[line].op1_use].phi_use_chain && (ssa->var_info[i].type & MAY_BE_ANY) == (ssa->var_info[ssa->ops[line].op1_use].type & MAY_BE_ANY)) { zend_jit_add_hint(intervals, i, ssa->ops[line].op1_use); } else if (opline->opcode != ZEND_SUB && ssa->ops[line].op2_use >= 0 && intervals[ssa->ops[line].op2_use] && ssa->ops[line].op2_use_chain < 0 && !ssa->vars[ssa->ops[line].op2_use].phi_use_chain && (ssa->var_info[i].type & MAY_BE_ANY) == (ssa->var_info[ssa->ops[line].op2_use].type & MAY_BE_ANY)) { zend_jit_add_hint(intervals, i, ssa->ops[line].op2_use); } } break; } } } } } *list = zend_jit_sort_intervals(intervals, ssa->vars_count); if (*list) { zend_lifetime_interval *ival = *list; while (ival) { if (ival->hint) { ival->hint->used_as_hint = ival; } ival = ival->list_next; } } free_alloca(intervals, use_heap); return SUCCESS; failure: *list = NULL; free_alloca(intervals, use_heap); return FAILURE; } static uint32_t zend_interval_end(zend_lifetime_interval *ival) { zend_life_range *range = &ival->range; while (range->next) { range = range->next; } return range->end; } static bool zend_interval_covers(zend_lifetime_interval *ival, uint32_t position) { zend_life_range *range = &ival->range; do { if (position >= range->start && position <= range->end) { return 1; } range = range->next; } while (range); return 0; } static uint32_t zend_interval_intersection(zend_lifetime_interval *ival1, zend_lifetime_interval *ival2) { zend_life_range *r1 = &ival1->range; zend_life_range *r2 = &ival2->range; do { if (r1->start <= r2->end) { if (r2->start <= r1->end) { return MAX(r1->start, r2->start); } else { r2 = r2->next; } } else { r1 = r1->next; } } while (r1 && r2); return 0xffffffff; } /* See "Optimized Interval Splitting in a Linear Scan Register Allocator", Christian Wimmer VEE'05 (2005), Figure 4. Allocation without spilling */ static int zend_jit_try_allocate_free_reg(const zend_op_array *op_array, const zend_op **ssa_opcodes, zend_ssa *ssa, zend_lifetime_interval *current, zend_regset available, zend_regset *hints, zend_lifetime_interval *active, zend_lifetime_interval *inactive, zend_lifetime_interval **list, zend_lifetime_interval **free) { zend_lifetime_interval *it; uint32_t freeUntilPos[ZREG_NUM]; uint32_t pos, pos2; zend_reg i, reg, reg2; zend_reg hint = ZREG_NONE; zend_regset low_priority_regs; zend_life_range *range; if ((ssa->var_info[current->ssa_var].type & MAY_BE_ANY) == MAY_BE_DOUBLE) { available = ZEND_REGSET_INTERSECTION(available, ZEND_REGSET_FP); } else { available = ZEND_REGSET_INTERSECTION(available, ZEND_REGSET_GP); } /* TODO: Allow usage of preserved registers ??? * Their values have to be stored in prologue and restored in epilogue */ available = ZEND_REGSET_DIFFERENCE(available, ZEND_REGSET_PRESERVED); /* Set freeUntilPos of all physical registers to maxInt */ for (i = 0; i < ZREG_NUM; i++) { freeUntilPos[i] = 0xffffffff; } /* for each interval it in active do */ /* freeUntilPos[it.reg] = 0 */ it = active; if (ssa->vars[current->ssa_var].definition == current->range.start) { while (it) { if (current->range.start != zend_interval_end(it)) { freeUntilPos[it->reg] = 0; } else if (zend_jit_may_reuse_reg( ssa_opcodes ? ssa_opcodes[current->range.start] : op_array->opcodes + current->range.start, ssa->ops + current->range.start, ssa, current->ssa_var, it->ssa_var)) { if (!ZEND_REGSET_IN(*hints, it->reg) && /* TODO: Avoid most often scratch registers. Find a better way ??? */ (!current->used_as_hint || !ZEND_REGSET_IN(ZEND_REGSET_LOW_PRIORITY, it->reg))) { hint = it->reg; } } else { freeUntilPos[it->reg] = 0; } it = it->list_next; } } else { while (it) { freeUntilPos[it->reg] = 0; it = it->list_next; } } if (current->hint) { hint = current->hint->reg; if (hint != ZREG_NONE && current->hint->used_as_hint == current) { ZEND_REGSET_EXCL(*hints, hint); } } if (hint == ZREG_NONE && ZEND_REGSET_IS_EMPTY(available)) { return 0; } /* See "Linear Scan Register Allocation on SSA Form", Christian Wimmer and Michael Franz, CGO'10 (2010), Figure 6. */ if (current->flags & ZREG_SPLIT) { /* for each interval it in inactive intersecting with current do */ /* freeUntilPos[it.reg] = next intersection of it with current */ it = inactive; while (it) { uint32_t next = zend_interval_intersection(current, it); //ZEND_ASSERT(next != 0xffffffff && !current->split); if (next < freeUntilPos[it->reg]) { freeUntilPos[it->reg] = next; } it = it->list_next; } } /* Handle Scratch Registers */ /* TODO: Optimize ??? */ range = ¤t->range; do { uint32_t line = range->start; uint32_t last_use_line = (uint32_t)-1; zend_regset regset; zend_reg reg; if ((current->flags & ZREG_LAST_USE) && !range->next) { last_use_line = range->end; } if (ssa->ops[line].op1_def == current->ssa_var || ssa->ops[line].op2_def == current->ssa_var || ssa->ops[line].result_def == current->ssa_var) { regset = zend_jit_get_def_scratch_regset( ssa_opcodes ? ssa_opcodes[line] : op_array->opcodes + line, ssa->ops + line, op_array, ssa, current->ssa_var, line == last_use_line); ZEND_REGSET_FOREACH(regset, reg) { if (line < freeUntilPos[reg]) { freeUntilPos[reg] = line; } } ZEND_REGSET_FOREACH_END(); line++; } while (line <= range->end) { regset = zend_jit_get_scratch_regset( ssa_opcodes ? ssa_opcodes[line] : op_array->opcodes + line, ssa->ops + line, op_array, ssa, current->ssa_var, line == last_use_line); ZEND_REGSET_FOREACH(regset, reg) { if (line < freeUntilPos[reg]) { freeUntilPos[reg] = line; } } ZEND_REGSET_FOREACH_END(); line++; } range = range->next; } while (range); #if 0 /* Coalescing */ if (ssa->vars[current->ssa_var].definition == current->start) { zend_op *opline = op_array->opcodes + current->start; int hint = -1; switch (opline->opcode) { case ZEND_ASSIGN: hint = ssa->ops[current->start].op2_use; case ZEND_QM_ASSIGN: hint = ssa->ops[current->start].op1_use; break; case ZEND_ADD: case ZEND_SUB: case ZEND_MUL: hint = ssa->ops[current->start].op1_use; break; case ZEND_ASSIGN_OP: if (opline->extended_value == ZEND_ADD || opline->extended_value == ZEND_SUB || opline->extended_value == ZEND_MUL) { hint = ssa->ops[current->start].op1_use; } break; } if (hint >= 0) { } } #endif if (hint != ZREG_NONE && freeUntilPos[hint] > zend_interval_end(current)) { current->reg = hint; if (current->used_as_hint) { ZEND_REGSET_INCL(*hints, hint); } return 1; } if (ZEND_REGSET_IS_EMPTY(available)) { return 0; } pos = 0; reg = ZREG_NONE; pos2 = 0; reg2 = ZREG_NONE; low_priority_regs = *hints; if (current->used_as_hint) { /* TODO: Avoid most often scratch registers. Find a better way ??? */ low_priority_regs = ZEND_REGSET_UNION(low_priority_regs, ZEND_REGSET_LOW_PRIORITY); } ZEND_REGSET_FOREACH(available, i) { if (ZEND_REGSET_IN(low_priority_regs, i)) { if (freeUntilPos[i] > pos2) { reg2 = i; pos2 = freeUntilPos[i]; } } else if (freeUntilPos[i] > pos) { reg = i; pos = freeUntilPos[i]; } } ZEND_REGSET_FOREACH_END(); if (reg == ZREG_NONE) { if (reg2 != ZREG_NONE) { reg = reg2; pos = pos2; reg2 = ZREG_NONE; } } if (reg == ZREG_NONE) { /* no register available without spilling */ return 0; } else if (zend_interval_end(current) < pos) { /* register available for the whole interval */ current->reg = reg; if (current->used_as_hint) { ZEND_REGSET_INCL(*hints, reg); } return 1; #if 0 // TODO: allow low priority register usage } else if (reg2 != ZREG_NONE && zend_interval_end(current) < pos2) { /* register available for the whole interval */ current->reg = reg2; if (current->used_as_hint) { ZEND_REGSET_INCL(*hints, reg2); } return 1; #endif } else { /* TODO: enable interval splitting ??? */ /* register available for the first part of the interval */ if (1 || zend_jit_split_interval(current, pos, list, free) != SUCCESS) { return 0; } current->reg = reg; if (current->used_as_hint) { ZEND_REGSET_INCL(*hints, reg); } return 1; } } /* See "Optimized Interval Splitting in a Linear Scan Register Allocator", Christian Wimmer VEE'05 (2005), Figure 5. Allocation with spilling. and "Linear Scan Register Allocation on SSA Form", Christian Wimmer and Michael Franz, CGO'10 (2010), Figure 6. */ static int zend_jit_allocate_blocked_reg(void) { /* TODO: ??? */ return 0; } /* See "Optimized Interval Splitting in a Linear Scan Register Allocator", Christian Wimmer VEE'10 (2005), Figure 2. */ static zend_lifetime_interval* zend_jit_linear_scan(const zend_op_array *op_array, const zend_op **ssa_opcodes, zend_ssa *ssa, zend_lifetime_interval *list) { zend_lifetime_interval *unhandled, *active, *inactive, *handled, *free; zend_lifetime_interval *current, **p, *q; uint32_t position; zend_regset available = ZEND_REGSET_UNION(ZEND_REGSET_GP, ZEND_REGSET_FP); zend_regset hints = ZEND_REGSET_EMPTY; unhandled = list; /* active = inactive = handled = free = {} */ active = inactive = handled = free = NULL; while (unhandled != NULL) { current = unhandled; unhandled = unhandled->list_next; position = current->range.start; p = &active; while (*p) { uint32_t end = zend_interval_end(*p); q = *p; if (end < position) { /* move ival from active to handled */ ZEND_REGSET_INCL(available, q->reg); *p = q->list_next; q->list_next = handled; handled = q; } else if (!zend_interval_covers(q, position)) { /* move ival from active to inactive */ ZEND_REGSET_INCL(available, q->reg); *p = q->list_next; q->list_next = inactive; inactive = q; } else { p = &q->list_next; } } p = &inactive; while (*p) { uint32_t end = zend_interval_end(*p); q = *p; if (end < position) { /* move ival from inactive to handled */ *p = q->list_next; q->list_next = handled; handled = q; } else if (zend_interval_covers(q, position)) { /* move ival from inactive to active */ ZEND_REGSET_EXCL(available, q->reg); *p = q->list_next; q->list_next = active; active = q; } else { p = &q->list_next; } } if (zend_jit_try_allocate_free_reg(op_array, ssa_opcodes, ssa, current, available, &hints, active, inactive, &unhandled, &free) || zend_jit_allocate_blocked_reg()) { ZEND_REGSET_EXCL(available, current->reg); current->list_next = active; active = current; } else { current->list_next = free; free = current; } } /* move active to handled */ while (active) { current = active; active = active->list_next; current->list_next = handled; handled = current; } /* move inactive to handled */ while (inactive) { current = inactive; inactive = inactive->list_next; current->list_next = handled; handled = current; } return handled; } static void zend_jit_dump_lifetime_interval(const zend_op_array *op_array, const zend_ssa *ssa, const zend_lifetime_interval *ival) { zend_life_range *range; int var_num = ssa->vars[ival->ssa_var].var; fprintf(stderr, "#%d.", ival->ssa_var); zend_dump_var(op_array, (var_num < op_array->last_var ? IS_CV : 0), var_num); fprintf(stderr, ": %u-%u", ival->range.start, ival->range.end); range = ival->range.next; while (range) { fprintf(stderr, ", %u-%u", range->start, range->end); range = range->next; } if (ival->reg != ZREG_NONE) { fprintf(stderr, " (%s)", zend_reg_name[ival->reg]); } if (ival->flags & ZREG_LAST_USE) { fprintf(stderr, " last_use"); } if (ival->flags & ZREG_LOAD) { fprintf(stderr, " load"); } if (ival->flags & ZREG_STORE) { fprintf(stderr, " store"); } if (ival->hint) { fprintf(stderr, " hint"); if (ival->hint->ssa_var >= 0) { var_num = ssa->vars[ival->hint->ssa_var].var; fprintf(stderr, "=#%d.", ival->hint->ssa_var); zend_dump_var(op_array, (var_num < op_array->last_var ? IS_CV : 0), var_num); } if (ival->hint->reg != ZREG_NONE) { fprintf(stderr, " (%s)", zend_reg_name[ival->hint->reg]); } } fprintf(stderr, "\n"); } static zend_lifetime_interval** zend_jit_allocate_registers(const zend_op_array *op_array, zend_ssa *ssa) { void *checkpoint; int set_size, candidates_count, i; zend_bitset candidates = NULL; zend_lifetime_interval *list, *ival; zend_lifetime_interval **intervals; ALLOCA_FLAG(use_heap); if (!ssa->var_info) { return NULL; } /* Identify SSA variables suitable for register allocation */ set_size = zend_bitset_len(ssa->vars_count); candidates = ZEND_BITSET_ALLOCA(set_size, use_heap); if (!candidates) { return NULL; } candidates_count = 0; zend_bitset_clear(candidates, set_size); for (i = 0; i < ssa->vars_count; i++) { if (zend_jit_may_be_in_reg(op_array, ssa, i)) { zend_bitset_incl(candidates, i); candidates_count++; } } if (!candidates_count) { free_alloca(candidates, use_heap); return NULL; } checkpoint = zend_arena_checkpoint(CG(arena)); /* Find life-time intervals */ if (zend_jit_compute_liveness(op_array, ssa, candidates, &list) != SUCCESS) { goto failure; } if (list) { /* Set ZREG_LAST_USE flags */ ival = list; while (ival) { zend_life_range *range = &ival->range; while (range->next) { range = range->next; } if (zend_ssa_is_last_use(op_array, ssa, ival->ssa_var, range->end)) { ival->flags |= ZREG_LAST_USE; } ival = ival->list_next; } } if (list) { if (JIT_G(debug) & ZEND_JIT_DEBUG_REG_ALLOC) { fprintf(stderr, "Live Ranges \"%s\"\n", op_array->function_name ? ZSTR_VAL(op_array->function_name) : "[main]"); ival = list; while (ival) { zend_jit_dump_lifetime_interval(op_array, ssa, ival); ival = ival->list_next; } fprintf(stderr, "\n"); } /* Linear Scan Register Allocation */ list = zend_jit_linear_scan(op_array, NULL, ssa, list); if (list) { intervals = zend_arena_calloc(&CG(arena), ssa->vars_count, sizeof(zend_lifetime_interval*)); if (!intervals) { goto failure; } ival = list; while (ival != NULL) { zend_lifetime_interval *next = ival->list_next; ival->list_next = intervals[ival->ssa_var]; intervals[ival->ssa_var] = ival; ival = next; } if (JIT_G(opt_flags) & ZEND_JIT_REG_ALLOC_GLOBAL) { /* Naive SSA resolution */ for (i = 0; i < ssa->vars_count; i++) { if (ssa->vars[i].definition_phi && !ssa->vars[i].no_val) { zend_ssa_phi *phi = ssa->vars[i].definition_phi; int k, src; if (phi->pi >= 0) { if (!ssa->vars[i].phi_use_chain || ssa->vars[i].phi_use_chain->block != phi->block) { src = phi->sources[0]; if (intervals[i]) { if (!intervals[src]) { intervals[i]->flags |= ZREG_LOAD; } else if (intervals[i]->reg != intervals[src]->reg) { intervals[i]->flags |= ZREG_LOAD; intervals[src]->flags |= ZREG_STORE; } } else if (intervals[src]) { intervals[src]->flags |= ZREG_STORE; } } } else { int need_move = 0; for (k = 0; k < ssa->cfg.blocks[phi->block].predecessors_count; k++) { src = phi->sources[k]; if (src >= 0) { if (ssa->vars[src].definition_phi && ssa->vars[src].definition_phi->pi >= 0 && phi->block == ssa->vars[src].definition_phi->block) { /* Skip zero-length interval for Pi variable */ src = ssa->vars[src].definition_phi->sources[0]; } if (intervals[i]) { if (!intervals[src]) { need_move = 1; } else if (intervals[i]->reg != intervals[src]->reg) { need_move = 1; } } else if (intervals[src]) { need_move = 1; } } } if (need_move) { if (intervals[i]) { intervals[i]->flags |= ZREG_LOAD; } for (k = 0; k < ssa->cfg.blocks[phi->block].predecessors_count; k++) { src = phi->sources[k]; if (src >= 0) { if (ssa->vars[src].definition_phi && ssa->vars[src].definition_phi->pi >= 0 && phi->block == ssa->vars[src].definition_phi->block) { /* Skip zero-length interval for Pi variable */ src = ssa->vars[src].definition_phi->sources[0]; } if (intervals[src]) { intervals[src]->flags |= ZREG_STORE; } } } } } } } /* Remove useless register allocation */ for (i = 0; i < ssa->vars_count; i++) { if (intervals[i] && ((intervals[i]->flags & ZREG_LOAD) || ((intervals[i]->flags & ZREG_STORE) && ssa->vars[i].definition >= 0)) && ssa->vars[i].use_chain < 0) { bool may_remove = 1; zend_ssa_phi *phi = ssa->vars[i].phi_use_chain; while (phi) { if (intervals[phi->ssa_var] && !(intervals[phi->ssa_var]->flags & ZREG_LOAD)) { may_remove = 0; break; } phi = zend_ssa_next_use_phi(ssa, i, phi); } if (may_remove) { intervals[i] = NULL; } } } /* Remove intervals used once */ for (i = 0; i < ssa->vars_count; i++) { if (intervals[i] && (intervals[i]->flags & ZREG_LOAD) && (intervals[i]->flags & ZREG_STORE) && (ssa->vars[i].use_chain < 0 || zend_ssa_next_use(ssa->ops, i, ssa->vars[i].use_chain) < 0)) { bool may_remove = 1; zend_ssa_phi *phi = ssa->vars[i].phi_use_chain; while (phi) { if (intervals[phi->ssa_var] && !(intervals[phi->ssa_var]->flags & ZREG_LOAD)) { may_remove = 0; break; } phi = zend_ssa_next_use_phi(ssa, i, phi); } if (may_remove) { intervals[i] = NULL; } } } } if (JIT_G(debug) & ZEND_JIT_DEBUG_REG_ALLOC) { fprintf(stderr, "Allocated Live Ranges \"%s\"\n", op_array->function_name ? ZSTR_VAL(op_array->function_name) : "[main]"); for (i = 0; i < ssa->vars_count; i++) { ival = intervals[i]; while (ival) { zend_jit_dump_lifetime_interval(op_array, ssa, ival); ival = ival->list_next; } } fprintf(stderr, "\n"); } free_alloca(candidates, use_heap); return intervals; } } failure: zend_arena_release(&CG(arena), checkpoint); free_alloca(candidates, use_heap); return NULL; } static bool zend_jit_next_is_send_result(const zend_op *opline) { if (opline->result_type == IS_TMP_VAR && (opline+1)->opcode == ZEND_SEND_VAL && (opline+1)->op1_type == IS_TMP_VAR && (opline+1)->op2_type != IS_CONST && (opline+1)->op1.var == opline->result.var) { return 1; } return 0; } static bool zend_jit_supported_binary_op(zend_uchar op, uint32_t op1_info, uint32_t op2_info) { if ((op1_info & MAY_BE_UNDEF) || (op2_info & MAY_BE_UNDEF)) { return false; } switch (op) { case ZEND_POW: case ZEND_DIV: // TODO: check for division by zero ??? return false; case ZEND_ADD: case ZEND_SUB: case ZEND_MUL: return (op1_info & (MAY_BE_LONG|MAY_BE_DOUBLE)) && (op2_info & (MAY_BE_LONG|MAY_BE_DOUBLE)); case ZEND_BW_OR: case ZEND_BW_AND: case ZEND_BW_XOR: case ZEND_SL: case ZEND_SR: case ZEND_MOD: return (op1_info & MAY_BE_LONG) && (op2_info & MAY_BE_LONG); case ZEND_CONCAT: return (op1_info & MAY_BE_STRING) && (op2_info & MAY_BE_STRING); EMPTY_SWITCH_DEFAULT_CASE() } } static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op *rt_opline) { int b, i, end; zend_op *opline; dasm_State* dasm_state = NULL; void *handler; int call_level = 0; void *checkpoint = NULL; zend_lifetime_interval **ra = NULL; bool is_terminated = 1; /* previous basic block is terminated by jump */ bool recv_emitted = 0; /* emitted at least one RECV opcode */ zend_uchar smart_branch_opcode; uint32_t target_label, target_label2; uint32_t op1_info, op1_def_info, op2_info, res_info, res_use_info; zend_jit_addr op1_addr, op1_def_addr, op2_addr, op2_def_addr, res_addr; zend_class_entry *ce; bool ce_is_instanceof; bool on_this; if (JIT_G(bisect_limit)) { jit_bisect_pos++; if (jit_bisect_pos >= JIT_G(bisect_limit)) { if (jit_bisect_pos == JIT_G(bisect_limit)) { fprintf(stderr, "Not JITing %s%s%s in %s:%d and after due to jit_bisect_limit\n", op_array->scope ? ZSTR_VAL(op_array->scope->name) : "", op_array->scope ? "::" : "", op_array->function_name ? ZSTR_VAL(op_array->function_name) : "{main}", ZSTR_VAL(op_array->filename), op_array->line_start); } return FAILURE; } } if (JIT_G(opt_flags) & (ZEND_JIT_REG_ALLOC_LOCAL|ZEND_JIT_REG_ALLOC_GLOBAL)) { checkpoint = zend_arena_checkpoint(CG(arena)); ra = zend_jit_allocate_registers(op_array, ssa); } /* mark hidden branch targets */ for (b = 0; b < ssa->cfg.blocks_count; b++) { if (ssa->cfg.blocks[b].flags & ZEND_BB_REACHABLE && ssa->cfg.blocks[b].len > 1) { opline = op_array->opcodes + ssa->cfg.blocks[b].start + ssa->cfg.blocks[b].len - 1; if (opline->opcode == ZEND_DO_FCALL && (opline-1)->opcode == ZEND_NEW) { ssa->cfg.blocks[ssa->cfg.blocks[b].successors[0]].flags |= ZEND_BB_TARGET; } } } dasm_init(&dasm_state, DASM_MAXSECTION); dasm_setupglobal(&dasm_state, dasm_labels, zend_lb_MAX); dasm_setup(&dasm_state, dasm_actions); dasm_growpc(&dasm_state, ssa->cfg.blocks_count * 2 + 1); zend_jit_align_func(&dasm_state); for (b = 0; b < ssa->cfg.blocks_count; b++) { if ((ssa->cfg.blocks[b].flags & ZEND_BB_REACHABLE) == 0) { continue; } //#ifndef CONTEXT_THREADED_JIT if (ssa->cfg.blocks[b].flags & ZEND_BB_ENTRY) { if (ssa->cfg.blocks[b].flags & ZEND_BB_TARGET) { /* pass */ } else if (JIT_G(opt_level) < ZEND_JIT_LEVEL_INLINE && ssa->cfg.blocks[b].len == 1 && (ssa->cfg.blocks[b].flags & ZEND_BB_EXIT) && op_array->opcodes[ssa->cfg.blocks[b].start].opcode != ZEND_JMP) { /* don't generate code for BB with single opcode */ continue; } if (ssa->cfg.blocks[b].flags & ZEND_BB_FOLLOW) { if (!is_terminated) { zend_jit_jmp(&dasm_state, b); } } zend_jit_label(&dasm_state, ssa->cfg.blocks_count + b); zend_jit_prologue(&dasm_state); } else //#endif if (ssa->cfg.blocks[b].flags & (ZEND_BB_START|ZEND_BB_RECV_ENTRY)) { opline = op_array->opcodes + ssa->cfg.blocks[b].start; if (ssa->cfg.flags & ZEND_CFG_RECV_ENTRY) { if (opline->opcode == ZEND_RECV_INIT) { if (opline == op_array->opcodes || (opline-1)->opcode != ZEND_RECV_INIT) { if (recv_emitted) { zend_jit_jmp(&dasm_state, b); } zend_jit_label(&dasm_state, ssa->cfg.blocks_count + b); for (i = 1; (opline+i)->opcode == ZEND_RECV_INIT; i++) { zend_jit_label(&dasm_state, ssa->cfg.blocks_count + b + i); } zend_jit_prologue(&dasm_state); } recv_emitted = 1; } else if (opline->opcode == ZEND_RECV) { if (!(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) { /* skip */ continue; } else if (recv_emitted) { zend_jit_jmp(&dasm_state, b); zend_jit_label(&dasm_state, ssa->cfg.blocks_count + b); zend_jit_prologue(&dasm_state); } else { zend_arg_info *arg_info; if (opline->op1.num <= op_array->num_args) { arg_info = &op_array->arg_info[opline->op1.num - 1]; } else if (op_array->fn_flags & ZEND_ACC_VARIADIC) { arg_info = &op_array->arg_info[op_array->num_args]; } else { /* skip */ continue; } if (!ZEND_TYPE_IS_SET(arg_info->type)) { /* skip */ continue; } zend_jit_label(&dasm_state, ssa->cfg.blocks_count + b); zend_jit_prologue(&dasm_state); recv_emitted = 1; } } else { if (recv_emitted) { zend_jit_jmp(&dasm_state, b); } else if (JIT_G(opt_level) < ZEND_JIT_LEVEL_INLINE && ssa->cfg.blocks[b].len == 1 && (ssa->cfg.blocks[b].flags & ZEND_BB_EXIT)) { /* don't generate code for BB with single opcode */ dasm_free(&dasm_state); if (JIT_G(opt_flags) & (ZEND_JIT_REG_ALLOC_LOCAL|ZEND_JIT_REG_ALLOC_GLOBAL)) { zend_arena_release(&CG(arena), checkpoint); } return SUCCESS; } zend_jit_label(&dasm_state, ssa->cfg.blocks_count + b); zend_jit_prologue(&dasm_state); recv_emitted = 1; } } else if (JIT_G(opt_level) < ZEND_JIT_LEVEL_INLINE && ssa->cfg.blocks[b].len == 1 && (ssa->cfg.blocks[b].flags & ZEND_BB_EXIT)) { /* don't generate code for BB with single opcode */ dasm_free(&dasm_state); if (JIT_G(opt_flags) & (ZEND_JIT_REG_ALLOC_LOCAL|ZEND_JIT_REG_ALLOC_GLOBAL)) { zend_arena_release(&CG(arena), checkpoint); } return SUCCESS; } else { zend_jit_label(&dasm_state, ssa->cfg.blocks_count + b); zend_jit_prologue(&dasm_state); } } is_terminated = 0; zend_jit_label(&dasm_state, b); if (JIT_G(opt_level) < ZEND_JIT_LEVEL_INLINE) { if ((ssa->cfg.blocks[b].flags & ZEND_BB_FOLLOW) && ssa->cfg.blocks[b].start != 0 && (op_array->opcodes[ssa->cfg.blocks[b].start - 1].opcode == ZEND_NOP || op_array->opcodes[ssa->cfg.blocks[b].start - 1].opcode == ZEND_SWITCH_LONG || op_array->opcodes[ssa->cfg.blocks[b].start - 1].opcode == ZEND_SWITCH_STRING || op_array->opcodes[ssa->cfg.blocks[b].start - 1].opcode == ZEND_MATCH)) { zend_jit_reset_last_valid_opline(); if (!zend_jit_set_ip(&dasm_state, op_array->opcodes + ssa->cfg.blocks[b].start)) { goto jit_failure; } } else { zend_jit_set_last_valid_opline(op_array->opcodes + ssa->cfg.blocks[b].start); } } else if (ssa->cfg.blocks[b].flags & ZEND_BB_TARGET) { zend_jit_reset_last_valid_opline(); } else if (ssa->cfg.blocks[b].flags & (ZEND_BB_START|ZEND_BB_RECV_ENTRY|ZEND_BB_ENTRY)) { zend_jit_set_last_valid_opline(op_array->opcodes + ssa->cfg.blocks[b].start); } if (ssa->cfg.blocks[b].flags & ZEND_BB_LOOP_HEADER) { if (!zend_jit_check_timeout(&dasm_state, op_array->opcodes + ssa->cfg.blocks[b].start, NULL)) { goto jit_failure; } } if (!ssa->cfg.blocks[b].len) { continue; } if ((JIT_G(opt_flags) & ZEND_JIT_REG_ALLOC_GLOBAL) && ra) { zend_ssa_phi *phi = ssa->blocks[b].phis; while (phi) { zend_lifetime_interval *ival = ra[phi->ssa_var]; if (ival) { if (ival->flags & ZREG_LOAD) { ZEND_ASSERT(ival->reg != ZREG_NONE); if (!zend_jit_load_var(&dasm_state, ssa->var_info[phi->ssa_var].type, ssa->vars[phi->ssa_var].var, ival->reg)) { goto jit_failure; } } else if (ival->flags & ZREG_STORE) { ZEND_ASSERT(ival->reg != ZREG_NONE); if (!zend_jit_store_var(&dasm_state, ssa->var_info[phi->ssa_var].type, ssa->vars[phi->ssa_var].var, ival->reg, 1)) { goto jit_failure; } } } phi = phi->next; } } end = ssa->cfg.blocks[b].start + ssa->cfg.blocks[b].len - 1; for (i = ssa->cfg.blocks[b].start; i <= end; i++) { zend_ssa_op *ssa_op = ssa->ops ? &ssa->ops[i] : NULL; opline = op_array->opcodes + i; switch (opline->opcode) { case ZEND_INIT_FCALL: case ZEND_INIT_FCALL_BY_NAME: case ZEND_INIT_NS_FCALL_BY_NAME: case ZEND_INIT_METHOD_CALL: case ZEND_INIT_DYNAMIC_CALL: case ZEND_INIT_STATIC_METHOD_CALL: case ZEND_INIT_USER_CALL: case ZEND_NEW: call_level++; } if (JIT_G(opt_level) >= ZEND_JIT_LEVEL_INLINE) { switch (opline->opcode) { case ZEND_PRE_INC: case ZEND_PRE_DEC: case ZEND_POST_INC: case ZEND_POST_DEC: if (opline->op1_type != IS_CV) { break; } op1_info = OP1_INFO(); if (!(op1_info & MAY_BE_LONG)) { break; } if (opline->result_type != IS_UNUSED) { res_use_info = -1; if (opline->result_type == IS_CV && ssa->vars && ssa_op->result_use >= 0 && !ssa->vars[ssa_op->result_use].no_val) { zend_jit_addr res_use_addr = RES_USE_REG_ADDR(); if (Z_MODE(res_use_addr) != IS_REG || Z_LOAD(res_use_addr) || Z_STORE(res_use_addr)) { res_use_info = RES_USE_INFO(); } } res_info = RES_INFO(); res_addr = RES_REG_ADDR(); } else { res_use_info = -1; res_info = -1; res_addr = 0; } op1_def_info = OP1_DEF_INFO(); if (!zend_jit_inc_dec(&dasm_state, opline, op1_info, OP1_REG_ADDR(), op1_def_info, OP1_DEF_REG_ADDR(), res_use_info, res_info, res_addr, (op1_info & MAY_BE_LONG) && (op1_def_info & MAY_BE_DOUBLE) && zend_may_overflow(opline, ssa_op, op_array, ssa), zend_may_throw(opline, ssa_op, op_array, ssa))) { goto jit_failure; } goto done; case ZEND_BW_OR: case ZEND_BW_AND: case ZEND_BW_XOR: case ZEND_SL: case ZEND_SR: case ZEND_MOD: if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) { break; } op1_info = OP1_INFO(); op2_info = OP2_INFO(); if ((op1_info & MAY_BE_UNDEF) || (op2_info & MAY_BE_UNDEF)) { break; } if (!(op1_info & MAY_BE_LONG) || !(op2_info & MAY_BE_LONG)) { break; } res_addr = RES_REG_ADDR(); if (Z_MODE(res_addr) != IS_REG && (i + 1) <= end && zend_jit_next_is_send_result(opline)) { i++; res_use_info = -1; res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_RX, (opline+1)->result.var); if (!zend_jit_reuse_ip(&dasm_state)) { goto jit_failure; } } else { res_use_info = -1; if (opline->result_type == IS_CV && ssa->vars && ssa_op->result_use >= 0 && !ssa->vars[ssa_op->result_use].no_val) { zend_jit_addr res_use_addr = RES_USE_REG_ADDR(); if (Z_MODE(res_use_addr) != IS_REG || Z_LOAD(res_use_addr) || Z_STORE(res_use_addr)) { res_use_info = RES_USE_INFO(); } } } if (!zend_jit_long_math(&dasm_state, opline, op1_info, OP1_RANGE(), OP1_REG_ADDR(), op2_info, OP2_RANGE(), OP2_REG_ADDR(), res_use_info, RES_INFO(), res_addr, zend_may_throw(opline, ssa_op, op_array, ssa))) { goto jit_failure; } goto done; case ZEND_ADD: case ZEND_SUB: case ZEND_MUL: // case ZEND_DIV: // TODO: check for division by zero ??? if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) { break; } op1_info = OP1_INFO(); op2_info = OP2_INFO(); if ((op1_info & MAY_BE_UNDEF) || (op2_info & MAY_BE_UNDEF)) { break; } if (opline->opcode == ZEND_ADD && (op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY && (op2_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY) { /* pass */ } else if (!(op1_info & (MAY_BE_LONG|MAY_BE_DOUBLE)) || !(op2_info & (MAY_BE_LONG|MAY_BE_DOUBLE))) { break; } res_addr = RES_REG_ADDR(); if (Z_MODE(res_addr) != IS_REG && (i + 1) <= end && zend_jit_next_is_send_result(opline)) { i++; res_use_info = -1; res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_RX, (opline+1)->result.var); if (!zend_jit_reuse_ip(&dasm_state)) { goto jit_failure; } } else { res_use_info = -1; if (opline->result_type == IS_CV && ssa->vars && ssa_op->result_use >= 0 && !ssa->vars[ssa_op->result_use].no_val) { zend_jit_addr res_use_addr = RES_USE_REG_ADDR(); if (Z_MODE(res_use_addr) != IS_REG || Z_LOAD(res_use_addr) || Z_STORE(res_use_addr)) { res_use_info = RES_USE_INFO(); } } } res_info = RES_INFO(); if (opline->opcode == ZEND_ADD && (op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY && (op2_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY) { if (!zend_jit_add_arrays(&dasm_state, opline, op1_info, OP1_REG_ADDR(), op2_info, OP2_REG_ADDR(), res_addr)) { goto jit_failure; } } else { if (!zend_jit_math(&dasm_state, opline, op1_info, OP1_REG_ADDR(), op2_info, OP2_REG_ADDR(), res_use_info, res_info, res_addr, (op1_info & MAY_BE_LONG) && (op2_info & MAY_BE_LONG) && (res_info & MAY_BE_DOUBLE) && zend_may_overflow(opline, ssa_op, op_array, ssa), zend_may_throw(opline, ssa_op, op_array, ssa))) { goto jit_failure; } } goto done; case ZEND_CONCAT: case ZEND_FAST_CONCAT: if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) { break; } op1_info = OP1_INFO(); op2_info = OP2_INFO(); if ((op1_info & MAY_BE_UNDEF) || (op2_info & MAY_BE_UNDEF)) { break; } if (!(op1_info & MAY_BE_STRING) || !(op2_info & MAY_BE_STRING)) { break; } res_addr = RES_REG_ADDR(); if ((i + 1) <= end && zend_jit_next_is_send_result(opline)) { i++; res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_RX, (opline+1)->result.var); if (!zend_jit_reuse_ip(&dasm_state)) { goto jit_failure; } } if (!zend_jit_concat(&dasm_state, opline, op1_info, op2_info, res_addr, zend_may_throw(opline, ssa_op, op_array, ssa))) { goto jit_failure; } goto done; case ZEND_ASSIGN_OP: if (opline->op1_type != IS_CV || opline->result_type != IS_UNUSED) { break; } if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) { break; } op1_info = OP1_INFO(); op2_info = OP2_INFO(); if (!zend_jit_supported_binary_op( opline->extended_value, op1_info, op2_info)) { break; } op1_def_info = OP1_DEF_INFO(); if (!zend_jit_assign_op(&dasm_state, opline, op1_info, op1_def_info, OP1_RANGE(), op2_info, OP2_RANGE(), (op1_info & MAY_BE_LONG) && (op2_info & MAY_BE_LONG) && (op1_def_info & MAY_BE_DOUBLE) && zend_may_overflow(opline, ssa_op, op_array, ssa), zend_may_throw(opline, ssa_op, op_array, ssa))) { goto jit_failure; } goto done; case ZEND_ASSIGN_DIM_OP: if (opline->op1_type != IS_CV || opline->result_type != IS_UNUSED) { break; } if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) { break; } if (!zend_jit_supported_binary_op( opline->extended_value, MAY_BE_ANY, OP1_DATA_INFO())) { break; } if (!zend_jit_assign_dim_op(&dasm_state, opline, OP1_INFO(), OP1_DEF_INFO(), OP1_REG_ADDR(), OP2_INFO(), OP1_DATA_INFO(), OP1_DATA_RANGE(), IS_UNKNOWN, zend_may_throw(opline, ssa_op, op_array, ssa))) { goto jit_failure; } goto done; case ZEND_ASSIGN_DIM: if (opline->op1_type != IS_CV) { break; } if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) { break; } if (!zend_jit_assign_dim(&dasm_state, opline, OP1_INFO(), OP1_REG_ADDR(), OP2_INFO(), OP1_DATA_INFO(), IS_UNKNOWN, zend_may_throw(opline, ssa_op, op_array, ssa))) { goto jit_failure; } goto done; case ZEND_PRE_INC_OBJ: case ZEND_PRE_DEC_OBJ: case ZEND_POST_INC_OBJ: case ZEND_POST_DEC_OBJ: if (opline->op2_type != IS_CONST || Z_TYPE_P(RT_CONSTANT(opline, opline->op2)) != IS_STRING || Z_STRVAL_P(RT_CONSTANT(opline, opline->op2))[0] == '\0') { break; } if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) { break; } ce = NULL; ce_is_instanceof = 0; on_this = 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; on_this = 1; } 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_incdec_obj(&dasm_state, opline, op_array, ssa, ssa_op, op1_info, op1_addr, 0, ce, ce_is_instanceof, on_this, 0, NULL, IS_UNKNOWN)) { goto jit_failure; } goto done; case ZEND_ASSIGN_OBJ_OP: if (opline->result_type != IS_UNUSED) { break; } if (opline->op2_type != IS_CONST || Z_TYPE_P(RT_CONSTANT(opline, opline->op2)) != IS_STRING || Z_STRVAL_P(RT_CONSTANT(opline, opline->op2))[0] == '\0') { break; } if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) { break; } if (!zend_jit_supported_binary_op( opline->extended_value, MAY_BE_ANY, OP1_DATA_INFO())) { break; } ce = NULL; ce_is_instanceof = 0; on_this = 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; on_this = 1; } 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_assign_obj_op(&dasm_state, opline, op_array, ssa, ssa_op, op1_info, op1_addr, OP1_DATA_INFO(), OP1_DATA_RANGE(), 0, ce, ce_is_instanceof, on_this, 0, NULL, IS_UNKNOWN)) { goto jit_failure; } goto done; case ZEND_ASSIGN_OBJ: if (opline->op2_type != IS_CONST || Z_TYPE_P(RT_CONSTANT(opline, opline->op2)) != IS_STRING || Z_STRVAL_P(RT_CONSTANT(opline, opline->op2))[0] == '\0') { break; } if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) { break; } ce = NULL; ce_is_instanceof = 0; on_this = 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; on_this = 1; } 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_assign_obj(&dasm_state, opline, op_array, ssa, ssa_op, op1_info, op1_addr, OP1_DATA_INFO(), 0, ce, ce_is_instanceof, on_this, 0, NULL, IS_UNKNOWN, zend_may_throw(opline, ssa_op, op_array, ssa))) { goto jit_failure; } goto done; case ZEND_ASSIGN: if (opline->op1_type != IS_CV) { break; } if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) { break; } op2_addr = OP2_REG_ADDR(); if (ra && ssa->ops[opline - op_array->opcodes].op2_def >= 0 && !ssa->vars[ssa->ops[opline - op_array->opcodes].op2_def].no_val) { op2_def_addr = OP2_DEF_REG_ADDR(); } else { op2_def_addr = op2_addr; } op1_info = OP1_INFO(); if (ra && ssa->vars[ssa_op->op1_use].no_val) { op1_info |= MAY_BE_UNDEF; // requres type assignment } if (opline->result_type == IS_UNUSED) { res_addr = 0; res_info = -1; } else { res_addr = RES_REG_ADDR(); res_info = RES_INFO(); if (Z_MODE(res_addr) != IS_REG && (i + 1) <= end && zend_jit_next_is_send_result(opline) && (!(op1_info & MAY_HAVE_DTOR) || !(op1_info & MAY_BE_RC1))) { i++; res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_RX, (opline+1)->result.var); if (!zend_jit_reuse_ip(&dasm_state)) { goto jit_failure; } } } if (!zend_jit_assign(&dasm_state, opline, op1_info, OP1_REG_ADDR(), OP1_DEF_INFO(), OP1_DEF_REG_ADDR(), OP2_INFO(), op2_addr, op2_def_addr, res_info, res_addr, zend_may_throw(opline, ssa_op, op_array, ssa))) { goto jit_failure; } goto done; case ZEND_QM_ASSIGN: op1_addr = OP1_REG_ADDR(); if (ra && ssa->ops[opline - op_array->opcodes].op1_def >= 0 && !ssa->vars[ssa->ops[opline - op_array->opcodes].op1_def].no_val) { op1_def_addr = OP1_DEF_REG_ADDR(); } else { op1_def_addr = op1_addr; } if (!zend_jit_qm_assign(&dasm_state, opline, OP1_INFO(), op1_addr, op1_def_addr, -1, RES_INFO(), RES_REG_ADDR())) { goto jit_failure; } goto done; case ZEND_INIT_FCALL: case ZEND_INIT_FCALL_BY_NAME: case ZEND_INIT_NS_FCALL_BY_NAME: if (!zend_jit_init_fcall(&dasm_state, opline, b, op_array, ssa, ssa_op, call_level, NULL, 0)) { goto jit_failure; } goto done; case ZEND_SEND_VAL: case ZEND_SEND_VAL_EX: if (opline->op2_type == IS_CONST) { /* Named parameters not supported in JIT (yet) */ break; } if (opline->opcode == ZEND_SEND_VAL_EX && opline->op2.num > MAX_ARG_FLAG_NUM) { break; } if (!zend_jit_send_val(&dasm_state, opline, OP1_INFO(), OP1_REG_ADDR())) { goto jit_failure; } goto done; case ZEND_SEND_REF: if (opline->op2_type == IS_CONST) { /* Named parameters not supported in JIT (yet) */ break; } if (!zend_jit_send_ref(&dasm_state, opline, op_array, OP1_INFO(), 0)) { goto jit_failure; } goto done; case ZEND_SEND_VAR: case ZEND_SEND_VAR_EX: case ZEND_SEND_VAR_NO_REF: case ZEND_SEND_VAR_NO_REF_EX: case ZEND_SEND_FUNC_ARG: if (opline->op2_type == IS_CONST) { /* Named parameters not supported in JIT (yet) */ break; } if ((opline->opcode == ZEND_SEND_VAR_EX || opline->opcode == ZEND_SEND_VAR_NO_REF_EX) && opline->op2.num > MAX_ARG_FLAG_NUM) { break; } op1_addr = OP1_REG_ADDR(); if (ra && ssa->ops[opline - op_array->opcodes].op1_def >= 0 && !ssa->vars[ssa->ops[opline - op_array->opcodes].op1_def].no_val) { op1_def_addr = OP1_DEF_REG_ADDR(); } else { op1_def_addr = op1_addr; } if (!zend_jit_send_var(&dasm_state, opline, op_array, OP1_INFO(), op1_addr, op1_def_addr)) { goto jit_failure; } goto done; case ZEND_CHECK_FUNC_ARG: if (opline->op2_type == IS_CONST) { /* Named parameters not supported in JIT (yet) */ break; } if (opline->op2.num > MAX_ARG_FLAG_NUM) { break; } if (!zend_jit_check_func_arg(&dasm_state, opline)) { goto jit_failure; } goto done; case ZEND_CHECK_UNDEF_ARGS: if (!zend_jit_check_undef_args(&dasm_state, opline)) { goto jit_failure; } goto done; case ZEND_DO_UCALL: is_terminated = 1; ZEND_FALLTHROUGH; case ZEND_DO_ICALL: case ZEND_DO_FCALL_BY_NAME: case ZEND_DO_FCALL: if (!zend_jit_do_fcall(&dasm_state, opline, op_array, ssa, call_level, b + 1, NULL)) { goto jit_failure; } goto done; case ZEND_IS_EQUAL: case ZEND_IS_NOT_EQUAL: case ZEND_IS_SMALLER: case ZEND_IS_SMALLER_OR_EQUAL: case ZEND_CASE: { res_addr = RES_REG_ADDR(); if ((opline->result_type & IS_TMP_VAR) && (i + 1) <= end && ((opline+1)->opcode == ZEND_JMPZ || (opline+1)->opcode == ZEND_JMPNZ || (opline+1)->opcode == ZEND_JMPZ_EX || (opline+1)->opcode == ZEND_JMPNZ_EX) && (opline+1)->op1_type == IS_TMP_VAR && (opline+1)->op1.var == opline->result.var) { i++; smart_branch_opcode = (opline+1)->opcode; target_label = ssa->cfg.blocks[b].successors[0]; target_label2 = ssa->cfg.blocks[b].successors[1]; /* For EX variant write into the result of EX opcode. */ if ((opline+1)->opcode == ZEND_JMPZ_EX || (opline+1)->opcode == ZEND_JMPNZ_EX) { res_addr = OP_REG_ADDR(opline + 1, result_type, result, result_def); } } else { smart_branch_opcode = 0; target_label = target_label2 = (uint32_t)-1; } if (!zend_jit_cmp(&dasm_state, opline, OP1_INFO(), OP1_RANGE(), OP1_REG_ADDR(), OP2_INFO(), OP2_RANGE(), OP2_REG_ADDR(), res_addr, zend_may_throw(opline, ssa_op, op_array, ssa), smart_branch_opcode, target_label, target_label2, NULL, 0)) { goto jit_failure; } goto done; } case ZEND_IS_IDENTICAL: case ZEND_IS_NOT_IDENTICAL: case ZEND_CASE_STRICT: if ((opline->result_type & IS_TMP_VAR) && (i + 1) <= end && ((opline+1)->opcode == ZEND_JMPZ || (opline+1)->opcode == ZEND_JMPNZ) && (opline+1)->op1_type == IS_TMP_VAR && (opline+1)->op1.var == opline->result.var) { i++; smart_branch_opcode = (opline+1)->opcode; target_label = ssa->cfg.blocks[b].successors[0]; target_label2 = ssa->cfg.blocks[b].successors[1]; } else { smart_branch_opcode = 0; target_label = target_label2 = (uint32_t)-1; } if (!zend_jit_identical(&dasm_state, opline, OP1_INFO(), OP1_RANGE(), OP1_REG_ADDR(), OP2_INFO(), OP2_RANGE(), OP2_REG_ADDR(), RES_REG_ADDR(), zend_may_throw(opline, ssa_op, op_array, ssa), smart_branch_opcode, target_label, target_label2, NULL, 0)) { goto jit_failure; } goto done; case ZEND_DEFINED: if ((opline->result_type & IS_TMP_VAR) && (i + 1) <= end && ((opline+1)->opcode == ZEND_JMPZ || (opline+1)->opcode == ZEND_JMPNZ) && (opline+1)->op1_type == IS_TMP_VAR && (opline+1)->op1.var == opline->result.var) { i++; smart_branch_opcode = (opline+1)->opcode; target_label = ssa->cfg.blocks[b].successors[0]; target_label2 = ssa->cfg.blocks[b].successors[1]; } else { smart_branch_opcode = 0; target_label = target_label2 = (uint32_t)-1; } if (!zend_jit_defined(&dasm_state, opline, smart_branch_opcode, target_label, target_label2, NULL)) { goto jit_failure; } goto done; case ZEND_TYPE_CHECK: if (opline->extended_value == MAY_BE_RESOURCE) { // TODO: support for is_resource() ??? break; } if ((opline->result_type & IS_TMP_VAR) && (i + 1) <= end && ((opline+1)->opcode == ZEND_JMPZ || (opline+1)->opcode == ZEND_JMPNZ) && (opline+1)->op1_type == IS_TMP_VAR && (opline+1)->op1.var == opline->result.var) { i++; smart_branch_opcode = (opline+1)->opcode; target_label = ssa->cfg.blocks[b].successors[0]; target_label2 = ssa->cfg.blocks[b].successors[1]; } else { smart_branch_opcode = 0; target_label = target_label2 = (uint32_t)-1; } if (!zend_jit_type_check(&dasm_state, opline, OP1_INFO(), smart_branch_opcode, target_label, target_label2, NULL)) { goto jit_failure; } goto done; case ZEND_RETURN: op1_info = OP1_INFO(); if ((PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) || op_array->type == ZEND_EVAL_CODE // TODO: support for top-level code || !op_array->function_name // TODO: support for IS_UNDEF ??? || (op1_info & MAY_BE_UNDEF)) { if (!zend_jit_tail_handler(&dasm_state, opline)) { goto jit_failure; } } else { int j; bool left_frame = 0; if (!zend_jit_return(&dasm_state, opline, op_array, op1_info, OP1_REG_ADDR())) { goto jit_failure; } if (jit_return_label >= 0) { if (!zend_jit_jmp(&dasm_state, jit_return_label)) { goto jit_failure; } goto done; } jit_return_label = ssa->cfg.blocks_count * 2; if (!zend_jit_label(&dasm_state, jit_return_label)) { goto jit_failure; } if (op_array->last_var > 100) { /* To many CVs to unroll */ if (!zend_jit_free_cvs(&dasm_state)) { goto jit_failure; } left_frame = 1; } if (!left_frame) { for (j = 0 ; j < op_array->last_var; j++) { uint32_t info = zend_ssa_cv_info(op_array, ssa, j); if (info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) { if (!left_frame) { left_frame = 1; if (!zend_jit_leave_frame(&dasm_state)) { goto jit_failure; } } if (!zend_jit_free_cv(&dasm_state, info, j)) { goto jit_failure; } } } } if (!zend_jit_leave_func(&dasm_state, op_array, opline, op1_info, left_frame, NULL, NULL, (ssa->cfg.flags & ZEND_FUNC_INDIRECT_VAR_ACCESS) != 0, 1)) { goto jit_failure; } } goto done; case ZEND_BOOL: case ZEND_BOOL_NOT: if (!zend_jit_bool_jmpznz(&dasm_state, opline, OP1_INFO(), OP1_REG_ADDR(), RES_REG_ADDR(), -1, -1, zend_may_throw(opline, ssa_op, op_array, ssa), opline->opcode, NULL)) { goto jit_failure; } goto done; case ZEND_JMPZ: case ZEND_JMPNZ: if (opline > op_array->opcodes + ssa->cfg.blocks[b].start && ((opline-1)->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0) { /* smart branch */ if (!zend_jit_cond_jmp(&dasm_state, opline + 1, ssa->cfg.blocks[b].successors[0])) { goto jit_failure; } goto done; } ZEND_FALLTHROUGH; case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: if (opline->result_type == IS_UNDEF) { res_addr = 0; } else { res_addr = RES_REG_ADDR(); } if (!zend_jit_bool_jmpznz(&dasm_state, opline, OP1_INFO(), OP1_REG_ADDR(), res_addr, ssa->cfg.blocks[b].successors[0], ssa->cfg.blocks[b].successors[1], zend_may_throw(opline, ssa_op, op_array, ssa), opline->opcode, NULL)) { goto jit_failure; } goto done; case ZEND_ISSET_ISEMPTY_CV: if ((opline->extended_value & ZEND_ISEMPTY)) { // TODO: support for empty() ??? break; } if ((opline->result_type & IS_TMP_VAR) && (i + 1) <= end && ((opline+1)->opcode == ZEND_JMPZ || (opline+1)->opcode == ZEND_JMPNZ) && (opline+1)->op1_type == IS_TMP_VAR && (opline+1)->op1.var == opline->result.var) { i++; smart_branch_opcode = (opline+1)->opcode; target_label = ssa->cfg.blocks[b].successors[0]; target_label2 = ssa->cfg.blocks[b].successors[1]; } else { smart_branch_opcode = 0; target_label = target_label2 = (uint32_t)-1; } if (!zend_jit_isset_isempty_cv(&dasm_state, opline, OP1_INFO(), OP1_REG_ADDR(), smart_branch_opcode, target_label, target_label2, NULL)) { goto jit_failure; } goto done; case ZEND_IN_ARRAY: if (opline->op1_type == IS_VAR || opline->op1_type == IS_TMP_VAR) { break; } op1_info = OP1_INFO(); if ((op1_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) != MAY_BE_STRING) { break; } if ((opline->result_type & IS_TMP_VAR) && (i + 1) <= end && ((opline+1)->opcode == ZEND_JMPZ || (opline+1)->opcode == ZEND_JMPNZ) && (opline+1)->op1_type == IS_TMP_VAR && (opline+1)->op1.var == opline->result.var) { i++; smart_branch_opcode = (opline+1)->opcode; target_label = ssa->cfg.blocks[b].successors[0]; target_label2 = ssa->cfg.blocks[b].successors[1]; } else { smart_branch_opcode = 0; target_label = target_label2 = (uint32_t)-1; } if (!zend_jit_in_array(&dasm_state, opline, op1_info, OP1_REG_ADDR(), smart_branch_opcode, target_label, target_label2, NULL)) { goto jit_failure; } goto done; case ZEND_FETCH_DIM_R: case ZEND_FETCH_DIM_IS: case ZEND_FETCH_LIST_R: if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) { break; } if (!zend_jit_fetch_dim_read(&dasm_state, opline, ssa, ssa_op, OP1_INFO(), OP1_REG_ADDR(), 0, OP2_INFO(), RES_INFO(), RES_REG_ADDR(), IS_UNKNOWN)) { goto jit_failure; } goto done; case ZEND_FETCH_DIM_W: case ZEND_FETCH_DIM_RW: // case ZEND_FETCH_DIM_UNSET: case ZEND_FETCH_LIST_W: if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) { break; } if (opline->op1_type != IS_CV) { break; } if (!zend_jit_fetch_dim(&dasm_state, opline, OP1_INFO(), OP1_REG_ADDR(), OP2_INFO(), RES_REG_ADDR(), IS_UNKNOWN)) { goto jit_failure; } goto done; case ZEND_ISSET_ISEMPTY_DIM_OBJ: if ((opline->extended_value & ZEND_ISEMPTY)) { // TODO: support for empty() ??? break; } if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) { break; } if ((opline->result_type & IS_TMP_VAR) && (i + 1) <= end && ((opline+1)->opcode == ZEND_JMPZ || (opline+1)->opcode == ZEND_JMPNZ) && (opline+1)->op1_type == IS_TMP_VAR && (opline+1)->op1.var == opline->result.var) { i++; smart_branch_opcode = (opline+1)->opcode; target_label = ssa->cfg.blocks[b].successors[0]; target_label2 = ssa->cfg.blocks[b].successors[1]; } else { smart_branch_opcode = 0; target_label = target_label2 = (uint32_t)-1; } if (!zend_jit_isset_isempty_dim(&dasm_state, opline, OP1_INFO(), OP1_REG_ADDR(), 0, OP2_INFO(), IS_UNKNOWN, zend_may_throw(opline, ssa_op, op_array, ssa), smart_branch_opcode, target_label, target_label2, NULL)) { goto jit_failure; } goto done; case ZEND_FETCH_OBJ_R: case ZEND_FETCH_OBJ_IS: case ZEND_FETCH_OBJ_W: if (opline->op2_type != IS_CONST || Z_TYPE_P(RT_CONSTANT(opline, opline->op2)) != IS_STRING || Z_STRVAL_P(RT_CONSTANT(opline, opline->op2))[0] == '\0') { break; } ce = NULL; ce_is_instanceof = 0; on_this = 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; on_this = 1; } 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_fetch_obj(&dasm_state, opline, op_array, ssa, ssa_op, op1_info, op1_addr, 0, ce, ce_is_instanceof, on_this, 0, 0, NULL, IS_UNKNOWN, zend_may_throw(opline, ssa_op, op_array, ssa))) { goto jit_failure; } goto done; case ZEND_BIND_GLOBAL: if (!ssa->ops || !ssa->var_info) { op1_info = MAY_BE_ANY|MAY_BE_REF; } else { op1_info = OP1_INFO(); } if (!zend_jit_bind_global(&dasm_state, opline, op1_info)) { goto jit_failure; } goto done; case ZEND_RECV: if (!zend_jit_recv(&dasm_state, opline, op_array)) { goto jit_failure; } goto done; case ZEND_RECV_INIT: if (!zend_jit_recv_init(&dasm_state, opline, op_array, (opline + 1)->opcode != ZEND_RECV_INIT, zend_may_throw(opline, ssa_op, op_array, ssa))) { goto jit_failure; } goto done; case ZEND_FREE: case ZEND_FE_FREE: if (!zend_jit_free(&dasm_state, opline, OP1_INFO(), zend_may_throw(opline, ssa_op, op_array, ssa))) { goto jit_failure; } goto done; case ZEND_ECHO: op1_info = OP1_INFO(); if ((op1_info & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF)) != MAY_BE_STRING) { break; } if (!zend_jit_echo(&dasm_state, opline, op1_info)) { goto jit_failure; } goto done; case ZEND_STRLEN: op1_info = OP1_INFO(); if ((op1_info & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF)) != MAY_BE_STRING) { break; } if (!zend_jit_strlen(&dasm_state, opline, op1_info, OP1_REG_ADDR(), RES_REG_ADDR())) { goto jit_failure; } goto done; case ZEND_COUNT: op1_info = OP1_INFO(); if ((op1_info & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF)) != MAY_BE_ARRAY) { break; } if (!zend_jit_count(&dasm_state, opline, op1_info, OP1_REG_ADDR(), RES_REG_ADDR(), zend_may_throw(opline, ssa_op, op_array, ssa))) { goto jit_failure; } goto done; case ZEND_FETCH_THIS: if (!zend_jit_fetch_this(&dasm_state, opline, op_array, 0)) { goto jit_failure; } goto done; case ZEND_SWITCH_LONG: case ZEND_SWITCH_STRING: case ZEND_MATCH: if (!zend_jit_switch(&dasm_state, opline, op_array, ssa, NULL, NULL)) { goto jit_failure; } goto done; case ZEND_VERIFY_RETURN_TYPE: if (opline->op1_type == IS_UNUSED) { /* Always throws */ break; } if (opline->op1_type == IS_CONST) { /* TODO Different instruction format, has return value */ break; } if (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) { /* Not worth bothering with */ break; } if (OP1_INFO() & MAY_BE_REF) { /* TODO May need reference unwrapping. */ break; } if (!zend_jit_verify_return_type(&dasm_state, opline, op_array, OP1_INFO())) { goto jit_failure; } goto done; case ZEND_FE_RESET_R: op1_info = OP1_INFO(); if ((op1_info & (MAY_BE_ANY|MAY_BE_REF|MAY_BE_UNDEF)) != MAY_BE_ARRAY) { break; } if (!zend_jit_fe_reset(&dasm_state, opline, op1_info)) { goto jit_failure; } goto done; case ZEND_FE_FETCH_R: op1_info = OP1_INFO(); if ((op1_info & MAY_BE_ANY) != MAY_BE_ARRAY) { break; } if (!zend_jit_fe_fetch(&dasm_state, opline, op1_info, OP2_INFO(), ssa->cfg.blocks[b].successors[0], opline->opcode, NULL)) { goto jit_failure; } goto done; case ZEND_FETCH_CONSTANT: if (!zend_jit_fetch_constant(&dasm_state, opline, op_array, ssa, ssa_op, RES_REG_ADDR())) { 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; on_this = 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; on_this = 1; } 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, on_this, 0, NULL, NULL, 0, 0)) { goto jit_failure; } goto done; case ZEND_ROPE_INIT: case ZEND_ROPE_ADD: case ZEND_ROPE_END: op2_info = OP2_INFO(); if ((op2_info & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF)) != MAY_BE_STRING) { break; } if (!zend_jit_rope(&dasm_state, opline, op2_info)) { goto jit_failure; } goto done; default: break; } } switch (opline->opcode) { case ZEND_RECV_INIT: case ZEND_BIND_GLOBAL: if (opline == op_array->opcodes || opline->opcode != op_array->opcodes[i-1].opcode) { /* repeatable opcodes */ if (!zend_jit_handler(&dasm_state, opline, zend_may_throw(opline, ssa_op, op_array, ssa))) { goto jit_failure; } } zend_jit_set_last_valid_opline(opline+1); break; case ZEND_NOP: case ZEND_OP_DATA: case ZEND_SWITCH_LONG: case ZEND_SWITCH_STRING: break; case ZEND_MATCH: /* We have to exit to the VM because the MATCH handler performs an N-way jump for * which we can't generate simple (opcache.jit=1201) JIT code. */ if (!zend_jit_tail_handler(&dasm_state, opline)) { goto jit_failure; } break; case ZEND_JMP: if (JIT_G(opt_level) < ZEND_JIT_LEVEL_INLINE) { const zend_op *target = OP_JMP_ADDR(opline, opline->op1); if (!zend_jit_set_ip(&dasm_state, target)) { goto jit_failure; } } if (!zend_jit_jmp(&dasm_state, ssa->cfg.blocks[b].successors[0])) { goto jit_failure; } is_terminated = 1; break; case ZEND_CATCH: case ZEND_FAST_CALL: case ZEND_FAST_RET: case ZEND_GENERATOR_CREATE: case ZEND_GENERATOR_RETURN: case ZEND_RETURN_BY_REF: case ZEND_RETURN: case ZEND_EXIT: case ZEND_MATCH_ERROR: /* switch through trampoline */ case ZEND_YIELD: case ZEND_YIELD_FROM: if (!zend_jit_tail_handler(&dasm_state, opline)) { goto jit_failure; } is_terminated = 1; break; /* stackless execution */ case ZEND_INCLUDE_OR_EVAL: case ZEND_DO_FCALL: case ZEND_DO_UCALL: case ZEND_DO_FCALL_BY_NAME: if (!zend_jit_call(&dasm_state, opline, b + 1)) { goto jit_failure; } is_terminated = 1; break; case ZEND_JMPZ: case ZEND_JMPNZ: if (opline > op_array->opcodes + ssa->cfg.blocks[b].start && ((opline-1)->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0) { /* smart branch */ if (!zend_jit_cond_jmp(&dasm_state, opline + 1, ssa->cfg.blocks[b].successors[0])) { goto jit_failure; } goto done; } ZEND_FALLTHROUGH; case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: case ZEND_JMP_SET: case ZEND_COALESCE: case ZEND_JMP_NULL: case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: case ZEND_ASSERT_CHECK: case ZEND_FE_FETCH_R: case ZEND_FE_FETCH_RW: if (!zend_jit_handler(&dasm_state, opline, zend_may_throw(opline, ssa_op, op_array, ssa)) || !zend_jit_cond_jmp(&dasm_state, opline + 1, ssa->cfg.blocks[b].successors[0])) { goto jit_failure; } break; case ZEND_NEW: if (!zend_jit_handler(&dasm_state, opline, 1)) { return 0; } if (opline->extended_value == 0 && (opline+1)->opcode == ZEND_DO_FCALL) { zend_class_entry *ce = NULL; if (JIT_G(opt_level) >= ZEND_JIT_LEVEL_OPT_FUNC) { if (ssa->ops && ssa->var_info) { zend_ssa_var_info *res_ssa = &ssa->var_info[ssa->ops[opline - op_array->opcodes].result_def]; if (res_ssa->ce && !res_ssa->is_instanceof) { ce = res_ssa->ce; } } } else { if (opline->op1_type == IS_CONST) { zval *zv = RT_CONSTANT(opline, opline->op1); if (Z_TYPE_P(zv) == IS_STRING) { zval *lc = zv + 1; ce = (zend_class_entry*)zend_hash_find_ptr(EG(class_table), Z_STR_P(lc)); } } } i++; if (!ce || !(ce->ce_flags & ZEND_ACC_LINKED) || ce->constructor) { const zend_op *next_opline = opline + 1; zend_jit_cond_jmp(&dasm_state, next_opline, ssa->cfg.blocks[b].successors[0]); if (JIT_G(opt_level) < ZEND_JIT_LEVEL_INLINE) { zend_jit_call(&dasm_state, next_opline, b + 1); is_terminated = 1; } else { zend_jit_do_fcall(&dasm_state, next_opline, op_array, ssa, call_level, b + 1, NULL); } } /* We skip over the DO_FCALL, so decrement call_level ourselves. */ call_level--; } break; default: if (!zend_jit_handler(&dasm_state, opline, zend_may_throw(opline, ssa_op, op_array, ssa))) { goto jit_failure; } if (i == end && (opline->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0) { /* smart branch split across basic blocks */ if (!zend_jit_cond_jmp(&dasm_state, opline + 2, ssa->cfg.blocks[b+1].successors[0])) { goto jit_failure; } if (!zend_jit_jmp(&dasm_state, ssa->cfg.blocks[b+1].successors[1])) { goto jit_failure; } is_terminated = 1; } } done: switch (opline->opcode) { case ZEND_DO_FCALL: case ZEND_DO_ICALL: case ZEND_DO_UCALL: case ZEND_DO_FCALL_BY_NAME: case ZEND_CALLABLE_CONVERT: call_level--; } } } handler = dasm_link_and_encode(&dasm_state, op_array, ssa, rt_opline, ra, NULL, 0, (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) ? SP_ADJ_VM : SP_ADJ_RET, SP_ADJ_JIT); if (!handler) { goto jit_failure; } dasm_free(&dasm_state); if (JIT_G(opt_flags) & (ZEND_JIT_REG_ALLOC_LOCAL|ZEND_JIT_REG_ALLOC_GLOBAL)) { zend_arena_release(&CG(arena), checkpoint); } return SUCCESS; jit_failure: if (dasm_state) { dasm_free(&dasm_state); } if (JIT_G(opt_flags) & (ZEND_JIT_REG_ALLOC_LOCAL|ZEND_JIT_REG_ALLOC_GLOBAL)) { zend_arena_release(&CG(arena), checkpoint); } return FAILURE; } static void zend_jit_collect_calls(zend_op_array *op_array, zend_script *script) { zend_func_info *func_info; if (JIT_G(trigger) == ZEND_JIT_ON_FIRST_EXEC || JIT_G(trigger) == ZEND_JIT_ON_PROF_REQUEST || JIT_G(trigger) == ZEND_JIT_ON_HOT_COUNTERS) { func_info = ZEND_FUNC_INFO(op_array); } else { func_info = zend_arena_calloc(&CG(arena), 1, sizeof(zend_func_info)); ZEND_SET_FUNC_INFO(op_array, func_info); } zend_analyze_calls(&CG(arena), script, ZEND_CALL_TREE, op_array, func_info); } static void zend_jit_cleanup_func_info(zend_op_array *op_array) { zend_func_info *func_info = ZEND_FUNC_INFO(op_array); zend_call_info *caller_info, *callee_info; if (func_info) { caller_info = func_info->caller_info; callee_info = func_info->callee_info; if (JIT_G(trigger) == ZEND_JIT_ON_FIRST_EXEC || JIT_G(trigger) == ZEND_JIT_ON_PROF_REQUEST || JIT_G(trigger) == ZEND_JIT_ON_HOT_COUNTERS) { func_info->num = 0; func_info->flags &= ZEND_FUNC_JIT_ON_FIRST_EXEC | ZEND_FUNC_JIT_ON_PROF_REQUEST | ZEND_FUNC_JIT_ON_HOT_COUNTERS | ZEND_FUNC_JIT_ON_HOT_TRACE; memset(&func_info->ssa, 0, sizeof(zend_func_info) - offsetof(zend_func_info, ssa)); } else { ZEND_SET_FUNC_INFO(op_array, NULL); } while (caller_info) { if (caller_info->caller_op_array) { zend_jit_cleanup_func_info(caller_info->caller_op_array); } caller_info = caller_info->next_caller; } while (callee_info) { if (callee_info->callee_func && callee_info->callee_func->type == ZEND_USER_FUNCTION) { zend_jit_cleanup_func_info(&callee_info->callee_func->op_array); } callee_info = callee_info->next_callee; } } } static int zend_real_jit_func(zend_op_array *op_array, zend_script *script, const zend_op *rt_opline) { zend_ssa ssa; void *checkpoint; zend_func_info *func_info; if (*dasm_ptr == dasm_end) { return FAILURE; } checkpoint = zend_arena_checkpoint(CG(arena)); /* Build SSA */ memset(&ssa, 0, sizeof(zend_ssa)); if (zend_jit_op_array_analyze1(op_array, script, &ssa) != SUCCESS) { goto jit_failure; } if (JIT_G(opt_level) >= ZEND_JIT_LEVEL_OPT_FUNCS) { zend_jit_collect_calls(op_array, script); func_info = ZEND_FUNC_INFO(op_array); func_info->call_map = zend_build_call_map(&CG(arena), func_info, op_array); if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { zend_init_func_return_info(op_array, script, &func_info->return_info); } } if (zend_jit_op_array_analyze2(op_array, script, &ssa, ZCG(accel_directives).optimization_level) != SUCCESS) { goto jit_failure; } if (JIT_G(debug) & ZEND_JIT_DEBUG_SSA) { zend_dump_op_array(op_array, ZEND_DUMP_HIDE_UNREACHABLE|ZEND_DUMP_RC_INFERENCE|ZEND_DUMP_SSA, "JIT", &ssa); } if (zend_jit(op_array, &ssa, rt_opline) != SUCCESS) { goto jit_failure; } zend_jit_cleanup_func_info(op_array); zend_arena_release(&CG(arena), checkpoint); return SUCCESS; jit_failure: zend_jit_cleanup_func_info(op_array); zend_arena_release(&CG(arena), checkpoint); return FAILURE; } /* Run-time JIT handler */ static int ZEND_FASTCALL zend_runtime_jit(void) { zend_execute_data *execute_data = EG(current_execute_data); zend_op_array *op_array = &EX(func)->op_array; zend_op *opline = op_array->opcodes; zend_jit_op_array_extension *jit_extension; bool do_bailout = 0; zend_shared_alloc_lock(); if (ZEND_FUNC_INFO(op_array)) { SHM_UNPROTECT(); zend_jit_unprotect(); zend_try { /* restore original opcode handlers */ if (!(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) { while (opline->opcode == ZEND_RECV || opline->opcode == ZEND_RECV_INIT) { opline++; } } jit_extension = (zend_jit_op_array_extension*)ZEND_FUNC_INFO(op_array); opline->handler = jit_extension->orig_handler; /* perform real JIT for this function */ zend_real_jit_func(op_array, NULL, NULL); } zend_catch { do_bailout = true; } zend_end_try(); zend_jit_protect(); SHM_PROTECT(); } zend_shared_alloc_unlock(); if (do_bailout) { zend_bailout(); } /* JIT-ed code is going to be called by VM */ return 0; } void zend_jit_check_funcs(HashTable *function_table, bool is_method) { zend_op *opline; zend_function *func; zend_op_array *op_array; uintptr_t counter; zend_jit_op_array_extension *jit_extension; ZEND_HASH_MAP_REVERSE_FOREACH_PTR(function_table, func) { if (func->type == ZEND_INTERNAL_FUNCTION) { break; } op_array = &func->op_array; opline = op_array->opcodes; if (!(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) { while (opline->opcode == ZEND_RECV || opline->opcode == ZEND_RECV_INIT) { opline++; } } if (opline->handler == zend_jit_profile_jit_handler) { if (!RUN_TIME_CACHE(op_array)) { continue; } counter = (uintptr_t)ZEND_COUNTER_INFO(op_array); ZEND_COUNTER_INFO(op_array) = 0; jit_extension = (zend_jit_op_array_extension*)ZEND_FUNC_INFO(op_array); opline->handler = jit_extension->orig_handler; if (((double)counter / (double)zend_jit_profile_counter) > JIT_G(prof_threshold)) { zend_real_jit_func(op_array, NULL, NULL); } } } ZEND_HASH_FOREACH_END(); } void ZEND_FASTCALL zend_jit_hot_func(zend_execute_data *execute_data, const zend_op *opline) { zend_op_array *op_array = &EX(func)->op_array; zend_jit_op_array_hot_extension *jit_extension; uint32_t i; bool do_bailout = 0; zend_shared_alloc_lock(); jit_extension = (zend_jit_op_array_hot_extension*)ZEND_FUNC_INFO(op_array); if (jit_extension) { SHM_UNPROTECT(); zend_jit_unprotect(); zend_try { for (i = 0; i < op_array->last; i++) { op_array->opcodes[i].handler = jit_extension->orig_handlers[i]; } /* perform real JIT for this function */ zend_real_jit_func(op_array, NULL, opline); } zend_catch { do_bailout = 1; } zend_end_try(); zend_jit_protect(); SHM_PROTECT(); } zend_shared_alloc_unlock(); if (do_bailout) { zend_bailout(); } /* JIT-ed code is going to be called by VM */ } static void zend_jit_setup_hot_counters_ex(zend_op_array *op_array, zend_cfg *cfg) { if (JIT_G(hot_func)) { zend_op *opline = op_array->opcodes; if (!(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) { while (opline->opcode == ZEND_RECV || opline->opcode == ZEND_RECV_INIT) { opline++; } } opline->handler = (const void*)zend_jit_func_hot_counter_handler; } if (JIT_G(hot_loop)) { uint32_t i; for (i = 0; i < cfg->blocks_count; i++) { if ((cfg->blocks[i].flags & ZEND_BB_REACHABLE) && (cfg->blocks[i].flags & ZEND_BB_LOOP_HEADER)) { op_array->opcodes[cfg->blocks[i].start].handler = (const void*)zend_jit_loop_hot_counter_handler; } } } } static int zend_jit_restart_hot_counters(zend_op_array *op_array) { zend_jit_op_array_hot_extension *jit_extension; zend_cfg cfg; uint32_t i; jit_extension = (zend_jit_op_array_hot_extension*)ZEND_FUNC_INFO(op_array); for (i = 0; i < op_array->last; i++) { op_array->opcodes[i].handler = jit_extension->orig_handlers[i]; } if (zend_jit_build_cfg(op_array, &cfg) != SUCCESS) { return FAILURE; } zend_jit_setup_hot_counters_ex(op_array, &cfg); return SUCCESS; } static int zend_jit_setup_hot_counters(zend_op_array *op_array) { zend_jit_op_array_hot_extension *jit_extension; zend_cfg cfg; uint32_t i; ZEND_ASSERT(zend_jit_func_hot_counter_handler != NULL); ZEND_ASSERT(zend_jit_loop_hot_counter_handler != NULL); if (zend_jit_build_cfg(op_array, &cfg) != SUCCESS) { return FAILURE; } jit_extension = (zend_jit_op_array_hot_extension*)zend_shared_alloc(sizeof(zend_jit_op_array_hot_extension) + (op_array->last - 1) * sizeof(void*)); if (!jit_extension) { return FAILURE; } memset(&jit_extension->func_info, 0, sizeof(zend_func_info)); jit_extension->func_info.flags = ZEND_FUNC_JIT_ON_HOT_COUNTERS; jit_extension->counter = &zend_jit_hot_counters[zend_jit_op_array_hash(op_array) & (ZEND_HOT_COUNTERS_COUNT - 1)]; for (i = 0; i < op_array->last; i++) { jit_extension->orig_handlers[i] = op_array->opcodes[i].handler; } ZEND_SET_FUNC_INFO(op_array, (void*)jit_extension); zend_jit_setup_hot_counters_ex(op_array, &cfg); zend_shared_alloc_register_xlat_entry(op_array->opcodes, jit_extension); return SUCCESS; } #include "jit/zend_jit_trace.c" ZEND_EXT_API int zend_jit_op_array(zend_op_array *op_array, zend_script *script) { if (dasm_ptr == NULL) { return FAILURE; } if (JIT_G(trigger) == ZEND_JIT_ON_FIRST_EXEC) { zend_jit_op_array_extension *jit_extension; zend_op *opline = op_array->opcodes; if (CG(compiler_options) & ZEND_COMPILE_PRELOAD) { ZEND_SET_FUNC_INFO(op_array, NULL); zend_error(E_WARNING, "Preloading is incompatible with first-exec and profile triggered JIT"); return SUCCESS; } /* Set run-time JIT handler */ ZEND_ASSERT(zend_jit_runtime_jit_handler != NULL); if (!(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) { while (opline->opcode == ZEND_RECV || opline->opcode == ZEND_RECV_INIT) { opline++; } } jit_extension = (zend_jit_op_array_extension*)zend_shared_alloc(sizeof(zend_jit_op_array_extension)); if (!jit_extension) { return FAILURE; } memset(&jit_extension->func_info, 0, sizeof(zend_func_info)); jit_extension->func_info.flags = ZEND_FUNC_JIT_ON_FIRST_EXEC; jit_extension->orig_handler = (void*)opline->handler; ZEND_SET_FUNC_INFO(op_array, (void*)jit_extension); opline->handler = (const void*)zend_jit_runtime_jit_handler; zend_shared_alloc_register_xlat_entry(op_array->opcodes, jit_extension); return SUCCESS; } else if (JIT_G(trigger) == ZEND_JIT_ON_PROF_REQUEST) { zend_jit_op_array_extension *jit_extension; zend_op *opline = op_array->opcodes; if (CG(compiler_options) & ZEND_COMPILE_PRELOAD) { ZEND_SET_FUNC_INFO(op_array, NULL); zend_error(E_WARNING, "Preloading is incompatible with first-exec and profile triggered JIT"); return SUCCESS; } ZEND_ASSERT(zend_jit_profile_jit_handler != NULL); if (op_array->function_name) { if (!(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) { while (opline->opcode == ZEND_RECV || opline->opcode == ZEND_RECV_INIT) { opline++; } } jit_extension = (zend_jit_op_array_extension*)zend_shared_alloc(sizeof(zend_jit_op_array_extension)); if (!jit_extension) { return FAILURE; } memset(&jit_extension->func_info, 0, sizeof(zend_func_info)); jit_extension->func_info.flags = ZEND_FUNC_JIT_ON_PROF_REQUEST; jit_extension->orig_handler = (void*)opline->handler; ZEND_SET_FUNC_INFO(op_array, (void*)jit_extension); opline->handler = (const void*)zend_jit_profile_jit_handler; zend_shared_alloc_register_xlat_entry(op_array->opcodes, jit_extension); } return SUCCESS; } else if (JIT_G(trigger) == ZEND_JIT_ON_HOT_COUNTERS) { return zend_jit_setup_hot_counters(op_array); } else if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) { return zend_jit_setup_hot_trace_counters(op_array); } else if (JIT_G(trigger) == ZEND_JIT_ON_SCRIPT_LOAD) { return zend_real_jit_func(op_array, script, NULL); } else { ZEND_UNREACHABLE(); } } ZEND_EXT_API int zend_jit_script(zend_script *script) { void *checkpoint; zend_call_graph call_graph; zend_func_info *info; int i; if (dasm_ptr == NULL || *dasm_ptr == dasm_end) { return FAILURE; } checkpoint = zend_arena_checkpoint(CG(arena)); call_graph.op_arrays_count = 0; zend_build_call_graph(&CG(arena), script, &call_graph); zend_analyze_call_graph(&CG(arena), script, &call_graph); if (JIT_G(trigger) == ZEND_JIT_ON_FIRST_EXEC || JIT_G(trigger) == ZEND_JIT_ON_PROF_REQUEST || JIT_G(trigger) == ZEND_JIT_ON_HOT_COUNTERS || JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) { for (i = 0; i < call_graph.op_arrays_count; i++) { if (zend_jit_op_array(call_graph.op_arrays[i], script) != SUCCESS) { goto jit_failure; } } } else if (JIT_G(trigger) == ZEND_JIT_ON_SCRIPT_LOAD) { for (i = 0; i < call_graph.op_arrays_count; i++) { info = ZEND_FUNC_INFO(call_graph.op_arrays[i]); if (info) { if (zend_jit_op_array_analyze1(call_graph.op_arrays[i], script, &info->ssa) != SUCCESS) { goto jit_failure; } info->flags = info->ssa.cfg.flags; } } for (i = 0; i < call_graph.op_arrays_count; i++) { info = ZEND_FUNC_INFO(call_graph.op_arrays[i]); if (info) { info->call_map = zend_build_call_map(&CG(arena), info, call_graph.op_arrays[i]); if (call_graph.op_arrays[i]->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { zend_init_func_return_info(call_graph.op_arrays[i], script, &info->return_info); } } } for (i = 0; i < call_graph.op_arrays_count; i++) { info = ZEND_FUNC_INFO(call_graph.op_arrays[i]); if (info) { if (zend_jit_op_array_analyze2(call_graph.op_arrays[i], script, &info->ssa, ZCG(accel_directives).optimization_level) != SUCCESS) { goto jit_failure; } info->flags = info->ssa.cfg.flags; } } if (JIT_G(debug) & ZEND_JIT_DEBUG_SSA) { for (i = 0; i < call_graph.op_arrays_count; i++) { info = ZEND_FUNC_INFO(call_graph.op_arrays[i]); if (info) { zend_dump_op_array(call_graph.op_arrays[i], ZEND_DUMP_HIDE_UNREACHABLE|ZEND_DUMP_RC_INFERENCE|ZEND_DUMP_SSA, "JIT", &info->ssa); } } } for (i = 0; i < call_graph.op_arrays_count; i++) { info = ZEND_FUNC_INFO(call_graph.op_arrays[i]); if (info) { if (zend_jit(call_graph.op_arrays[i], &info->ssa, NULL) != SUCCESS) { goto jit_failure; } } } for (i = 0; i < call_graph.op_arrays_count; i++) { ZEND_SET_FUNC_INFO(call_graph.op_arrays[i], NULL); } } else { ZEND_UNREACHABLE(); } zend_arena_release(&CG(arena), checkpoint); if (JIT_G(trigger) == ZEND_JIT_ON_FIRST_EXEC || JIT_G(trigger) == ZEND_JIT_ON_PROF_REQUEST || JIT_G(trigger) == ZEND_JIT_ON_HOT_COUNTERS || JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) { zend_class_entry *ce; zend_op_array *op_array; ZEND_HASH_MAP_FOREACH_PTR(&script->class_table, ce) { ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, op_array) { if (!ZEND_FUNC_INFO(op_array)) { void *jit_extension = zend_shared_alloc_get_xlat_entry(op_array->opcodes); if (jit_extension) { ZEND_SET_FUNC_INFO(op_array, jit_extension); } } } ZEND_HASH_FOREACH_END(); } ZEND_HASH_FOREACH_END(); } return SUCCESS; jit_failure: if (JIT_G(trigger) == ZEND_JIT_ON_SCRIPT_LOAD) { for (i = 0; i < call_graph.op_arrays_count; i++) { ZEND_SET_FUNC_INFO(call_graph.op_arrays[i], NULL); } } zend_arena_release(&CG(arena), checkpoint); return FAILURE; } ZEND_EXT_API void zend_jit_unprotect(void) { #ifdef HAVE_MPROTECT if (!(JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP))) { int opts = PROT_READ | PROT_WRITE; #ifdef ZTS #ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP if (zend_write_protect) { pthread_jit_write_protect_np(0); } #endif opts |= PROT_EXEC; #endif if (mprotect(dasm_buf, dasm_size, opts) != 0) { fprintf(stderr, "mprotect() failed [%d] %s\n", errno, strerror(errno)); } } #elif _WIN32 if (!(JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP))) { DWORD old, new; #ifdef ZTS new = PAGE_EXECUTE_READWRITE; #else new = PAGE_READWRITE; #endif if (!VirtualProtect(dasm_buf, dasm_size, new, &old)) { DWORD err = GetLastError(); char *msg = php_win32_error_to_msg(err); fprintf(stderr, "VirtualProtect() failed [%u] %s\n", err, msg); php_win32_error_msg_free(msg); } } #endif } ZEND_EXT_API void zend_jit_protect(void) { #ifdef HAVE_MPROTECT if (!(JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP))) { #ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP if (zend_write_protect) { pthread_jit_write_protect_np(1); } #endif if (mprotect(dasm_buf, dasm_size, PROT_READ | PROT_EXEC) != 0) { fprintf(stderr, "mprotect() failed [%d] %s\n", errno, strerror(errno)); } } #elif _WIN32 if (!(JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP))) { DWORD old; if (!VirtualProtect(dasm_buf, dasm_size, PAGE_EXECUTE_READ, &old)) { DWORD err = GetLastError(); char *msg = php_win32_error_to_msg(err); fprintf(stderr, "VirtualProtect() failed [%u] %s\n", err, msg); php_win32_error_msg_free(msg); } } #endif } static void zend_jit_init_handlers(void) { if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) { zend_jit_runtime_jit_handler = dasm_labels[zend_lbhybrid_runtime_jit]; zend_jit_profile_jit_handler = dasm_labels[zend_lbhybrid_profile_jit]; zend_jit_func_hot_counter_handler = dasm_labels[zend_lbhybrid_func_hot_counter]; zend_jit_loop_hot_counter_handler = dasm_labels[zend_lbhybrid_loop_hot_counter]; zend_jit_func_trace_counter_handler = dasm_labels[zend_lbhybrid_func_trace_counter]; zend_jit_ret_trace_counter_handler = dasm_labels[zend_lbhybrid_ret_trace_counter]; zend_jit_loop_trace_counter_handler = dasm_labels[zend_lbhybrid_loop_trace_counter]; } else { zend_jit_runtime_jit_handler = (const void*)zend_runtime_jit; zend_jit_profile_jit_handler = (const void*)zend_jit_profile_helper; zend_jit_func_hot_counter_handler = (const void*)zend_jit_func_counter_helper; zend_jit_loop_hot_counter_handler = (const void*)zend_jit_loop_counter_helper; zend_jit_func_trace_counter_handler = (const void*)zend_jit_func_trace_helper; zend_jit_ret_trace_counter_handler = (const void*)zend_jit_ret_trace_helper; zend_jit_loop_trace_counter_handler = (const void*)zend_jit_loop_trace_helper; } } static int zend_jit_make_stubs(void) { dasm_State* dasm_state = NULL; uint32_t i; dasm_init(&dasm_state, DASM_MAXSECTION); dasm_setupglobal(&dasm_state, dasm_labels, zend_lb_MAX); for (i = 0; i < sizeof(zend_jit_stubs)/sizeof(zend_jit_stubs[0]); i++) { dasm_setup(&dasm_state, dasm_actions); if (!zend_jit_stubs[i].stub(&dasm_state)) { return 0; } if (!dasm_link_and_encode(&dasm_state, NULL, NULL, NULL, NULL, zend_jit_stubs[i].name, 0, zend_jit_stubs[i].offset, zend_jit_stubs[i].adjustment)) { return 0; } } zend_jit_init_handlers(); dasm_free(&dasm_state); return 1; } static void zend_jit_globals_ctor(zend_jit_globals *jit_globals) { memset(jit_globals, 0, sizeof(zend_jit_globals)); zend_jit_trace_init_caches(); } #ifdef ZTS static void zend_jit_globals_dtor(zend_jit_globals *jit_globals) { zend_jit_trace_free_caches(jit_globals); } #endif static int zend_jit_parse_config_num(zend_long jit) { if (jit == 0) { JIT_G(on) = 0; return SUCCESS; } if (jit < 0) return FAILURE; if (jit % 10 == 0 || jit % 10 > 5) return FAILURE; JIT_G(opt_level) = jit % 10; jit /= 10; if (jit % 10 > 5 || jit % 10 == 4) return FAILURE; JIT_G(trigger) = jit % 10; jit /= 10; if (jit % 10 > 2) return FAILURE; JIT_G(opt_flags) = jit % 10; jit /= 10; if (jit % 10 > 1) return FAILURE; JIT_G(opt_flags) |= ((jit % 10) ? ZEND_JIT_CPU_AVX : 0); if (jit / 10 != 0) return FAILURE; JIT_G(on) = 1; return SUCCESS; } ZEND_EXT_API int zend_jit_config(zend_string *jit, int stage) { if (stage != ZEND_INI_STAGE_STARTUP && !JIT_G(enabled)) { if (stage == ZEND_INI_STAGE_RUNTIME) { zend_error(E_WARNING, "Cannot change opcache.jit setting at run-time (JIT is disabled)"); } return FAILURE; } if (zend_string_equals_literal_ci(jit, "disable")) { JIT_G(enabled) = 0; JIT_G(on) = 0; return SUCCESS; } else if (ZSTR_LEN(jit) == 0 || zend_string_equals_literal_ci(jit, "0") || zend_string_equals_literal_ci(jit, "off") || zend_string_equals_literal_ci(jit, "no") || zend_string_equals_literal_ci(jit, "false")) { JIT_G(enabled) = 1; JIT_G(on) = 0; return SUCCESS; } else if (zend_string_equals_literal_ci(jit, "1") || zend_string_equals_literal_ci(jit, "on") || zend_string_equals_literal_ci(jit, "yes") || zend_string_equals_literal_ci(jit, "true") || zend_string_equals_literal_ci(jit, "tracing")) { JIT_G(enabled) = 1; JIT_G(on) = 1; JIT_G(opt_level) = ZEND_JIT_LEVEL_OPT_FUNCS; JIT_G(trigger) = ZEND_JIT_ON_HOT_TRACE; JIT_G(opt_flags) = ZEND_JIT_REG_ALLOC_GLOBAL | ZEND_JIT_CPU_AVX; return SUCCESS; } else if (zend_string_equals_literal_ci(jit, "function")) { JIT_G(enabled) = 1; JIT_G(on) = 1; JIT_G(opt_level) = ZEND_JIT_LEVEL_OPT_SCRIPT; JIT_G(trigger) = ZEND_JIT_ON_SCRIPT_LOAD; JIT_G(opt_flags) = ZEND_JIT_REG_ALLOC_GLOBAL | ZEND_JIT_CPU_AVX; return SUCCESS; } else { char *end; zend_long num = ZEND_STRTOL(ZSTR_VAL(jit), &end, 10); if (end != ZSTR_VAL(jit) + ZSTR_LEN(jit) || zend_jit_parse_config_num(num) != SUCCESS) { goto failure; } JIT_G(enabled) = 1; return SUCCESS; } failure: zend_error(E_WARNING, "Invalid \"opcache.jit\" setting. Should be \"disable\", \"on\", \"off\", \"tracing\", \"function\" or 4-digit number"); JIT_G(enabled) = 0; JIT_G(on) = 0; return FAILURE; } ZEND_EXT_API int zend_jit_debug_config(zend_long old_val, zend_long new_val, int stage) { if (stage != ZEND_INI_STAGE_STARTUP) { if (((old_val ^ new_val) & ZEND_JIT_DEBUG_PERSISTENT) != 0) { if (stage == ZEND_INI_STAGE_RUNTIME) { zend_error(E_WARNING, "Some opcache.jit_debug bits cannot be changed after startup"); } return FAILURE; } #ifdef HAVE_DISASM if (new_val & (ZEND_JIT_DEBUG_ASM|ZEND_JIT_DEBUG_ASM_STUBS)) { if (JIT_G(enabled) && !JIT_G(symbols) && !zend_jit_disasm_init()) { // TODO: error reporting and cleanup ??? return FAILURE; } // TODO: symbols for JIT-ed code compiled before are missing ??? } #endif } return SUCCESS; } ZEND_EXT_API void zend_jit_init(void) { #ifdef ZTS jit_globals_id = ts_allocate_id(&jit_globals_id, sizeof(zend_jit_globals), (ts_allocate_ctor) zend_jit_globals_ctor, (ts_allocate_dtor) zend_jit_globals_dtor); #else zend_jit_globals_ctor(&jit_globals); #endif } ZEND_EXT_API int zend_jit_check_support(void) { int i; zend_jit_vm_kind = zend_vm_kind(); if (zend_jit_vm_kind != ZEND_VM_KIND_CALL && zend_jit_vm_kind != ZEND_VM_KIND_HYBRID) { zend_error(E_WARNING, "JIT is compatible only with CALL and HYBRID VM. JIT disabled."); JIT_G(enabled) = 0; JIT_G(on) = 0; return FAILURE; } if (zend_execute_ex != execute_ex) { if (zend_dtrace_enabled) { zend_error(E_WARNING, "JIT is incompatible with DTrace. JIT disabled."); } else if (strcmp(sapi_module.name, "phpdbg") != 0) { zend_error(E_WARNING, "JIT is incompatible with third party extensions that override zend_execute_ex(). JIT disabled."); } JIT_G(enabled) = 0; JIT_G(on) = 0; return FAILURE; } for (i = 0; i <= 256; i++) { switch (i) { /* JIT has no effect on these opcodes */ case ZEND_BEGIN_SILENCE: case ZEND_END_SILENCE: case ZEND_EXIT: break; default: if (zend_get_user_opcode_handler(i) != NULL) { zend_error(E_WARNING, "JIT is incompatible with third party extensions that setup user opcode handlers. JIT disabled."); JIT_G(enabled) = 0; JIT_G(on) = 0; return FAILURE; } } } return SUCCESS; } ZEND_EXT_API int zend_jit_startup(void *buf, size_t size, bool reattached) { int ret; zend_jit_halt_op = zend_get_halt_op(); if (zend_jit_setup() != SUCCESS) { // TODO: error reporting and cleanup ??? return FAILURE; } zend_jit_profile_counter_rid = zend_get_op_array_extension_handle(ACCELERATOR_PRODUCT_NAME); #ifdef HAVE_GDB zend_jit_gdb_init(); #endif #ifdef HAVE_OPROFILE if (JIT_G(debug) & ZEND_JIT_DEBUG_OPROFILE) { if (!zend_jit_oprofile_startup()) { // TODO: error reporting and cleanup ??? return FAILURE; } } #endif #ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP zend_write_protect = pthread_jit_write_protect_supported_np(); #endif dasm_buf = buf; dasm_size = size; #ifdef HAVE_MPROTECT #ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP if (zend_write_protect) { pthread_jit_write_protect_np(1); } #endif if (JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP)) { if (mprotect(dasm_buf, dasm_size, PROT_READ | PROT_WRITE | PROT_EXEC) != 0) { fprintf(stderr, "mprotect() failed [%d] %s\n", errno, strerror(errno)); } } else { if (mprotect(dasm_buf, dasm_size, PROT_READ | PROT_EXEC) != 0) { fprintf(stderr, "mprotect() failed [%d] %s\n", errno, strerror(errno)); } } #elif _WIN32 if (JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP)) { DWORD old; if (!VirtualProtect(dasm_buf, dasm_size, PAGE_EXECUTE_READWRITE, &old)) { DWORD err = GetLastError(); char *msg = php_win32_error_to_msg(err); fprintf(stderr, "VirtualProtect() failed [%u] %s\n", err, msg); php_win32_error_msg_free(msg); } } else { DWORD old; if (!VirtualProtect(dasm_buf, dasm_size, PAGE_EXECUTE_READ, &old)) { DWORD err = GetLastError(); char *msg = php_win32_error_to_msg(err); fprintf(stderr, "VirtualProtect() failed [%u] %s\n", err, msg); php_win32_error_msg_free(msg); } } #endif dasm_ptr = dasm_end = (void*)(((char*)dasm_buf) + size - sizeof(*dasm_ptr) * 2); if (!reattached) { zend_jit_unprotect(); *dasm_ptr = dasm_buf; #if _WIN32 /* reserve space for global labels */ *dasm_ptr = (void**)*dasm_ptr + zend_lb_MAX; #endif zend_jit_protect(); } #ifdef HAVE_DISASM if (JIT_G(debug) & (ZEND_JIT_DEBUG_ASM|ZEND_JIT_DEBUG_ASM_STUBS)) { if (!zend_jit_disasm_init()) { // TODO: error reporting and cleanup ??? return FAILURE; } } #endif #ifdef HAVE_PERFTOOLS if (JIT_G(debug) & ZEND_JIT_DEBUG_PERF_DUMP) { zend_jit_perf_jitdump_open(); } #endif if (!reattached) { zend_jit_unprotect(); ret = zend_jit_make_stubs(); #if _WIN32 /* save global labels */ memcpy(dasm_buf, dasm_labels, sizeof(void*) * zend_lb_MAX); #endif zend_jit_protect(); if (!ret) { // TODO: error reporting and cleanup ??? return FAILURE; } } else { #if _WIN32 /* restore global labels */ memcpy(dasm_labels, dasm_buf, sizeof(void*) * zend_lb_MAX); zend_jit_init_handlers(); #endif } if (zend_jit_trace_startup(reattached) != SUCCESS) { return FAILURE; } zend_jit_unprotect(); #if ZEND_JIT_TARGET_ARM64 /* reserve space for global labels veneers */ dasm_labels_veneers = *dasm_ptr; *dasm_ptr = (void**)*dasm_ptr + ZEND_MM_ALIGNED_SIZE_EX(zend_lb_MAX, DASM_ALIGNMENT); memset(dasm_labels_veneers, 0, sizeof(void*) * ZEND_MM_ALIGNED_SIZE_EX(zend_lb_MAX, DASM_ALIGNMENT)); #endif /* save JIT buffer pos */ dasm_ptr[1] = dasm_ptr[0]; zend_jit_protect(); return SUCCESS; } ZEND_EXT_API void zend_jit_shutdown(void) { if (JIT_G(debug) & ZEND_JIT_DEBUG_SIZE && dasm_ptr != NULL) { fprintf(stderr, "\nJIT memory usage: %td\n", (ptrdiff_t)((char*)*dasm_ptr - (char*)dasm_buf)); } #ifdef HAVE_OPROFILE if (JIT_G(debug) & ZEND_JIT_DEBUG_OPROFILE) { zend_jit_oprofile_shutdown(); } #endif #ifdef HAVE_GDB if (JIT_G(debug) & ZEND_JIT_DEBUG_GDB) { zend_jit_gdb_unregister(); } #endif #ifdef HAVE_DISASM zend_jit_disasm_shutdown(); #endif #ifdef HAVE_PERFTOOLS if (JIT_G(debug) & ZEND_JIT_DEBUG_PERF_DUMP) { zend_jit_perf_jitdump_close(); } #endif #ifdef ZTS ts_free_id(jit_globals_id); #else zend_jit_trace_free_caches(&jit_globals); #endif } static void zend_jit_reset_counters(void) { int i; for (i = 0; i < ZEND_HOT_COUNTERS_COUNT; i++) { zend_jit_hot_counters[i] = ZEND_JIT_COUNTER_INIT; } } ZEND_EXT_API void zend_jit_activate(void) { zend_jit_profile_counter = 0; if (JIT_G(on)) { if (JIT_G(trigger) == ZEND_JIT_ON_HOT_COUNTERS) { zend_jit_reset_counters(); } else if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) { zend_jit_reset_counters(); zend_jit_trace_reset_caches(); } } } ZEND_EXT_API void zend_jit_deactivate(void) { if (zend_jit_profile_counter && !CG(unclean_shutdown)) { zend_class_entry *ce; zend_shared_alloc_lock(); SHM_UNPROTECT(); zend_jit_unprotect(); zend_jit_check_funcs(EG(function_table), 0); ZEND_HASH_MAP_REVERSE_FOREACH_PTR(EG(class_table), ce) { if (ce->type == ZEND_INTERNAL_CLASS) { break; } zend_jit_check_funcs(&ce->function_table, 1); } ZEND_HASH_FOREACH_END(); zend_jit_protect(); SHM_PROTECT(); zend_shared_alloc_unlock(); } zend_jit_profile_counter = 0; } static void zend_jit_restart_preloaded_op_array(zend_op_array *op_array) { zend_func_info *func_info = ZEND_FUNC_INFO(op_array); if (!func_info) { return; } if (func_info->flags & ZEND_FUNC_JIT_ON_HOT_TRACE) { zend_jit_restart_hot_trace_counters(op_array); } else if (func_info->flags & ZEND_FUNC_JIT_ON_HOT_COUNTERS) { zend_jit_restart_hot_counters(op_array); #if 0 // TODO: We have to restore handlers for some inner basic-blocks, but we didn't store them ??? } else if (func_info->flags & (ZEND_FUNC_JIT_ON_FIRST_EXEC|ZEND_FUNC_JIT_ON_PROF_REQUEST)) { zend_op *opline = op_array->opcodes; zend_jit_op_array_extension *jit_extension = (zend_jit_op_array_extension*)func_info; if (!(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) { while (opline->opcode == ZEND_RECV || opline->opcode == ZEND_RECV_INIT) { opline++; } } if (func_info->flags & ZEND_FUNC_JIT_ON_FIRST_EXEC) { opline->handler = (const void*)zend_jit_runtime_jit_handler; } else { opline->handler = (const void*)zend_jit_profile_jit_handler; } #endif } if (op_array->num_dynamic_func_defs) { for (uint32_t i = 0; i < op_array->num_dynamic_func_defs; i++) { zend_jit_restart_preloaded_op_array(op_array->dynamic_func_defs[i]); } } } static void zend_jit_restart_preloaded_script(zend_persistent_script *script) { zend_class_entry *ce; zend_op_array *op_array; zend_jit_restart_preloaded_op_array(&script->script.main_op_array); ZEND_HASH_MAP_FOREACH_PTR(&script->script.function_table, op_array) { zend_jit_restart_preloaded_op_array(op_array); } ZEND_HASH_FOREACH_END(); ZEND_HASH_MAP_FOREACH_PTR(&script->script.class_table, ce) { ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, op_array) { if (op_array->type == ZEND_USER_FUNCTION) { zend_jit_restart_preloaded_op_array(op_array); } } ZEND_HASH_FOREACH_END(); } ZEND_HASH_FOREACH_END(); } ZEND_EXT_API void zend_jit_restart(void) { if (dasm_buf) { zend_jit_unprotect(); #if ZEND_JIT_TARGET_ARM64 memset(dasm_labels_veneers, 0, sizeof(void*) * ZEND_MM_ALIGNED_SIZE_EX(zend_lb_MAX, DASM_ALIGNMENT)); #endif /* restore JIT buffer pos */ dasm_ptr[0] = dasm_ptr[1]; zend_jit_trace_restart(); if (ZCSG(preload_script)) { zend_jit_restart_preloaded_script(ZCSG(preload_script)); if (ZCSG(saved_scripts)) { zend_persistent_script **p = ZCSG(saved_scripts); while (*p) { zend_jit_restart_preloaded_script(*p); p++; } } } zend_jit_protect(); #ifdef HAVE_DISASM if (JIT_G(debug) & (ZEND_JIT_DEBUG_ASM|ZEND_JIT_DEBUG_ASM_STUBS)) { zend_jit_disasm_shutdown(); zend_jit_disasm_init(); } #endif } } #endif /* HAVE_JIT */