Skip to content

Implement iterative Pearce's SCC finding algoritm #12528

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 26, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
287 changes: 260 additions & 27 deletions Zend/Optimizer/zend_inference.c
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,12 @@
#define CHECK_SCC_VAR(var2) \
do { \
if (!ssa->vars[var2].no_val) { \
if (dfs[var2] < 0) { \
zend_ssa_check_scc_var(op_array, ssa, var2, index, dfs, root, stack); \
if (ssa->vars[var2].scc < 0) { \
zend_ssa_check_scc_var(op_array, ssa, var2, index, stack); \
} \
if (ssa->vars[var2].scc < 0 && dfs[root[var]] >= dfs[root[var2]]) { \
root[var] = root[var2]; \
if (ssa->vars[var2].scc < ssa->vars[var].scc) { \
ssa->vars[var].scc = ssa->vars[var2].scc; \
is_root = 0; \
} \
} \
} while (0)
Expand Down Expand Up @@ -172,15 +173,17 @@ static inline bool sub_will_overflow(zend_long a, zend_long b) {
}
#endif

static void zend_ssa_check_scc_var(const zend_op_array *op_array, zend_ssa *ssa, int var, int *index, int *dfs, int *root, zend_worklist_stack *stack) /* {{{ */
#if 0
/* Recursive Pearce's SCC algorithm implementation */
static void zend_ssa_check_scc_var(const zend_op_array *op_array, zend_ssa *ssa, int var, int *index, zend_worklist_stack *stack) /* {{{ */
{
int is_root = 1;
#ifdef SYM_RANGE
zend_ssa_phi *p;
#endif

dfs[var] = *index;
ssa->vars[var].scc = *index;
(*index)++;
root[var] = var;

FOR_EACH_VAR_USAGE(var, CHECK_SCC_VAR);

Expand All @@ -193,17 +196,20 @@ static void zend_ssa_check_scc_var(const zend_op_array *op_array, zend_ssa *ssa,
}
#endif

if (root[var] == var) {
ssa->vars[var].scc = ssa->sccs;
if (is_root) {
ssa->sccs--;
while (stack->len > 0) {
int var2 = zend_worklist_stack_peek(stack);
if (dfs[var2] <= dfs[var]) {
if (ssa->vars[var2].scc < ssa->vars[var].scc) {
break;
}
zend_worklist_stack_pop(stack);
ssa->vars[var2].scc = ssa->sccs;
(*index)--;
}
ssa->sccs++;
ssa->vars[var].scc = ssa->sccs;
ssa->vars[var].scc_entry = 1;
(*index)--;
} else {
zend_worklist_stack_push(stack, var);
}
Expand All @@ -212,48 +218,275 @@ static void zend_ssa_check_scc_var(const zend_op_array *op_array, zend_ssa *ssa,

ZEND_API void zend_ssa_find_sccs(const zend_op_array *op_array, zend_ssa *ssa) /* {{{ */
{
int index = 0, *dfs, *root;
int index = 0;
zend_worklist_stack stack;
int j;
ALLOCA_FLAG(dfs_use_heap)
ALLOCA_FLAG(root_use_heap)
ALLOCA_FLAG(stack_use_heap)

dfs = do_alloca(sizeof(int) * ssa->vars_count, dfs_use_heap);
memset(dfs, -1, sizeof(int) * ssa->vars_count);
root = do_alloca(sizeof(int) * ssa->vars_count, root_use_heap);
ZEND_WORKLIST_STACK_ALLOCA(&stack, ssa->vars_count, stack_use_heap);

/* Find SCCs using Tarjan's algorithm. */
/* Find SCCs using Pearce's algorithm. */
ssa->sccs = ssa->vars_count;
for (j = 0; j < ssa->vars_count; j++) {
if (!ssa->vars[j].no_val && dfs[j] < 0) {
zend_ssa_check_scc_var(op_array, ssa, j, &index, dfs, root, &stack);
if (!ssa->vars[j].no_val && ssa->vars[j].scc < 0) {
zend_ssa_check_scc_var(op_array, ssa, j, &index, &stack);
}
}

if (ssa->sccs) {
/* Shift SCC indexes. */
for (j = 0; j < ssa->vars_count; j++) {
if (ssa->vars[j].scc >= 0) {
ssa->vars[j].scc -= ssa->sccs;
}
}
}
ssa->sccs = ssa->vars_count - ssa->sccs;

/* Revert SCC order. This results in a topological order. */
for (j = 0; j < ssa->vars_count; j++) {
if (ssa->vars[j].scc >= 0) {
ssa->vars[j].scc = ssa->sccs - (ssa->vars[j].scc + 1);
int var = j;
FOR_EACH_VAR_USAGE(var, CHECK_SCC_ENTRY);
}
}

ZEND_WORKLIST_STACK_FREE_ALLOCA(&stack, stack_use_heap);
}
/* }}} */

#else
/* Iterative Pearce's SCC algorithm implementation */

typedef struct _zend_scc_iterator {
int state;
int last;
union {
int use;
zend_ssa_phi *phi;
};
} zend_scc_iterator;

static int zend_scc_next(const zend_op_array *op_array, zend_ssa *ssa, int var, zend_scc_iterator *iterator) /* {{{ */
{
zend_ssa_phi *phi;
int use, var2;

switch (iterator->state) {
case 0: goto state_0;
case 1: use = iterator->use; goto state_1;
case 2: use = iterator->use; goto state_2;
case 3: use = iterator->use; goto state_3;
case 4: use = iterator->use; goto state_4;
case 5: use = iterator->use; goto state_5;
case 6: use = iterator->use; goto state_6;
case 7: use = iterator->use; goto state_7;
case 8: use = iterator->use; goto state_8;
case 9: phi = iterator->phi; goto state_9;
#ifdef SYM_RANGE
case 10: phi = iterator->phi; goto state_10;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Label state_10 is only defined in #ifdef SYM_RANGE. So this case must also be guarded with #ifdef SYM_RANGE.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah. thanks.

#endif
case 11: goto state_11;
}

state_0:
use = ssa->vars[var].use_chain;
while (use >= 0) {
iterator->use = use;
var2 = ssa->ops[use].op1_def;
if (var2 >= 0 && !ssa->vars[var2].no_val) {
iterator->state = 1;
return var2;
}
state_1:
var2 = ssa->ops[use].op2_def;
if (var2 >= 0 && !ssa->vars[var2].no_val) {
iterator->state = 2;
return var2;
}
state_2:
var2 = ssa->ops[use].result_def;
if (var2 >= 0 && !ssa->vars[var2].no_val) {
iterator->state = 3;
return var2;
}
state_3:
if (op_array->opcodes[use].opcode == ZEND_OP_DATA) {
var2 = ssa->ops[use-1].op1_def;
if (var2 >= 0 && !ssa->vars[var2].no_val) {
iterator->state = 4;
return var2;
}
state_4:
var2 = ssa->ops[use-1].op2_def;
if (var2 >= 0 && !ssa->vars[var2].no_val) {
iterator->state = 5;
return var2;
}
state_5:
var2 = ssa->ops[use-1].result_def;
if (var2 >= 0 && !ssa->vars[var2].no_val) {
iterator->state = 8;
return var2;
}
} else if ((uint32_t)use+1 < op_array->last &&
op_array->opcodes[use+1].opcode == ZEND_OP_DATA) {
var2 = ssa->ops[use+1].op1_def;
if (var2 >= 0 && !ssa->vars[var2].no_val) {
iterator->state = 6;
return var2;
}
state_6:
var2 = ssa->ops[use+1].op2_def;
if (var2 >= 0 && !ssa->vars[var2].no_val) {
iterator->state = 7;
return var2;
}
state_7:
var2 = ssa->ops[use+1].result_def;
if (var2 >= 0 && !ssa->vars[var2].no_val) {
iterator->state = 8;
return var2;
}
}
state_8:
use = zend_ssa_next_use(ssa->ops, var, use);
}

phi = ssa->vars[var].phi_use_chain;
while (phi) {
var2 = phi->ssa_var;
if (!ssa->vars[var2].no_val) {
iterator->state = 9;
iterator->phi = phi;
return var2;
}
state_9:
phi = zend_ssa_next_use_phi(ssa, var, phi);
}

#ifdef SYM_RANGE
/* Process symbolic control-flow constraints */
phi = ssa->vars[var].sym_use_chain;
while (phi) {
var2 = phi->ssa_var;
if (!ssa->vars[var2].no_val) {
iterator->state = 10;
iterator->phi = phi;
return var2;
}
state_10:
phi = phi->sym_use_chain;
}
#endif

iterator->state = 11;
state_11:
return -1;
}
/* }}} */

static void zend_ssa_check_scc_var(const zend_op_array *op_array, zend_ssa *ssa, int var, int *index, zend_worklist_stack *stack, zend_worklist_stack *vstack, zend_scc_iterator *iterators) /* {{{ */
{
restart:
zend_worklist_stack_push(vstack, var);
iterators[var].state = 0;
iterators[var].last = -1;
ssa->vars[var].scc_entry = 1;
ssa->vars[var].scc = *index;
(*index)++;

while (vstack->len > 0) {
var = zend_worklist_stack_peek(vstack);
while (1) {
int var2;

if (iterators[var].last >= 0) {
/* finish edge */
var2 = iterators[var].last;
if (ssa->vars[var2].scc < ssa->vars[var].scc) {
ssa->vars[var].scc = ssa->vars[var2].scc;
ssa->vars[var].scc_entry = 0;
}
}
var2 = zend_scc_next(op_array, ssa, var, iterators + var);
iterators[var].last = var2;
if (var2 < 0) break;
/* begin edge */
if (ssa->vars[var2].scc < 0) {
var = var2;
goto restart;
}
}

/* finish visiting */
zend_worklist_stack_pop(vstack);
if (ssa->vars[var].scc_entry) {
ssa->sccs--;
while (stack->len > 0) {
int var2 = zend_worklist_stack_peek(stack);
if (ssa->vars[var2].scc < ssa->vars[var].scc) {
break;
}
zend_worklist_stack_pop(stack);
ssa->vars[var2].scc = ssa->sccs;
(*index)--;
}
ssa->vars[var].scc = ssa->sccs;
(*index)--;
} else {
zend_worklist_stack_push(stack, var);
}
}
}
/* }}} */

ZEND_API void zend_ssa_find_sccs(const zend_op_array *op_array, zend_ssa *ssa) /* {{{ */
{
int index = 0;
zend_worklist_stack stack, vstack;
zend_scc_iterator *iterators;
int j;
ALLOCA_FLAG(stack_use_heap)
ALLOCA_FLAG(vstack_use_heap)
ALLOCA_FLAG(iterators_use_heap)

iterators = do_alloca(sizeof(zend_scc_iterator) * ssa->vars_count, iterators_use_heap);
ZEND_WORKLIST_STACK_ALLOCA(&vstack, ssa->vars_count, vstack_use_heap);
ZEND_WORKLIST_STACK_ALLOCA(&stack, ssa->vars_count, stack_use_heap);

/* Find SCCs using Pearce's algorithm. */
ssa->sccs = ssa->vars_count;
for (j = 0; j < ssa->vars_count; j++) {
if (!ssa->vars[j].no_val && ssa->vars[j].scc < 0) {
zend_ssa_check_scc_var(op_array, ssa, j, &index, &stack, &vstack, iterators);
}
}

if (ssa->sccs) {
/* Shift SCC indexes. */
for (j = 0; j < ssa->vars_count; j++) {
if (ssa->vars[j].scc >= 0) {
ssa->vars[j].scc -= ssa->sccs;
}
}
}
ssa->sccs = ssa->vars_count - ssa->sccs;

for (j = 0; j < ssa->vars_count; j++) {
if (ssa->vars[j].scc >= 0) {
int var = j;
if (root[j] == j) {
ssa->vars[j].scc_entry = 1;
}
FOR_EACH_VAR_USAGE(var, CHECK_SCC_ENTRY);
}
}

ZEND_WORKLIST_STACK_FREE_ALLOCA(&stack, stack_use_heap);
free_alloca(root, root_use_heap);
free_alloca(dfs, dfs_use_heap);
ZEND_WORKLIST_STACK_FREE_ALLOCA(&vstack, vstack_use_heap);
free_alloca(iterators, iterators_use_heap);
}
/* }}} */

#endif

ZEND_API void zend_ssa_find_false_dependencies(const zend_op_array *op_array, zend_ssa *ssa) /* {{{ */
{
zend_ssa_var *ssa_vars = ssa->vars;
Expand Down