From: Dmitry Stogov Date: Tue, 7 Apr 2020 18:30:47 +0000 (+0300) Subject: cleanup X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=5db5f71f2831df4d32484a5638f6f6aa72e364a5;p=php cleanup --- diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index 171ecd7d51..9f17f681d6 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -803,6 +803,11 @@ static int is_checked_guard(const zend_ssa *tssa, const zend_op **ssa_opcodes, u return 0; } +typedef struct _zend_tssa { + zend_ssa ssa; + const zend_op **tssa_opcodes; +} zend_tssa; + 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; @@ -913,7 +918,7 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin *num_op_arrays_ptr = num_op_arrays; /* 2. Construct TSSA */ - tssa = zend_arena_calloc(&CG(arena), 1, sizeof(zend_ssa)); + tssa = zend_arena_calloc(&CG(arena), 1, sizeof(zend_tssa)); 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)); @@ -921,6 +926,7 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin 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); + ((zend_tssa*)tssa)->tssa_opcodes = ssa_opcodes; if (trace_buffer->stop == ZEND_JIT_TRACE_STOP_LOOP) { tssa->cfg.blocks_count = 2; @@ -1678,6 +1684,784 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin return tssa; } +static int zend_jit_trace_try_allocate_free_reg(zend_jit_trace_rec *trace_buffer, const zend_op **ssa_opcodes, zend_ssa *ssa, zend_lifetime_interval *current, zend_regset available, zend_regset *hints, zend_lifetime_interval *active, zend_lifetime_interval *inactive, zend_lifetime_interval **list, zend_lifetime_interval **free) +{ + zend_lifetime_interval *it; + uint32_t freeUntilPos[ZREG_NUM]; + uint32_t pos, pos2; + zend_reg i, reg, reg2; + zend_reg hint = ZREG_NONE; + zend_regset low_priority_regs; + zend_life_range *range; + + if ((ssa->var_info[current->ssa_var].type & MAY_BE_ANY) == MAY_BE_DOUBLE) { + available = ZEND_REGSET_INTERSECTION(available, ZEND_REGSET_FP); + } else { + available = ZEND_REGSET_INTERSECTION(available, ZEND_REGSET_GP); + } + + /* TODO: Allow usage of preserved registers ??? + * Their values have to be stored in prologuee and restored in epilogue + */ + available = ZEND_REGSET_DIFFERENCE(available, ZEND_REGSET_PRESERVED); + + if (ZEND_REGSET_IS_EMPTY(available)) { + return 0; + } + + /* Set freeUntilPos of all physical registers to maxInt */ + for (i = 0; i < ZREG_NUM; i++) { + freeUntilPos[i] = 0xffffffff; + } + + /* for each interval it in active do */ + /* freeUntilPos[it.reg] = 0 */ + it = active; + if (ssa->vars[current->ssa_var].definition == current->range.start) { + while (it) { + if (current->range.start != zend_interval_end(it)) { + freeUntilPos[it->reg] = 0; + } else if (zend_jit_may_reuse_reg(ssa_opcodes[current->range.start], ssa->ops + current->range.start, ssa, current->ssa_var, it->ssa_var)) { + if (!ZEND_REGSET_IN(*hints, it->reg) && + /* TODO: Avoid most often scratch registers. Find a better way ??? */ + (!current->used_as_hint || + (it->reg != ZREG_R0 && it->reg != ZREG_R1 && it->reg != ZREG_XMM0 && it->reg != ZREG_XMM1))) { + hint = it->reg; + } + } else { + freeUntilPos[it->reg] = 0; + } + it = it->list_next; + } + } else { + while (it) { + freeUntilPos[it->reg] = 0; + it = it->list_next; + } + } + if (current->hint) { + hint = current->hint->reg; + if (hint != ZREG_NONE && current->hint->used_as_hint == current) { + ZEND_REGSET_EXCL(*hints, hint); + } + } + + /* See "Linear Scan Register Allocation on SSA Form", Christian Wimmer and + Michael Franz, CGO'10 (2010), Figure 6. */ + if (current->split) { + /* for each interval it in inactive intersecting with current do */ + /* freeUntilPos[it.reg] = next intersection of it with current */ + it = inactive; + while (it) { + uint32_t next = zend_interval_intersection(current, it); + + //ZEND_ASSERT(next != 0xffffffff && !current->split); + if (next < freeUntilPos[it->reg]) { + freeUntilPos[it->reg] = next; + } + it = it->list_next; + } + } + + /* Handle Scratch Registers */ + /* TODO: Optimize ??? */ + range = ¤t->range; + do { + uint32_t line = range->start; + zend_regset regset; + zend_reg reg; + + if (ssa->ops[line].op1_def == current->ssa_var || + ssa->ops[line].op2_def == current->ssa_var || + ssa->ops[line].result_def == current->ssa_var) { + line++; + } + while (line <= range->end) { + regset = zend_jit_get_scratch_regset( + ssa_opcodes[line], + ssa->ops + line, + // TODO: Support for nested call frames ??? + trace_buffer->op_array, ssa, current->ssa_var); + ZEND_REGSET_FOREACH(regset, reg) { + if (line < freeUntilPos[reg]) { + freeUntilPos[reg] = line; + } + } ZEND_REGSET_FOREACH_END(); + line++; + } + range = range->next; + } while (range); + +#if 0 + /* Coalesing */ + if (ssa->vars[current->ssa_var].definition == current->start) { + zend_op *opline = op_array->opcodes + current->start; + int hint = -1; + + switch (opline->opcode) { + case ZEND_ASSIGN: + hint = ssa->ops[current->start].op2_use; + case ZEND_QM_ASSIGN: + hint = ssa->ops[current->start].op1_use; + break; + case ZEND_ADD: + case ZEND_SUB: + case ZEND_MUL: + hint = ssa->ops[current->start].op1_use; + break; + case ZEND_ASSIGN_OP: + if (opline->extended_value == ZEND_ADD + || opline->extended_value == ZEND_SUB + || opline->extended_value == ZEND_MUL) { + hint = ssa->ops[current->start].op1_use; + } + break; + } + if (hint >= 0) { + } + } +#endif + + if (hint != ZREG_NONE && freeUntilPos[hint] > zend_interval_end(current)) { + current->reg = hint; + if (current->used_as_hint) { + ZEND_REGSET_INCL(*hints, hint); + } + return 1; + } + + pos = 0; reg = ZREG_NONE; + pos2 = 0; reg2 = ZREG_NONE; + low_priority_regs = *hints; + if (current->used_as_hint) { + /* TODO: Avoid most often scratch registers. Find a better way ??? */ + ZEND_REGSET_INCL(low_priority_regs, ZREG_R0); + ZEND_REGSET_INCL(low_priority_regs, ZREG_R1); + ZEND_REGSET_INCL(low_priority_regs, ZREG_XMM0); + ZEND_REGSET_INCL(low_priority_regs, ZREG_XMM1); + } + + ZEND_REGSET_FOREACH(available, i) { + if (ZEND_REGSET_IN(low_priority_regs, i)) { + if (freeUntilPos[i] > pos2) { + reg2 = i; + pos2 = freeUntilPos[i]; + } + } else if (freeUntilPos[i] > pos) { + reg = i; + pos = freeUntilPos[i]; + } + } ZEND_REGSET_FOREACH_END(); + + if (reg == ZREG_NONE) { + if (reg2 != ZREG_NONE) { + reg = reg2; + pos = pos2; + reg2 = ZREG_NONE; + } + } + + if (reg == ZREG_NONE) { + /* no register available without spilling */ + return 0; + } else if (zend_interval_end(current) < pos) { + /* register available for the whole interval */ + current->reg = reg; + if (current->used_as_hint) { + ZEND_REGSET_INCL(*hints, reg); + } + return 1; +#if 0 + // TODO: allow low prioirity register usage + } else if (reg2 != ZREG_NONE && zend_interval_end(current) < pos2) { + /* register available for the whole interval */ + current->reg = reg2; + if (current->used_as_hint) { + ZEND_REGSET_INCL(*hints, reg2); + } + return 1; +#endif + } else { + /* TODO: enable interval splitting ??? */ + /* register available for the first part of the interval */ + if (1 || zend_jit_split_interval(current, pos, list, free) != SUCCESS) { + return 0; + } + current->reg = reg; + if (current->used_as_hint) { + ZEND_REGSET_INCL(*hints, reg); + } + return 1; + } +} + +static zend_lifetime_interval* zend_jit_trace_linear_scan(zend_jit_trace_rec *trace_buffer, const zend_op **ssa_opcodes, zend_ssa *ssa, zend_lifetime_interval *list) +{ + zend_lifetime_interval *unhandled, *active, *inactive, *handled, *free; + zend_lifetime_interval *current, **p, *q; + uint32_t position; + zend_regset available = ZEND_REGSET_UNION(ZEND_REGSET_GP, ZEND_REGSET_FP); + zend_regset hints = ZEND_REGSET_EMPTY; + + unhandled = list; + /* active = inactive = handled = free = {} */ + active = inactive = handled = free = NULL; + while (unhandled != NULL) { + current = unhandled; + unhandled = unhandled->list_next; + position = current->range.start; + + p = &active; + while (*p) { + uint32_t end = zend_interval_end(*p); + + q = *p; + if (end < position) { + /* move ival from active to handled */ + ZEND_REGSET_INCL(available, q->reg); + *p = q->list_next; + q->list_next = handled; + handled = q; + } else if (!zend_interval_covers(q, position)) { + /* move ival from active to inactive */ + ZEND_REGSET_INCL(available, q->reg); + *p = q->list_next; + q->list_next = inactive; + inactive = q; + } else { + p = &q->list_next; + } + } + + p = &inactive; + while (*p) { + uint32_t end = zend_interval_end(*p); + + q = *p; + if (end < position) { + /* move ival from inactive to handled */ + *p = q->list_next; + q->list_next = handled; + handled = q; + } else if (zend_interval_covers(q, position)) { + /* move ival from inactive to active */ + ZEND_REGSET_EXCL(available, q->reg); + *p = q->list_next; + q->list_next = active; + active = q; + } else { + p = &q->list_next; + } + } + + if (zend_jit_trace_try_allocate_free_reg(trace_buffer, ssa_opcodes, ssa, current, available, &hints, active, inactive, &unhandled, &free) || + zend_jit_allocate_blocked_reg()) { + ZEND_REGSET_EXCL(available, current->reg); + current->list_next = active; + active = current; + } else { + current->list_next = free; + free = current; + } + } + + /* move active to handled */ + while (active) { + current = active; + active = active->list_next; + current->list_next = handled; + handled = current; + } + + /* move inactive to handled */ + while (inactive) { + current = inactive; + inactive = inactive->list_next; + current->list_next = handled; + handled = current; + } + + return handled; +} + +static zend_lifetime_interval** zend_jit_trace_allocate_registers(zend_jit_trace_rec *trace_buffer, zend_ssa *ssa) +{ + const zend_op **ssa_opcodes = ((zend_tssa*)ssa)->tssa_opcodes; + zend_jit_trace_rec *p; + const zend_op_array *op_array; + const zend_ssa_op *ssa_op; + int i, j, idx, count, level; + int *start, *end; + zend_lifetime_interval **intervals, *list; + void *checkpoint; + ALLOCA_FLAG(use_heap); + + ZEND_ASSERT(ssa->var_info != NULL); + + start =do_alloca(sizeof(int) * ssa->vars_count * 2, use_heap); + if (!start) { + return NULL; + } + end = start + ssa->vars_count; + + memset(start, -1, sizeof(int) * ssa->vars_count * 2); + + op_array = trace_buffer->op_array; + count = 0; + + i = 0; + j = op_array->last_var; + if (trace_buffer->start != ZEND_JIT_TRACE_START_ENTER) { + j += op_array->T; + } + while (i < j) { + /* We don't start intervals for variables used in Phi */ + if ((ssa->vars[i].use_chain >= 0 /*|| ssa->vars[i].phi_use_chain*/) + && zend_jit_var_supports_reg(ssa, i)) { + start[i] = 0; + count++; + } + i++; + } + + if (trace_buffer->stop == ZEND_JIT_TRACE_STOP_LOOP) { + zend_ssa_phi *phi = ssa->blocks[1].phis; + + while (phi) { + if (ssa->vars[phi->ssa_var].use_chain >= 0 + && zend_jit_var_supports_reg(ssa, phi->ssa_var)) { + start[phi->ssa_var] = 0; + count++; + } + phi = phi->next; + } + } + + p = trace_buffer + ZEND_JIT_TRACE_START_REC_SIZE; + level = 0; + ssa_op = ssa->ops; + idx = 0; + for (;;p++) { + if (p->op == ZEND_JIT_TRACE_VM) { + const zend_op *opline = p->opline; + int len; + zend_bool support_opline; + + support_opline = + zend_jit_opline_supports_reg(op_array, ssa, opline, ssa_op); + if (support_opline) { + if (ssa_op->op1_use >= 0 + && start[ssa_op->op1_use] >= 0 + && !zend_ssa_is_no_val_use(opline, ssa_op, ssa_op->op1_use)) { + end[ssa_op->op1_use] = idx; + } + if (ssa_op->op2_use >= 0 + && start[ssa_op->op2_use] >= 0 + && !zend_ssa_is_no_val_use(opline, ssa_op, ssa_op->op2_use)) { + end[ssa_op->op2_use] = idx; + } + if (ssa_op->result_use >= 0 + && start[ssa_op->result_use] >= 0 + && !zend_ssa_is_no_val_use(opline, ssa_op, ssa_op->result_use)) { + end[ssa_op->result_use] = idx; + } + if (ssa_op->result_def >= 0 + && (ssa->vars[ssa_op->result_def].use_chain >= 0 + || ssa->vars[ssa_op->result_def].phi_use_chain) + && zend_jit_var_supports_reg(ssa, ssa_op->result_def)) { + start[ssa_op->result_def] = idx; + count++; + } + if (ssa_op->op1_def >= 0 + && (ssa->vars[ssa_op->op1_def].use_chain >= 0 + || ssa->vars[ssa_op->op1_def].phi_use_chain) + && zend_jit_var_supports_reg(ssa, ssa_op->op1_def)) { + start[ssa_op->op1_def] = idx; + count++; + } + if (ssa_op->op2_def >= 0 + && (ssa->vars[ssa_op->op2_def].use_chain >= 0 + || ssa->vars[ssa_op->op2_def].phi_use_chain) + && zend_jit_var_supports_reg(ssa, ssa_op->op2_def)) { + start[ssa_op->op2_def] = idx; + count++; + } + } else { + if (ssa_op->op1_use >= 0 + && start[ssa_op->op1_use] >= 0 + && !zend_ssa_is_no_val_use(opline, ssa_op, ssa_op->op1_use)) { + start[ssa_op->op1_use] = -1; + end[ssa_op->op1_use] = -1; + count--; + } + if (ssa_op->op2_use >= 0 + && start[ssa_op->op2_use] >= 0 + && !zend_ssa_is_no_val_use(opline, ssa_op, ssa_op->op2_use)) { + start[ssa_op->op2_use] = -1; + end[ssa_op->op2_use] = -1; + count--; + } + if (ssa_op->result_use >= 0 + && start[ssa_op->result_use] >= 0 + && !zend_ssa_is_no_val_use(opline, ssa_op, ssa_op->result_use)) { + start[ssa_op->result_use] = -1; + end[ssa_op->result_use] = -1; + count--; + } + } + + len = zend_jit_trace_op_len(opline); + 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 (support_opline) { + if (ssa_op->op1_use >= 0 + && start[ssa_op->op1_use] >= 0 + && !zend_ssa_is_no_val_use(opline, ssa_op, ssa_op->op1_use)) { + end[ssa_op->op1_use] = idx; + } + if (ssa_op->op1_def >= 0 + && (ssa->vars[ssa_op->op1_def].use_chain >= 0 + || ssa->vars[ssa_op->op1_def].phi_use_chain) + && zend_jit_var_supports_reg(ssa, ssa_op->op1_def)) { + start[ssa_op->op1_def] = idx; + count++; + } + } else { + if (ssa_op->op1_use >= 0 + && start[ssa_op->op1_use] >= 0 + && !zend_ssa_is_no_val_use(opline, ssa_op, ssa_op->op1_use)) { + start[ssa_op->op1_use] = -1; + end[ssa_op->op1_use] = -1; + count--; + } + } + ssa_op++; + opline++; + idx+=2; + break; + case ZEND_RECV_INIT: + ssa_op++; + opline++; + idx++; + while (opline->opcode == ZEND_RECV_INIT) { + /* RECV_INIT doesn't support registers */ + ssa_op++; + opline++; + idx++; + } + break; + case ZEND_BIND_GLOBAL: + ssa_op++; + opline++; + idx++; + while (opline->opcode == ZEND_BIND_GLOBAL) { + /* BIND_GLOBAL doesn't support registers */ + ssa_op++; + opline++; + idx++; + } + break; + default: + ssa_op += len; + idx += len; + break; + } + } else if (p->op == ZEND_JIT_TRACE_ENTER) { + op_array = p->op_array; + /* New call frames */ + i = op_array->last_var; + j = p->first_ssa_var; + while (i) { + if (ssa->vars[j].use_chain >= 0 + && zend_jit_var_supports_reg(ssa, j)) { + start[j] = idx; + count++; + } + j++; + i--; + } + level++; + } else if (p->op == ZEND_JIT_TRACE_BACK) { + // TODO: Close exiting call frames ??? + op_array = p->op_array; + if (level == 0) { + /* New return frames */ + i = op_array->last_var + op_array->T; + j = p->first_ssa_var; + while (i) { + if (ssa->vars[j].use_chain >= 0 + && zend_jit_var_supports_reg(ssa, j)) { + start[j] = idx; + count++; + } + j++; + i--; + } + } else { + level--; + } + } else if (p->op == ZEND_JIT_TRACE_END) { + break; + } + } + + if (trace_buffer->stop == ZEND_JIT_TRACE_STOP_LOOP) { + zend_ssa_phi *phi = ssa->blocks[1].phis; + + while (phi) { + if (start[phi->sources[1]] >= 0) { + end[phi->sources[1]] = idx - 1; + } + phi = phi->next; + } + } + + if (!count) { + free_alloca(start, use_heap); + return NULL; + } + + checkpoint = zend_arena_checkpoint(CG(arena)); + intervals = zend_arena_calloc(&CG(arena), ssa->vars_count, sizeof(zend_lifetime_interval)); + memset(intervals, 0, sizeof(zend_lifetime_interval*) * ssa->vars_count); + list = zend_arena_alloc(&CG(arena), sizeof(zend_lifetime_interval) * count); + j = 0; +//fprintf(stderr, "(%d)\n", count); + for (i = 0; i < ssa->vars_count; i++) { + if (start[i] >= 0) { + if (end[i] < 0) { + // TODO: ??? + end[i] = idx; + // continue; + } +//fprintf(stderr, "#%d: %d..%d\n", i, start[i], end[i]); + ZEND_ASSERT(j < count); + intervals[i] = &list[j]; + list[j].ssa_var = i; + list[j].reg = ZREG_NONE; + list[j].split = 0; + list[j].store = 0; + list[j].load = 0; + list[j].range.start = start[i]; + list[j].range.end = end[i]; + list[j].range.next = NULL; + list[j].hint = NULL; + list[j].used_as_hint = NULL; + list[j].list_next = NULL; + j++; + } + } + ZEND_ASSERT(j == count); + free_alloca(start, use_heap); + start = end = NULL; + + /* Add hints */ + if (trace_buffer->stop == ZEND_JIT_TRACE_STOP_LOOP) { + zend_ssa_phi *phi = ssa->blocks[1].phis; + + while (phi) { + if (intervals[phi->ssa_var]) { + if (intervals[phi->sources[1]]) { + intervals[phi->sources[1]]->hint = intervals[phi->ssa_var]; + } + } + phi = phi->next; + } + } + + for (i = 0; i < ssa->vars_count; i++) { + if (intervals[i] && !intervals[i]->hint) { + + if (ssa->vars[i].definition >= 0) { + uint32_t line = ssa->vars[i].definition; + const zend_op *opline = ssa_opcodes[line]; + + switch (opline->opcode) { + case ZEND_QM_ASSIGN: + case ZEND_POST_INC: + case ZEND_POST_DEC: + if (ssa->ops[line].op1_use >= 0 && + intervals[ssa->ops[line].op1_use] && + (i == ssa->ops[line].op1_def || + (i == ssa->ops[line].result_def && + (ssa->ops[line].op1_def < 0 || + !intervals[ssa->ops[line].op1_def])))) { + zend_jit_add_hint(intervals, i, ssa->ops[line].op1_use); + } + break; + case ZEND_SEND_VAR: + case ZEND_PRE_INC: + case ZEND_PRE_DEC: + if (i == ssa->ops[line].op1_def && + ssa->ops[line].op1_use >= 0 && + intervals[ssa->ops[line].op1_use]) { + zend_jit_add_hint(intervals, i, ssa->ops[line].op1_use); + } + break; + case ZEND_ASSIGN: + if (ssa->ops[line].op2_use >= 0 && + intervals[ssa->ops[line].op2_use] && + (i == ssa->ops[line].op2_def || + (i == ssa->ops[line].op1_def && + (ssa->ops[line].op2_def < 0 || + !intervals[ssa->ops[line].op2_def])) || + (i == ssa->ops[line].result_def && + (ssa->ops[line].op2_def < 0 || + !intervals[ssa->ops[line].op2_def]) && + (ssa->ops[line].op1_def < 0 || + !intervals[ssa->ops[line].op1_def])))) { + zend_jit_add_hint(intervals, i, ssa->ops[line].op2_use); + } + break; + } + } + } + } + + list = zend_jit_sort_intervals(intervals, ssa->vars_count); + + if (list) { + zend_lifetime_interval *ival = list; + while (ival) { + if (ival->hint) { + ival->hint->used_as_hint = ival; + } + ival = ival->list_next; + } + + if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_REG_ALLOC) { + // TODO: Support for nested call frames ??? + op_array = trace_buffer->op_array; + ival = list; + while (ival) { + zend_life_range *range; + int var_num = ssa->vars[ival->ssa_var].var; + + fprintf(stderr, "#%d.", ival->ssa_var); + // TODO: Support for nested call frames ??? + zend_dump_var(op_array, (var_num < op_array->last_var ? IS_CV : 0), var_num); + fprintf(stderr, ": %u-%u", ival->range.start, ival->range.end); + range = ival->range.next; + while (range) { + fprintf(stderr, ", %u-%u", range->start, range->end); + range = range->next; + } + if (ival->load) { + fprintf(stderr, " load"); + } + if (ival->store) { + fprintf(stderr, " store"); + } + if (ival->hint) { + var_num = ssa->vars[ival->hint->ssa_var].var; + fprintf(stderr, " hint=#%d.", ival->hint->ssa_var); + // TODO: Support for nested call frames ??? + zend_dump_var(op_array, (var_num < op_array->last_var ? IS_CV : 0), var_num); + } + fprintf(stderr, "\n"); + ival = ival->list_next; + } + fprintf(stderr, "\n"); + } + } + + /* Linear Scan Register Allocation */ + list = zend_jit_trace_linear_scan(trace_buffer, ssa_opcodes, ssa, list); + + if (list) { + zend_lifetime_interval *ival, *next; + + memset(intervals, 0, ssa->vars_count * sizeof(zend_lifetime_interval*)); + ival = list; + while (ival != NULL) { + ZEND_ASSERT(ival->reg != ZREG_NONE); + next = ival->list_next; + ival->list_next = intervals[ival->ssa_var]; + intervals[ival->ssa_var] = ival; + ival = next; + } + + /* SSA resolution */ + if (trace_buffer->stop == ZEND_JIT_TRACE_STOP_LOOP) { + zend_ssa_phi *phi = ssa->blocks[1].phis; + + while (phi) { + int def = phi->ssa_var; + int use = phi->sources[1]; + + if (intervals[def]) { + if (!intervals[use]) { + intervals[def]->load = 1; + } else if (intervals[def]->reg != intervals[use]->reg) { + intervals[def]->load = 1; + intervals[use]->store = 1; + } + } else if (intervals[use]) { + intervals[use]->store = 1; + } + phi = phi->next; + } + } + + // Remove useless register allocation ??? + // Remove intervals used once ??? + + if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_REG_ALLOC) { + // TODO: Support for nested call frames ??? + fprintf(stderr, "---- TRACE %d Allocated Live Ranges\n", ZEND_JIT_TRACE_NUM); + for (i = 0; i < ssa->vars_count; i++) { + ival = intervals[i]; + while (ival) { + zend_life_range *range; + int var_num = ssa->vars[ival->ssa_var].var; + + fprintf(stderr, "#%d.", ival->ssa_var); + zend_dump_var(op_array, (var_num < op_array->last_var ? IS_CV : 0), var_num); + fprintf(stderr, ": %u-%u", ival->range.start, ival->range.end); + range = ival->range.next; + while (range) { + fprintf(stderr, ", %u-%u", range->start, range->end); + range = range->next; + } + fprintf(stderr, " (%s)", zend_reg_name[ival->reg]); + if (ival->load) { + fprintf(stderr, " load"); + } + if (ival->store) { + fprintf(stderr, " store"); + } + if (ival->hint) { + var_num = ssa->vars[ival->hint->ssa_var].var; + fprintf(stderr, " hint=#%d.", ival->hint->ssa_var); + zend_dump_var(op_array, (var_num < op_array->last_var ? IS_CV : 0), var_num); + if (ival->hint->reg != ZREG_NONE) { + fprintf(stderr, " (%s)", zend_reg_name[ival->hint->reg]); + } + } + fprintf(stderr, "\n"); + ival = ival->list_next; + } + } + fprintf(stderr, "\n"); + } + + return intervals; + } + + zend_arena_release(&CG(arena), checkpoint); //??? + return NULL; +} + static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t parent_trace, uint32_t exit_num) { const void *handler = NULL; @@ -1711,6 +2495,11 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par ssa = zend_jit_trace_build_tssa(trace_buffer, parent_trace, exit_num, script, op_arrays, &num_op_arrays); + /* Register allocation */ + if (zend_jit_reg_alloc) { + ra = zend_jit_trace_allocate_registers(trace_buffer, ssa); + } + p = trace_buffer; ZEND_ASSERT(p->op == ZEND_JIT_TRACE_START); op_array = p->op_array; @@ -1772,8 +2561,6 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par (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 label for loop */ zend_jit_align_func(&dasm_state); @@ -1817,6 +2604,14 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par SET_STACK_TYPE(stack, i, concrete_type(info)); } } + if (ra) { + zend_ssa_phi *phi = ssa->vars[i].phi_use_chain; + if (phi && ra[phi->ssa_var] && !ra[phi->ssa_var]->load) { + if (!zend_jit_load_var(&dasm_state, ssa->var_info[i].type, i, ra[phi->ssa_var]->reg)) { + goto jit_failure; + } + } + } } }