diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 5c0e5954f3d..df89a42d60d 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -842,40 +842,63 @@ int pass_two_wrapper(zval *el) return pass_two((zend_op_array *) Z_PTR_P(el)); } -typedef struct _var_live_info { - struct _var_live_info *next; - uint32_t start; - uint32_t end; -} var_live_info; - +/* The following liveliness analyzing algorithm assumes that + * 1) temporary variables are defined before use + * 2) they have linear live-ranges without "holes" + * 3) Opcodes never use and define the same temorary variables + */ typedef struct _op_var_info { struct _op_var_info *next; uint32_t var; } op_var_info; -/* The following liveliness analyzing algorithm assumes that - * 1) temporary variables are defined before use - * 2) they have linear live-ranges without "holes" - */ +static zend_always_inline uint32_t liveliness_kill_var(zend_op_array *op_array, zend_op *cur_op, uint32_t var, uint32_t *Tstart, op_var_info **opTs) +{ + uint32_t start = Tstart[var]; + uint32_t end = cur_op - op_array->opcodes; + uint32_t count = 0; + uint32_t var_offset, j; + + Tstart[var] = -1; + if (cur_op->opcode == ZEND_OP_DATA) { + end--; + } + start++; + if (op_array->opcodes[start].opcode == ZEND_OP_DATA + || op_array->opcodes[start].opcode == ZEND_FE_FETCH_R + || op_array->opcodes[start].opcode == ZEND_FE_FETCH_RW) { + start++; + } + if (start < end) { + var_offset = (uint32_t)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL, op_array->last_var + var); + if (op_array->opcodes[end].opcode == ZEND_ROPE_END) { + var_offset |= ZEND_LIVE_ROPE; + } else if (op_array->opcodes[end].opcode == ZEND_END_SILENCE) { + var_offset |= ZEND_LIVE_SILENCE; + } else if (op_array->opcodes[end].opcode == ZEND_FE_FREE) { + var_offset |= ZEND_LIVE_LOOP; + } + for (j = start; j < end; j++) { + op_var_info *newOpTs = zend_arena_alloc(&CG(arena), sizeof(op_var_info)); + newOpTs->next = opTs[j]; + newOpTs->var = var_offset; + opTs[j] = newOpTs; + count++; + } + } + return count; +} + ZEND_API uint32_t *generate_var_liveliness_info(zend_op_array *op_array) { uint32_t i, op_live_total = 0; void *checkpoint = zend_arena_checkpoint(CG(arena)); uint32_t *info, info_off = op_array->last + 1; - var_live_info **Ts = zend_arena_alloc(&CG(arena), sizeof(var_live_info *) * op_array->T); + uint32_t *Tstart = zend_arena_alloc(&CG(arena), sizeof(uint32_t) * op_array->T); op_var_info **opTs = zend_arena_alloc(&CG(arena), sizeof(op_var_info *) * (op_array->last + 1)); - for (i = 0; i < op_array->T; i++) { - Ts[i] = zend_arena_alloc(&CG(arena), sizeof(var_live_info)); - Ts[i]->next = NULL; - Ts[i]->start = Ts[i]->end = -1; - } - - for (i = 0; i <= op_array->last; i++) { - opTs[i] = zend_arena_alloc(&CG(arena), sizeof(op_var_info)); - opTs[i]->var = -1; - opTs[i]->next = NULL; - } + memset(Tstart, -1, sizeof(uint32_t) * op_array->T); + memset(opTs, 0, sizeof(op_var_info *) * (op_array->last + 1)); zend_op *end_op = op_array->opcodes + op_array->last; zend_op *cur_op = op_array->opcodes; @@ -885,112 +908,61 @@ ZEND_API uint32_t *generate_var_liveliness_info(zend_op_array *op_array) && cur_op->opcode != ZEND_BOOL /* the following opcodes are used in inline branching (and anyway always bool, so no need to free) and may not be defined depending on the taken branch */ && cur_op->opcode != ZEND_JMPZ_EX && cur_op->opcode != ZEND_JMPNZ_EX - && (cur_op->opcode != ZEND_QM_ASSIGN || (cur_op + 1)->opcode != ZEND_JMP)) { /* these two consecutive ops appear on ternary; the result of true branch is undefined for false branch */ - var_live_info *T = Ts[cur_op->result.var]; - if (T->end != (uint32_t) -1) { - Ts[cur_op->result.var] = zend_arena_alloc(&CG(arena), sizeof(var_live_info)); - Ts[cur_op->result.var]->next = T; - T = Ts[cur_op->result.var]; - T->start = T->end = -1; - } - if (T->start == -1 /* we need the exceptions *here* and not around the whole branch, else temporary ranges may be accidentally reused. If any of these opcodes reuses a temporary, we must mark the previous range as definitely terminated first */ - && cur_op->opcode != ZEND_CASE /* exception for opcache, it might nowhere use the temporary (anyway bool, so no need to free) */ - && cur_op->opcode != ZEND_ROPE_ADD /* the following opcodes reuse TMP created before */ - && cur_op->opcode != ZEND_ADD_ARRAY_ELEMENT - && cur_op->opcode != ZEND_FAST_CALL /* passes fast_call */ - && cur_op->opcode != ZEND_FETCH_CLASS /* the following opcodes pass class_entry */ - && cur_op->opcode != ZEND_DECLARE_CLASS - && cur_op->opcode != ZEND_DECLARE_INHERITED_CLASS - && cur_op->opcode != ZEND_DECLARE_INHERITED_CLASS_DELAYED - && cur_op->opcode != ZEND_DECLARE_ANON_CLASS - && cur_op->opcode != ZEND_DECLARE_ANON_INHERITED_CLASS) { - /* Objects created via ZEND_NEW are only fully initialized after the DO_FCALL (constructor call) */ - if (cur_op->opcode == ZEND_NEW) { - T->start = cur_op->op2.opline_num - 1; - } else { - T->start = cur_op - op_array->opcodes; - } + && (cur_op->opcode != ZEND_QM_ASSIGN || (cur_op + 1)->opcode != ZEND_JMP) /* these two consecutive ops appear on ternary; the result of true branch is undefined for false branch */ + && cur_op->opcode != ZEND_CASE /* exception for opcache, it might nowhere use the temporary (anyway bool, so no need to free) */ + && cur_op->opcode != ZEND_ROPE_ADD /* the following opcodes reuse TMP created before */ + && cur_op->opcode != ZEND_ADD_ARRAY_ELEMENT + && cur_op->opcode != ZEND_FAST_CALL /* passes fast_call */ + && cur_op->opcode != ZEND_FETCH_CLASS /* the following opcodes pass class_entry */ + && cur_op->opcode != ZEND_DECLARE_CLASS + && cur_op->opcode != ZEND_DECLARE_INHERITED_CLASS + && cur_op->opcode != ZEND_DECLARE_INHERITED_CLASS_DELAYED + && cur_op->opcode != ZEND_DECLARE_ANON_CLASS + && cur_op->opcode != ZEND_DECLARE_ANON_INHERITED_CLASS) { + /* Objects created via ZEND_NEW are only fully initialized after the DO_FCALL (constructor call) */ + if (cur_op->opcode == ZEND_NEW) { + Tstart[cur_op->result.var] = cur_op->op2.opline_num - 1; + } else { + Tstart[cur_op->result.var] = cur_op - op_array->opcodes; } } if ((cur_op->op1_type & (IS_VAR | IS_TMP_VAR)) - && cur_op->opcode != ZEND_ROPE_ADD) { /* the following opcodes don't free TMP */ - var_live_info *T = Ts[cur_op->op1.var]; - if (T->start != (uint32_t) -1) { - T->end = cur_op - op_array->opcodes; - if (cur_op->opcode == ZEND_OP_DATA) { - T->end--; - } - } + && Tstart[cur_op->op1.var] != (uint32_t)-1 + && cur_op->opcode != ZEND_ROPE_ADD /* the following opcodes don't free TMP */ + && cur_op->opcode != ZEND_FETCH_LIST + && cur_op->opcode != ZEND_CASE + && cur_op->opcode != ZEND_FE_FETCH_R + && cur_op->opcode != ZEND_FE_FETCH_RW) { + op_live_total += liveliness_kill_var(op_array, cur_op, cur_op->op1.var, Tstart, opTs); } - if (cur_op->op2_type & (IS_VAR | IS_TMP_VAR)) { - var_live_info *T = Ts[cur_op->op2.var]; - if (T->start != (uint32_t) -1) { - T->end = cur_op - op_array->opcodes; - if (cur_op->opcode == ZEND_OP_DATA) { - T->end--; - } - } + if (cur_op->op2_type & (IS_VAR | IS_TMP_VAR) + && Tstart[cur_op->op2.var] != (uint32_t)-1) { + op_live_total += liveliness_kill_var(op_array, cur_op, cur_op->op2.var, Tstart, opTs); } } +#if ZEND_DEBUG + /* Check that all TMP variable live-ranges are closed */ for (i = 0; i < op_array->T; i++) { - int j; - var_live_info *T = Ts[i]; - - do { - if (T->start == (uint32_t) -1) { - continue; - } - - /* if one lands here, some opcode range isn't properly terminated or part of the exceptions */ - ZEND_ASSERT(T->end != (uint32_t) -1); - - j = T->start + 1; - if (op_array->opcodes[j].opcode == ZEND_OP_DATA - || op_array->opcodes[j].opcode == ZEND_FE_FETCH_R - || op_array->opcodes[j].opcode == ZEND_FE_FETCH_RW) { - /* On exception TMP variable is destroyed by current opcode */ - j++; - } - - for (; j < T->end; j++) { - op_var_info *opT = opTs[j]; - if (opT->var != (uint32_t) -1) { - opTs[j] = zend_arena_alloc(&CG(arena), sizeof(op_var_info)); - opTs[j]->next = opT; - opT = opTs[j]; - } - opT->var = (uint32_t)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL, op_array->last_var + i);; - if (op_array->opcodes[T->end].opcode == ZEND_ROPE_END) { - opT->var |= ZEND_LIVE_ROPE; - } else if (op_array->opcodes[T->end].opcode == ZEND_END_SILENCE) { - opT->var |= ZEND_LIVE_SILENCE; - } else if (op_array->opcodes[T->end].opcode == ZEND_FE_FREE) { - opT->var |= ZEND_LIVE_LOOP; - } - op_live_total++; - } - } while ((T = T->next)); + ZEND_ASSERT(Tstart[i] == (uint32_t)-1); } +#endif if (!op_live_total) { - zend_arena_release(&CG(arena), checkpoint); - return NULL; - } + info = NULL; + } else { + info = emalloc(op_live_total * sizeof(uint32_t) + (op_array->last + 1) * sizeof(uint32_t)); - info = emalloc(op_live_total * sizeof(uint32_t) + (op_array->last + 1) * sizeof(uint32_t)); - - for (i = 0; i < op_array->last; i++) { - op_var_info *opT = opTs[i]; - info[i] = info_off; - if (opT->var == (uint32_t) -1) { - continue; + for (i = 0; i < op_array->last; i++) { + op_var_info *opT = opTs[i]; + info[i] = info_off; + while (opT) { + info[info_off++] = opT->var; + opT = opT->next; + } } - do { - info[info_off++] = opT->var; - } while ((opT = opT->next)); + info[i] = info_off; } - info[i] = info_off; zend_arena_release(&CG(arena), checkpoint);