]> granicus.if.org Git - php/commitdiff
cleanup
authorDmitry Stogov <dmitry@zend.com>
Tue, 7 Apr 2020 18:30:47 +0000 (21:30 +0300)
committerDmitry Stogov <dmitry@zend.com>
Tue, 7 Apr 2020 18:30:47 +0000 (21:30 +0300)
ext/opcache/jit/zend_jit_trace.c

index 171ecd7d51b7f58866244fc00072c7b89294cd8e..9f17f681d687f59fe3a3366a82007f5645c5d2c4 100644 (file)
@@ -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 = &current->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;
+                                               }
+                                       }
+                               }
                        }
                }