mirror of
https://github.com/php/php-src.git
synced 2025-08-16 05:58:45 +02:00
Implement single-pass live range calculation
Instead of interleaving creation of live-ranges with the main compiler code, compute them in a separate pass over the opcodes as part of pass_two. Additionally, do not keep live ranges synchronized during optimization in opcache and instead use the same mechanism to recompute them after optimization.
This commit is contained in:
parent
276d3a7d15
commit
3269e88468
18 changed files with 301 additions and 655 deletions
|
@ -547,6 +547,123 @@ static uint32_t zend_get_brk_cont_target(const zend_op_array *op_array, const ze
|
|||
return opline->opcode == ZEND_BRK ? jmp_to->brk : jmp_to->cont;
|
||||
}
|
||||
|
||||
static void emit_live_range(
|
||||
zend_op_array *op_array, uint32_t var_num, uint32_t start, uint32_t end,
|
||||
zend_needs_live_range_cb needs_live_range) {
|
||||
zend_op *def_opline = &op_array->opcodes[start], *orig_def_opline = def_opline;
|
||||
zend_op *use_opline = &op_array->opcodes[end];
|
||||
zend_live_range *range;
|
||||
uint32_t kind = ZEND_LIVE_TMPVAR;
|
||||
|
||||
switch (def_opline->opcode) {
|
||||
/* These should never be the first def. */
|
||||
case ZEND_ADD_ARRAY_ELEMENT:
|
||||
case ZEND_ROPE_ADD:
|
||||
ZEND_ASSERT(0);
|
||||
return;
|
||||
/* Result is boolean, it doesn't have to be destroyed. */
|
||||
case ZEND_JMPZ_EX:
|
||||
case ZEND_JMPNZ_EX:
|
||||
case ZEND_BOOL:
|
||||
case ZEND_BOOL_NOT:
|
||||
/* Classes don't have to be destroyed. */
|
||||
case ZEND_FETCH_CLASS:
|
||||
case ZEND_DECLARE_ANON_CLASS:
|
||||
case ZEND_DECLARE_ANON_INHERITED_CLASS:
|
||||
/* FAST_CALLs don't have to be destroyed. */
|
||||
case ZEND_FAST_CALL:
|
||||
return;
|
||||
case ZEND_BEGIN_SILENCE:
|
||||
kind = ZEND_LIVE_SILENCE;
|
||||
break;
|
||||
case ZEND_ROPE_INIT:
|
||||
kind = ZEND_LIVE_ROPE;
|
||||
/* ROPE live ranges include the generating opcode. */
|
||||
def_opline--;
|
||||
break;
|
||||
case ZEND_FE_RESET_R:
|
||||
case ZEND_FE_RESET_RW:
|
||||
kind = ZEND_LIVE_LOOP;
|
||||
break;
|
||||
/* Objects created via ZEND_NEW are only fully initialized
|
||||
* after the DO_FCALL (constructor call). */
|
||||
case ZEND_NEW: {
|
||||
int level = 0;
|
||||
while (def_opline + 1 < use_opline) {
|
||||
def_opline++;
|
||||
if (def_opline->opcode == ZEND_DO_FCALL) {
|
||||
if (level == 0) {
|
||||
break;
|
||||
}
|
||||
level--;
|
||||
} else {
|
||||
switch (def_opline->opcode) {
|
||||
case ZEND_INIT_FCALL:
|
||||
case ZEND_INIT_FCALL_BY_NAME:
|
||||
case ZEND_INIT_NS_FCALL_BY_NAME:
|
||||
case ZEND_INIT_DYNAMIC_CALL:
|
||||
case ZEND_INIT_USER_CALL:
|
||||
case ZEND_INIT_METHOD_CALL:
|
||||
case ZEND_INIT_STATIC_METHOD_CALL:
|
||||
case ZEND_NEW:
|
||||
level++;
|
||||
break;
|
||||
case ZEND_DO_ICALL:
|
||||
case ZEND_DO_UCALL:
|
||||
case ZEND_DO_FCALL_BY_NAME:
|
||||
level--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
start = def_opline + 1 - op_array->opcodes;
|
||||
if (start == end) {
|
||||
/* Trivial live-range, no need to store it. */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check hook to determine whether a live range is necessary, e.g. based on type info. */
|
||||
if (needs_live_range && !needs_live_range(op_array, orig_def_opline)) {
|
||||
return;
|
||||
}
|
||||
|
||||
op_array->last_live_range++;
|
||||
op_array->live_range = erealloc(op_array->live_range,
|
||||
sizeof(zend_live_range) * op_array->last_live_range);
|
||||
|
||||
ZEND_ASSERT(start < end);
|
||||
range = &op_array->live_range[op_array->last_live_range - 1];
|
||||
range->var = (uint32_t) (intptr_t) ZEND_CALL_VAR_NUM(NULL, op_array->last_var + var_num);
|
||||
range->var |= kind;
|
||||
range->start = start;
|
||||
range->end = end;
|
||||
}
|
||||
|
||||
static zend_bool is_fake_def(zend_op *opline) {
|
||||
/* These opcodes only modify the result, not create it. */
|
||||
return opline->opcode == ZEND_ROPE_ADD
|
||||
|| opline->opcode == ZEND_ADD_ARRAY_ELEMENT;
|
||||
}
|
||||
|
||||
static zend_bool keeps_op1_alive(zend_op *opline) {
|
||||
/* These opcodes don't consume their OP1 operand,
|
||||
* it is later freed by something else. */
|
||||
return opline->opcode == ZEND_CASE
|
||||
|| opline->opcode == ZEND_SWITCH_LONG
|
||||
|| opline->opcode == ZEND_SWITCH_STRING
|
||||
|| opline->opcode == ZEND_FE_FETCH_R
|
||||
|| opline->opcode == ZEND_FE_FETCH_RW
|
||||
|| opline->opcode == ZEND_FETCH_LIST_R
|
||||
|| opline->opcode == ZEND_FETCH_LIST_W
|
||||
|| opline->opcode == ZEND_VERIFY_RETURN_TYPE
|
||||
|| opline->opcode == ZEND_BIND_LEXICAL
|
||||
|| opline->opcode == ZEND_ROPE_ADD;
|
||||
}
|
||||
|
||||
/* Live ranges must be sorted by increasing start opline */
|
||||
static int cmp_live_range(const zend_live_range *a, const zend_live_range *b) {
|
||||
return a->start - b->start;
|
||||
|
@ -556,9 +673,71 @@ static void swap_live_range(zend_live_range *a, zend_live_range *b) {
|
|||
*a = *b;
|
||||
*b = tmp;
|
||||
}
|
||||
static void zend_sort_live_ranges(zend_op_array *op_array) {
|
||||
zend_sort(op_array->live_range, op_array->last_live_range, sizeof(zend_live_range),
|
||||
(compare_func_t) cmp_live_range, (swap_func_t) swap_live_range);
|
||||
|
||||
static void zend_calc_live_ranges(
|
||||
zend_op_array *op_array, zend_needs_live_range_cb needs_live_range) {
|
||||
zend_op *opline = &op_array->opcodes[op_array->last - 1];
|
||||
zend_op *begin = op_array->opcodes;
|
||||
ALLOCA_FLAG(use_heap)
|
||||
uint32_t var_offset = op_array->last_var;
|
||||
uint32_t *last_use = do_alloca(sizeof(uint32_t) * op_array->T, use_heap);
|
||||
memset(last_use, -1, sizeof(uint32_t) * op_array->T);
|
||||
|
||||
ZEND_ASSERT(!op_array->live_range);
|
||||
for (; opline >= begin; opline--) {
|
||||
uint32_t opnum = opline - begin;
|
||||
|
||||
if (opline->opcode == ZEND_OP_DATA) {
|
||||
/* OP_DATA is really part of the previous opcode. */
|
||||
opnum--;
|
||||
}
|
||||
|
||||
if ((opline->result_type & (IS_TMP_VAR|IS_VAR)) && !is_fake_def(opline)) {
|
||||
uint32_t var_num = EX_VAR_TO_NUM(opline->result.var) - var_offset;
|
||||
/* Defs without uses can occur for two reasons: Either because the result is
|
||||
* genuinely unused (e.g. omitted FREE opcode for an unused boolean result), or
|
||||
* because there are multiple defining opcodes (e.g. JMPZ_EX and QM_ASSIGN), in
|
||||
* which case the last one starts the live range. As such, we can simply ignore
|
||||
* missing uses here. */
|
||||
if (last_use[var_num] != (uint32_t) -1) {
|
||||
emit_live_range(op_array, var_num, opnum, last_use[var_num], needs_live_range);
|
||||
last_use[var_num] = (uint32_t) -1;
|
||||
}
|
||||
}
|
||||
|
||||
if ((opline->op1_type & (IS_TMP_VAR|IS_VAR)) && !keeps_op1_alive(opline)) {
|
||||
uint32_t var_num = EX_VAR_TO_NUM(opline->op1.var) - var_offset;
|
||||
if (last_use[var_num] == (uint32_t) -1) {
|
||||
last_use[var_num] = opnum;
|
||||
}
|
||||
}
|
||||
if (opline->op2_type & (IS_TMP_VAR|IS_VAR)) {
|
||||
uint32_t var_num = EX_VAR_TO_NUM(opline->op2.var) - var_offset;
|
||||
if (last_use[var_num] == (uint32_t) -1) {
|
||||
last_use[var_num] = opnum;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (op_array->live_range) {
|
||||
zend_sort(op_array->live_range, op_array->last_live_range, sizeof(zend_live_range),
|
||||
(compare_func_t) cmp_live_range, (swap_func_t) swap_live_range);
|
||||
}
|
||||
|
||||
free_alloca(last_use, use_heap);
|
||||
}
|
||||
|
||||
ZEND_API void zend_recalc_live_ranges(
|
||||
zend_op_array *op_array, zend_needs_live_range_cb needs_live_range) {
|
||||
/* We assume that we never create live-ranges where there were none before. */
|
||||
if (!op_array->live_range) {
|
||||
return;
|
||||
}
|
||||
|
||||
efree(op_array->live_range);
|
||||
op_array->live_range = NULL;
|
||||
op_array->last_live_range = 0;
|
||||
zend_calc_live_ranges(op_array, needs_live_range);
|
||||
}
|
||||
|
||||
ZEND_API int pass_two(zend_op_array *op_array)
|
||||
|
@ -726,16 +905,7 @@ ZEND_API int pass_two(zend_op_array *op_array)
|
|||
opline++;
|
||||
}
|
||||
|
||||
if (op_array->live_range) {
|
||||
int i;
|
||||
|
||||
zend_sort_live_ranges(op_array);
|
||||
for (i = 0; i < op_array->last_live_range; i++) {
|
||||
op_array->live_range[i].var =
|
||||
(uint32_t)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL, op_array->last_var + (op_array->live_range[i].var / sizeof(zval))) |
|
||||
(op_array->live_range[i].var & ZEND_LIVE_MASK);
|
||||
}
|
||||
}
|
||||
zend_calc_live_ranges(op_array, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue