diff options
Diffstat (limited to 'ext/opcache/jit/zend_jit_trace.c')
-rw-r--r-- | ext/opcache/jit/zend_jit_trace.c | 3972 |
1 files changed, 3972 insertions, 0 deletions
diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c new file mode 100644 index 0000000000..abc2a0146d --- /dev/null +++ b/ext/opcache/jit/zend_jit_trace.c @@ -0,0 +1,3972 @@ +/* + +----------------------------------------------------------------------+ + | 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: | + | http://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 <dmitry@php.net> | + +----------------------------------------------------------------------+ +*/ + +static zend_jit_trace_info *zend_jit_traces = NULL; +static const void **zend_jit_exit_groups = NULL; + +#define ZEND_JIT_COUNTER_NUM zend_jit_traces[0].root +#define ZEND_JIT_TRACE_NUM zend_jit_traces[0].id +#define ZEND_JIT_EXIT_NUM zend_jit_traces[0].exit_count +#define ZEND_JIT_EXIT_COUNTERS zend_jit_traces[0].exit_counters + +static int zend_jit_trace_startup(void) +{ + zend_jit_traces = (zend_jit_trace_info*)zend_shared_alloc(sizeof(zend_jit_trace_info) * ZEND_JIT_TRACE_MAX_TRACES); + if (!zend_jit_traces) { + return FAILURE; + } + zend_jit_exit_groups = (const void**)zend_shared_alloc(sizeof(void*) * (ZEND_JIT_TRACE_MAX_EXITS/ZEND_JIT_EXIT_POINTS_PER_GROUP)); + if (!zend_jit_exit_groups) { + return FAILURE; + } + ZEND_JIT_TRACE_NUM = 1; + ZEND_JIT_COUNTER_NUM = 0; + ZEND_JIT_EXIT_NUM = 0; + ZEND_JIT_EXIT_COUNTERS = 0; + return SUCCESS; +} + +static const void *zend_jit_trace_allocate_exit_group(uint32_t n) +{ + dasm_State* dasm_state = NULL; + const void *entry; + char name[32]; + + dasm_init(&dasm_state, DASM_MAXSECTION); + dasm_setupglobal(&dasm_state, dasm_labels, zend_lb_MAX); + dasm_setup(&dasm_state, dasm_actions); + zend_jit_trace_exit_group_stub(&dasm_state, n); + + sprintf(name, "jit$$trace_exit_%d", n); + entry = dasm_link_and_encode(&dasm_state, NULL, NULL, NULL, NULL, name, 0); + dasm_free(&dasm_state); + +#ifdef HAVE_DISASM + if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_ASM) { + uint32_t i; + + for (i = 0; i < ZEND_JIT_EXIT_POINTS_PER_GROUP; i++) { + sprintf(name, "jit$$trace_exit_%d", n + i); + zend_jit_disasm_add_symbol(name, (uintptr_t)entry + (i * ZEND_JIT_EXIT_POINTS_SPACING), ZEND_JIT_EXIT_POINTS_SPACING); + } + } +#endif + + return entry; +} + +static const void *zend_jit_trace_allocate_exit_point(uint32_t n) +{ + const void *group = NULL; + + if (UNEXPECTED(n >= ZEND_JIT_TRACE_MAX_EXITS)) { + return NULL; + } + do { + group = zend_jit_trace_allocate_exit_group(ZEND_JIT_EXIT_NUM); + if (!group) { + return NULL; + } + zend_jit_exit_groups[ZEND_JIT_EXIT_NUM / ZEND_JIT_EXIT_POINTS_PER_GROUP] = + group; + ZEND_JIT_EXIT_NUM += ZEND_JIT_EXIT_POINTS_PER_GROUP; + } while (n >= ZEND_JIT_EXIT_NUM); + return (const void*) + ((const char*)group + + ((n % ZEND_JIT_EXIT_POINTS_PER_GROUP) * ZEND_JIT_EXIT_POINTS_SPACING)); +} + +static const void *zend_jit_trace_get_exit_addr(uint32_t n) +{ + if (UNEXPECTED(n >= ZEND_JIT_EXIT_NUM)) { + return zend_jit_trace_allocate_exit_point(n); + } + return (const void*) + ((const char*)zend_jit_exit_groups[n / ZEND_JIT_EXIT_POINTS_PER_GROUP] + + ((n % ZEND_JIT_EXIT_POINTS_PER_GROUP) * ZEND_JIT_EXIT_POINTS_SPACING)); +} + +static uint32_t zend_jit_trace_get_exit_point(const zend_op *from_opline, const zend_op *to_opline, zend_jit_trace_rec *trace) +{ + zend_jit_trace_info *t = &zend_jit_traces[ZEND_JIT_TRACE_NUM]; + uint32_t exit_point; + const zend_op_array *op_array = &JIT_G(current_frame)->func->op_array; + uint32_t stack_offset = (uint32_t)-1; + uint32_t stack_size = op_array->last_var + op_array->T; + zend_jit_trace_stack *stack = NULL; + + if (stack_size) { + stack = JIT_G(current_frame)->stack; + do { + if (stack[stack_size-1] != IS_UNKNOWN) { + break; + } + stack_size--; + } while (stack_size); + } + + /* Try to reuse exit points */ + if (to_opline != NULL && t->exit_count > 0) { + uint32_t i = t->exit_count; + + do { + i--; + if (stack_size == 0 + || (t->exit_info[i].stack_size >= stack_size + && memcmp(t->stack_map + t->exit_info[i].stack_offset, stack, stack_size * sizeof(zend_jit_trace_stack)) == 0)) { + stack_offset = t->exit_info[i].stack_offset; + if (t->exit_info[i].opline == to_opline) { + return i; + } + } + } while (i > 0); + } + + exit_point = t->exit_count; + if (exit_point < ZEND_JIT_TRACE_MAX_EXITS) { + if (stack_size != 0 && stack_offset == (uint32_t)-1) { + stack_offset = t->stack_map_size; + t->stack_map_size += stack_size; + // TODO: reduce number of reallocations ??? + t->stack_map = erealloc(t->stack_map, t->stack_map_size * sizeof(zend_jit_trace_stack)); + memcpy(t->stack_map + stack_offset, stack, stack_size * sizeof(zend_jit_trace_stack)); + } + t->exit_count++; + t->exit_info[exit_point].opline = to_opline; + t->exit_info[exit_point].stack_size = stack_size; + t->exit_info[exit_point].stack_offset = stack_offset; + } + + return exit_point; +} + +static void zend_jit_trace_add_code(const void *start, uint32_t size) +{ + zend_jit_trace_info *t = &zend_jit_traces[ZEND_JIT_TRACE_NUM]; + + t->code_start = start; + t->code_size = size; +} + +static uint32_t zend_jit_find_trace(const void *addr) +{ + uint32_t i; + + for (i = 1; i < ZEND_JIT_TRACE_NUM; i++) { + if (zend_jit_traces[i].code_start == addr) { + return i; + } + } + ZEND_ASSERT(0); + return 0; +} + +static zend_string *zend_jit_trace_name(const zend_op_array *op_array, uint32_t lineno) +{ + smart_str buf = {0}; + + smart_str_appends(&buf, TRACE_PREFIX); + smart_str_append_long(&buf, (zend_long)ZEND_JIT_TRACE_NUM); + smart_str_appendc(&buf, '$'); + if (op_array->function_name) { + if (op_array->scope) { + 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)); + } else { + smart_str_appendl(&buf, ZSTR_VAL(op_array->function_name), ZSTR_LEN(op_array->function_name)); + } + } else if (op_array->filename) { + smart_str_appendl(&buf, ZSTR_VAL(op_array->filename), ZSTR_LEN(op_array->filename)); + } + smart_str_appendc(&buf, '$'); + smart_str_append_long(&buf, (zend_long)lineno); + smart_str_0(&buf); + return buf.s; +} + +static int zend_jit_trace_may_exit(const zend_op_array *op_array, const zend_op *opline, zend_jit_trace_rec *trace) +{ + switch (opline->opcode) { + case ZEND_IS_IDENTICAL: + case ZEND_IS_NOT_IDENTICAL: + case ZEND_IS_EQUAL: + case ZEND_IS_NOT_EQUAL: + case ZEND_IS_SMALLER: + case ZEND_IS_SMALLER_OR_EQUAL: + case ZEND_CASE: + case ZEND_ISSET_ISEMPTY_CV: + case ZEND_ISSET_ISEMPTY_VAR: + case ZEND_ISSET_ISEMPTY_DIM_OBJ: + case ZEND_ISSET_ISEMPTY_PROP_OBJ: + case ZEND_ISSET_ISEMPTY_STATIC_PROP: + case ZEND_INSTANCEOF: + case ZEND_TYPE_CHECK: + case ZEND_DEFINED: + case ZEND_IN_ARRAY: + case ZEND_ARRAY_KEY_EXISTS: + if (opline->result_type & (IS_SMART_BRANCH_JMPNZ | IS_SMART_BRANCH_JMPZ)) { + /* smart branch */ + return 1; + } + break; + case ZEND_JMPZNZ: + case ZEND_JMPZ: + case ZEND_JMPNZ: + case ZEND_JMPZ_EX: + case ZEND_JMPNZ_EX: + case ZEND_JMP_SET: + case ZEND_COALESCE: + case ZEND_FE_RESET_R: + case ZEND_FE_RESET_RW: + case ZEND_ASSERT_CHECK: + case ZEND_FE_FETCH_R: + case ZEND_FE_FETCH_RW: + case ZEND_SWITCH_LONG: + case ZEND_SWITCH_STRING: + /* branch opcdoes */ + return 1; + case ZEND_NEW: + if (opline->extended_value == 0 && (opline+1)->opcode == ZEND_DO_FCALL) { + /* NEW may skip constructor without arguments */ + return 1; + } + break; + case ZEND_CATCH: + case ZEND_FAST_CALL: + case ZEND_FAST_RET: + case ZEND_GENERATOR_CREATE: + case ZEND_GENERATOR_RETURN: + case ZEND_EXIT: + case ZEND_YIELD: + case ZEND_YIELD_FROM: + case ZEND_INCLUDE_OR_EVAL: + /* unsupported */ + return 1; + case ZEND_DO_FCALL: + /* potentially polymorphic call */ + return 1; +#if 0 + case ZEND_DO_UCALL: + case ZEND_DO_FCALL_BY_NAME: + /* monomorphic call */ + // TODO: recompilation may change traget ??? + return 0; +#endif + case ZEND_RETURN_BY_REF: + case ZEND_RETURN: + /* return */ + return trace->op == ZEND_JIT_TRACE_BACK && trace->recursive; + default: + break; + } + return 0; +} + +static zend_always_inline uint32_t zend_jit_trace_type_to_info_ex(zend_uchar type, uint32_t info) +{ + if (type == IS_UNKNOWN) { + return info; + } + ZEND_ASSERT(info & (1 << type)); + if (type < IS_STRING) { + return (1 << type); + } else if (type != IS_ARRAY) { + return (1 << type) | (info & (MAY_BE_RC1|MAY_BE_RCN)); + } else { + return MAY_BE_ARRAY | (info & (MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF|MAY_BE_ARRAY_KEY_ANY|MAY_BE_RC1|MAY_BE_RCN)); + } +} + +static zend_always_inline uint32_t zend_jit_trace_type_to_info(zend_uchar type) +{ + return zend_jit_trace_type_to_info_ex(type, -1); +} + +#define STACK_VAR_TYPE(_var) \ + ((zend_uchar)stack[EX_VAR_TO_NUM(_var)]) + +#define SET_STACK_VAR_TYPE(_var, _type) do { \ + stack[EX_VAR_TO_NUM(_var)] = _type; \ + } while (0) + +#define CHECK_OP_TRACE_TYPE(_var, _ssa_var, op_info, op_type) do { \ + if (op_type != IS_UNKNOWN) { \ + if ((op_info & MAY_BE_GUARD) != 0 \ + && op_type != STACK_VAR_TYPE(_var)) { \ + if (!zend_jit_type_guard(&dasm_state, opline, _var, op_type)) { \ + goto jit_failure; \ + } \ + op_info &= ~MAY_BE_GUARD; \ + ssa->var_info[_ssa_var].type &= op_info; \ + } \ + SET_STACK_VAR_TYPE(_var, op_type); \ + } \ + } while (0) + +#define USE_OP_TRACE_TYPE(_type, _var, op_info) do { \ + if (_type & (IS_TMP_VAR|IS_VAR|IS_CV)) { \ + op_info = zend_jit_trace_type_to_info_ex(STACK_VAR_TYPE(_var), op_info); \ + } \ + } while (0) + +#define CHECK_OP1_TRACE_TYPE() \ + CHECK_OP_TRACE_TYPE(opline->op1.var, ssa_op->op1_use, op1_info, op1_type) +#define CHECK_OP2_TRACE_TYPE() \ + CHECK_OP_TRACE_TYPE(opline->op2.var, ssa_op->op2_use, op2_info, op2_type) +#define CHECK_OP1_DATA_TRACE_TYPE() \ + CHECK_OP_TRACE_TYPE((opline+1)->op1.var, (ssa_op+1)->op1_use, op1_data_info, op3_type) +#define USE_OP1_TRACE_TYPE() \ + USE_OP_TRACE_TYPE(opline->op1_type, opline->op1.var, op1_info) +#define USE_OP2_TRACE_TYPE() \ + USE_OP_TRACE_TYPE(opline->op2_type, opline->op2.var, op2_info) +#define USE_OP1_DATA_TRACE_TYPE() \ + USE_OP_TRACE_TYPE((opline+1)->op1_type, (opline+1)->op1.var, op1_data_info) +#define USE_RES_TRACE_TYPE() \ + USE_OP_TRACE_TYPE(opline->result_type, opline->result.var, res_use_info) +#define SET_OP1_STACK_VAR_TYPE(_type) \ + SET_STACK_VAR_TYPE(opline->op1.var, _type) +#define SET_OP2_STACK_VAR_TYPE( _type) \ + SET_STACK_VAR_TYPE(opline->op2.var, _type) +#define SET_OP1_DATA_STACK_VAR_TYPE(_type) \ + SET_STACK_VAR_TYPE((opline+1)->op1.var, _type) +#define SET_RES_STACK_VAR_TYPE(_type) \ + SET_STACK_VAR_TYPE(opline->result.var, _type) + +static zend_always_inline size_t zend_jit_trace_frame_size(const zend_op_array *op_array) +{ + if (op_array->type == ZEND_USER_FUNCTION) { + return offsetof(zend_jit_trace_stack_frame, stack) + ZEND_MM_ALIGNED_SIZE((op_array->last_var + op_array->T) * sizeof(zend_jit_trace_stack)); + } else { + return offsetof(zend_jit_trace_stack_frame, stack); + } +} + +static zend_jit_trace_stack_frame* zend_jit_trace_call_frame(zend_jit_trace_stack_frame *frame, const zend_op_array *op_array) +{ + return (zend_jit_trace_stack_frame*)((char*)frame + zend_jit_trace_frame_size(op_array)); +} + +static zend_jit_trace_stack_frame* zend_jit_trace_ret_frame(zend_jit_trace_stack_frame *frame, const zend_op_array *op_array) +{ + return (zend_jit_trace_stack_frame*)((char*)frame - zend_jit_trace_frame_size(op_array)); +} + +static void zend_jit_trace_send_type(const zend_op *opline, zend_jit_trace_stack_frame *call, zend_uchar type) +{ + zend_jit_trace_stack *stack = call->stack; + const zend_op_array *op_array = &call->func->op_array; + uint32_t arg_num = opline->op2.num; + + if (arg_num > op_array->num_args) { + return; + } + if (op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS) { + zend_arg_info *arg_info; + + ZEND_ASSERT(arg_num <= op_array->num_args); + arg_info = &op_array->arg_info[arg_num-1]; + + if (ZEND_TYPE_IS_SET(arg_info->type)) { + if (!(ZEND_TYPE_FULL_MASK(arg_info->type) & (1u << type))) { + return; + } + } + } + SET_STACK_VAR_TYPE(opline->result.var, type); +} + +static zend_ssa *zend_jit_trace_build_ssa(const zend_op_array *op_array, zend_script *script) +{ + zend_jit_op_array_trace_extension *jit_extension; + zend_ssa *ssa; + + jit_extension = + (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array); + memset(&jit_extension->func_info, 0, sizeof(jit_extension->func_info)); + jit_extension->func_info.num_args = -1; + jit_extension->func_info.return_value_used = -1; + ssa = &jit_extension->func_info.ssa; + + if (zend_jit_level >= ZEND_JIT_LEVEL_OPT_FUNC) { + do { + if (zend_jit_op_array_analyze1(op_array, script, ssa) != SUCCESS) { + break; + } + + if (zend_jit_level >= ZEND_JIT_LEVEL_OPT_FUNCS) { + if (zend_analyze_calls(&CG(arena), script, ZEND_CALL_TREE, (zend_op_array*)op_array, &jit_extension->func_info) != SUCCESS) { + break; + } + jit_extension->func_info.call_map = zend_build_call_map(&CG(arena), &jit_extension->func_info, (zend_op_array*)op_array); + if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { + zend_init_func_return_info(op_array, script, &jit_extension->func_info.return_info); + } + } + + if (zend_jit_op_array_analyze2(op_array, script, ssa) != SUCCESS) { + break; + } + + if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_SSA) { + zend_dump_op_array(op_array, ZEND_DUMP_HIDE_UNREACHABLE|ZEND_DUMP_RC_INFERENCE|ZEND_DUMP_SSA, "JIT", ssa); + } + return ssa; + } while (0); + } + + memset(ssa, 0, sizeof(zend_ssa)); + ssa->cfg.blocks_count = 1; + return ssa; +} + +static void zend_jit_dump_trace(zend_jit_trace_rec *trace_buffer, zend_ssa *tssa); + +static zend_always_inline int zend_jit_trace_op_len(const zend_op *opline) +{ + int len; + + switch (opline->opcode) { + case ZEND_ASSIGN_DIM: + case ZEND_ASSIGN_OBJ: + case ZEND_ASSIGN_STATIC_PROP: + case ZEND_ASSIGN_DIM_OP: + case ZEND_ASSIGN_OBJ_OP: + case ZEND_ASSIGN_STATIC_PROP_OP: + case ZEND_ASSIGN_OBJ_REF: + case ZEND_ASSIGN_STATIC_PROP_REF: + return 2; /* OP_DATA */ + case ZEND_RECV_INIT: + len = 1; + opline++; + while (opline->opcode == ZEND_RECV_INIT) { + len++; + opline++; + } + return len; + case ZEND_BIND_GLOBAL: + len = 1; + opline++; + while (opline->opcode == ZEND_BIND_GLOBAL) { + len++; + opline++; + } + return len; +// case ZEND_IS_IDENTICAL: +// case ZEND_IS_NOT_IDENTICAL: +// case ZEND_IS_EQUAL: +// case ZEND_IS_NOT_EQUAL: +// case ZEND_IS_SMALLER: +// case ZEND_IS_SMALLER_OR_EQUAL: +// case ZEND_CASE: +// case ZEND_ISSET_ISEMPTY_CV: +// case ZEND_ISSET_ISEMPTY_VAR: +// case ZEND_ISSET_ISEMPTY_DIM_OBJ: +// case ZEND_ISSET_ISEMPTY_PROP_OBJ: +// case ZEND_ISSET_ISEMPTY_STATIC_PROP: +// case ZEND_INSTANCEOF: +// case ZEND_TYPE_CHECK: +// case ZEND_DEFINED: +// case ZEND_IN_ARRAY: +// case ZEND_ARRAY_KEY_EXISTS: + default: + if ((opline->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0) { + return 2; /* JMPZ/JMPNZ */ + } + return 1; + } +} + +static int zend_jit_trace_add_phis(zend_jit_trace_rec *trace_buffer, uint32_t ssa_vars_count, zend_ssa *tssa, int *var) +{ + const zend_op_array *op_array; + zend_jit_trace_rec *p; + int k, vars_count; + zend_bitset use, def; + uint32_t build_flags = ZEND_SSA_RC_INFERENCE | ZEND_SSA_USE_CV_RESULTS; + uint32_t set_size; + zend_ssa_phi *prev = NULL; + int level = 0; + ALLOCA_FLAG(use_heap); + + op_array = trace_buffer->op_array; + set_size = zend_bitset_len(op_array->last_var + op_array->T); + use = ZEND_BITSET_ALLOCA(set_size * 2, use_heap); + memset(use, 0, set_size * 2 * ZEND_BITSET_ELM_SIZE); + def = use + set_size; + p = trace_buffer + ZEND_JIT_TRACE_START_REC_SIZE; + for (;;p++) { + if (p->op == ZEND_JIT_TRACE_VM && level == 0) { + const zend_op *opline = p->opline; + int len; + + zend_dfg_add_use_def_op(op_array, opline, build_flags, use, def); + len = zend_jit_trace_op_len(opline); + while (len > 1) { + opline++; + if (opline->opcode != ZEND_OP_DATA) { + zend_dfg_add_use_def_op(op_array, opline, build_flags, use, def); + } + len--; + } + } else if (p->op == ZEND_JIT_TRACE_INIT_CALL) { + } else if (p->op == ZEND_JIT_TRACE_DO_ICALL) { + } else if (p->op == ZEND_JIT_TRACE_ENTER) { + level++; + } else if (p->op == ZEND_JIT_TRACE_BACK) { + if (level == 0) { + // Phi for recursive calls and returns are not supporte yet ??? + assert(0); + } else { + level--; + } + } else if (p->op == ZEND_JIT_TRACE_END) { + break; + } + } + + zend_bitset_intersection(use, def, set_size); + + if (trace_buffer->start == ZEND_JIT_TRACE_START_ENTER) { + vars_count = op_array->last_var; + } else { + vars_count = op_array->last_var + op_array->T; + } + for (k = 0; k < vars_count; k++) { + if (zend_bitset_in(use, k)) { + zend_ssa_phi *phi = zend_arena_calloc(&CG(arena), 1, + ZEND_MM_ALIGNED_SIZE(sizeof(zend_ssa_phi)) + + ZEND_MM_ALIGNED_SIZE(sizeof(int) * 2) + + sizeof(void*) * 2); + phi->sources = (int*)(((char*)phi) + ZEND_MM_ALIGNED_SIZE(sizeof(zend_ssa_phi))); + phi->sources[0] = var[k]; + phi->sources[1] = -1; + phi->use_chains = (zend_ssa_phi**)(((char*)phi->sources) + ZEND_MM_ALIGNED_SIZE(sizeof(int) * 2)); + phi->pi = -1; + phi->var = k; + phi->ssa_var = ssa_vars_count; + var[k] = ssa_vars_count; + ssa_vars_count++; + phi->block = 1; + if (prev) { + prev->next = phi; + } else { + tssa->blocks[1].phis = phi; + } + prev = phi; + } + } + + free_alloca(use, use_heap); + + return ssa_vars_count; +} + +static int zend_jit_trace_copy_ssa_var_info(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op **tssa_opcodes, zend_ssa *tssa, int ssa_var) +{ + int var, use; + zend_ssa_op *op; + zend_ssa_var_info *info; + unsigned int no_val; + + if (tssa->vars[ssa_var].phi_use_chain) { + // TODO: this may be incorrect ??? + var = tssa->vars[ssa_var].phi_use_chain->ssa_var; + } else { + var = ssa_var; + } + use = tssa->vars[var].use_chain; + if (use >= 0) { + ZEND_ASSERT((tssa_opcodes[use] - op_array->opcodes) < op_array->last); + op = ssa->ops + (tssa_opcodes[use] - op_array->opcodes); + if (tssa->ops[use].op1_use == var) { + no_val = ssa->vars[op->op1_use].no_val; + info = ssa->var_info + op->op1_use; + } else if (tssa->ops[use].op2_use == var) { + no_val = ssa->vars[op->op2_use].no_val; + info = ssa->var_info + op->op2_use; + } else if (tssa->ops[use].result_use == var) { + no_val = ssa->vars[op->result_use].no_val; + info = ssa->var_info + op->result_use; + } else { + assert(0); + } + tssa->vars[ssa_var].no_val = no_val; + memcpy(&tssa->var_info[ssa_var], info, sizeof(zend_ssa_var_info)); + return 1; + } + return 0; +} + +static int zend_jit_trace_copy_ssa_var_range(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op **tssa_opcodes, zend_ssa *tssa, int ssa_var) +{ + int def; + zend_ssa_op *op; + zend_ssa_var_info *info; + unsigned int no_val; + + def = tssa->vars[ssa_var].definition; + if (def >= 0) { + ZEND_ASSERT((tssa_opcodes[def] - op_array->opcodes) < op_array->last); + op = ssa->ops + (tssa_opcodes[def] - op_array->opcodes); + if (tssa->ops[def].op1_def == ssa_var) { + no_val = ssa->vars[op->op1_def].no_val; + info = ssa->var_info + op->op1_def; + } else if (tssa->ops[def].op2_def == ssa_var) { + no_val = ssa->vars[op->op2_def].no_val; + info = ssa->var_info + op->op2_def; + } else if (tssa->ops[def].result_def == ssa_var) { + no_val = ssa->vars[op->result_def].no_val; + info = ssa->var_info + op->result_def; + } else { + assert(0); + } + + tssa->vars[ssa_var].no_val = no_val; + + if (info->has_range) { + if (tssa->var_info[ssa_var].has_range) { + tssa->var_info[ssa_var].range.min = MAX(tssa->var_info[ssa_var].range.min, info->range.min); + tssa->var_info[ssa_var].range.max = MIN(tssa->var_info[ssa_var].range.max, info->range.max); + tssa->var_info[ssa_var].range.underflow = tssa->var_info[ssa_var].range.underflow && info->range.underflow; + tssa->var_info[ssa_var].range.overflow = tssa->var_info[ssa_var].range.overflow && info->range.overflow; + } else { + tssa->var_info[ssa_var].has_range = 1; + tssa->var_info[ssa_var].range = info->range; + } + } + return 1; + } + return 0; +} + +static int zend_jit_trace_restrict_ssa_var_info(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op **tssa_opcodes, zend_ssa *tssa, int ssa_var) +{ + int def; + zend_ssa_op *op; + zend_ssa_var_info *info; + + def = tssa->vars[ssa_var].definition; + if (def >= 0) { + ZEND_ASSERT((tssa_opcodes[def] - op_array->opcodes) < op_array->last); + op = ssa->ops + (tssa_opcodes[def] - op_array->opcodes); + if (tssa->ops[def].op1_def == ssa_var) { + info = ssa->var_info + op->op1_def; + } else if (tssa->ops[def].op2_def == ssa_var) { + info = ssa->var_info + op->op2_def; + } else if (tssa->ops[def].result_def == ssa_var) { + info = ssa->var_info + op->result_def; + } else { + assert(0); + } + tssa->var_info[ssa_var].type &= info->type; + if (info->ce) { + if (tssa->var_info[ssa_var].ce) { + ZEND_ASSERT(tssa->var_info[ssa_var].ce == info->ce); + tssa->var_info[ssa_var].is_instanceof = + tssa->var_info[ssa_var].is_instanceof && info->is_instanceof; + } else { + tssa->var_info[ssa_var].ce = info->ce; + tssa->var_info[ssa_var].is_instanceof = info->is_instanceof; + } + } + if (info->has_range) { + if (tssa->var_info[ssa_var].has_range) { + tssa->var_info[ssa_var].range.min = MAX(tssa->var_info[ssa_var].range.min, info->range.min); + tssa->var_info[ssa_var].range.max = MIN(tssa->var_info[ssa_var].range.max, info->range.max); + tssa->var_info[ssa_var].range.underflow = tssa->var_info[ssa_var].range.underflow && info->range.underflow; + tssa->var_info[ssa_var].range.overflow = tssa->var_info[ssa_var].range.overflow && info->range.overflow; + } else { + tssa->var_info[ssa_var].has_range = 1; + tssa->var_info[ssa_var].range = info->range; + } + } + return 1; + } + return 0; +} + +static int find_return_ssa_var(zend_jit_trace_rec *p, zend_ssa_op *ssa_op) +{ + while (1) { + if (p->op == ZEND_JIT_TRACE_VM) { + if (p->opline->opcode == ZEND_DO_UCALL + || p->opline->opcode == ZEND_DO_FCALL_BY_NAME + || p->opline->opcode == ZEND_DO_FCALL) { + if (p->opline->result_type != IS_UNUSED) { + return ssa_op->result_def; + } + } + return -1; + } else if (p->op == ZEND_JIT_TRACE_OP1_TYPE || p->op == ZEND_JIT_TRACE_OP2_TYPE) { + /*skip */ + } else { + return -1; + } + p--; + } +} + +static int find_call_num_args(zend_jit_trace_rec *p) +{ + while (1) { + if (p->op == ZEND_JIT_TRACE_VM) { + if (p->opline->opcode == ZEND_INIT_FCALL + || p->opline->opcode == ZEND_INIT_FCALL_BY_NAME + || p->opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME + || p->opline->opcode == ZEND_INIT_DYNAMIC_CALL + || p->opline->opcode == ZEND_INIT_USER_CALL + || p->opline->opcode == ZEND_NEW + || p->opline->opcode == ZEND_INIT_METHOD_CALL + || p->opline->opcode == ZEND_INIT_STATIC_METHOD_CALL) { + if (p->opline->extended_value <= 127) { + return p->opline->extended_value; + } else { + return -1; + } + } + return -1; + } else if (p->op == ZEND_JIT_TRACE_OP1_TYPE || p->op == ZEND_JIT_TRACE_OP2_TYPE) { + /*skip */ + } else { + return -1; + } + p--; + } +} + +static int is_checked_guard(const zend_ssa *tssa, const zend_op **ssa_opcodes, uint32_t var, uint32_t phi_var) +{ + if ((tssa->var_info[phi_var].type & MAY_BE_ANY) == MAY_BE_LONG) { + uint32_t idx = tssa->vars[var].definition; + + if (idx >= 0) { + if (tssa->ops[idx].op1_def == var) { + const zend_op *opline = ssa_opcodes[idx]; + if (opline->opcode == ZEND_PRE_DEC + || opline->opcode == ZEND_PRE_INC + || opline->opcode == ZEND_POST_DEC + || opline->opcode == ZEND_POST_INC) { + return 1; + } + } + if (tssa->ops[idx].result_def == var) { + const zend_op *opline = ssa_opcodes[idx]; + if (opline->opcode == ZEND_ADD + || opline->opcode == ZEND_SUB + || opline->opcode == ZEND_MUL + || opline->opcode == ZEND_PRE_DEC + || opline->opcode == ZEND_PRE_INC + || opline->opcode == ZEND_POST_DEC + || opline->opcode == ZEND_POST_INC) { + return 1; + } + } + } + } + return 0; +} + +static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uint32_t parent_trace, uint32_t exit_num, zend_script *script, const zend_op_array **op_arrays, int *num_op_arrays_ptr) +{ + zend_ssa *tssa; + zend_ssa_op *ssa_ops, *op; + zend_ssa_var *ssa_vars; + zend_ssa_var_info *ssa_var_info; + const zend_op_array *op_array; + const zend_op *opline; + const zend_op **ssa_opcodes; + zend_jit_trace_rec *p; + int i, v, idx, len, ssa_ops_count, vars_count, ssa_vars_count; + int *var; + uint32_t build_flags = ZEND_SSA_RC_INFERENCE | ZEND_SSA_USE_CV_RESULTS; + uint32_t optimization_level = ZCG(accel_directives).optimization_level; + int call_level, level, num_op_arrays; + size_t frame_size, stack_top, stack_size, stack_bottom; + zend_jit_op_array_trace_extension *jit_extension; + zend_ssa *ssa; + zend_jit_trace_stack_frame *frame, *top, *call; + zend_ssa_var_info return_value_info; + + /* 1. Count number of TSSA opcodes; + * Count number of activation frames; + * Calculate size of abstract stack; + * Construct regular SSA for involved op_array */ + op_array = trace_buffer->op_array; + stack_top = stack_size = zend_jit_trace_frame_size(op_array); + stack_bottom = 0; + p = trace_buffer + ZEND_JIT_TRACE_START_REC_SIZE; + ssa_ops_count = 0; + call_level = 0; + level = 0; + num_op_arrays = 0; + /* Remember op_array to cleanup */ + op_arrays[num_op_arrays++] = op_array; + /* Build SSA */ + ssa = zend_jit_trace_build_ssa(op_array, script); + for (;;p++) { + if (p->op == ZEND_JIT_TRACE_VM) { + ssa_ops_count += zend_jit_trace_op_len(p->opline); + } else if (p->op == ZEND_JIT_TRACE_INIT_CALL) { + call_level++; + stack_top += zend_jit_trace_frame_size(p->op_array); + if (stack_top > stack_size) { + stack_size = stack_top; + } + } else if (p->op == ZEND_JIT_TRACE_DO_ICALL) { + frame_size = zend_jit_trace_frame_size(p->op_array); + if (call_level == 0) { + if (stack_top + frame_size > stack_size) { + stack_size = stack_top + frame_size; + } + } else { + call_level--; + stack_top -= frame_size; + } + } else if (p->op == ZEND_JIT_TRACE_ENTER) { + op_array = p->op_array; + if (call_level == 0) { + stack_top += zend_jit_trace_frame_size(op_array); + if (stack_top > stack_size) { + stack_size = stack_top; + } + } else { + call_level--; + } + level++; + jit_extension = + (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array); + ssa = &jit_extension->func_info.ssa; + if (ssa->cfg.blocks_count) { + /* pass */ + } else if (num_op_arrays == ZEND_JIT_TRACE_MAX_FUNCS) { + /* Too many functions in single trace */ + return NULL; + } else { + /* Remember op_array to cleanup */ + op_arrays[num_op_arrays++] = op_array; + /* Build SSA */ + ssa = zend_jit_trace_build_ssa(op_array, script); + } + } else if (p->op == ZEND_JIT_TRACE_BACK) { + if (level == 0) { + stack_bottom += zend_jit_trace_frame_size(op_array);; + jit_extension = + (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array); + ssa = &jit_extension->func_info.ssa; + if (ssa->cfg.blocks_count) { + /* pass */ + } else if (num_op_arrays == ZEND_JIT_TRACE_MAX_FUNCS) { + /* Too many functions in single trace */ + return NULL; + } else { + /* Remember op_array to cleanup */ + op_arrays[num_op_arrays++] = op_array; + /* Build SSA */ + ssa = zend_jit_trace_build_ssa(op_array, script); + } + } else { + stack_top -= zend_jit_trace_frame_size(op_array); + level--; + } + op_array = p->op_array; + } else if (p->op == ZEND_JIT_TRACE_END) { + break; + } + } + *num_op_arrays_ptr = num_op_arrays; + + /* 2. Construct TSSA */ + tssa = zend_arena_calloc(&CG(arena), 1, sizeof(zend_ssa)); + tssa->cfg.blocks = zend_arena_calloc(&CG(arena), 2, sizeof(zend_basic_block)); + tssa->blocks = zend_arena_calloc(&CG(arena), 2, sizeof(zend_ssa_block)); + tssa->cfg.predecessors = zend_arena_calloc(&CG(arena), 2, sizeof(int)); + tssa->ops = ssa_ops = zend_arena_alloc(&CG(arena), ssa_ops_count * sizeof(zend_ssa_op)); + memset(ssa_ops, -1, ssa_ops_count * sizeof(zend_ssa_op)); + ssa_opcodes = zend_arena_calloc(&CG(arena), ssa_ops_count, sizeof(zend_op*)); + JIT_G(current_frame) = frame = (zend_jit_trace_stack_frame*)((char*)zend_arena_alloc(&CG(arena), stack_bottom + stack_size) + stack_bottom); + + if (trace_buffer->stop == ZEND_JIT_TRACE_STOP_LOOP) { + tssa->cfg.blocks_count = 2; + tssa->cfg.edges_count = 2; + + tssa->cfg.predecessors[0] = 0; + tssa->cfg.predecessors[1] = 1; + + tssa->cfg.blocks[0].flags = ZEND_BB_START|ZEND_BB_REACHABLE; + tssa->cfg.blocks[0].successors_count = 1; + tssa->cfg.blocks[0].predecessors_count = 0; + tssa->cfg.blocks[0].successors = tssa->cfg.blocks[0].successors_storage; + tssa->cfg.blocks[0].successors[0] = 1; + + tssa->cfg.blocks[0].flags = ZEND_BB_FOLLOW|ZEND_BB_TARGET|ZEND_BB_LOOP_HEADER|ZEND_BB_REACHABLE; + tssa->cfg.blocks[1].successors_count = 1; + tssa->cfg.blocks[1].predecessors_count = 2; + tssa->cfg.blocks[1].successors = tssa->cfg.blocks[1].successors_storage; + tssa->cfg.blocks[1].successors[1] = 1; + } else { + tssa->cfg.blocks_count = 1; + tssa->cfg.edges_count = 0; + + tssa->cfg.blocks[0].flags = ZEND_BB_START|ZEND_BB_EXIT|ZEND_BB_REACHABLE; + tssa->cfg.blocks[0].successors_count = 0; + tssa->cfg.blocks[0].predecessors_count = 0; + } + + op_array = trace_buffer->op_array; + if (trace_buffer->start == ZEND_JIT_TRACE_START_ENTER) { + ssa_vars_count = op_array->last_var; + } else { + ssa_vars_count = op_array->last_var + op_array->T; + } + var = frame->stack; + for (i = 0; i < ssa_vars_count; i++) { + var[i] = i; + } + + if (trace_buffer->stop == ZEND_JIT_TRACE_STOP_LOOP) { + // TODO: For tracing, it's possible, to create pseudo Phi functions + // at the end of loop, without this additional pass (like LuaJIT) ??? + ssa_vars_count = zend_jit_trace_add_phis(trace_buffer, ssa_vars_count, tssa, var); + } + + p = trace_buffer + ZEND_JIT_TRACE_START_REC_SIZE; + idx = 0; + level = 0; + for (;;p++) { + if (p->op == ZEND_JIT_TRACE_VM) { + opline = p->opline; + ssa_opcodes[idx] = opline; + ssa_vars_count = zend_ssa_rename_op(op_array, opline, idx, build_flags, ssa_vars_count, ssa_ops, var); + idx++; + len = zend_jit_trace_op_len(p->opline); + while (len > 1) { + opline++; + ssa_opcodes[idx] = opline; + if (opline->opcode != ZEND_OP_DATA) { + ssa_vars_count = zend_ssa_rename_op(op_array, opline, idx, build_flags, ssa_vars_count, ssa_ops, var); + } + idx++; + len--; + } + } else if (p->op == ZEND_JIT_TRACE_ENTER) { + frame = zend_jit_trace_call_frame(frame, op_array); + var = frame->stack; + op_array = p->op_array; + level++; + ZEND_ASSERT(ssa_vars_count < 0xff); + p->first_ssa_var = ssa_vars_count; + for (i = 0; i < op_array->last_var; i++) { + var[i] = ssa_vars_count++; + } + } else if (p->op == ZEND_JIT_TRACE_BACK) { + op_array = p->op_array; + frame = zend_jit_trace_ret_frame(frame, op_array); + var = frame->stack; + if (level == 0) { + ZEND_ASSERT(ssa_vars_count <= 0xff); + p->first_ssa_var = ssa_vars_count; + for (i = 0; i < op_array->last_var + op_array->T; i++) { + var[i] = ssa_vars_count++; + } + } else { + level--; + } + } else if (p->op == ZEND_JIT_TRACE_END) { + break; + } + } + + op_array = trace_buffer->op_array; + tssa->vars_count = ssa_vars_count; + tssa->vars = ssa_vars = zend_arena_calloc(&CG(arena), tssa->vars_count, sizeof(zend_ssa_var)); + if (trace_buffer->start == ZEND_JIT_TRACE_START_ENTER) { + vars_count = op_array->last_var; + } else { + vars_count = op_array->last_var + op_array->T; + } + i = 0; + while (i < vars_count) { + ssa_vars[i].var = i; + ssa_vars[i].scc = -1; + ssa_vars[i].definition = -1; + ssa_vars[i].use_chain = -1; + i++; + } + while (i < tssa->vars_count) { + ssa_vars[i].var = -1; + ssa_vars[i].scc = -1; + ssa_vars[i].definition = -1; + ssa_vars[i].use_chain = -1; + i++; + } + + if (trace_buffer->stop == ZEND_JIT_TRACE_STOP_LOOP) { + /* Update Phi sources */ + zend_ssa_phi *phi = tssa->blocks[1].phis; + + while (phi) { + phi->sources[1] = var[phi->var]; + ssa_vars[phi->ssa_var].var = phi->var; + ssa_vars[phi->ssa_var].definition_phi = phi; + ssa_vars[phi->sources[0]].phi_use_chain = phi; + ssa_vars[phi->sources[1]].phi_use_chain = phi; + phi = phi->next; + } + } + + /* 3. Compute use-def chains */ + idx = (ssa_ops_count - 1); + op = ssa_ops + idx; + while (idx >= 0) { + opline = ssa_opcodes[idx]; + if (op->op1_use >= 0) { + op->op1_use_chain = ssa_vars[op->op1_use].use_chain; + ssa_vars[op->op1_use].use_chain = idx; + } + if (op->op2_use >= 0 && op->op2_use != op->op1_use) { + op->op2_use_chain = ssa_vars[op->op2_use].use_chain; + ssa_vars[op->op2_use].use_chain = idx; + } + if (op->result_use >= 0 && op->result_use != op->op1_use && op->result_use != op->op2_use) { + op->res_use_chain = ssa_vars[op->result_use].use_chain; + ssa_vars[op->result_use].use_chain = idx; + } + if (op->op1_def >= 0) { + ssa_vars[op->op1_def].var = EX_VAR_TO_NUM(opline->op1.var); + ssa_vars[op->op1_def].definition = idx; + } + if (op->op2_def >= 0) { + ssa_vars[op->op2_def].var = EX_VAR_TO_NUM(opline->op2.var); + ssa_vars[op->op2_def].definition = idx; + } + if (op->result_def >= 0) { + ssa_vars[op->result_def].var = EX_VAR_TO_NUM(opline->result.var); + ssa_vars[op->result_def].definition = idx; + } + op--; + idx--; + } + + /* 4. Type inference */ + op_array = trace_buffer->op_array; + jit_extension = + (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array); + ssa = &jit_extension->func_info.ssa; + + tssa->var_info = ssa_var_info = zend_arena_calloc(&CG(arena), tssa->vars_count, sizeof(zend_ssa_var_info)); + + if (trace_buffer->start == ZEND_JIT_TRACE_START_ENTER) { + i = 0; + while (i < op_array->last_var) { + if (!ssa->var_info + || !zend_jit_trace_copy_ssa_var_info(op_array, ssa, ssa_opcodes, tssa, i)) { + if (i < op_array->num_args) { + if (op_array->arg_info) { + zend_arg_info *arg_info = &op_array->arg_info[i]; + zend_class_entry *ce; + uint32_t tmp = zend_fetch_arg_info_type(script, arg_info, &ce); + + if (ZEND_ARG_SEND_MODE(arg_info)) { + tmp |= MAY_BE_REF; + } + ssa_var_info[i].type = tmp; + } else { + ssa_var_info[i].type = MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + } + } else { + ssa_var_info[i].type = MAY_BE_UNDEF; + } + } + i++; + } + } else { + int parent_vars_count = 0; + zend_jit_trace_stack *parent_stack = NULL; + + i = 0; + if (parent_trace) { + parent_vars_count = MIN(zend_jit_traces[parent_trace].exit_info[exit_num].stack_size, + op_array->last_var + op_array->T); + if (parent_vars_count) { + parent_stack = + zend_jit_traces[parent_trace].stack_map + + zend_jit_traces[parent_trace].exit_info[exit_num].stack_offset; + } + } + while (i < op_array->last_var + op_array->T) { + if (!ssa->var_info + || !zend_jit_trace_copy_ssa_var_info(op_array, ssa, ssa_opcodes, tssa, i)) { + if (i < op_array->last_var) { + ssa_var_info[i].type = MAY_BE_UNDEF | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + } else { + ssa_var_info[i].type = MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + } + } + if (i < parent_vars_count) { + /* Initialize TSSA variable from parent trace */ + zend_uchar op_type = parent_stack[i]; + + if (op_type != IS_UNKNOWN) { + ssa_var_info[i].type &= zend_jit_trace_type_to_info(op_type); + } + } + i++; + } + } + + if (trace_buffer->stop == ZEND_JIT_TRACE_STOP_LOOP) { + /* Propagae initial value through Phi functions */ + zend_ssa_phi *phi = tssa->blocks[1].phis; + + while (phi) { + if (!ssa->var_info + || !zend_jit_trace_copy_ssa_var_info(op_array, ssa, ssa_opcodes, tssa, phi->ssa_var)) { + ssa_var_info[phi->ssa_var].type = ssa_var_info[phi->sources[0]].type; + } + phi = phi->next; + } + } + + frame = JIT_G(current_frame); + top = zend_jit_trace_call_frame(frame, op_array); + frame->call = NULL; + frame->prev = NULL; + frame->func = (const zend_function*)op_array; + frame->return_ssa_var = -1; + for (i = 0; i < op_array->last_var + op_array->T; i++) { + frame->stack[i] = -1; + } + memset(&return_value_info, 0, sizeof(return_value_info)); + + p = trace_buffer + ZEND_JIT_TRACE_START_REC_SIZE; + idx = 0; + level = 0; + for (;;p++) { + if (p->op == ZEND_JIT_TRACE_VM) { + uint8_t op1_type, op2_type, op3_type; + + // TODO: use types recorded in trace (add guards) ??? + // TODO: range inference ??? + opline = p->opline; + + + op1_type = p->op1_type; + op2_type = p->op2_type; + op3_type = p->op3_type; + if (op1_type & IS_TRACE_REFERENCE) { + op1_type = IS_UNKNOWN; + } + if (op2_type & IS_TRACE_REFERENCE) { + op2_type = IS_UNKNOWN; + } + if (op3_type & IS_TRACE_REFERENCE) { + op3_type = IS_UNKNOWN; + } + + if ((p+1)->op == ZEND_JIT_TRACE_OP1_TYPE) { + // TODO: support for recorded classes ??? + p++; + } + if ((p+1)->op == ZEND_JIT_TRACE_OP2_TYPE) { + // TODO: support for recorded classes ??? + p++; + } + + switch (opline->opcode) { + case ZEND_ASSIGN_DIM_OP: + case ZEND_ASSIGN_DIM: + if (tssa->ops[idx+1].op1_use >= 0 && op3_type != IS_UNKNOWN) { + zend_ssa_var_info *info = &ssa_var_info[ssa_ops[idx+1].op1_use]; + if ((info->type & (MAY_BE_ANY|MAY_BE_UNDEF)) != (1 << op3_type)) { + info->type = MAY_BE_GUARD | zend_jit_trace_type_to_info_ex(op3_type, info->type); + } + } + /* break missing intentionally */ + case ZEND_BW_OR: + case ZEND_BW_AND: + case ZEND_BW_XOR: + case ZEND_SL: + case ZEND_SR: + case ZEND_MOD: + case ZEND_ADD: + case ZEND_SUB: + case ZEND_MUL: +// case ZEND_DIV: // TODO: check for division by zero ??? + case ZEND_CONCAT: + case ZEND_FAST_CONCAT: + case ZEND_ASSIGN_OP: + case ZEND_IS_EQUAL: + case ZEND_IS_NOT_EQUAL: + case ZEND_IS_SMALLER: + case ZEND_IS_SMALLER_OR_EQUAL: + case ZEND_CASE: + case ZEND_IS_IDENTICAL: + case ZEND_IS_NOT_IDENTICAL: + case ZEND_FETCH_DIM_R: + case ZEND_FETCH_DIM_IS: + case ZEND_ISSET_ISEMPTY_DIM_OBJ: + if (tssa->ops[idx].op2_use >= 0 && op2_type != IS_UNKNOWN) { + zend_ssa_var_info *info = &ssa_var_info[ssa_ops[idx].op2_use]; + if ((info->type & (MAY_BE_ANY|MAY_BE_UNDEF)) != (1 << op2_type)) { + info->type = MAY_BE_GUARD | zend_jit_trace_type_to_info_ex(op2_type, info->type); + } + } + /* break missing intentionally */ + case ZEND_PRE_INC: + case ZEND_PRE_DEC: + case ZEND_POST_INC: + case ZEND_POST_DEC: + case ZEND_QM_ASSIGN: + case ZEND_BOOL: + case ZEND_BOOL_NOT: + case ZEND_JMPZ: + case ZEND_JMPNZ: + case ZEND_JMPZNZ: + case ZEND_JMPZ_EX: + case ZEND_JMPNZ_EX: + if (tssa->ops[idx].op1_use >= 0 && op1_type != IS_UNKNOWN) { + zend_ssa_var_info *info = &ssa_var_info[ssa_ops[idx].op1_use]; + if ((info->type & (MAY_BE_ANY|MAY_BE_UNDEF)) != (1 << op1_type)) { + info->type = MAY_BE_GUARD | zend_jit_trace_type_to_info_ex(op1_type, info->type); + } + } + break; + case ZEND_ASSIGN: + if (tssa->ops[idx].op2_use >= 0 && op2_type != IS_UNKNOWN) { + zend_ssa_var_info *info = &ssa_var_info[ssa_ops[idx].op2_use]; + if ((info->type & (MAY_BE_ANY|MAY_BE_UNDEF)) != (1 << op2_type)) { + info->type = MAY_BE_GUARD | zend_jit_trace_type_to_info_ex(op2_type, info->type); + } + } + break; + case ZEND_SEND_VAL: + case ZEND_SEND_VAL_EX: + case ZEND_SEND_VAR: + case ZEND_SEND_VAR_EX: + case ZEND_SEND_VAR_NO_REF: + case ZEND_SEND_VAR_NO_REF_EX: + if (tssa->ops[idx].op1_use >= 0 && op1_type != IS_UNKNOWN) { + zend_ssa_var_info *info = &ssa_var_info[ssa_ops[idx].op1_use]; + if ((info->type & (MAY_BE_ANY|MAY_BE_UNDEF)) != (1 << op1_type)) { + info->type = MAY_BE_GUARD | zend_jit_trace_type_to_info_ex(op1_type, info->type); + } + } + /* Propagate argument type */ + if (frame->call + && frame->call->func->type == ZEND_USER_FUNCTION + && opline->op2.num <= frame->call->func->op_array.num_args) { + uint32_t info; + + if (opline->op1_type == IS_CONST) { + info = zend_jit_trace_type_to_info(Z_TYPE_P(RT_CONSTANT(opline, opline->op1))); + } else { + ZEND_ASSERT(ssa_ops[idx].op1_use >= 0); + info = ssa_var_info[ssa_ops[idx].op1_use].type & ~MAY_BE_GUARD; + } + if (frame->call->func->op_array.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) { + zend_arg_info *arg_info; + + ZEND_ASSERT(frame->call->func->op_array.arg_info); + arg_info = &frame->call->func->op_array.arg_info[opline->op2.num - 1]; + if (ZEND_TYPE_IS_SET(arg_info->type)) { + info &= ZEND_TYPE_FULL_MASK(arg_info->type); + if (!info) { + break; + } + } + } + if (info & MAY_BE_UNDEF) { + info |= MAY_BE_NULL; + info &= ~MAY_BE_UNDEF; + } + if (ARG_SHOULD_BE_SENT_BY_REF(frame->call->func, opline->op2.num)) { + info |= MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_KEY_ANY; + } + frame->call->stack[opline->op2.num - 1] = info; + } + break; + case ZEND_RETURN: + if (tssa->ops[idx].op1_use >= 0 && op1_type != IS_UNKNOWN) { + zend_ssa_var_info *info = &ssa_var_info[ssa_ops[idx].op1_use]; + if ((info->type & (MAY_BE_ANY|MAY_BE_UNDEF)) != (1 << op1_type)) { + info->type = MAY_BE_GUARD | zend_jit_trace_type_to_info_ex(op1_type, info->type); + } + } + /* Propagate return value types */ + if (opline->op1_type == IS_UNUSED) { + return_value_info.type = MAY_BE_NULL; + } else if (opline->op1_type == IS_CONST) { + return_value_info.type = zend_jit_trace_type_to_info(Z_TYPE_P(RT_CONSTANT(opline, opline->op1))); + } else { + ZEND_ASSERT(ssa_ops[idx].op1_use >= 0); + return_value_info = ssa_var_info[ssa_ops[idx].op1_use]; + return_value_info.type &= ~MAY_BE_GUARD; + } + break; + default: + break; + } + len = zend_jit_trace_op_len(opline); + if (ssa->var_info) { + /* Add statically inferred ranges */ + if (ssa_ops[idx].op1_def >= 0) { + zend_jit_trace_copy_ssa_var_range(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx].op1_def); + } + if (ssa_ops[idx].op2_def >= 0) { + zend_jit_trace_copy_ssa_var_range(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx].op2_def); + } + if (ssa_ops[idx].result_def >= 0) { + zend_jit_trace_copy_ssa_var_range(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx].result_def); + } + if (len == 2 && (opline+1)->opcode == ZEND_OP_DATA) { + if (ssa_ops[idx+1].op1_def >= 0) { + zend_jit_trace_copy_ssa_var_range(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx+1].op1_def); + } + if (ssa_ops[idx+1].op2_def >= 0) { + zend_jit_trace_copy_ssa_var_range(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx+1].op2_def); + } + if (ssa_ops[idx+1].result_def >= 0) { + zend_jit_trace_copy_ssa_var_range(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx+1].result_def); + } + } + } + if (opline->opcode == ZEND_RECV_INIT + && !(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) { + /* RECV_INIT always copy the constant */ + ssa_var_info[ssa_ops[idx].result_def].type = zend_jit_trace_type_to_info(Z_TYPE_P(RT_CONSTANT(opline, opline->op2))); + } else { + if (zend_update_type_info(op_array, tssa, script, (zend_op*)opline, ssa_ops + idx, ssa_opcodes, optimization_level) == FAILURE) { + // TODO: + assert(0); + } + } + if (ssa->var_info) { + /* Add statically inferred restrictions */ + if (ssa_ops[idx].op1_def >= 0) { + if ((opline->opcode == ZEND_SEND_VAR_EX + || opline->opcode == ZEND_FETCH_DIM_FUNC_ARG + || opline->opcode == ZEND_FETCH_OBJ_FUNC_ARG) + && frame + && frame->call + && frame->call->func + && !ARG_SHOULD_BE_SENT_BY_REF(frame->call->func, opline->op2.num)) { + ssa_var_info[ssa_ops[idx].op1_def] = ssa_var_info[ssa_ops[idx].op1_use]; + if (opline->opcode == ZEND_SEND_VAR_EX) { + ssa_var_info[ssa_ops[idx].op1_def].type &= ~MAY_BE_GUARD; + } + if (ssa_var_info[ssa_ops[idx].op1_def].type & MAY_BE_RC1) { + ssa_var_info[ssa_ops[idx].op1_def].type |= MAY_BE_RCN; + } + } else { + zend_jit_trace_restrict_ssa_var_info(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx].op1_def); + } + } + if (ssa_ops[idx].op2_def >= 0) { + zend_jit_trace_restrict_ssa_var_info(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx].op2_def); + } + if (ssa_ops[idx].result_def >= 0) { + zend_jit_trace_restrict_ssa_var_info(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx].result_def); + } + } + idx++; + while (len > 1) { + opline++; + if (opline->opcode != ZEND_OP_DATA) { + if (ssa->var_info) { + /* Add statically inferred ranges */ + if (ssa_ops[idx].op1_def >= 0) { + zend_jit_trace_copy_ssa_var_range(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx].op1_def); + } + if (ssa_ops[idx].op2_def >= 0) { + zend_jit_trace_copy_ssa_var_range(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx].op2_def); + } + if (ssa_ops[idx].result_def >= 0) { + zend_jit_trace_copy_ssa_var_range(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx].result_def); + } + } + if (opline->opcode == ZEND_RECV_INIT + && !(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) { + /* RECV_INIT always copy the constant */ + ssa_var_info[ssa_ops[idx].result_def].type = zend_jit_trace_type_to_info(Z_TYPE_P(RT_CONSTANT(opline, opline->op2))); + } else { + if (zend_update_type_info(op_array, tssa, script, (zend_op*)opline, ssa_ops + idx, ssa_opcodes, optimization_level) == FAILURE) { + // TODO: + assert(0); + } + } + } + if (ssa->var_info) { + /* Add statically inferred restrictions */ + if (ssa_ops[idx].op1_def >= 0) { + zend_jit_trace_restrict_ssa_var_info(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx].op1_def); + } + if (ssa_ops[idx].op2_def >= 0) { + zend_jit_trace_restrict_ssa_var_info(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx].op2_def); + } + if (ssa_ops[idx].result_def >= 0) { + zend_jit_trace_restrict_ssa_var_info(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx].result_def); + } + } + idx++; + len--; + } + + } else if (p->op == ZEND_JIT_TRACE_ENTER) { + op_array = p->op_array; + jit_extension = + (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array); + ssa = &jit_extension->func_info.ssa; + + call = frame->call; + if (!call) { + /* Trace missed INIT_FCALL opcode */ + call = top; + call->call = NULL; + call->prev = NULL; + call->func = (const zend_function*)op_array; + top = zend_jit_trace_call_frame(top, op_array); + for (i = 0; i < op_array->last_var + op_array->T; i++) { + call->stack[i] = -1; + } + } else { + ZEND_ASSERT(&call->func->op_array == op_array); + } + frame->call = call->prev; + call->prev = frame; + call->return_ssa_var = find_return_ssa_var(p - 1, ssa_ops + (idx -1)); + frame = call; + + level++; + i = 0; + v = p->first_ssa_var; + while (i < op_array->last_var) { + if (!ssa->var_info + || !zend_jit_trace_copy_ssa_var_info(op_array, ssa, ssa_opcodes, tssa, v)) { + if (i < op_array->num_args) { + if (op_array->arg_info) { + zend_arg_info *arg_info = &op_array->arg_info[i]; + zend_class_entry *ce; + uint32_t tmp = zend_fetch_arg_info_type(script, arg_info, &ce); + + if (ZEND_ARG_SEND_MODE(arg_info)) { + tmp |= MAY_BE_REF; + } + ssa_var_info[v].type = tmp; + } else { + ssa_var_info[v].type = MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + } + } else { + ssa_var_info[v].type = MAY_BE_UNDEF; + } + } + if (i < op_array->num_args) { + /* Propagate argument type */ + ssa_var_info[v].type &= frame->stack[i]; + } + i++; + v++; + } + + } else if (p->op == ZEND_JIT_TRACE_BACK) { + op_array = p->op_array; + jit_extension = + (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array); + ssa = &jit_extension->func_info.ssa; + if (level == 0) { + i = 0; + v = p->first_ssa_var; + while (i < op_array->last_var) { + if (!ssa->var_info + || !zend_jit_trace_copy_ssa_var_info(op_array, ssa, ssa_opcodes, tssa, v)) { + ssa_var_info[v].type = MAY_BE_UNDEF | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + } + i++; + v++; + } + while (i < op_array->last_var + op_array->T) { + if (!ssa->var_info + || !zend_jit_trace_copy_ssa_var_info(op_array, ssa, ssa_opcodes, tssa, v)) { + ssa_var_info[v].type = MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; + } + i++; + v++; + } + if (return_value_info.type != 0) { + if ((p+1)->op == ZEND_JIT_TRACE_VM) { + const zend_op *opline = (p+1)->opline - 1; + if (opline->result_type != IS_UNUSED) { + ssa_var_info[ + p->first_ssa_var + + EX_VAR_TO_NUM(opline->result.var)] = return_value_info; + } + } + memset(&return_value_info, 0, sizeof(return_value_info)); + } + } else { + level--; + if (return_value_info.type != 0) { + if ((p+1)->op == ZEND_JIT_TRACE_VM) { + const zend_op *opline = (p+1)->opline - 1; + if (opline->result_type != IS_UNUSED) { + if (frame->return_ssa_var >= 0) { + ssa_var_info[frame->return_ssa_var] = return_value_info; + } + } + } + memset(&return_value_info, 0, sizeof(return_value_info)); + } + } + + top = frame; + if (frame->prev) { + frame = frame->prev; + ZEND_ASSERT(&frame->func->op_array == op_array); + } else { + frame = zend_jit_trace_ret_frame(frame, op_array); + frame->call = NULL; + frame->prev = NULL; + frame->func = (const zend_function*)op_array; + frame->return_ssa_var = -1; + for (i = 0; i < op_array->last_var + op_array->T; i++) { + frame->stack[i] = -1; + } + } + + } else if (p->op == ZEND_JIT_TRACE_INIT_CALL) { + call = top; + call->call = NULL; + call->prev = frame->call; + call->func = p->func; + frame->call = call; + top = zend_jit_trace_call_frame(top, p->op_array); + if (p->func->type == ZEND_USER_FUNCTION) { + for (i = 0; i < p->op_array->last_var + p->op_array->T; i++) { + call->stack[i] = -1; + } + } + } else if (p->op == ZEND_JIT_TRACE_DO_ICALL) { + call = frame->call; + if (call) { + top = call; + frame->call = call->prev; + } + } else if (p->op == ZEND_JIT_TRACE_END) { + break; + } + } + + if (trace_buffer->stop == ZEND_JIT_TRACE_STOP_LOOP) { + /* Propagate guards through Phi sources */ + zend_ssa_phi *phi = tssa->blocks[1].phis; + + while (phi) { + uint32_t t = ssa_var_info[phi->ssa_var].type; + uint32_t t0 = ssa_var_info[phi->sources[0]].type; + uint32_t t1 = ssa_var_info[phi->sources[1]].type; + + if (t & MAY_BE_GUARD) { + if (((t0 | t1) & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) == (t & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF))) { + if (!((t0 | t1) & MAY_BE_GUARD)) { + ssa_var_info[phi->ssa_var].type = t & ~MAY_BE_GUARD; + } + } else if ((t1 & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) == (t & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF))) { + if (!(t1 & MAY_BE_GUARD)) { + ssa_var_info[phi->ssa_var].type = t & ~MAY_BE_GUARD; + ssa_var_info[phi->sources[0]].type = t | MAY_BE_GUARD; + } + } else { + if ((t0 & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) != (t & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF))) { + ssa_var_info[phi->sources[0]].type = MAY_BE_GUARD | (t & t0); + } + if ((t1 & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) != (t & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF))) { + if (is_checked_guard(tssa, ssa_opcodes, phi->sources[1], phi->ssa_var)) { + ssa_var_info[phi->sources[1]].type = MAY_BE_GUARD | (t & t1); + ssa_var_info[phi->ssa_var].type = t & ~MAY_BE_GUARD; + } + } + } + } + phi = phi->next; + } + } + + if (UNEXPECTED(ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_TSSA)) { + zend_jit_dump_trace(trace_buffer, tssa); + fprintf(stderr, "---- TRACE analysed\n"); + } + + return tssa; +} + +static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t parent_trace, uint32_t exit_num) +{ + const void *handler = NULL; + dasm_State* dasm_state = NULL; + zend_script *script = NULL; + zend_lifetime_interval **ra = NULL; + zend_string *name = NULL; + void *checkpoint; + const zend_op_array *op_array; + zend_ssa *ssa, *op_array_ssa; + zend_jit_trace_rec *p; + int call_level = -1; // TODO: proper support for inlined functions ??? + zend_jit_op_array_trace_extension *jit_extension; + int num_op_arrays = 0; + zend_jit_trace_info *t; + const zend_op_array *op_arrays[ZEND_JIT_TRACE_MAX_FUNCS]; + zend_uchar smart_branch_opcode; + const void *exit_addr; + uint32_t op1_info, op1_def_info, op2_info, res_info, res_use_info, op1_data_info; + zend_bool send_result = 0; + zend_jit_addr op1_addr, op1_def_addr, op2_addr, op2_def_addr, res_addr; + zend_class_entry *ce; + uint32_t i; + zend_jit_trace_stack_frame *frame, *top, *call; + zend_jit_trace_stack *stack; + zend_uchar res_type = IS_UNKNOWN; + const zend_op *opline, *orig_opline; + const zend_ssa_op *ssa_op, *orig_ssa_op; + + checkpoint = zend_arena_checkpoint(CG(arena)); + + ssa = zend_jit_trace_build_tssa(trace_buffer, parent_trace, exit_num, script, op_arrays, &num_op_arrays); + + p = trace_buffer; + ZEND_ASSERT(p->op == ZEND_JIT_TRACE_START); + op_array = p->op_array; + frame = JIT_G(current_frame); + top = zend_jit_trace_call_frame(frame, op_array); + frame->call = NULL; + frame->prev = NULL; + frame->func = (const zend_function*)op_array; + frame->return_value_used = -1; + frame->nested = 0; + frame->num_args = -1; + stack = frame->stack; + + if (trace_buffer->start == ZEND_JIT_TRACE_START_ENTER) { + i = 0; + while (i < op_array->last_var) { + if (!(ssa->var_info[i].type & MAY_BE_GUARD) + && has_concrete_type(ssa->var_info[i].type)) { + stack[i] = concrete_type(ssa->var_info[i].type); + } else if (i < op_array->num_args) { + stack[i] = IS_UNKNOWN; + } else { + stack[i] = IS_UNDEF; + } + i++; + } + } else { + int parent_vars_count = 0; + zend_jit_trace_stack *parent_stack = NULL; + + i = 0; + if (parent_trace) { + parent_vars_count = MIN(zend_jit_traces[parent_trace].exit_info[exit_num].stack_size, + op_array->last_var + op_array->T); + if (parent_vars_count) { + parent_stack = + zend_jit_traces[parent_trace].stack_map + + zend_jit_traces[parent_trace].exit_info[exit_num].stack_offset; + } + } + while (i < op_array->last_var + op_array->T) { + if (!(ssa->var_info[i].type & MAY_BE_GUARD) + && has_concrete_type(ssa->var_info[i].type)) { + stack[i] = concrete_type(ssa->var_info[i].type); + } else if (i < parent_vars_count + && (zend_uchar)parent_stack[i] != IS_UNKNOWN) { + stack[i] = parent_stack[i]; + } else { + stack[i] = IS_UNKNOWN; + } + i++; + } + } + + opline = ((zend_jit_trace_start_rec*)p)->opline; + name = zend_jit_trace_name(op_array, opline->lineno); + p += ZEND_JIT_TRACE_START_REC_SIZE; + + dasm_init(&dasm_state, DASM_MAXSECTION); + dasm_setupglobal(&dasm_state, dasm_labels, zend_lb_MAX); + dasm_setup(&dasm_state, dasm_actions); + + jit_extension = + (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array); + op_array_ssa = &jit_extension->func_info.ssa; + + // TODO: register allocation ??? + + dasm_growpc(&dasm_state, 1); /* trace needs just one global lable for loop */ + + zend_jit_align_func(&dasm_state); + if (!parent_trace) { + zend_jit_prologue(&dasm_state); + } + zend_jit_trace_begin(&dasm_state, ZEND_JIT_TRACE_NUM); + + if (!parent_trace) { + zend_jit_set_opline(&dasm_state, opline); + } else { + if (!((uintptr_t)zend_jit_traces[parent_trace].exit_info[exit_num].opline & ~(ZEND_JIT_EXIT_JITED|ZEND_JIT_EXIT_BLACKLISTED))) { + zend_jit_trace_opline_guard(&dasm_state, opline); + zend_jit_set_opline(&dasm_state, opline); + } else { + zend_jit_reset_opline(&dasm_state, opline); + } + } + + if (trace_buffer->stop == ZEND_JIT_TRACE_STOP_LOOP + || trace_buffer->stop == ZEND_JIT_TRACE_STOP_RECURSIVE_CALL + || trace_buffer->stop == ZEND_JIT_TRACE_STOP_RECURSIVE_RET) { + + if (trace_buffer->stop == ZEND_JIT_TRACE_STOP_LOOP) { + /* Check loop-invariant varaible types */ + for (i = 0; i < op_array->last_var + op_array->T; i++) { + uint32_t info = ssa->var_info[i].type; + + ZEND_ASSERT(ssa->vars[i].definition == -1); + ZEND_ASSERT(ssa->vars[i].definition_phi == NULL); + ZEND_ASSERT(ssa->vars[i].var == i); + + if (info & MAY_BE_GUARD) { + if (ssa->vars[i].use_chain != -1 + || (ssa->vars[i].phi_use_chain + && !(ssa->var_info[ssa->vars[i].phi_use_chain->ssa_var].type & MAY_BE_GUARD))) { + if (!zend_jit_type_guard(&dasm_state, opline, EX_NUM_TO_VAR(i), concrete_type(info))) { + goto jit_failure; + } + ssa->var_info[i].type = info & ~MAY_BE_GUARD; + stack[i] = concrete_type(info); + } + } + } + } + + zend_jit_label(&dasm_state, 0); /* start of of trace loop */ + + if (trace_buffer->stop != ZEND_JIT_TRACE_STOP_RECURSIVE_RET) { + // TODO: interupt exit may require deoptimization through side exit ??? + zend_jit_check_timeout(&dasm_state, opline); + } + } + + ssa_op = ssa->ops; + for (;;p++) { + if (p->op == ZEND_JIT_TRACE_VM) { + uint8_t op1_type = p->op1_type; + uint8_t op2_type = p->op2_type; + uint8_t op3_type = p->op3_type; + + opline = p->opline; + if (op1_type & IS_TRACE_REFERENCE) { + op1_type = IS_UNKNOWN; + } + if (op2_type & IS_TRACE_REFERENCE) { + op2_type = IS_UNKNOWN; + } + if (op3_type & IS_TRACE_REFERENCE) { + op3_type = IS_UNKNOWN; + } + + if ((p+1)->op == ZEND_JIT_TRACE_OP1_TYPE) { + // TODO: support for recorded classes ??? + p++; + } + if ((p+1)->op == ZEND_JIT_TRACE_OP2_TYPE) { + // TODO: support for recorded classes ??? + p++; + } + +#if 0 + // TODO: call level calculation doesn't work for traces ??? + 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++; + } +#endif + + if (zend_jit_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_EX(); + CHECK_OP1_TRACE_TYPE(); + if (!(op1_info & MAY_BE_LONG)) { + break; + } + if (opline->result_type != IS_UNUSED) { + res_use_info = RES_USE_INFO_EX(); + USE_RES_TRACE_TYPE(); + res_info = RES_INFO_EX(); + res_addr = RES_REG_ADDR(); + } else { + res_use_info = -1; + res_info = -1; + res_addr = 0; + } + op1_def_info = OP1_DEF_INFO_EX(); + if (!zend_jit_inc_dec(&dasm_state, opline, op_array, + op1_info, OP1_REG_ADDR(), + op1_def_info, OP1_DEF_REG_ADDR(), + res_use_info, res_info, + res_addr, + (op1_def_info & MAY_BE_LONG) && (op1_def_info & (MAY_BE_DOUBLE|MAY_BE_GUARD)) && zend_may_overflow_ex(opline, ssa_op, op_array, ssa))) { + goto jit_failure; + } + if ((op1_def_info & (MAY_BE_ANY|MAY_BE_GUARD)) == (MAY_BE_LONG|MAY_BE_GUARD)) { + ssa->var_info[ssa_op->op1_def].type &= ~MAY_BE_GUARD; + if (opline->result_type != IS_UNUSED) { + ssa->var_info[ssa_op->result_def].type &= ~MAY_BE_GUARD; + } + } + goto done; + case ZEND_BW_OR: + case ZEND_BW_AND: + case ZEND_BW_XOR: + case ZEND_SL: + case ZEND_SR: + case ZEND_MOD: + op1_info = OP1_INFO_EX(); + CHECK_OP1_TRACE_TYPE(); + op2_info = OP2_INFO_EX(); + CHECK_OP2_TRACE_TYPE(); + if ((op1_info & MAY_BE_UNDEF) || (op2_info & MAY_BE_UNDEF)) { + break; + } + if (!(op1_info & MAY_BE_LONG) + || !(op2_info & MAY_BE_LONG)) { + break; + } + if (opline->result_type == IS_TMP_VAR + && (p+1)->op == ZEND_JIT_TRACE_VM + && (p+1)->opline == opline + 1 + && (opline+1)->opcode == ZEND_SEND_VAL + && (opline+1)->op1_type == IS_TMP_VAR + && (opline+1)->op1.var == opline->result.var) { + p++; + if (frame->call) { + uint8_t res_type = p->op1_type; + if (res_type & IS_TRACE_REFERENCE) { + res_type = IS_UNKNOWN;; + } + if (res_type != IS_UNKNOWN) { + zend_jit_trace_send_type(opline+1, frame->call, res_type); + } + } + while ((p+1)->op == ZEND_JIT_TRACE_OP1_TYPE || + (p+1)->op == ZEND_JIT_TRACE_OP2_TYPE) { + p++; + } + send_result = 1; + res_use_info = -1; + res_addr = 0; /* set inside backend */ + } else { + send_result = 0; + res_use_info = RES_USE_INFO_EX(); + USE_RES_TRACE_TYPE(); + res_addr = RES_REG_ADDR(); + } + res_info = RES_INFO_EX(); + if (!zend_jit_long_math(&dasm_state, opline, op_array, + op1_info, OP1_RANGE_EX(), OP1_REG_ADDR(), + op2_info, OP2_RANGE_EX(), OP2_REG_ADDR(), + res_use_info, res_info, res_addr, + send_result, + zend_may_throw_ex(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 ??? + op1_info = OP1_INFO_EX(); + CHECK_OP1_TRACE_TYPE(); + op2_info = OP2_INFO_EX(); + CHECK_OP2_TRACE_TYPE(); + if ((op1_info & MAY_BE_UNDEF) || (op2_info & MAY_BE_UNDEF)) { + break; + } + if (!(op1_info & (MAY_BE_LONG|MAY_BE_DOUBLE)) || + !(op2_info & (MAY_BE_LONG|MAY_BE_DOUBLE))) { + break; + } + if (opline->result_type == IS_TMP_VAR + && (p+1)->op == ZEND_JIT_TRACE_VM + && (p+1)->opline == opline + 1 + && (opline+1)->opcode == ZEND_SEND_VAL + && (opline+1)->op1_type == IS_TMP_VAR + && (opline+1)->op1.var == opline->result.var) { + p++; + if (frame->call + && frame->call->func->type == ZEND_USER_FUNCTION) { + uint8_t res_type = p->op1_type; + if (res_type & IS_TRACE_REFERENCE) { + res_type = IS_UNKNOWN;; + } + if (res_type != IS_UNKNOWN) { + zend_jit_trace_send_type(opline+1, frame->call, res_type); + } + } + while ((p+1)->op == ZEND_JIT_TRACE_OP1_TYPE || + (p+1)->op == ZEND_JIT_TRACE_OP2_TYPE) { + p++; + } + send_result = 1; + res_use_info = -1; + res_addr = 0; /* set inside backend */ + } else { + send_result = 0; + res_use_info = RES_USE_INFO_EX(); + USE_RES_TRACE_TYPE(); + res_addr = RES_REG_ADDR(); + } + res_info = RES_INFO_EX(); + if (!zend_jit_math(&dasm_state, opline, op_array, + op1_info, OP1_REG_ADDR(), + op2_info, OP2_REG_ADDR(), + res_use_info, res_info, res_addr, + send_result, + (res_info & MAY_BE_LONG) && (res_info & (MAY_BE_DOUBLE|MAY_BE_GUARD)) && zend_may_overflow_ex(opline, ssa_op, op_array, ssa), + zend_may_throw_ex(opline, ssa_op, op_array, ssa))) { + goto jit_failure; + } + if ((res_info & (MAY_BE_ANY|MAY_BE_GUARD)) == (MAY_BE_LONG|MAY_BE_GUARD)) { + ssa->var_info[ssa_op->result_def].type &= ~MAY_BE_GUARD; + } + goto done; + case ZEND_CONCAT: + case ZEND_FAST_CONCAT: + op1_info = OP1_INFO_EX(); + CHECK_OP1_TRACE_TYPE(); + op2_info = OP2_INFO_EX(); + CHECK_OP2_TRACE_TYPE(); + if ((op1_info & MAY_BE_UNDEF) || (op2_info & MAY_BE_UNDEF)) { + break; + } + if (!(op1_info & MAY_BE_STRING) || + !(op2_info & MAY_BE_STRING)) { + break; + } + if (opline->result_type == IS_TMP_VAR + && (p+1)->op == ZEND_JIT_TRACE_VM + && (p+1)->opline == opline + 1 + && (opline+1)->opcode == ZEND_SEND_VAL + && (opline+1)->op1_type == IS_TMP_VAR + && (opline+1)->op1.var == opline->result.var) { + p++; + if (frame->call + && frame->call->func->type == ZEND_USER_FUNCTION) { + uint8_t res_type = p->op1_type; + if (res_type & IS_TRACE_REFERENCE) { + res_type = IS_UNKNOWN;; + } + if (res_type != IS_UNKNOWN) { + zend_jit_trace_send_type(opline+1, frame->call, res_type); + } + } + while ((p+1)->op == ZEND_JIT_TRACE_OP1_TYPE || + (p+1)->op == ZEND_JIT_TRACE_OP2_TYPE) { + p++; + } + send_result = 1; + } else { + send_result = 0; + } + res_info = RES_INFO_EX(); + if (!zend_jit_concat(&dasm_state, opline, op_array, + op1_info, op2_info, res_info, send_result, + zend_may_throw_ex(opline, ssa_op, op_array, ssa))) { + goto jit_failure; + } + goto done; + case ZEND_ASSIGN_OP: + if (opline->extended_value == ZEND_POW + || opline->extended_value == ZEND_DIV) { + // TODO: check for division by zero ??? + break; + } + if (opline->op1_type != IS_CV || opline->result_type != IS_UNUSED) { + break; + } + op1_info = OP1_INFO_EX(); + CHECK_OP1_TRACE_TYPE(); + op2_info = OP2_INFO_EX(); + CHECK_OP2_TRACE_TYPE(); + if ((op1_info & MAY_BE_UNDEF) || (op2_info & MAY_BE_UNDEF)) { + break; + } + if (opline->extended_value == ZEND_ADD + || opline->extended_value == ZEND_SUB + || opline->extended_value == ZEND_MUL + || opline->extended_value == ZEND_DIV) { + if (!(op1_info & (MAY_BE_LONG|MAY_BE_DOUBLE)) + || !(op2_info & (MAY_BE_LONG|MAY_BE_DOUBLE))) { + break; + } + } else if (opline->extended_value == ZEND_BW_OR + || opline->extended_value == ZEND_BW_AND + || opline->extended_value == ZEND_BW_XOR + || opline->extended_value == ZEND_SL + || opline->extended_value == ZEND_SR + || opline->extended_value == ZEND_MOD) { + if (!(op1_info & MAY_BE_LONG) + || !(op2_info & MAY_BE_LONG)) { + break; + } + } else if (opline->extended_value == ZEND_CONCAT) { + if (!(op1_info & MAY_BE_STRING) + || !(op2_info & MAY_BE_STRING)) { + break; + } + } + op1_def_info = OP1_DEF_INFO_EX(); + if (!zend_jit_assign_op(&dasm_state, opline, op_array, + op1_info, op1_def_info, OP1_RANGE_EX(), + op2_info, OP2_RANGE_EX(), + (op1_def_info & MAY_BE_LONG) && (op1_def_info & (MAY_BE_DOUBLE|MAY_BE_GUARD)) && zend_may_overflow_ex(opline, ssa_op, op_array, ssa), + zend_may_throw_ex(opline, ssa_op, op_array, ssa))) { + goto jit_failure; + } + if ((op1_def_info & (MAY_BE_ANY|MAY_BE_GUARD)) == (MAY_BE_LONG|MAY_BE_GUARD)) { + ssa->var_info[ssa_op->op1_def].type &= ~MAY_BE_GUARD; + if (opline->result_type != IS_UNUSED) { + ssa->var_info[ssa_op->result_def].type &= ~MAY_BE_GUARD; + } + } + goto done; + case ZEND_ASSIGN_DIM_OP: + if (opline->extended_value == ZEND_POW + || opline->extended_value == ZEND_DIV) { + // TODO: check for division by zero ??? + break; + } + if (opline->op1_type != IS_CV || opline->result_type != IS_UNUSED) { + break; + } + op1_info = OP1_INFO_EX(); + CHECK_OP1_TRACE_TYPE(); + op2_info = OP2_INFO_EX(); + CHECK_OP2_TRACE_TYPE(); + op1_data_info = OP1_DATA_INFO_EX(); + CHECK_OP1_DATA_TRACE_TYPE(); + op1_def_info = OP1_DEF_INFO_EX(); + if (!zend_jit_assign_dim_op(&dasm_state, opline, op_array, + op1_info, op1_def_info, op2_info, + op1_data_info, OP1_DATA_RANGE(), + zend_may_throw_ex(opline, ssa_op, op_array, ssa))) { + goto jit_failure; + } + goto done; + case ZEND_ASSIGN_DIM: + if (opline->op1_type != IS_CV) { + break; + } + op1_info = OP1_INFO_EX(); + CHECK_OP1_TRACE_TYPE(); + op2_info = OP2_INFO_EX(); + CHECK_OP2_TRACE_TYPE(); + op1_data_info = OP1_DATA_INFO_EX(); + CHECK_OP1_DATA_TRACE_TYPE(); + if (!zend_jit_assign_dim(&dasm_state, opline, op_array, + op1_info, op2_info, op1_data_info, + zend_may_throw_ex(opline, ssa_op, op_array, ssa))) { + goto jit_failure; + } + goto done; + case ZEND_ASSIGN: + if (opline->op1_type != IS_CV) { + break; + } + if (opline->result_type == IS_UNUSED) { + res_addr = 0; + res_info = -1; + } else { + res_addr = RES_REG_ADDR(); + res_info = RES_INFO_EX(); + } + op2_addr = OP2_REG_ADDR(); + if (ra + && ssa_op->op2_def >= 0 + && !ssa->vars[ssa_op->op2_def].no_val) { + op2_def_addr = OP2_DEF_REG_ADDR(); + } else { + op2_def_addr = op2_addr; + } + op2_info = OP2_INFO_EX(); + CHECK_OP2_TRACE_TYPE(); + op1_info = OP1_INFO_EX(); + op1_def_info = OP1_DEF_INFO_EX(); + USE_OP1_TRACE_TYPE(); + if (!zend_jit_assign(&dasm_state, opline, op_array, + 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_ex(opline, ssa_op, op_array, ssa))) { + goto jit_failure; + } + goto done; + case ZEND_QM_ASSIGN: + op1_addr = OP1_REG_ADDR(); + if (ra + && ssa_op->op1_def >= 0 + && !ssa->vars[ssa_op->op1_def].no_val) { + op1_def_addr = OP1_DEF_REG_ADDR(); + } else { + op1_def_addr = op1_addr; + } + op1_info = OP1_INFO_EX(); + CHECK_OP1_TRACE_TYPE();//???USE_OP1_TRACE_TYPE(); + res_info = RES_INFO_EX(); + if (!zend_jit_qm_assign(&dasm_state, opline, op_array, + op1_info, op1_addr, op1_def_addr, + res_info, RES_REG_ADDR())) { + goto jit_failure; + } + goto done; + case ZEND_INIT_FCALL: + case ZEND_INIT_FCALL_BY_NAME: + if (!zend_jit_init_fcall(&dasm_state, opline, op_array_ssa->cfg.map ? op_array_ssa->cfg.map[opline - op_array->opcodes] : -1, op_array, op_array_ssa, call_level, p + 1)) { + goto jit_failure; + } + goto done; + case ZEND_SEND_VAL: + case ZEND_SEND_VAL_EX: + if (opline->opcode == ZEND_SEND_VAL_EX + && opline->op2.num > MAX_ARG_FLAG_NUM) { + break; + } + op1_info = OP1_INFO_EX(); + CHECK_OP1_TRACE_TYPE(); //???USE_OP1_TRACE_TYPE(); + if (!zend_jit_send_val(&dasm_state, opline, op_array, + op1_info, OP1_REG_ADDR())) { + goto jit_failure; + } + if (frame->call + && frame->call->func->type == ZEND_USER_FUNCTION) { + if (opline->op1_type == IS_CONST) { + zend_jit_trace_send_type(opline, frame->call, Z_TYPE_P(RT_CONSTANT(opline, opline->op1))); + } else if (op1_type != IS_UNKNOWN) { + zend_jit_trace_send_type(opline, frame->call, op1_type); + } + } + goto done; + case ZEND_SEND_REF: + op1_info = OP1_INFO_EX(); + USE_OP1_TRACE_TYPE(); + 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: + 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_op->op1_def >= 0 + && !ssa->vars[ssa_op->op1_def].no_val) { + op1_def_addr = OP1_DEF_REG_ADDR(); + } else { + op1_def_addr = op1_addr; + } + op1_info = OP1_INFO_EX(); + CHECK_OP1_TRACE_TYPE(); //???USE_OP1_TRACE_TYPE(); + if (!zend_jit_send_var(&dasm_state, opline, op_array, + op1_info, op1_addr, op1_def_addr)) { + goto jit_failure; + } + if (frame->call + && frame->call->func->type == ZEND_USER_FUNCTION) { + if (opline->opcode == ZEND_SEND_VAR_EX + && ARG_SHOULD_BE_SENT_BY_REF(frame->call->func, opline->op2.num)) { + // TODO: this may require invalidation, if caller is changed ??? + goto done; + } + if (op1_type != IS_UNKNOWN) { + zend_jit_trace_send_type(opline, frame->call, op1_type); + } + } + goto done; + case ZEND_DO_UCALL: + case ZEND_DO_ICALL: + case ZEND_DO_FCALL_BY_NAME: + case ZEND_DO_FCALL: + if (!zend_jit_do_fcall(&dasm_state, opline, op_array, op_array_ssa, call_level, -1, p + 1)) { + 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: + op1_info = OP1_INFO_EX(); + CHECK_OP1_TRACE_TYPE(); + op2_info = OP2_INFO_EX(); + CHECK_OP2_TRACE_TYPE(); + if ((opline->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0) { + zend_bool exit_if_true = 0; + const zend_op *exit_opline = zend_jit_trace_get_exit_opline(p + 1, opline + 1, &exit_if_true); + uint32_t exit_point = zend_jit_trace_get_exit_point(opline, exit_opline, p + 1); + + exit_addr = zend_jit_trace_get_exit_addr(exit_point); + if (!exit_addr) { + goto jit_failure; + } + smart_branch_opcode = exit_if_true ? ZEND_JMPNZ : ZEND_JMPZ; + } else { + smart_branch_opcode = 0; + exit_addr = NULL; + } + if (!zend_jit_cmp(&dasm_state, opline, op_array, + op1_info, OP1_REG_ADDR(), + op2_info, OP2_REG_ADDR(), + RES_REG_ADDR(), + zend_may_throw_ex(opline, ssa_op, op_array, ssa), + smart_branch_opcode, -1, -1, exit_addr)) { + goto jit_failure; + } + goto done; + case ZEND_IS_IDENTICAL: + case ZEND_IS_NOT_IDENTICAL: + op1_info = OP1_INFO_EX(); + CHECK_OP1_TRACE_TYPE(); + op2_info = OP2_INFO_EX(); + CHECK_OP2_TRACE_TYPE(); + if ((opline->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0) { + zend_bool exit_if_true = 0; + const zend_op *exit_opline = zend_jit_trace_get_exit_opline(p + 1, opline + 1, &exit_if_true); + uint32_t exit_point = zend_jit_trace_get_exit_point(opline, exit_opline, p + 1); + + exit_addr = zend_jit_trace_get_exit_addr(exit_point); + if (!exit_addr) { + goto jit_failure; + } + if (opline->opcode == ZEND_IS_NOT_IDENTICAL) { + exit_if_true = !exit_if_true; + } + smart_branch_opcode = exit_if_true ? ZEND_JMPNZ : ZEND_JMPZ; + } else { + smart_branch_opcode = 0; + exit_addr = NULL; + } + if (!zend_jit_identical(&dasm_state, opline, op_array, + op1_info, OP1_REG_ADDR(), + op2_info, OP2_REG_ADDR(), + RES_REG_ADDR(), + zend_may_throw_ex(opline, ssa_op, op_array, ssa), + smart_branch_opcode, -1, -1, exit_addr)) { + goto jit_failure; + } + goto done; + case ZEND_DEFINED: + if ((opline->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0) { + zend_bool exit_if_true = 0; + const zend_op *exit_opline = zend_jit_trace_get_exit_opline(p + 1, opline + 1, &exit_if_true); + uint32_t exit_point = zend_jit_trace_get_exit_point(opline, exit_opline, p + 1); + + exit_addr = zend_jit_trace_get_exit_addr(exit_point); + if (!exit_addr) { + goto jit_failure; + } + smart_branch_opcode = exit_if_true ? ZEND_JMPNZ : ZEND_JMPZ; + } else { + smart_branch_opcode = 0; + exit_addr = NULL; + } + if (!zend_jit_defined(&dasm_state, opline, op_array, smart_branch_opcode, -1, -1, exit_addr)) { + goto jit_failure; + } + goto done; + case ZEND_TYPE_CHECK: + if (opline->extended_value == MAY_BE_RESOURCE) { + // TODO: support for is_resource() ??? + break; + } + op1_info = OP1_INFO_EX(); + USE_OP1_TRACE_TYPE(); + if ((opline->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0) { + zend_bool exit_if_true = 0; + const zend_op *exit_opline = zend_jit_trace_get_exit_opline(p + 1, opline + 1, &exit_if_true); + uint32_t exit_point = zend_jit_trace_get_exit_point(opline, exit_opline, p + 1); + + exit_addr = zend_jit_trace_get_exit_addr(exit_point); + if (!exit_addr) { + goto jit_failure; + } + smart_branch_opcode = exit_if_true ? ZEND_JMPNZ : ZEND_JMPZ; + } else { + smart_branch_opcode = 0; + exit_addr = NULL; + } + if (!zend_jit_type_check(&dasm_state, opline, op_array, op1_info, smart_branch_opcode, -1, -1, exit_addr)) { + goto jit_failure; + } + goto done; + case ZEND_RETURN: + op1_info = OP1_INFO_EX(); + CHECK_OP1_TRACE_TYPE(); + if (opline->op1_type == IS_CONST) { + res_type = Z_TYPE_P(RT_CONSTANT(opline, opline->op1)); + } else if (op1_type != IS_UNKNOWN) { + res_type = op1_type; + } + if (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; + + if (!zend_jit_return(&dasm_state, opline, op_array, + op1_info, OP1_REG_ADDR())) { + goto jit_failure; + } + if (!zend_jit_leave_frame(&dasm_state)) { + goto jit_failure; + } + for (j = 0 ; j < op_array->last_var; j++) { + // TODO: get info from trace ??? + uint32_t info = zend_ssa_cv_info(opline, op_array, op_array_ssa, j); + zend_uchar type = stack[j]; + + info = zend_jit_trace_type_to_info_ex(type, info); + if (info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) { + if (!zend_jit_free_cv(&dasm_state, opline, op_array, info, j)) { + goto jit_failure; + } + } + } + if (!zend_jit_leave_func(&dasm_state, opline, op_array, p + 1)) { + goto jit_failure; + } + } + goto done; + case ZEND_BOOL: + case ZEND_BOOL_NOT: + op1_info = OP1_INFO_EX(); + CHECK_OP1_TRACE_TYPE(); + if (!zend_jit_bool_jmpznz(&dasm_state, opline, op_array, + op1_info, OP1_REG_ADDR(), RES_REG_ADDR(), + -1, -1, + zend_may_throw_ex(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->op1_type == IS_TMP_VAR && + ((opline-1)->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0) { + /* smart branch */ + break; + } + /* break missing intentionally */ + case ZEND_JMPZNZ: + case ZEND_JMPZ_EX: + case ZEND_JMPNZ_EX: + if (opline->result_type == IS_UNDEF) { + res_addr = 0; + } else { + res_addr = RES_REG_ADDR(); + } + op1_info = OP1_INFO_EX(); + CHECK_OP1_TRACE_TYPE(); + if ((p+1)->op == ZEND_JIT_TRACE_VM || (p+1)->op == ZEND_JIT_TRACE_END) { + const zend_op *exit_opline = NULL; + uint32_t exit_point; + + if ((p+1)->opline == OP_JMP_ADDR(opline, opline->op2)) { + /* taken branch */ + if (opline->opcode == ZEND_JMPNZ_EX) { + smart_branch_opcode = ZEND_JMPZ_EX; + } else if (opline->opcode == ZEND_JMPZ_EX) { + smart_branch_opcode = ZEND_JMPNZ_EX; + } else if (opline->opcode == ZEND_JMPNZ) { + smart_branch_opcode = ZEND_JMPZ; + } else { + smart_branch_opcode = ZEND_JMPNZ; + } + exit_opline = (opline->opcode == ZEND_JMPZNZ) ? + ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value) : + opline + 1; + } else if (opline->opcode == ZEND_JMPZNZ) { + ZEND_ASSERT((p+1)->opline == ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value)); + smart_branch_opcode = ZEND_JMPZ; + exit_opline = OP_JMP_ADDR(opline, opline->op2); + } else if ((p+1)->opline == opline + 1) { + /* not taken branch */ + smart_branch_opcode = opline->opcode; + exit_opline = OP_JMP_ADDR(opline, opline->op2); + } else { + ZEND_ASSERT(0); + } + exit_point = zend_jit_trace_get_exit_point(opline, exit_opline, p+1); + exit_addr = zend_jit_trace_get_exit_addr(exit_point); + if (!exit_addr) { + goto jit_failure; + } + } else { + ZEND_ASSERT(0); + } + if (!zend_jit_bool_jmpznz(&dasm_state, opline, op_array, + op1_info, OP1_REG_ADDR(), res_addr, + -1, -1, + zend_may_throw_ex(opline, ssa_op, op_array, ssa), + smart_branch_opcode, exit_addr)) { + goto jit_failure; + } + goto done; + case ZEND_FETCH_DIM_R: + case ZEND_FETCH_DIM_IS: + op1_info = OP1_INFO_EX(); + CHECK_OP1_TRACE_TYPE(); + op2_info = OP2_INFO_EX(); + CHECK_OP2_TRACE_TYPE(); + res_info = RES_INFO_EX(); + if (!zend_jit_fetch_dim_read(&dasm_state, opline, op_array, + op1_info, op2_info, res_info, + ( + (op1_info & MAY_BE_ANY) != MAY_BE_ARRAY || + (op2_info & (MAY_BE_ANY - (MAY_BE_LONG|MAY_BE_STRING))) != 0 || + ((op1_info & MAY_BE_UNDEF) != 0 && + opline->opcode == ZEND_FETCH_DIM_R) || + ((opline->op1_type & (IS_TMP_VAR|IS_VAR)) != 0 && + (op1_info & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY)) != 0) || + (op2_info & MAY_BE_UNDEF) != 0 || + ((opline->op2_type & (IS_TMP_VAR|IS_VAR)) != 0 && + (op2_info & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY)) != 0)))) { + goto jit_failure; + } + goto done; + goto done; + case ZEND_ISSET_ISEMPTY_DIM_OBJ: + if ((opline->extended_value & ZEND_ISEMPTY)) { + // TODO: support for empty() ??? + break; + } + op1_info = OP1_INFO_EX(); + CHECK_OP1_TRACE_TYPE(); + op2_info = OP2_INFO_EX(); + CHECK_OP2_TRACE_TYPE(); + if ((opline->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0) { + zend_bool exit_if_true = 0; + const zend_op *exit_opline = zend_jit_trace_get_exit_opline(p + 1, opline + 1, &exit_if_true); + uint32_t exit_point = zend_jit_trace_get_exit_point(opline, exit_opline, p + 1); + + exit_addr = zend_jit_trace_get_exit_addr(exit_point); + if (!exit_addr) { + goto jit_failure; + } + smart_branch_opcode = exit_if_true ? ZEND_JMPNZ : ZEND_JMPZ; + } else { + smart_branch_opcode = 0; + exit_addr = NULL; + } + if (!zend_jit_isset_isempty_dim(&dasm_state, opline, op_array, + op1_info, op2_info, + zend_may_throw_ex(opline, ssa_op, op_array, ssa), + smart_branch_opcode, -1, -1, + exit_addr)) { + goto jit_failure; + } + goto done; + case ZEND_FETCH_OBJ_R: + case ZEND_FETCH_OBJ_IS: + 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; + if (opline->op1_type == IS_UNUSED) { + op1_info = MAY_BE_OBJECT|MAY_BE_RC1|MAY_BE_RCN; + ce = op_array->scope; + } else { + op1_info = OP1_INFO_EX(); + if (ssa->var_info && ssa->ops) { + if (ssa_op->op1_use >= 0) { + zend_ssa_var_info *op1_ssa = ssa->var_info + ssa_op->op1_use; + if (op1_ssa->ce && !op1_ssa->is_instanceof && !op1_ssa->ce->create_object) { + ce = op1_ssa->ce; + } + } + } + } + if (!(op1_info & MAY_BE_OBJECT)) { + break; + } + if (!zend_jit_fetch_obj_read(&dasm_state, opline, op_array, + op1_info, ce, + zend_may_throw_ex(opline, ssa_op, op_array, ssa))) { + goto jit_failure; + } + goto done; + case ZEND_BIND_GLOBAL: + orig_opline = opline; + orig_ssa_op = ssa_op; + while (1) { + if (!ssa->ops || !ssa->var_info) { + op1_info = MAY_BE_ANY|MAY_BE_REF; + } else { + op1_info = OP1_INFO_EX(); + } + if (!zend_jit_bind_global(&dasm_state, opline, op_array, op1_info)) { + goto jit_failure; + } + if ((opline+1)->opcode == ZEND_BIND_GLOBAL) { + opline++; + ssa_op++; + } else { + break; + } + } + opline = orig_opline; + ssa_op = orig_ssa_op; + goto done; + case ZEND_RECV: + if (!zend_jit_recv(&dasm_state, opline, op_array)) { + goto jit_failure; + } + goto done; + case ZEND_RECV_INIT: + orig_opline = opline; + orig_ssa_op = ssa_op; + while (1) { + if (!zend_jit_recv_init(&dasm_state, opline, op_array, + (opline + 1)->opcode != ZEND_RECV_INIT, + zend_may_throw_ex(opline, ssa_op, op_array, ssa))) { + goto jit_failure; + } + if ((opline+1)->opcode == ZEND_RECV_INIT) { + opline++; + ssa_op++; + } else { + break; + } + } + opline = orig_opline; + ssa_op = orig_ssa_op; + goto done; + case ZEND_FREE: + case ZEND_FE_FREE: + op1_info = OP1_INFO_EX(); + USE_OP1_TRACE_TYPE(); + if (!zend_jit_free(&dasm_state, opline, op_array, op1_info, + zend_may_throw_ex(opline, ssa_op, op_array, ssa))) { + goto jit_failure; + } + goto done; + case ZEND_ECHO: + if (opline->op1_type != IS_CONST + || Z_TYPE_P(RT_CONSTANT(opline, opline->op1)) != IS_STRING) { + break; + } + if (!zend_jit_echo(&dasm_state, opline, op_array)) { + goto jit_failure; + } + goto done; +#if 0 + case ZEND_SWITCH_LONG: + case ZEND_SWITCH_STRING: + if (!zend_jit_switch(&dasm_state, opline, op_array, op_array_ssa)) { + goto jit_failure; + } + goto done; +#endif +// case ZEND_INIT_NS_FCALL_BY_NAME: + // TODO: we may need a guard after INIT_NS_FCALL??? + case ZEND_INIT_METHOD_CALL: + case ZEND_INIT_DYNAMIC_CALL: + if (!zend_jit_trace_handler(&dasm_state, op_array, opline, zend_may_throw_ex(opline, ssa_op, op_array, ssa), p + 1)) { + goto jit_failure; + } + if ((p+1)->op == ZEND_JIT_TRACE_INIT_CALL) { + if (!zend_jit_init_fcall_guard(&dasm_state, opline, (p+1)->func)) { + goto jit_failure; + } + } + goto done; + case ZEND_INIT_STATIC_METHOD_CALL: + if (!zend_jit_trace_handler(&dasm_state, op_array, opline, zend_may_throw_ex(opline, ssa_op, op_array, ssa), p + 1)) { + goto jit_failure; + } + if ((opline->op1_type != IS_CONST + || opline->op2_type != IS_CONST) + && (p+1)->op == ZEND_JIT_TRACE_INIT_CALL) { + if (!zend_jit_init_fcall_guard(&dasm_state, opline, (p+1)->func)) { + goto jit_failure; + } + } + goto done; + case ZEND_INIT_USER_CALL: + if (!zend_jit_trace_handler(&dasm_state, op_array, opline, zend_may_throw_ex(opline, ssa_op, op_array, ssa), p + 1)) { + goto jit_failure; + } + if (opline->op2_type != IS_CONST + && (p+1)->op == ZEND_JIT_TRACE_INIT_CALL) { + if (!zend_jit_init_fcall_guard(&dasm_state, opline, (p+1)->func)) { + goto jit_failure; + } + } + goto done; + case ZEND_NEW: + if (!zend_jit_trace_handler(&dasm_state, op_array, opline, zend_may_throw_ex(opline, ssa_op, op_array, ssa), p + 1)) { + goto jit_failure; + } + if (opline->op1_type != IS_CONST + && (p+1)->op == ZEND_JIT_TRACE_INIT_CALL) { + if (!zend_jit_init_fcall_guard(&dasm_state, opline, (p+1)->func)) { + goto jit_failure; + } + } + goto done; + default: + break; + } + } + + if (opline->opcode != ZEND_NOP && opline->opcode != ZEND_JMP) { + if (!zend_jit_trace_handler(&dasm_state, op_array, opline, zend_may_throw_ex(opline, ssa_op, op_array, ssa), p + 1)) { + goto jit_failure; + } + } + +done: +#if 0 + // TODO: call level calculation doesn't work for traces ??? + switch (opline->opcode) { + case ZEND_DO_FCALL: + case ZEND_DO_ICALL: + case ZEND_DO_UCALL: + case ZEND_DO_FCALL_BY_NAME: + call_level--; + } +#endif + + /* Keep information about known types on abstract stack */ + if (ssa_op->result_def >= 0) { + zend_uchar type = IS_UNKNOWN; + + if ((opline->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0 + || send_result) { + /* we didn't set result variable */ + type = IS_UNKNOWN; + } else if (!(ssa->var_info[ssa_op->result_def].type & MAY_BE_GUARD) + && has_concrete_type(ssa->var_info[ssa_op->result_def].type)) { + type = concrete_type(ssa->var_info[ssa_op->result_def].type); + } else if (opline->opcode == ZEND_QM_ASSIGN) { + if (opline->op1_type != IS_CONST) { + /* copy */ + type = STACK_VAR_TYPE(opline->op1.var); + } + } else if (opline->opcode == ZEND_ASSIGN) { + if (opline->op2_type != IS_CONST) { + /* copy */ + type = STACK_VAR_TYPE(opline->op2.var); + } + } else if (opline->opcode == ZEND_POST_INC + || opline->opcode == ZEND_POST_DEC) { + /* copy */ + type = STACK_VAR_TYPE(opline->op1.var); + } + SET_RES_STACK_VAR_TYPE(type); + if (type != IS_UNKNOWN) { + ssa->var_info[ssa_op->result_def].type &= ~MAY_BE_GUARD; + } + } + if (ssa_op->op1_def >= 0) { + zend_uchar type = IS_UNKNOWN; + + if (!(ssa->var_info[ssa_op->op1_def].type & MAY_BE_GUARD) + && has_concrete_type(ssa->var_info[ssa_op->op1_def].type)) { + type = concrete_type(ssa->var_info[ssa_op->op1_def].type); + } else if (opline->opcode == ZEND_ASSIGN) { + if (!(OP1_INFO_EX() & MAY_BE_REF) + || STACK_VAR_TYPE(opline->op1.var) != IS_UNKNOWN) { + if (opline->op2_type != IS_CONST) { + /* copy */ + type = STACK_VAR_TYPE(opline->op2.var); + } + } + } else if (opline->opcode == ZEND_SEND_VAR + || opline->opcode == ZEND_CAST + || opline->opcode == ZEND_QM_ASSIGN + || opline->opcode == ZEND_JMP_SET + || opline->opcode == ZEND_COALESCE + || opline->opcode == ZEND_FE_RESET_R) { + /* keep old value */ + type = STACK_VAR_TYPE(opline->op1.var); + } + SET_OP1_STACK_VAR_TYPE(type); + if (type != IS_UNKNOWN) { + ssa->var_info[ssa_op->op1_def].type &= ~MAY_BE_GUARD; + } + } + if (ssa_op->op2_def >= 0) { + zend_uchar type = IS_UNKNOWN; + + if (!(ssa->var_info[ssa_op->op2_def].type & MAY_BE_GUARD) + && has_concrete_type(ssa->var_info[ssa_op->op2_def].type)) { + type = concrete_type(ssa->var_info[ssa_op->op2_def].type); + } else if (opline->opcode == ZEND_ASSIGN) { + /* keep old value */ + type = STACK_VAR_TYPE(opline->op2.var); + } + SET_OP2_STACK_VAR_TYPE(type); + if (type != IS_UNKNOWN) { + ssa->var_info[ssa_op->op2_def].type &= ~MAY_BE_GUARD; + } + } + + switch (opline->opcode) { + case ZEND_ASSIGN_DIM: + case ZEND_ASSIGN_OBJ: + case ZEND_ASSIGN_STATIC_PROP: + case ZEND_ASSIGN_DIM_OP: + case ZEND_ASSIGN_OBJ_OP: + case ZEND_ASSIGN_STATIC_PROP_OP: + case ZEND_ASSIGN_OBJ_REF: + case ZEND_ASSIGN_STATIC_PROP_REF: + /* OP_DATA */ + ssa_op++; + opline++; + if (ssa_op->op1_def >= 0) { + zend_uchar type = IS_UNKNOWN; + + if (!(ssa->var_info[ssa_op->op1_def].type & MAY_BE_GUARD) + && has_concrete_type(ssa->var_info[ssa_op->op1_def].type)) { + type = concrete_type(ssa->var_info[ssa_op->op1_def].type); + } else if ((opline-1)->opcode == ZEND_ASSIGN_DIM + || (opline-1)->opcode == ZEND_ASSIGN_OBJ + || (opline-1)->opcode == ZEND_ASSIGN_STATIC_PROP) { + /* keep old value */ + type = STACK_VAR_TYPE(opline->op1.var); + } + SET_OP1_STACK_VAR_TYPE(type); + if (type != IS_UNKNOWN) { + ssa->var_info[ssa_op->op1_def].type &= ~MAY_BE_GUARD; + } + } + ssa_op++; + break; + case ZEND_RECV_INIT: + ssa_op++; + opline++; + while (opline->opcode == ZEND_RECV_INIT) { + if (ssa_op->result_def >= 0) { + zend_uchar type = IS_UNKNOWN; + + if (!(ssa->var_info[ssa_op->result_def].type & MAY_BE_GUARD) + && has_concrete_type(ssa->var_info[ssa_op->result_def].type)) { + type = concrete_type(ssa->var_info[ssa_op->result_def].type); + } + SET_RES_STACK_VAR_TYPE(type); + } + ssa_op++; + opline++; + } + break; + case ZEND_BIND_GLOBAL: + ssa_op++; + opline++; + while (opline->opcode == ZEND_BIND_GLOBAL) { + if (ssa_op->op1_def >= 0) { + zend_uchar type = IS_UNKNOWN; + + if (!(ssa->var_info[ssa_op->op1_def].type & MAY_BE_GUARD) + && has_concrete_type(ssa->var_info[ssa_op->op1_def].type)) { + type = concrete_type(ssa->var_info[ssa_op->op1_def].type); + } + SET_OP1_STACK_VAR_TYPE(type); + } + ssa_op++; + opline++; + } + break; + default: + ssa_op += zend_jit_trace_op_len(opline); + break; + } + + if (send_result) { + ssa_op++; + send_result = 0; + } + + } else if (p->op == ZEND_JIT_TRACE_ENTER) { + op_array = (zend_op_array*)p->op_array; + jit_extension = + (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array); + op_array_ssa = &jit_extension->func_info.ssa; + call = frame->call; + if (!call) { + /* Trace missed INIT_FCALL opcode */ + call = top; + call->call = NULL; + call->prev = NULL; + call->func = (const zend_function*)op_array; + call->nested = 0; + call->num_args = -1; // TODO: should be possible to get the real number ??? + top = zend_jit_trace_call_frame(top, op_array); + i = 0; + while (i < p->op_array->num_args) { + /* Initialize abstract stack using SSA */ + if (!(ssa->var_info[p->first_ssa_var + i].type & MAY_BE_GUARD) + && has_concrete_type(ssa->var_info[p->first_ssa_var + i].type)) { + call->stack[i] = concrete_type(ssa->var_info[p->first_ssa_var + i].type); + } else { + call->stack[i] = IS_UNKNOWN; + } + i++; + } + while (i < p->op_array->last_var) { + call->stack[i] = IS_UNDEF; + i++; + } + while (i < p->op_array->last_var + p->op_array->T) { + call->stack[i] = IS_UNKNOWN; + i++; + } + } else { + ZEND_ASSERT(&call->func->op_array == op_array); + } + frame->call = call->prev; + call->prev = frame; + call->return_value_used = p->return_value_used; + JIT_G(current_frame) = frame = call; + stack = frame->stack; + zend_jit_set_opline(&dasm_state, (p+1)->opline); + } else if (p->op == ZEND_JIT_TRACE_BACK) { + op_array = (zend_op_array*)p->op_array; + jit_extension = + (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array); + op_array_ssa = &jit_extension->func_info.ssa; + top = frame; + if (frame->prev) { + frame = frame->prev; + stack = frame->stack; + ZEND_ASSERT(&frame->func->op_array == op_array); + } else { + frame = zend_jit_trace_ret_frame(frame, op_array); + frame->call = NULL; + frame->prev = NULL; + frame->func = (const zend_function*)op_array; + frame->return_value_used = -1; + frame->nested = 0; + frame->num_args = -1; + stack = frame->stack; + for (i = 0; i < op_array->last_var + op_array->T; i++) { + /* Initialize abstract stack using SSA */ + if (!(ssa->var_info[p->first_ssa_var + i].type & MAY_BE_GUARD) + && has_concrete_type(ssa->var_info[p->first_ssa_var + i].type)) { + stack[i] = concrete_type(ssa->var_info[p->first_ssa_var + i].type); + } else { + stack[i] = IS_UNKNOWN; + } + } + } + JIT_G(current_frame) = frame; + if (res_type != IS_UNKNOWN + && (p+1)->op == ZEND_JIT_TRACE_VM) { + const zend_op *opline = (p+1)->opline - 1; + if (opline->result_type != IS_UNUSED) { + SET_RES_STACK_VAR_TYPE(res_type); + } + } + res_type = IS_UNKNOWN; + } else if (p->op == ZEND_JIT_TRACE_END) { + break; + } else if (p->op == ZEND_JIT_TRACE_INIT_CALL) { + call = top; + call->call = NULL; + call->prev = frame->call; + call->func = p->func; + call->nested = 1; + call->num_args = find_call_num_args(p-1); + frame->call = call; + top = zend_jit_trace_call_frame(top, p->op_array); + if (p->func->type == ZEND_USER_FUNCTION) { + i = 0; + while (i < p->op_array->num_args) { + /* Types of arguments are going to be stored in abstract stack when proseccin SEV onstruction */ + call->stack[i] = IS_UNKNOWN; + i++; + } + while (i < p->op_array->last_var) { + call->stack[i] = IS_UNDEF; + i++; + } + while (i < p->op_array->last_var + p->op_array->T) { + call->stack[i] = IS_UNKNOWN; + i++; + } + } + if (p->fake) { + if (!zend_jit_init_fcall_guard(&dasm_state, NULL, p->func)) { + goto jit_failure; + } + } + } else if (p->op == ZEND_JIT_TRACE_DO_ICALL) { + call = frame->call; + if (call) { + top = call; + frame->call = call->prev; + } + } else { + ZEND_ASSERT(0); + } + } + + ZEND_ASSERT(p->op == ZEND_JIT_TRACE_END); + + t = &zend_jit_traces[ZEND_JIT_TRACE_NUM]; + + if (p->stop == ZEND_JIT_TRACE_STOP_LOOP + || p->stop == ZEND_JIT_TRACE_STOP_RECURSIVE_CALL + || p->stop == ZEND_JIT_TRACE_STOP_RECURSIVE_RET) { + if (p->stop == ZEND_JIT_TRACE_STOP_LOOP) { + if (!zend_jit_set_valid_ip(&dasm_state, p->opline)) { + goto jit_failure; + } + } + t->link = ZEND_JIT_TRACE_NUM; + zend_jit_jmp(&dasm_state, 0); /* jump back to start of the trace loop */ + } else if (p->stop == ZEND_JIT_TRACE_STOP_LINK) { + if (!zend_jit_set_valid_ip(&dasm_state, p->opline)) { + goto jit_failure; + } + t->link = zend_jit_find_trace(p->opline->handler); + zend_jit_trace_link_to_root(&dasm_state, p->opline->handler); + } else if (p->stop == ZEND_JIT_TRACE_STOP_RETURN) { + zend_jit_trace_return(&dasm_state); + } else { + // TODO: not implemented ??? + ZEND_ASSERT(0 && p->stop); + } + + if (ZEND_JIT_EXIT_COUNTERS + t->exit_count >= ZEND_JIT_TRACE_MAX_EXIT_COUNTERS) { + goto jit_failure; + } + + handler = dasm_link_and_encode(&dasm_state, NULL, NULL, NULL, NULL, ZSTR_VAL(name), 1); + +jit_failure: + dasm_free(&dasm_state); + + if (name) { + zend_string_release(name); + } + + /* Clenup used op_arrays */ + while (num_op_arrays > 0) { + op_array = op_arrays[--num_op_arrays]; + jit_extension = + (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array); + + memset(&jit_extension->func_info, 0, sizeof(jit_extension->func_info)); + jit_extension->func_info.num_args = -1; + jit_extension->func_info.return_value_used = -1; + } + + zend_arena_release(&CG(arena), checkpoint); + + JIT_G(current_frame) = NULL; + + return handler; +} + +static int zend_jit_trace_exit_needs_deoptimization(uint32_t trace_num, uint32_t exit_num) +{ + const zend_op *opline = zend_jit_traces[trace_num].exit_info[exit_num].opline; + + opline = (const zend_op*)((uintptr_t)opline & ~(ZEND_JIT_EXIT_JITED|ZEND_JIT_EXIT_BLACKLISTED)); + if (opline) { + return 1; + } + + return 0; +} + +static const void *zend_jit_trace_exit_to_vm(uint32_t trace_num, uint32_t exit_num) +{ + const void *handler = NULL; + dasm_State* dasm_state = NULL; + void *checkpoint; + char name[32]; + const zend_op *opline; + + if (!zend_jit_trace_exit_needs_deoptimization(trace_num, exit_num)) { + return dasm_labels[zend_lbtrace_escape]; + } + + checkpoint = zend_arena_checkpoint(CG(arena));; + + sprintf(name, "ESCAPE-%d-%d", trace_num, exit_num); + + 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, 0); + + zend_jit_align_func(&dasm_state); + + // TODO: Generate deoptimization code ??? + + opline = zend_jit_traces[trace_num].exit_info[exit_num].opline; + opline = (const zend_op*)((uintptr_t)opline & ~(ZEND_JIT_EXIT_JITED|ZEND_JIT_EXIT_BLACKLISTED)); + if (opline) { + zend_jit_set_ip(&dasm_state, opline); + } + + zend_jit_trace_return(&dasm_state); + + handler = dasm_link_and_encode(&dasm_state, NULL, NULL, NULL, NULL, name, 1); + + dasm_free(&dasm_state); + + zend_arena_release(&CG(arena), checkpoint); + + return handler; +} + +static zend_jit_trace_stop zend_jit_compile_root_trace(zend_jit_trace_rec *trace_buffer, const zend_op *opline, size_t offset) +{ + zend_jit_trace_stop ret; + const void *handler; + zend_jit_trace_info *t; + zend_jit_trace_exit_info exit_info[ZEND_JIT_TRACE_MAX_EXITS]; + + zend_shared_alloc_lock(); + + /* Checks under lock */ + if ((ZEND_OP_TRACE_INFO(opline, offset)->trace_flags & ZEND_JIT_TRACE_JITED)) { + ret = ZEND_JIT_TRACE_STOP_ALREADY_DONE; + } else if (ZEND_JIT_TRACE_NUM >= ZEND_JIT_TRACE_MAX_TRACES) { + ret = ZEND_JIT_TRACE_STOP_TOO_MANY_TRACES; + } else { + SHM_UNPROTECT(); + zend_jit_unprotect(); + + t = &zend_jit_traces[ZEND_JIT_TRACE_NUM]; + + t->id = ZEND_JIT_TRACE_NUM; + t->root = ZEND_JIT_TRACE_NUM; + t->parent = 0; + t->link = 0; + t->exit_count = 0; + t->child_count = 0; + t->stack_map_size = 0; + t->exit_info = exit_info; + t->stack_map = NULL; + + handler = zend_jit_trace(trace_buffer, 0, 0); + + if (handler) { + zend_jit_trace_exit_info *shared_exit_info = NULL; + + t->exit_info = NULL; + if (t->exit_count) { + /* reallocate exit_info into shared memory */ + shared_exit_info = (zend_jit_trace_exit_info*)zend_shared_alloc( + sizeof(zend_jit_trace_exit_info) * t->exit_count); + + if (!shared_exit_info) { + if (t->stack_map) { + efree(t->stack_map); + t->stack_map = NULL; + } + ret = ZEND_JIT_TRACE_STOP_NO_SHM; + goto exit; + } + memcpy(shared_exit_info, exit_info, + sizeof(zend_jit_trace_exit_info) * t->exit_count); + t->exit_info = shared_exit_info; + } + + if (t->stack_map_size) { + zend_jit_trace_stack *shared_stack_map = (zend_jit_trace_stack*)zend_shared_alloc(t->stack_map_size * sizeof(zend_jit_trace_stack)); + if (!shared_stack_map) { + ret = ZEND_JIT_TRACE_STOP_NO_SHM; + goto exit; + } + memcpy(shared_stack_map, t->stack_map, t->stack_map_size * sizeof(zend_jit_trace_stack)); + efree(t->stack_map); + t->stack_map = shared_stack_map; + } + + t->exit_counters = ZEND_JIT_EXIT_COUNTERS; + ZEND_JIT_EXIT_COUNTERS += t->exit_count; + + ((zend_op*)opline)->handler = handler; + + ZEND_JIT_TRACE_NUM++; + ZEND_OP_TRACE_INFO(opline, offset)->trace_flags |= ZEND_JIT_TRACE_JITED; + + ret = ZEND_JIT_TRACE_STOP_COMPILED; + } else if (t->exit_count >= ZEND_JIT_TRACE_MAX_EXITS || + ZEND_JIT_EXIT_COUNTERS + t->exit_count >= ZEND_JIT_TRACE_MAX_EXIT_COUNTERS) { + if (t->stack_map) { + efree(t->stack_map); + t->stack_map = NULL; + } + ret = ZEND_JIT_TRACE_STOP_TOO_MANY_EXITS; + } else { + if (t->stack_map) { + efree(t->stack_map); + t->stack_map = NULL; + } + ret = ZEND_JIT_TRACE_STOP_COMPILER_ERROR; + } + +exit: + zend_jit_protect(); + SHM_PROTECT(); + } + + zend_shared_alloc_unlock(); + + return ret; +} + +static void zend_jit_blacklist_root_trace(const zend_op *opline, size_t offset) +{ + zend_shared_alloc_lock(); + + if (!(ZEND_OP_TRACE_INFO(opline, offset)->trace_flags & ZEND_JIT_TRACE_BLACKLISTED)) { + SHM_UNPROTECT(); + zend_jit_unprotect(); + + ((zend_op*)opline)->handler = + ZEND_OP_TRACE_INFO(opline, offset)->orig_handler; + + ZEND_OP_TRACE_INFO(opline, offset)->trace_flags |= ZEND_JIT_TRACE_BLACKLISTED; + + zend_jit_protect(); + SHM_PROTECT(); + } + + zend_shared_alloc_unlock(); +} + +static zend_bool zend_jit_trace_is_bad_root(const zend_op *opline, zend_jit_trace_stop stop, size_t offset) +{ + const zend_op **cache_opline = JIT_G(bad_root_cache_opline); + uint8_t *cache_count = JIT_G(bad_root_cache_count); + uint8_t *cache_stop = JIT_G(bad_root_cache_stop); + uint32_t cache_slot = JIT_G(bad_root_slot); + uint32_t i; + + for (i = 0; i < ZEND_JIT_TRACE_BAD_ROOT_SLOTS; i++) { + if (cache_opline[i] == opline) { + if (cache_count[i] >= ZEND_JIT_TRACE_MAX_ROOT_FAILURES - 1) { + cache_opline[i] = NULL; + return 1; + } else { +#if 0 + if (ZEND_OP_TRACE_INFO(opline, offset)->counter) { + *ZEND_OP_TRACE_INFO(opline, offset)->counter = + random() % ZEND_JIT_TRACE_COUNTER_MAX; + } +#endif + cache_count[i]++; + cache_stop[i] = stop; + return 0; + } + } + } + i = cache_slot; + cache_opline[i] = opline; + cache_count[i] = 1; + cache_stop[i] = stop; + cache_slot = (i + 1) % ZEND_JIT_TRACE_BAD_ROOT_SLOTS; + JIT_G(bad_root_slot) = cache_slot; + return 0; +} + +#define ZEND_JIT_TRACE_STOP_DESCRIPTION(name, description) \ + description, + +static const char * zend_jit_trace_stop_description[] = { + ZEND_JIT_TRACE_STOP(ZEND_JIT_TRACE_STOP_DESCRIPTION) +}; + +static void zend_jit_dump_trace(zend_jit_trace_rec *trace_buffer, zend_ssa *tssa) +{ + zend_jit_trace_rec *p = trace_buffer; + const zend_op_array *op_array; + const zend_op *opline; + uint32_t level = 1 + trace_buffer[0].level; + int idx, len, i, v, vars_count, call_level; + + ZEND_ASSERT(p->op == ZEND_JIT_TRACE_START); + op_array = p->op_array; + p += ZEND_JIT_TRACE_START_REC_SIZE; + idx = 0; + call_level = 0; + + if (tssa && tssa->var_info) { + if (trace_buffer->start == ZEND_JIT_TRACE_START_ENTER) { + vars_count = op_array->last_var; + } else { + vars_count = op_array->last_var + op_array->T; + } + for (i = 0; i < vars_count; i++) { + if (tssa->vars[i].use_chain >= 0 || tssa->vars[i].phi_use_chain) { + fprintf(stderr, " %*c;", level, ' '); + zend_dump_ssa_var(op_array, tssa, i, 0, i, ZEND_DUMP_RC_INFERENCE); + fprintf(stderr, "\n"); + } + } + if (trace_buffer->stop == ZEND_JIT_TRACE_STOP_LOOP + || trace_buffer->stop == ZEND_JIT_TRACE_STOP_RECURSIVE_CALL + || trace_buffer->stop == ZEND_JIT_TRACE_STOP_RECURSIVE_RET) { + fprintf(stderr, "LOOP:\n"); + if (trace_buffer->stop == ZEND_JIT_TRACE_STOP_LOOP) { + zend_ssa_phi *p = tssa->blocks[1].phis; + + while (p) { + fprintf(stderr, " ;"); + zend_dump_ssa_var(op_array, tssa, p->ssa_var, 0, p->var, ZEND_DUMP_RC_INFERENCE); + fprintf(stderr, " = Phi("); + zend_dump_ssa_var(op_array, tssa, p->sources[0], 0, p->var, ZEND_DUMP_RC_INFERENCE); + fprintf(stderr, ", "); + zend_dump_ssa_var(op_array, tssa, p->sources[1], 0, p->var, ZEND_DUMP_RC_INFERENCE); + fprintf(stderr, ")\n"); + p = p->next; + } + } + } + } + + while (1) { + if (p->op == ZEND_JIT_TRACE_VM) { + uint8_t op1_type, op2_type, op3_type; + + opline = p->opline; + fprintf(stderr, "%04d%*c", + (int)(opline - op_array->opcodes), + level, ' '); + zend_dump_op(op_array, NULL, opline, ZEND_DUMP_NUMERIC_OPLINES|ZEND_DUMP_RC_INFERENCE, tssa, (tssa && tssa->ops) ? tssa->ops + idx : NULL); + + op1_type = p->op1_type; + op2_type = p->op2_type; + op3_type = p->op3_type; + if (op1_type != IS_UNKNOWN || op2_type != IS_UNKNOWN || op3_type != IS_UNKNOWN) { + fprintf(stderr, " ;"); + if (op1_type != IS_UNKNOWN) { + const char *ref = (op1_type & IS_TRACE_REFERENCE) ? "&" : ""; + if ((p+1)->op == ZEND_JIT_TRACE_OP1_TYPE) { + p++; + fprintf(stderr, " op1(%sobject of class %s)", ref, + ZSTR_VAL(p->ce->name)); + } else { + const char *type = (op1_type == 0) ? "undef" : zend_get_type_by_const(op1_type & ~IS_TRACE_REFERENCE); + fprintf(stderr, " op1(%s%s)", ref, type); + } + } + if (op2_type != IS_UNKNOWN) { + const char *ref = (op2_type & IS_TRACE_REFERENCE) ? "&" : ""; + if ((p+1)->op == ZEND_JIT_TRACE_OP2_TYPE) { + p++; + fprintf(stderr, " op2(%sobject of class %s)", ref, + ZSTR_VAL(p->ce->name)); + } else { + const char *type = (op2_type == 0) ? "undef" : zend_get_type_by_const(op2_type & ~IS_TRACE_REFERENCE); + fprintf(stderr, " op2(%s%s)", ref, type); + } + } + if (op3_type != IS_UNKNOWN) { + const char *ref = (op3_type & IS_TRACE_REFERENCE) ? "&" : ""; + const char *type = (op3_type == 0) ? "undef" : zend_get_type_by_const(op3_type & ~IS_TRACE_REFERENCE); + fprintf(stderr, " op3(%s%s)", ref, type); + } + } + fprintf(stderr, "\n"); + idx++; + + len = zend_jit_trace_op_len(opline); + while (len > 1) { + opline++; + fprintf(stderr, "%04d%*c;", + (int)(opline - op_array->opcodes), + level, ' '); + zend_dump_op(op_array, NULL, opline, ZEND_DUMP_NUMERIC_OPLINES|ZEND_DUMP_RC_INFERENCE, tssa, (tssa && tssa->ops) ? tssa->ops + idx : NULL); + idx++; + len--; + fprintf(stderr, "\n"); + } + } else if (p->op == ZEND_JIT_TRACE_ENTER) { + op_array = p->op_array; + fprintf(stderr, " %*c>enter %s%s%s\n", + level, ' ', + op_array->scope ? ZSTR_VAL(op_array->scope->name) : "", + op_array->scope ? "::" : "", + op_array->function_name ? + ZSTR_VAL(op_array->function_name) : + ZSTR_VAL(op_array->filename)); + level++; + if (tssa && tssa->var_info) { + call_level++; + v = p->first_ssa_var; + vars_count = op_array->last_var; + for (i = 0; i < vars_count; i++, v++) { + if (tssa->vars[v].use_chain >= 0 || tssa->vars[v].phi_use_chain) { + fprintf(stderr, " %*c;", level, ' '); + zend_dump_ssa_var(op_array, tssa, v, 0, i, ZEND_DUMP_RC_INFERENCE); + fprintf(stderr, "\n"); + } + } + } + } else if (p->op == ZEND_JIT_TRACE_BACK) { + op_array = p->op_array; + level--; + fprintf(stderr, " %*c<back %s%s%s\n", + level, ' ', + op_array->scope ? ZSTR_VAL(op_array->scope->name) : "", + op_array->scope ? "::" : "", + op_array->function_name ? + ZSTR_VAL(op_array->function_name) : + ZSTR_VAL(op_array->filename)); + if (tssa && tssa->var_info) { + if (call_level == 0) { + v = p->first_ssa_var; + vars_count = op_array->last_var + op_array->T; + for (i = 0; i < vars_count; i++, v++) { + if (tssa->vars[v].use_chain >= 0 || tssa->vars[v].phi_use_chain) { + fprintf(stderr, " %*c;", level, ' '); + zend_dump_ssa_var(op_array, tssa, v, 0, i, ZEND_DUMP_RC_INFERENCE); + fprintf(stderr, "\n"); + } + } + } else { + call_level--; + } + } + } else if (p->op == ZEND_JIT_TRACE_INIT_CALL) { + if (p->func != (zend_function*)&zend_pass_function) { + fprintf(stderr, p->fake ? " %*c>fake_init %s%s%s\n" : " %*c>init %s%s%s\n", + level, ' ', + p->func->common.scope ? ZSTR_VAL(p->func->common.scope->name) : "", + p->func->common.scope ? "::" : "", + ZSTR_VAL(p->func->common.function_name)); + } else { + fprintf(stderr, " %*c>skip\n", + level, ' '); + } + } else if (p->op == ZEND_JIT_TRACE_DO_ICALL) { + if (p->func != (zend_function*)&zend_pass_function) { + fprintf(stderr, " %*c>call %s%s%s\n", + level, ' ', + p->func->common.scope ? ZSTR_VAL(p->func->common.scope->name) : "", + p->func->common.scope ? "::" : "", + ZSTR_VAL(p->func->common.function_name)); + } else { + fprintf(stderr, " %*c>skip\n", + level, ' '); + } + } else if (p->op == ZEND_JIT_TRACE_END) { + break; + } + p++; + } +} + +static zend_always_inline const char *zend_jit_trace_star_desc(uint8_t trace_flags) +{ + if (trace_flags & ZEND_JIT_TRACE_START_LOOP) { + return "loop"; + } else if (trace_flags & ZEND_JIT_TRACE_START_ENTER) { + return "enter"; + } else if (trace_flags & ZEND_JIT_TRACE_START_RETURN) { + return "return"; + } else { + ZEND_ASSERT(0); + return "???"; + } +} + +int ZEND_FASTCALL zend_jit_trace_hot_root(zend_execute_data *execute_data, const zend_op *opline) +{ + const zend_op *orig_opline; + zend_jit_trace_stop stop; + zend_op_array *op_array; + zend_jit_op_array_trace_extension *jit_extension; + size_t offset; + uint32_t trace_num; + zend_jit_trace_rec trace_buffer[ZEND_JIT_TRACE_MAX_LENGTH]; + + ZEND_ASSERT(EX(func)->type == ZEND_USER_FUNCTION); + ZEND_ASSERT(opline >= EX(func)->op_array.opcodes && + opline < EX(func)->op_array.opcodes + EX(func)->op_array.last); + +repeat: + trace_num = ZEND_JIT_TRACE_NUM; + orig_opline = opline; + op_array = &EX(func)->op_array; + jit_extension = (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array); + offset = jit_extension->offset; + + EX(opline) = opline; + + /* Lock-free check if the root trace was already JIT-ed or blacklist-ed in another process */ + if (ZEND_OP_TRACE_INFO(opline, offset)->trace_flags & (ZEND_JIT_TRACE_JITED|ZEND_JIT_TRACE_BLACKLISTED)) { + return 0; + } + + if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_START) { + fprintf(stderr, "---- TRACE %d start (%s) %s() %s:%d\n", + trace_num, + zend_jit_trace_star_desc(ZEND_OP_TRACE_INFO(opline, offset)->trace_flags), + EX(func)->op_array.function_name ? + ZSTR_VAL(EX(func)->op_array.function_name) : "$main", + ZSTR_VAL(EX(func)->op_array.filename), + opline->lineno); + } + + if (ZEND_JIT_TRACE_NUM >= ZEND_JIT_TRACE_MAX_TRACES) { + stop = ZEND_JIT_TRACE_STOP_TOO_MANY_TRACES; + goto abort; + } + + stop = zend_jit_trace_execute(execute_data, opline, trace_buffer, + ZEND_OP_TRACE_INFO(opline, offset)->trace_flags & ZEND_JIT_TRACE_START_MASK); + + if (stop == ZEND_JIT_TRACE_STOP_TOPLEVEL) { + /* op_array may be already deallocated */ + if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_ABORT) { + fprintf(stderr, "---- TRACE %d abort (%s)\n", + trace_num, + zend_jit_trace_stop_description[stop]); + } + goto blacklist; + } + + if (UNEXPECTED(ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_BYTECODE)) { + zend_jit_dump_trace(trace_buffer, NULL); + } + + if (ZEND_JIT_TRACE_STOP_OK(stop)) { + if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_STOP) { + if (stop == ZEND_JIT_TRACE_STOP_LINK) { + uint32_t link_to = zend_jit_find_trace(EG(current_execute_data)->opline->handler);; + fprintf(stderr, "---- TRACE %d stop (link to %d)\n", + trace_num, + link_to); + } else { + fprintf(stderr, "---- TRACE %d stop (%s)\n", + trace_num, + zend_jit_trace_stop_description[stop]); + } + } + stop = zend_jit_compile_root_trace(trace_buffer, orig_opline, offset); + if (EXPECTED(ZEND_JIT_TRACE_STOP_DONE(stop))) { + if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_COMPILED) { + fprintf(stderr, "---- TRACE %d %s\n", + trace_num, + zend_jit_trace_stop_description[stop]); + } + } else { + goto abort; + } + } else { +abort: + if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_ABORT) { + fprintf(stderr, "---- TRACE %d abort (%s)\n", + trace_num, + zend_jit_trace_stop_description[stop]); + } +blacklist: + if (!ZEND_JIT_TRACE_STOP_MAY_RECOVER(stop) + || zend_jit_trace_is_bad_root(orig_opline, stop, offset)) { + if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_BLACKLIST) { + fprintf(stderr, "---- TRACE %d blacklisted\n", + trace_num); + } + zend_jit_blacklist_root_trace(orig_opline, offset); + } + if (ZEND_JIT_TRACE_STOP_REPEAT(stop)) { + execute_data = EG(current_execute_data); + opline = EX(opline); + goto repeat; + } + } + + if (ZCG(accel_directives).jit_debug & (ZEND_JIT_DEBUG_TRACE_STOP|ZEND_JIT_DEBUG_TRACE_ABORT|ZEND_JIT_DEBUG_TRACE_COMPILED|ZEND_JIT_DEBUG_TRACE_BLACKLIST)) { + fprintf(stderr, "\n"); + } + + return (stop == ZEND_JIT_TRACE_STOP_HALT) ? -1 : 0; +} + +static void zend_jit_blacklist_trace_exit(uint32_t trace_num, uint32_t exit_num) +{ + const void *handler; + + zend_shared_alloc_lock(); + + if (!((uintptr_t)zend_jit_traces[trace_num].exit_info[exit_num].opline & ZEND_JIT_EXIT_BLACKLISTED)) { + SHM_UNPROTECT(); + zend_jit_unprotect(); + + handler = zend_jit_trace_exit_to_vm(trace_num, exit_num); + + if (handler) { + zend_jit_link_side_trace( + zend_jit_traces[trace_num].code_start, + zend_jit_traces[trace_num].code_size, + exit_num, + handler); + } + + zend_jit_traces[trace_num].exit_info[exit_num].opline = (const zend_op*) + ((uintptr_t)zend_jit_traces[trace_num].exit_info[exit_num].opline | ZEND_JIT_EXIT_BLACKLISTED); + + zend_jit_protect(); + SHM_PROTECT(); + } + + zend_shared_alloc_unlock(); +} + +static zend_bool zend_jit_trace_exit_is_bad(uint32_t trace_num, uint32_t exit_num) +{ + uint8_t *counter = JIT_G(exit_counters) + + zend_jit_traces[trace_num].exit_counters + exit_num; + + if (*counter + 1 >= ZEND_JIT_TRACE_HOT_SIDE_COUNT + ZEND_JIT_TRACE_MAX_SIDE_FAILURES) { + return 1; + } + (*counter)++; + return 0; +} + +static zend_bool zend_jit_trace_exit_is_hot(uint32_t trace_num, uint32_t exit_num) +{ + uint8_t *counter = JIT_G(exit_counters) + + zend_jit_traces[trace_num].exit_counters + exit_num; + + if (*counter + 1 >= ZEND_JIT_TRACE_HOT_SIDE_COUNT) { + return 1; + } + (*counter)++; + return 0; +} + +static zend_jit_trace_stop zend_jit_compile_side_trace(zend_jit_trace_rec *trace_buffer, uint32_t parent_num, uint32_t exit_num) +{ + zend_jit_trace_stop ret; + const void *handler; + zend_jit_trace_info *t; + zend_jit_trace_exit_info exit_info[ZEND_JIT_TRACE_MAX_EXITS]; + + zend_shared_alloc_lock(); + + /* Checks under lock */ + if (((uintptr_t)zend_jit_traces[parent_num].exit_info[exit_num].opline & ZEND_JIT_EXIT_JITED)) { + ret = ZEND_JIT_TRACE_STOP_ALREADY_DONE; + } else if (ZEND_JIT_TRACE_NUM >= ZEND_JIT_TRACE_MAX_TRACES) { + ret = ZEND_JIT_TRACE_STOP_TOO_MANY_TRACES; + } else if (zend_jit_traces[zend_jit_traces[parent_num].root].child_count >= ZEND_JIT_TRACE_MAX_SIDE_TRACES) { + ret = ZEND_JIT_TRACE_STOP_TOO_MANY_CHILDREN; + } else { + SHM_UNPROTECT(); + zend_jit_unprotect(); + + t = &zend_jit_traces[ZEND_JIT_TRACE_NUM]; + + t->id = ZEND_JIT_TRACE_NUM; + t->root = zend_jit_traces[parent_num].root; + t->parent = parent_num; + t->link = 0; + t->exit_count = 0; + t->child_count = 0; + t->stack_map_size = 0; + t->exit_info = exit_info; + t->stack_map = NULL; + + handler = zend_jit_trace(trace_buffer, parent_num, exit_num); + + if (handler) { + zend_jit_trace_exit_info *shared_exit_info = NULL; + + t->exit_info = NULL; + if (t->exit_count) { + /* reallocate exit_info into shared memory */ + shared_exit_info = (zend_jit_trace_exit_info*)zend_shared_alloc( + sizeof(zend_jit_trace_exit_info) * t->exit_count); + + if (!shared_exit_info) { + if (t->stack_map) { + efree(t->stack_map); + t->stack_map = NULL; + } + ret = ZEND_JIT_TRACE_STOP_NO_SHM; + goto exit; + } + memcpy(shared_exit_info, exit_info, + sizeof(zend_jit_trace_exit_info) * t->exit_count); + t->exit_info = shared_exit_info; + } + + if (t->stack_map_size) { + zend_jit_trace_stack *shared_stack_map = (zend_jit_trace_stack*)zend_shared_alloc(t->stack_map_size * sizeof(zend_jit_trace_stack)); + if (!shared_stack_map) { + efree(t->stack_map); + ret = ZEND_JIT_TRACE_STOP_NO_SHM; + goto exit; + } + memcpy(shared_stack_map, t->stack_map, t->stack_map_size * sizeof(zend_jit_trace_stack)); + efree(t->stack_map); + t->stack_map = shared_stack_map; + } + + zend_jit_link_side_trace( + zend_jit_traces[parent_num].code_start, + zend_jit_traces[parent_num].code_size, + exit_num, + handler); + + t->exit_counters = ZEND_JIT_EXIT_COUNTERS; + ZEND_JIT_EXIT_COUNTERS += t->exit_count; + + zend_jit_traces[zend_jit_traces[parent_num].root].child_count++; + ZEND_JIT_TRACE_NUM++; + zend_jit_traces[parent_num].exit_info[exit_num].opline = (const zend_op*) + ((uintptr_t)zend_jit_traces[parent_num].exit_info[exit_num].opline | ZEND_JIT_EXIT_JITED); + + ret = ZEND_JIT_TRACE_STOP_COMPILED; + } else if (t->exit_count >= ZEND_JIT_TRACE_MAX_EXITS || + ZEND_JIT_EXIT_COUNTERS + t->exit_count >= ZEND_JIT_TRACE_MAX_EXIT_COUNTERS) { + if (t->stack_map) { + efree(t->stack_map); + t->stack_map = NULL; + } + ret = ZEND_JIT_TRACE_STOP_TOO_MANY_EXITS; + } else { + if (t->stack_map) { + efree(t->stack_map); + t->stack_map = NULL; + } + ret = ZEND_JIT_TRACE_STOP_COMPILER_ERROR; + } + +exit: + zend_jit_protect(); + SHM_PROTECT(); + } + + zend_shared_alloc_unlock(); + + return ret; +} + +int ZEND_FASTCALL zend_jit_trace_hot_side(zend_execute_data *execute_data, uint32_t parent_num, uint32_t exit_num) +{ + zend_jit_trace_stop stop; + uint32_t trace_num; + zend_jit_trace_rec trace_buffer[ZEND_JIT_TRACE_MAX_LENGTH]; + + trace_num = ZEND_JIT_TRACE_NUM; + + /* Lock-free check if the side trace was already JIT-ed or blacklist-ed in another process */ + if ((uintptr_t)zend_jit_traces[parent_num].exit_info[exit_num].opline & (ZEND_JIT_EXIT_JITED|ZEND_JIT_EXIT_BLACKLISTED)) { + return 0; + } + + if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_START) { + fprintf(stderr, "---- TRACE %d start (side trace %d/%d) %s() %s:%d\n", + trace_num, parent_num, exit_num, + EX(func)->op_array.function_name ? + ZSTR_VAL(EX(func)->op_array.function_name) : "$main", + ZSTR_VAL(EX(func)->op_array.filename), + EX(opline)->lineno); + } + + if (ZEND_JIT_TRACE_NUM >= ZEND_JIT_TRACE_MAX_TRACES) { + stop = ZEND_JIT_TRACE_STOP_TOO_MANY_TRACES; + goto abort; + } + + if (zend_jit_traces[zend_jit_traces[parent_num].root].child_count >= ZEND_JIT_TRACE_MAX_SIDE_TRACES) { + stop = ZEND_JIT_TRACE_STOP_TOO_MANY_CHILDREN; + goto abort; + } + + stop = zend_jit_trace_execute(execute_data, EX(opline), trace_buffer, ZEND_JIT_TRACE_START_SIDE); + + if (stop == ZEND_JIT_TRACE_STOP_TOPLEVEL) { + /* op_array may be already deallocated */ + if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_ABORT) { + fprintf(stderr, "---- TRACE %d abort (%s)\n", + trace_num, + zend_jit_trace_stop_description[stop]); + } + goto blacklist; + } + + if (UNEXPECTED(ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_BYTECODE)) { + zend_jit_dump_trace(trace_buffer, NULL); + } + + if (ZEND_JIT_TRACE_STOP_OK(stop)) { + if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_STOP) { + if (stop == ZEND_JIT_TRACE_STOP_LINK) { + uint32_t link_to = zend_jit_find_trace(EG(current_execute_data)->opline->handler);; + fprintf(stderr, "---- TRACE %d stop (link to %d)\n", + trace_num, + link_to); + } else { + fprintf(stderr, "---- TRACE %d stop (%s)\n", + trace_num, + zend_jit_trace_stop_description[stop]); + } + } + if (EXPECTED(stop != ZEND_JIT_TRACE_STOP_LOOP)) { + stop = zend_jit_compile_side_trace(trace_buffer, parent_num, exit_num); + } else { + const zend_op_array *op_array = trace_buffer[0].op_array; + zend_jit_op_array_trace_extension *jit_extension = + (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array); + const zend_op *opline = trace_buffer[1].opline; + + stop = zend_jit_compile_root_trace(trace_buffer, opline, jit_extension->offset); + } + if (EXPECTED(ZEND_JIT_TRACE_STOP_DONE(stop))) { + if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_COMPILED) { + fprintf(stderr, "---- TRACE %d %s\n", + trace_num, + zend_jit_trace_stop_description[stop]); + } + } else { + goto abort; + } + } else { +abort: + if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_ABORT) { + fprintf(stderr, "---- TRACE %d abort (%s)\n", + trace_num, + zend_jit_trace_stop_description[stop]); + } +blacklist: + if (!ZEND_JIT_TRACE_STOP_MAY_RECOVER(stop) + || zend_jit_trace_exit_is_bad(parent_num, exit_num)) { + zend_jit_blacklist_trace_exit(parent_num, exit_num); + if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_BLACKLIST) { + fprintf(stderr, "---- EXIT %d/%d blacklisted\n", + parent_num, exit_num); + } + } + } + + if (ZCG(accel_directives).jit_debug & (ZEND_JIT_DEBUG_TRACE_STOP|ZEND_JIT_DEBUG_TRACE_ABORT|ZEND_JIT_DEBUG_TRACE_COMPILED|ZEND_JIT_DEBUG_TRACE_BLACKLIST)) { + fprintf(stderr, "\n"); + } + + return (stop == ZEND_JIT_TRACE_STOP_HALT) ? -1 : 0; +} + +int ZEND_FASTCALL zend_jit_trace_exit(uint32_t trace_num, uint32_t exit_num) +{ + zend_execute_data *execute_data = EG(current_execute_data); + const zend_op *opline; + zend_jit_trace_info *t = &zend_jit_traces[trace_num]; + + // TODO: Deoptimizatoion of VM stack state ??? + + /* Lock-free check if the side trace was already JIT-ed or blacklist-ed in another process */ + // TODO: We may remoive this, becaus of the same check in zend_jit_trace_hot_side() ??? + opline = t->exit_info[exit_num].opline; + if ((uintptr_t)opline & (ZEND_JIT_EXIT_JITED|ZEND_JIT_EXIT_BLACKLISTED)) { + opline = (const zend_op*)((uintptr_t)opline & ~(ZEND_JIT_EXIT_JITED|ZEND_JIT_EXIT_BLACKLISTED)); + if (opline) { + /* Set VM opline to continue interpretation */ + EX(opline) = opline; + } + return 0; + } + + if (opline) { + /* Set VM opline to continue interpretation */ + EX(opline) = opline; + } + + ZEND_ASSERT(EX(func)->type == ZEND_USER_FUNCTION); + ZEND_ASSERT(EX(opline) >= EX(func)->op_array.opcodes && + EX(opline) < EX(func)->op_array.opcodes + EX(func)->op_array.last); + + if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_EXIT) { + fprintf(stderr, " TRACE %d exit %d %s() %s:%d\n", + trace_num, + exit_num, + EX(func)->op_array.function_name ? + ZSTR_VAL(EX(func)->op_array.function_name) : "$main", + ZSTR_VAL(EX(func)->op_array.filename), + EX(opline)->lineno); + } + + if (zend_jit_trace_exit_is_hot(trace_num, exit_num)) { + return zend_jit_trace_hot_side(execute_data, trace_num, exit_num); + } + + return 0; +} + +static zend_always_inline uint8_t zend_jit_trace_supported(const zend_op *opline) +{ + switch (opline->opcode) { + case ZEND_CATCH: + case ZEND_FAST_CALL: + case ZEND_FAST_RET: + case ZEND_GENERATOR_CREATE: + case ZEND_GENERATOR_RETURN: + case ZEND_EXIT: + case ZEND_YIELD: + case ZEND_YIELD_FROM: + case ZEND_INCLUDE_OR_EVAL: + return ZEND_JIT_TRACE_UNSUPPORTED; + default: + return ZEND_JIT_TRACE_SUPPORTED; + } +} + +static int zend_jit_setup_hot_trace_counters(zend_op_array *op_array) +{ + zend_op *opline; + zend_jit_op_array_trace_extension *jit_extension; + zend_cfg cfg; + uint32_t i; + + ZEND_ASSERT(zend_jit_func_counter_handler != NULL); + ZEND_ASSERT(zend_jit_ret_counter_handler != NULL); + ZEND_ASSERT(zend_jit_loop_counter_handler != NULL); + ZEND_ASSERT(sizeof(zend_op_trace_info) == sizeof(zend_op)); + + if (zend_jit_build_cfg(op_array, &cfg) != SUCCESS) { + return FAILURE; + } + + jit_extension = (zend_jit_op_array_trace_extension*)zend_shared_alloc(sizeof(zend_jit_op_array_trace_extension) + (op_array->last - 1) * sizeof(zend_op_trace_info)); + memset(&jit_extension->func_info, 0, sizeof(jit_extension->func_info)); + jit_extension->func_info.num_args = -1; + jit_extension->func_info.return_value_used = -1; + jit_extension->offset = (char*)jit_extension->trace_info - (char*)op_array->opcodes; + for (i = 0; i < op_array->last; i++) { + jit_extension->trace_info[i].orig_handler = op_array->opcodes[i].handler; + jit_extension->trace_info[i].call_handler = zend_get_opcode_handler_func(&op_array->opcodes[i]); + jit_extension->trace_info[i].counter = NULL; + jit_extension->trace_info[i].trace_flags = + zend_jit_trace_supported(&op_array->opcodes[i]); + } + ZEND_SET_FUNC_INFO(op_array, (void*)jit_extension); + + 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 (!(ZEND_OP_TRACE_INFO(opline, jit_extension->offset)->trace_flags & ZEND_JIT_TRACE_UNSUPPORTED)) { + /* function entry */ + opline->handler = (const void*)zend_jit_func_counter_handler; + ZEND_OP_TRACE_INFO(opline, jit_extension->offset)->counter = + &zend_jit_hot_counters[ZEND_JIT_COUNTER_NUM]; + ZEND_JIT_COUNTER_NUM = (ZEND_JIT_COUNTER_NUM + 1) % ZEND_HOT_COUNTERS_COUNT; + ZEND_OP_TRACE_INFO(opline, jit_extension->offset)->trace_flags |= + ZEND_JIT_TRACE_START_ENTER; + } + + for (i = 0; i < cfg.blocks_count; i++) { + if (cfg.blocks[i].flags & ZEND_BB_REACHABLE) { + if (cfg.blocks[i].flags & ZEND_BB_ENTRY) { + /* continuation after return from function call */ + opline = op_array->opcodes + cfg.blocks[i].start; + if (!(ZEND_OP_TRACE_INFO(opline, jit_extension->offset)->trace_flags & ZEND_JIT_TRACE_UNSUPPORTED)) { + opline->handler = (const void*)zend_jit_ret_counter_handler; + if (!ZEND_OP_TRACE_INFO(opline, jit_extension->offset)->counter) { + ZEND_OP_TRACE_INFO(opline, jit_extension->offset)->counter = + &zend_jit_hot_counters[ZEND_JIT_COUNTER_NUM]; + ZEND_JIT_COUNTER_NUM = (ZEND_JIT_COUNTER_NUM + 1) % ZEND_HOT_COUNTERS_COUNT; + } + ZEND_OP_TRACE_INFO(opline, jit_extension->offset)->trace_flags |= + ZEND_JIT_TRACE_START_RETURN; + } + } + if (cfg.blocks[i].flags & ZEND_BB_LOOP_HEADER) { + /* loop header */ + opline = op_array->opcodes + cfg.blocks[i].start; + if (!(ZEND_OP_TRACE_INFO(opline, jit_extension->offset)->trace_flags & ZEND_JIT_TRACE_UNSUPPORTED)) { + opline->handler = (const void*)zend_jit_loop_counter_handler; + if (!ZEND_OP_TRACE_INFO(opline, jit_extension->offset)->counter) { + ZEND_OP_TRACE_INFO(opline, jit_extension->offset)->counter = + &zend_jit_hot_counters[ZEND_JIT_COUNTER_NUM]; + ZEND_JIT_COUNTER_NUM = (ZEND_JIT_COUNTER_NUM + 1) % ZEND_HOT_COUNTERS_COUNT; + } + ZEND_OP_TRACE_INFO(opline, jit_extension->offset)->trace_flags |= + ZEND_JIT_TRACE_START_LOOP; + } + } + } + } + + return SUCCESS; +} + +static void zend_jit_trace_init_caches(void) +{ + memset(JIT_G(bad_root_cache_opline), 0, sizeof(JIT_G(bad_root_cache_opline))); + memset(JIT_G(bad_root_cache_count), 0, sizeof(JIT_G(bad_root_cache_count))); + memset(JIT_G(bad_root_cache_stop), 0, sizeof(JIT_G(bad_root_cache_count))); + JIT_G(bad_root_slot) = 0; + + memset(JIT_G(exit_counters), 0, sizeof(JIT_G(exit_counters))); +} + +static void zend_jit_trace_reset_caches(void) +{ +} |