Improved algorithm. It's actually the same algorithm with second loop removed and simpler temporary data structures. The only difference may be in "re-defined" vatriable handling. Now live-range in that case started from the seconnd definition (this must be more safe).

This commit is contained in:
Dmitry Stogov 2015-07-06 20:59:34 +03:00
parent 9a16810f7a
commit a81c4bb8c6

View File

@ -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);