void zend_optimizer_remove_live_range(zend_op_array *op_array, uint32_t var) { if (op_array->last_live_range) { int i = 0; int j = 0; uint32_t *map; ALLOCA_FLAG(use_heap); map = (uint32_t *)do_alloca(sizeof(uint32_t) * op_array->last_live_range, use_heap); do { if ((op_array->live_range[i].var & ~ZEND_LIVE_MASK) != var) { map[i] = j; if (i != j) { op_array->live_range[j] = op_array->live_range[i]; } j++; } i++; } while (i < op_array->last_live_range); if (i != j) { zend_op *opline = op_array->opcodes; zend_op *end = opline + op_array->last; op_array->last_live_range = j; while (opline != end) { if ((opline->opcode == ZEND_FREE || opline->opcode == ZEND_FE_FREE) && opline->extended_value == ZEND_FREE_ON_RETURN) { opline->op2.num = map[opline->op2.num]; } opline++; } } } }
ZEND_API zval *zend_get_constant(zend_string *name) { zval *zv; zend_constant *c; ALLOCA_FLAG(use_heap) zv = zend_hash_find(EG(zend_constants), name); if (zv == NULL) { char *lcname = do_alloca(ZSTR_LEN(name) + 1, use_heap); zend_str_tolower_copy(lcname, ZSTR_VAL(name), ZSTR_LEN(name)); zv = zend_hash_str_find(EG(zend_constants), lcname, ZSTR_LEN(name)); if (zv != NULL) { c = Z_PTR_P(zv); if (c->flags & CONST_CS) { c = NULL; } } else { c = zend_get_special_constant(ZSTR_VAL(name), ZSTR_LEN(name)); } free_alloca(lcname, use_heap); return c ? &c->value : NULL; } else { return &((zend_constant*)Z_PTR_P(zv))->value; } }
static int32_t qb_transfer_operands_function_call(qb_compiler_context *cxt, qb_op_factory *f, uint32_t flags, qb_operand *operands, uint32_t operand_count, qb_operand *result, qb_operand *dest, uint32_t dest_count) { USE_TSRM qb_operand *func = &operands[0], *arguments = &operands[1], *argument_count = &operands[2]; const char *func_name = func->zend_function->common.function_name; qb_external_symbol_type symbol_type = (func->type == QB_OPERAND_STATIC_ZEND_FUNCTION) ? QB_EXT_SYM_STATIC_ZEND_FUNCTION : QB_EXT_SYM_ZEND_FUNCTION; uint32_t func_name_len = (uint32_t) strlen(func_name); uint32_t func_index = qb_import_external_symbol(symbol_type, func_name, func_name_len, func->zend_function TSRMLS_CC); uint32_t *var_indices, ret_index = INVALID_INDEX; uint32_t i; ALLOCA_FLAG(use_heap); var_indices = do_alloca(sizeof(uint32_t *) * argument_count->number, use_heap); for(i = 0; i < (uint32_t) argument_count->number; i++) { qb_operand *arg = &arguments->arguments[i]; var_indices[i] = qb_get_variable_index(cxt, arg->address); } if(result->type == QB_OPERAND_ADDRESS && result->address != cxt->zero_address) { ret_index = qb_get_variable_index(cxt, result->address); qb_mark_as_writable(cxt, result->address); } dest[0].address = qb_obtain_constant_U32(cxt, func_index); dest[0].type = QB_OPERAND_ADDRESS; dest[1].address = qb_obtain_constant_indices(cxt, var_indices, argument_count->number); dest[1].type = QB_OPERAND_ADDRESS; dest[2].address = qb_obtain_constant_U32(cxt, ret_index); dest[2].type = QB_OPERAND_ADDRESS; free_alloca(var_indices, use_heap); return TRUE; }
static inline zend_constant *zend_get_constant_impl(zend_string *name) { zval *zv; zend_constant *c; ALLOCA_FLAG(use_heap) zv = zend_hash_find(EG(zend_constants), name); if (zv == NULL) { char *lcname = do_alloca(ZSTR_LEN(name) + 1, use_heap); zend_str_tolower_copy(lcname, ZSTR_VAL(name), ZSTR_LEN(name)); zv = zend_hash_str_find(EG(zend_constants), lcname, ZSTR_LEN(name)); if (zv != NULL) { c = Z_PTR_P(zv); if (ZEND_CONSTANT_FLAGS(c) & CONST_CS) { c = NULL; } } else { c = zend_get_special_constant(ZSTR_VAL(name), ZSTR_LEN(name)); } free_alloca(lcname, use_heap); return c; } else { return (zend_constant *) Z_PTR_P(zv); } }
static inline zend_constant *zend_get_constant_str_impl(const char *name, size_t name_len) { zend_constant *c; ALLOCA_FLAG(use_heap) if ((c = zend_hash_str_find_ptr(EG(zend_constants), name, name_len)) == NULL) { char *lcname = do_alloca(name_len + 1, use_heap); zend_str_tolower_copy(lcname, name, name_len); if ((c = zend_hash_str_find_ptr(EG(zend_constants), lcname, name_len)) != NULL) { if (ZEND_CONSTANT_FLAGS(c) & CONST_CS) { c = NULL; } } else { c = zend_get_special_constant(name, name_len); } free_alloca(lcname, use_heap); } return c; }
ZEND_API zval *zend_get_constant(zend_string *name) { zend_constant *c; ALLOCA_FLAG(use_heap) if ((c = zend_hash_find_ptr(EG(zend_constants), name)) == NULL) { char *lcname = do_alloca(name->len + 1, use_heap); zend_str_tolower_copy(lcname, name->val, name->len); if ((c = zend_hash_str_find_ptr(EG(zend_constants), lcname, name->len)) != NULL) { if (c->flags & CONST_CS) { c = NULL; } } else { c = zend_get_special_constant(name->val, name->len); } free_alloca(lcname, use_heap); } return c ? &c->value : NULL; }
static zend_class_entry * spl_find_ce_by_name(char *name, int len, zend_bool autoload TSRMLS_DC) { zend_class_entry **ce; int found; if (!autoload) { char *lc_name; lc_name = do_alloca(len + 1); zend_str_tolower_copy(lc_name, name, len); found = zend_hash_find(EG(class_table), lc_name, len +1, (void **) &ce); free_alloca(lc_name); } else { found = zend_lookup_class(name, len, &ce TSRMLS_CC); } if (found != SUCCESS) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Class %s does not exist%s", name, autoload ? " and could not be loaded" : ""); return NULL; } return *ce; }
ZEND_API zval *zend_get_constant_ex(zend_string *cname, zend_class_entry *scope, uint32_t flags) { zend_constant *c; const char *colon; zend_class_entry *ce = NULL; const char *name = ZSTR_VAL(cname); size_t name_len = ZSTR_LEN(cname); /* Skip leading \\ */ if (name[0] == '\\') { name += 1; name_len -= 1; cname = NULL; } if ((colon = zend_memrchr(name, ':', name_len)) && colon > name && (*(colon - 1) == ':')) { int class_name_len = colon - name - 1; size_t const_name_len = name_len - class_name_len - 2; zend_string *constant_name = zend_string_init(colon + 1, const_name_len, 0); zend_string *class_name = zend_string_init(name, class_name_len, 0); zval *ret_constant = NULL; if (zend_string_equals_literal_ci(class_name, "self")) { if (UNEXPECTED(!scope)) { zend_throw_error(NULL, "Cannot access self:: when no class scope is active"); goto failure; } ce = scope; } else if (zend_string_equals_literal_ci(class_name, "parent")) { if (UNEXPECTED(!scope)) { zend_throw_error(NULL, "Cannot access parent:: when no class scope is active"); goto failure; } else if (UNEXPECTED(!scope->parent)) { zend_throw_error(NULL, "Cannot access parent:: when current class scope has no parent"); goto failure; } else { ce = scope->parent; } } else if (zend_string_equals_literal_ci(class_name, "static")) { ce = zend_get_called_scope(EG(current_execute_data)); if (UNEXPECTED(!ce)) { zend_throw_error(NULL, "Cannot access static:: when no class scope is active"); goto failure; } } else { ce = zend_fetch_class(class_name, flags); } if (ce) { zend_class_constant *c = zend_hash_find_ptr(&ce->constants_table, constant_name); if (c == NULL) { if ((flags & ZEND_FETCH_CLASS_SILENT) == 0) { zend_throw_error(NULL, "Undefined class constant '%s::%s'", ZSTR_VAL(class_name), ZSTR_VAL(constant_name)); goto failure; } ret_constant = NULL; } else { if (!zend_verify_const_access(c, scope)) { zend_throw_error(NULL, "Cannot access %s const %s::%s", zend_visibility_string(Z_ACCESS_FLAGS(c->value)), ZSTR_VAL(class_name), ZSTR_VAL(constant_name)); goto failure; } ret_constant = &c->value; } } if (ret_constant && Z_CONSTANT_P(ret_constant)) { if (Z_TYPE_P(ret_constant) == IS_CONSTANT_AST) { if (IS_CONSTANT_VISITED(ret_constant)) { zend_throw_error(NULL, "Cannot declare self-referencing constant '%s::%s'", ZSTR_VAL(class_name), ZSTR_VAL(constant_name)); ret_constant = NULL; goto failure; } MARK_CONSTANT_VISITED(ret_constant); } if (UNEXPECTED(zval_update_constant_ex(ret_constant, ce) != SUCCESS)) { RESET_CONSTANT_VISITED(ret_constant); ret_constant = NULL; goto failure; } RESET_CONSTANT_VISITED(ret_constant); } failure: zend_string_release(class_name); zend_string_free(constant_name); return ret_constant; } /* non-class constant */ if ((colon = zend_memrchr(name, '\\', name_len)) != NULL) { /* compound constant name */ int prefix_len = colon - name; size_t const_name_len = name_len - prefix_len - 1; const char *constant_name = colon + 1; char *lcname; size_t lcname_len; ALLOCA_FLAG(use_heap) lcname_len = prefix_len + 1 + const_name_len; lcname = do_alloca(lcname_len + 1, use_heap); zend_str_tolower_copy(lcname, name, prefix_len); /* Check for namespace constant */ lcname[prefix_len] = '\\'; memcpy(lcname + prefix_len + 1, constant_name, const_name_len + 1); if ((c = zend_hash_str_find_ptr(EG(zend_constants), lcname, lcname_len)) == NULL) { /* try lowercase */ zend_str_tolower(lcname + prefix_len + 1, const_name_len); if ((c = zend_hash_str_find_ptr(EG(zend_constants), lcname, lcname_len)) != NULL) { if ((c->flags & CONST_CS) != 0) { c = NULL; } } } free_alloca(lcname, use_heap); if (c) { return &c->value; } /* name requires runtime resolution, need to check non-namespaced name */ if ((flags & IS_CONSTANT_UNQUALIFIED) != 0) { return zend_get_constant_str(constant_name, const_name_len); } return NULL; } if (cname) { return zend_get_constant(cname); } else { return zend_get_constant_str(name, name_len); } }
/* {{{ collator_u_strtod * Taken from PHP6:zend_u_strtod() */ static double collator_u_strtod(const UChar *nptr, UChar **endptr) /* {{{ */ { const UChar *u = nptr, *nstart; UChar c = *u; int any = 0; ALLOCA_FLAG(use_heap); while (u_isspace(c)) { c = *++u; } nstart = u; if (c == 0x2D /*'-'*/ || c == 0x2B /*'+'*/) { c = *++u; } while (c >= 0x30 /*'0'*/ && c <= 0x39 /*'9'*/) { any = 1; c = *++u; } if (c == 0x2E /*'.'*/) { c = *++u; while (c >= 0x30 /*'0'*/ && c <= 0x39 /*'9'*/) { any = 1; c = *++u; } } if ((c == 0x65 /*'e'*/ || c == 0x45 /*'E'*/) && any) { const UChar *e = u; int any_exp = 0; c = *++u; if (c == 0x2D /*'-'*/ || c == 0x2B /*'+'*/) { c = *++u; } while (c >= 0x30 /*'0'*/ && c <= 0x39 /*'9'*/) { any_exp = 1; c = *++u; } if (!any_exp) { u = e; } } if (any) { char buf[64], *numbuf, *bufpos; size_t length = u - nstart; double value; if (length < sizeof(buf)) { numbuf = buf; } else { numbuf = (char *) do_alloca(length + 1, use_heap); } bufpos = numbuf; while (nstart < u) { *bufpos++ = (char) *nstart++; } *bufpos = '\0'; value = zend_strtod(numbuf, NULL); if (numbuf != buf) { free_alloca(numbuf, use_heap); } if (endptr != NULL) { *endptr = (UChar *)u; } return value; } if (endptr != NULL) { *endptr = (UChar *)nptr; } return 0; }
/* This pass removes all CVs and temporaries that are completely unused. It does *not* merge any CVs or TMPs. * This pass does not operate on SSA form anymore. */ void zend_optimizer_compact_vars(zend_op_array *op_array) { int i; ALLOCA_FLAG(use_heap1); ALLOCA_FLAG(use_heap2); uint32_t used_vars_len = zend_bitset_len(op_array->last_var + op_array->T); zend_bitset used_vars = ZEND_BITSET_ALLOCA(used_vars_len, use_heap1); uint32_t *vars_map = do_alloca((op_array->last_var + op_array->T) * sizeof(uint32_t), use_heap2); uint32_t num_cvs, num_tmps; /* Determine which CVs are used */ zend_bitset_clear(used_vars, used_vars_len); for (i = 0; i < op_array->last; i++) { zend_op *opline = &op_array->opcodes[i]; if (opline->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) { zend_bitset_incl(used_vars, VAR_NUM(opline->op1.var)); } if (opline->op2_type & (IS_CV|IS_VAR|IS_TMP_VAR)) { zend_bitset_incl(used_vars, VAR_NUM(opline->op2.var)); } if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) { zend_bitset_incl(used_vars, VAR_NUM(opline->result.var)); if (opline->opcode == ZEND_ROPE_INIT) { uint32_t num = ((opline->extended_value * sizeof(zend_string*)) + (sizeof(zval) - 1)) / sizeof(zval); while (num > 1) { num--; zend_bitset_incl(used_vars, VAR_NUM(opline->result.var) + num); } } } } num_cvs = 0; for (i = 0; i < op_array->last_var; i++) { if (zend_bitset_in(used_vars, i)) { vars_map[i] = num_cvs++; } else { vars_map[i] = (uint32_t) -1; } } num_tmps = 0; for (i = op_array->last_var; i < op_array->last_var + op_array->T; i++) { if (zend_bitset_in(used_vars, i)) { vars_map[i] = num_cvs + num_tmps++; } else { vars_map[i] = (uint32_t) -1; } } free_alloca(used_vars, use_heap1); if (num_cvs == op_array->last_var && num_tmps == op_array->T) { free_alloca(vars_map, use_heap2); return; } ZEND_ASSERT(num_cvs <= op_array->last_var); ZEND_ASSERT(num_tmps <= op_array->T); /* Update CV and TMP references in opcodes */ for (i = 0; i < op_array->last; i++) { zend_op *opline = &op_array->opcodes[i]; if (opline->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) { opline->op1.var = NUM_VAR(vars_map[VAR_NUM(opline->op1.var)]); } if (opline->op2_type & (IS_CV|IS_VAR|IS_TMP_VAR)) { opline->op2.var = NUM_VAR(vars_map[VAR_NUM(opline->op2.var)]); } if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) { opline->result.var = NUM_VAR(vars_map[VAR_NUM(opline->result.var)]); } } /* Update TMP references in live ranges */ if (op_array->live_range) { for (i = 0; i < op_array->last_live_range; i++) { op_array->live_range[i].var = (op_array->live_range[i].var & ZEND_LIVE_MASK) | NUM_VAR(vars_map[VAR_NUM(op_array->live_range[i].var & ~ZEND_LIVE_MASK)]); } } /* Update CV name table */ if (num_cvs != op_array->last_var) { zend_string **names = safe_emalloc(sizeof(zend_string *), num_cvs, 0); for (i = 0; i < op_array->last_var; i++) { if (vars_map[i] != (uint32_t) -1) { names[vars_map[i]] = op_array->vars[i]; } else { zend_string_release(op_array->vars[i]); } } efree(op_array->vars); op_array->vars = names; } op_array->last_var = num_cvs; op_array->T = num_tmps; free_alloca(vars_map, use_heap2); }
PHPAPI void php_register_variable_ex(char *var_name, zval *val, zval *track_vars_array) { char *p = NULL; char *ip = NULL; /* index pointer */ char *index; char *var, *var_orig; size_t var_len, index_len; zval gpc_element, *gpc_element_p; zend_bool is_array = 0; HashTable *symtable1 = NULL; ALLOCA_FLAG(use_heap) assert(var_name != NULL); if (track_vars_array && Z_TYPE_P(track_vars_array) == IS_ARRAY) { symtable1 = Z_ARRVAL_P(track_vars_array); } if (!symtable1) { /* Nothing to do */ zval_dtor(val); return; } /* ignore leading spaces in the variable name */ while (*var_name && *var_name==' ') { var_name++; } /* * Prepare variable name */ var_len = strlen(var_name); var = var_orig = do_alloca(var_len + 1, use_heap); memcpy(var_orig, var_name, var_len + 1); /* ensure that we don't have spaces or dots in the variable name (not binary safe) */ for (p = var; *p; p++) { if (*p == ' ' || *p == '.') { *p='_'; } else if (*p == '[') { is_array = 1; ip = p; *p = 0; break; } } var_len = p - var; if (var_len==0) { /* empty variable name, or variable name with a space in it */ zval_dtor(val); free_alloca(var_orig, use_heap); return; } /* GLOBALS hijack attempt, reject parameter */ if (symtable1 == &EG(symbol_table) && var_len == sizeof("GLOBALS")-1 && !memcmp(var, "GLOBALS", sizeof("GLOBALS")-1)) { zval_dtor(val); free_alloca(var_orig, use_heap); return; } index = var; index_len = var_len; if (is_array) { int nest_level = 0; while (1) { char *index_s; size_t new_idx_len = 0; if(++nest_level > PG(max_input_nesting_level)) { HashTable *ht; /* too many levels of nesting */ if (track_vars_array) { ht = Z_ARRVAL_P(track_vars_array); zend_symtable_str_del(ht, var, var_len); } zval_dtor(val); /* do not output the error message to the screen, this helps us to to avoid "information disclosure" */ if (!PG(display_errors)) { php_error_docref(NULL, E_WARNING, "Input variable nesting level exceeded " ZEND_LONG_FMT ". To increase the limit change max_input_nesting_level in php.ini.", PG(max_input_nesting_level)); } free_alloca(var_orig, use_heap); return; } ip++; index_s = ip; if (isspace(*ip)) { ip++; } if (*ip==']') { index_s = NULL; } else { ip = strchr(ip, ']'); if (!ip) { /* PHP variables cannot contain '[' in their names, so we replace the character with a '_' */ *(index_s - 1) = '_'; index_len = 0; if (index) { index_len = strlen(index); } goto plain_var; return; } *ip = 0; new_idx_len = strlen(index_s); } if (!index) { array_init(&gpc_element); if ((gpc_element_p = zend_hash_next_index_insert(symtable1, &gpc_element)) == NULL) { zval_ptr_dtor(&gpc_element); zval_dtor(val); free_alloca(var_orig, use_heap); return; } } else { gpc_element_p = zend_symtable_str_find(symtable1, index, index_len); if (!gpc_element_p) { zval tmp; array_init(&tmp); gpc_element_p = zend_symtable_str_update_ind(symtable1, index, index_len, &tmp); } else { if (Z_TYPE_P(gpc_element_p) == IS_INDIRECT) { gpc_element_p = Z_INDIRECT_P(gpc_element_p); } if (Z_TYPE_P(gpc_element_p) != IS_ARRAY) { zval_ptr_dtor(gpc_element_p); array_init(gpc_element_p); } } } symtable1 = Z_ARRVAL_P(gpc_element_p); /* ip pointed to the '[' character, now obtain the key */ index = index_s; index_len = new_idx_len; ip++; if (*ip == '[') { is_array = 1; *ip = 0; } else { goto plain_var; } } } else { plain_var: ZVAL_COPY_VALUE(&gpc_element, val); if (!index) { if ((gpc_element_p = zend_hash_next_index_insert(symtable1, &gpc_element)) == NULL) { zval_ptr_dtor(&gpc_element); } } else { /* * According to rfc2965, more specific paths are listed above the less specific ones. * If we encounter a duplicate cookie name, we should skip it, since it is not possible * to have the same (plain text) cookie name for the same path and we should not overwrite * more specific cookies with the less specific ones. */ if (Z_TYPE(PG(http_globals)[TRACK_VARS_COOKIE]) != IS_UNDEF && symtable1 == Z_ARRVAL(PG(http_globals)[TRACK_VARS_COOKIE]) && zend_symtable_str_exists(symtable1, index, index_len)) { zval_ptr_dtor(&gpc_element); } else { gpc_element_p = zend_symtable_str_update_ind(symtable1, index, index_len, &gpc_element); } } } free_alloca(var_orig, use_heap); }
static int zend_ssa_rename(const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa, int *var, int n) /* {{{ */ { zend_basic_block *blocks = ssa->cfg.blocks; zend_ssa_block *ssa_blocks = ssa->blocks; zend_ssa_op *ssa_ops = ssa->ops; int ssa_vars_count = ssa->vars_count; int i, j; uint32_t k; zend_op *opline; int *tmp = NULL; ALLOCA_FLAG(use_heap); // FIXME: Can we optimize this copying out in some cases? if (blocks[n].next_child >= 0) { tmp = do_alloca(sizeof(int) * (op_array->last_var + op_array->T), use_heap); memcpy(tmp, var, sizeof(int) * (op_array->last_var + op_array->T)); var = tmp; } if (ssa_blocks[n].phis) { zend_ssa_phi *phi = ssa_blocks[n].phis; do { if (phi->ssa_var < 0) { phi->ssa_var = ssa_vars_count; var[phi->var] = ssa_vars_count; ssa_vars_count++; } else { var[phi->var] = phi->ssa_var; } phi = phi->next; } while (phi); } for (k = blocks[n].start; k <= blocks[n].end; k++) { opline = op_array->opcodes + k; if (opline->opcode != ZEND_OP_DATA) { zend_op *next = opline + 1; if (k < blocks[n].end && next->opcode == ZEND_OP_DATA) { if (next->op1_type == IS_CV) { ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)]; //USE_SSA_VAR(next->op1.var); } else if (next->op1_type & (IS_VAR|IS_TMP_VAR)) { ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)]; //USE_SSA_VAR(op_array->last_var + next->op1.var); } if (next->op2_type == IS_CV) { ssa_ops[k + 1].op2_use = var[EX_VAR_TO_NUM(next->op2.var)]; //USE_SSA_VAR(next->op2.var); } else if (next->op2_type & (IS_VAR|IS_TMP_VAR)) { ssa_ops[k + 1].op2_use = var[EX_VAR_TO_NUM(next->op2.var)]; //USE_SSA_VAR(op_array->last_var + next->op2.var); } } if (opline->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) { ssa_ops[k].op1_use = var[EX_VAR_TO_NUM(opline->op1.var)]; //USE_SSA_VAR(op_array->last_var + opline->op1.var) } if (opline->opcode == ZEND_FE_FETCH_R || opline->opcode == ZEND_FE_FETCH_RW) { if (opline->op2_type == IS_CV) { ssa_ops[k].op2_use = var[EX_VAR_TO_NUM(opline->op2.var)]; } ssa_ops[k].op2_def = ssa_vars_count; var[EX_VAR_TO_NUM(opline->op2.var)] = ssa_vars_count; ssa_vars_count++; //NEW_SSA_VAR(opline->op2.var) } else if (opline->op2_type & (IS_CV|IS_VAR|IS_TMP_VAR)) { ssa_ops[k].op2_use = var[EX_VAR_TO_NUM(opline->op2.var)]; //USE_SSA_VAR(op_array->last_var + opline->op2.var) } switch (opline->opcode) { case ZEND_ASSIGN: if (opline->op1_type == IS_CV) { ssa_ops[k].op1_def = ssa_vars_count; var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; ssa_vars_count++; //NEW_SSA_VAR(opline->op1.var) } if ((build_flags & ZEND_SSA_RC_INFERENCE) && opline->op2_type == IS_CV) { ssa_ops[k].op2_def = ssa_vars_count; var[EX_VAR_TO_NUM(opline->op2.var)] = ssa_vars_count; ssa_vars_count++; //NEW_SSA_VAR(opline->op2.var) } break; case ZEND_ASSIGN_REF: //TODO: ??? if (opline->op1_type == IS_CV) { ssa_ops[k].op1_def = ssa_vars_count; var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; ssa_vars_count++; //NEW_SSA_VAR(opline->op1.var) } if (opline->op2_type == IS_CV) { ssa_ops[k].op2_def = ssa_vars_count; var[EX_VAR_TO_NUM(opline->op2.var)] = ssa_vars_count; ssa_vars_count++; //NEW_SSA_VAR(opline->op2.var) } break; case ZEND_BIND_GLOBAL: case ZEND_BIND_STATIC: if (opline->op1_type == IS_CV) { ssa_ops[k].op1_def = ssa_vars_count; var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; ssa_vars_count++; //NEW_SSA_VAR(opline->op1.var) } break; case ZEND_ASSIGN_DIM: case ZEND_ASSIGN_OBJ: if (opline->op1_type == IS_CV) { ssa_ops[k].op1_def = ssa_vars_count; var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; ssa_vars_count++; //NEW_SSA_VAR(opline->op1.var) } if ((build_flags & ZEND_SSA_RC_INFERENCE) && next->op1_type == IS_CV) { ssa_ops[k + 1].op1_def = ssa_vars_count; var[EX_VAR_TO_NUM(next->op1.var)] = ssa_vars_count; ssa_vars_count++; //NEW_SSA_VAR(next->op1.var) } break; case ZEND_ADD_ARRAY_ELEMENT: ssa_ops[k].result_use = var[EX_VAR_TO_NUM(opline->result.var)]; case ZEND_INIT_ARRAY: if (((build_flags & ZEND_SSA_RC_INFERENCE) || (opline->extended_value & ZEND_ARRAY_ELEMENT_REF)) && opline->op1_type == IS_CV) { ssa_ops[k].op1_def = ssa_vars_count; var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; ssa_vars_count++; //NEW_SSA_VAR(opline+->op1.var) } break; case ZEND_SEND_VAR_NO_REF: case ZEND_SEND_VAR_EX: case ZEND_SEND_REF: case ZEND_FE_RESET_RW: //TODO: ??? if (opline->op1_type == IS_CV) { ssa_ops[k].op1_def = ssa_vars_count; var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; ssa_vars_count++; //NEW_SSA_VAR(opline->op1.var) } break; case ZEND_FE_RESET_R: if ((build_flags & ZEND_SSA_RC_INFERENCE) && opline->op1_type == IS_CV) { ssa_ops[k].op1_def = ssa_vars_count; var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; ssa_vars_count++; //NEW_SSA_VAR(opline->op1.var) } break; case ZEND_ASSIGN_ADD: case ZEND_ASSIGN_SUB: case ZEND_ASSIGN_MUL: case ZEND_ASSIGN_DIV: case ZEND_ASSIGN_MOD: case ZEND_ASSIGN_SL: case ZEND_ASSIGN_SR: case ZEND_ASSIGN_CONCAT: case ZEND_ASSIGN_BW_OR: case ZEND_ASSIGN_BW_AND: case ZEND_ASSIGN_BW_XOR: case ZEND_ASSIGN_POW: case ZEND_PRE_INC: case ZEND_PRE_DEC: case ZEND_POST_INC: case ZEND_POST_DEC: if (opline->op1_type == IS_CV) { ssa_ops[k].op1_def = ssa_vars_count; var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; ssa_vars_count++; //NEW_SSA_VAR(opline->op1.var) } break; case ZEND_UNSET_VAR: if (opline->extended_value & ZEND_QUICK_SET) { ssa_ops[k].op1_def = ssa_vars_count; var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; ssa_vars_count++; } break; case ZEND_UNSET_DIM: case ZEND_UNSET_OBJ: case ZEND_FETCH_DIM_W: case ZEND_FETCH_DIM_RW: case ZEND_FETCH_DIM_FUNC_ARG: case ZEND_FETCH_DIM_UNSET: case ZEND_FETCH_OBJ_W: case ZEND_FETCH_OBJ_RW: case ZEND_FETCH_OBJ_FUNC_ARG: case ZEND_FETCH_OBJ_UNSET: if (opline->op1_type == IS_CV) { ssa_ops[k].op1_def = ssa_vars_count; var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; ssa_vars_count++; //NEW_SSA_VAR(opline->op1.var) } break; case ZEND_BIND_LEXICAL: if (opline->extended_value || (build_flags & ZEND_SSA_RC_INFERENCE)) { ssa_ops[k].op2_def = ssa_vars_count; var[EX_VAR_TO_NUM(opline->op2.var)] = ssa_vars_count; ssa_vars_count++; } break; case ZEND_YIELD: if (opline->op1_type == IS_CV && ((op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) || (build_flags & ZEND_SSA_RC_INFERENCE))) { ssa_ops[k].op1_def = ssa_vars_count; var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count; ssa_vars_count++; } default: break; } if (opline->result_type == IS_CV) { ssa_ops[k].result_def = ssa_vars_count; var[EX_VAR_TO_NUM(opline->result.var)] = ssa_vars_count; ssa_vars_count++; //NEW_SSA_VAR(opline->result.var) } else if (opline->result_type & (IS_VAR|IS_TMP_VAR)) { ssa_ops[k].result_def = ssa_vars_count; var[EX_VAR_TO_NUM(opline->result.var)] = ssa_vars_count; ssa_vars_count++; //NEW_SSA_VAR(op_array->last_var + opline->result.var) } } } for (i = 0; i < 2; i++) { int succ = blocks[n].successors[i]; if (succ >= 0) { zend_ssa_phi *p; for (p = ssa_blocks[succ].phis; p; p = p->next) { if (p->pi == n) { /* e-SSA Pi */ if (p->constraint.min_var >= 0) { p->constraint.min_ssa_var = var[p->constraint.min_var]; } if (p->constraint.max_var >= 0) { p->constraint.max_ssa_var = var[p->constraint.max_var]; } for (j = 0; j < blocks[succ].predecessors_count; j++) { p->sources[j] = var[p->var]; } if (p->ssa_var < 0) { p->ssa_var = ssa_vars_count; ssa_vars_count++; } } else if (p->pi < 0) { /* Normal Phi */ for (j = 0; j < blocks[succ].predecessors_count; j++) if (ssa->cfg.predecessors[blocks[succ].predecessor_offset + j] == n) { break; } ZEND_ASSERT(j < blocks[succ].predecessors_count); p->sources[j] = var[p->var]; } } for (p = ssa_blocks[succ].phis; p && (p->pi >= 0); p = p->next) { if (p->pi == n) { zend_ssa_phi *q = p->next; while (q) { if (q->pi < 0 && q->var == p->var) { for (j = 0; j < blocks[succ].predecessors_count; j++) { if (ssa->cfg.predecessors[blocks[succ].predecessor_offset + j] == n) { break; } } ZEND_ASSERT(j < blocks[succ].predecessors_count); q->sources[j] = p->ssa_var; } q = q->next; } } } } } ssa->vars_count = ssa_vars_count; j = blocks[n].children; while (j >= 0) { // FIXME: Tail call optimization? if (zend_ssa_rename(op_array, build_flags, ssa, var, j) != SUCCESS) return FAILURE; j = blocks[j].next_child; } if (tmp) { free_alloca(tmp, use_heap); } return SUCCESS; }
void zend_optimizer_pass3(zend_op_array *op_array) { zend_op *opline; zend_op *end = op_array->opcodes + op_array->last; zend_op **jmp_hitlist; int jmp_hitlist_count; int i; uint32_t opline_num = 0; ALLOCA_FLAG(use_heap); jmp_hitlist = (zend_op**)do_alloca(sizeof(zend_op*)*op_array->last, use_heap); opline = op_array->opcodes; while (opline < end) { jmp_hitlist_count = 0; switch (opline->opcode) { case ZEND_ADD: case ZEND_SUB: case ZEND_MUL: case ZEND_DIV: case ZEND_MOD: case ZEND_POW: case ZEND_CONCAT: case ZEND_SL: case ZEND_SR: case ZEND_BW_OR: case ZEND_BW_AND: case ZEND_BW_XOR: { zend_op *next_opline = opline + 1; while (next_opline < end && next_opline->opcode == ZEND_NOP) { ++next_opline; } if (next_opline >= end || next_opline->opcode != ZEND_ASSIGN) { break; } if ((opline->op2_type & (IS_VAR | IS_CV)) && opline->op2.var == next_opline->op1.var && (opline->opcode == ZEND_ADD || opline->opcode == ZEND_MUL || opline->opcode == ZEND_BW_OR || opline->opcode == ZEND_BW_AND || opline->opcode == ZEND_BW_XOR)) { /* change $i=expr+$i to $i=$i+expr so that the next * optimization works on it */ zend_uchar tmp_type = opline->op1_type; znode_op tmp = opline->op1; if (opline->opcode != ZEND_ADD || (opline->op1_type == IS_CONST && Z_TYPE(ZEND_OP1_LITERAL(opline)) != IS_ARRAY)) { /* protection from array add: $a = array + $a is not commutative! */ COPY_NODE(opline->op1, opline->op2); COPY_NODE(opline->op2, tmp); } } if ((opline->op1_type & (IS_VAR | IS_CV)) && opline->op1.var == next_opline->op1.var && opline->op1_type == next_opline->op1_type) { switch (opline->opcode) { case ZEND_ADD: opline->opcode = ZEND_ASSIGN_ADD; break; case ZEND_SUB: opline->opcode = ZEND_ASSIGN_SUB; break; case ZEND_MUL: opline->opcode = ZEND_ASSIGN_MUL; break; case ZEND_DIV: opline->opcode = ZEND_ASSIGN_DIV; break; case ZEND_MOD: opline->opcode = ZEND_ASSIGN_MOD; break; case ZEND_POW: opline->opcode = ZEND_ASSIGN_POW; break; case ZEND_CONCAT: opline->opcode = ZEND_ASSIGN_CONCAT; break; case ZEND_SL: opline->opcode = ZEND_ASSIGN_SL; break; case ZEND_SR: opline->opcode = ZEND_ASSIGN_SR; break; case ZEND_BW_OR: opline->opcode = ZEND_ASSIGN_BW_OR; break; case ZEND_BW_AND: opline->opcode = ZEND_ASSIGN_BW_AND; break; case ZEND_BW_XOR: opline->opcode = ZEND_ASSIGN_BW_XOR; break; } COPY_NODE(opline->result, next_opline->result); MAKE_NOP(next_opline); opline++; opline_num++; } } break; case ZEND_JMP: if (op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK) { break; } /* convert L: JMP L+1 to NOP */ if (ZEND_OP1_JMP_ADDR(opline) == opline + 1) { MAKE_NOP(opline); goto done_jmp_optimization; } /* convert JMP L1 ... L1: JMP L2 to JMP L2 .. L1: JMP L2 */ while (ZEND_OP1_JMP_ADDR(opline) < end && ZEND_OP1_JMP_ADDR(opline)->opcode == ZEND_JMP) { zend_op *target = ZEND_OP1_JMP_ADDR(opline); CHECK_JMP(target, done_jmp_optimization); ZEND_SET_OP_JMP_ADDR(opline, opline->op1, ZEND_OP1_JMP_ADDR(target)); } break; case ZEND_JMP_SET: case ZEND_COALESCE: if (op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK) { break; } while (ZEND_OP2_JMP_ADDR(opline) < end) { zend_op *target = ZEND_OP2_JMP_ADDR(opline); if (target->opcode == ZEND_JMP) { ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP1_JMP_ADDR(target)); } else { break; } } break; case ZEND_JMPZ: case ZEND_JMPNZ: if (op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK) { break; } while (ZEND_OP2_JMP_ADDR(opline) < end) { zend_op *target = ZEND_OP2_JMP_ADDR(opline); if (target->opcode == ZEND_JMP) { /* plain JMP */ /* JMPZ(X,L1), L1: JMP(L2) => JMPZ(X,L2), L1: JMP(L2) */ CHECK_JMP(target, done_jmp_optimization); ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP1_JMP_ADDR(target)); } else if (target->opcode == opline->opcode && SAME_VAR(opline->op1, target->op1)) { /* same opcode and same var as this opcode */ /* JMPZ(X,L1), L1: JMPZ(X,L2) => JMPZ(X,L2), L1: JMPZ(X,L2) */ CHECK_JMP2(target, done_jmp_optimization); ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP2_JMP_ADDR(target)); } else if (target->opcode == opline->opcode + 3 && SAME_VAR(opline->op1, target->op1)) { /* convert JMPZ(X,L1), L1: T JMPZ_EX(X,L2) to T = JMPZ_EX(X, L2) */ ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP2_JMP_ADDR(target)); opline->opcode += 3; COPY_NODE(opline->result, target->result); break; } else if (target->opcode == INV_COND(opline->opcode) && SAME_VAR(opline->op1, target->op1)) { /* convert JMPZ(X,L1), L1: JMPNZ(X,L2) to JMPZ(X,L1+1) */ ZEND_SET_OP_JMP_ADDR(opline, opline->op2, target + 1); break; } else if (target->opcode == INV_COND_EX(opline->opcode) && SAME_VAR(opline->op1, target->op1)) { /* convert JMPZ(X,L1), L1: T = JMPNZ_EX(X,L2) to T = JMPZ_EX(X,L1+1) */ ZEND_SET_OP_JMP_ADDR(opline, opline->op2, target + 1); opline->opcode += 3; COPY_NODE(opline->result, target->result); break; } else { break; } } break; case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: { zend_uchar T_type = opline->result_type; znode_op T = opline->result; if (op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK) { break; } /* convert L: T = JMPZ_EX X,L+1 to T = BOOL(X) */ /* convert L: T = JMPZ_EX T,L+1 to NOP */ if (ZEND_OP2_JMP_ADDR(opline) == opline + 1) { if (opline->op1.var == opline->result.var) { MAKE_NOP(opline); } else { opline->opcode = ZEND_BOOL; SET_UNUSED(opline->op2); } goto done_jmp_optimization; } while (ZEND_OP2_JMP_ADDR(opline) < end) { zend_op *target = ZEND_OP2_JMP_ADDR(opline); if (target->opcode == opline->opcode-3 && SAME_VAR(target->op1, T)) { /* convert T=JMPZ_EX(X,L1), L1: JMPZ(T,L2) to JMPZ_EX(X,L2) */ CHECK_JMP2(target, continue_jmp_ex_optimization); ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP2_JMP_ADDR(target)); } else if (target->opcode == opline->opcode && SAME_VAR(target->op1, T) && SAME_VAR(target->result, T)) { /* convert T=JMPZ_EX(X,L1), L1: T=JMPZ_EX(T,L2) to JMPZ_EX(X,L2) */ CHECK_JMP2(target, continue_jmp_ex_optimization); ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP2_JMP_ADDR(target)); } else if (target->opcode == ZEND_JMPZNZ && SAME_VAR(target->op1, T)) { /* Check for JMPZNZ with same cond variable */ zend_op *new_target; CHECK_JMP2(target, continue_jmp_ex_optimization); if (opline->opcode == ZEND_JMPZ_EX) { new_target = ZEND_OP2_JMP_ADDR(target); } else { /* JMPNZ_EX */ new_target = ZEND_OFFSET_TO_OPLINE(target, target->extended_value); } ZEND_SET_OP_JMP_ADDR(opline, opline->op2, new_target); } else if ((target->opcode == INV_EX_COND_EX(opline->opcode) || target->opcode == INV_EX_COND(opline->opcode)) && SAME_VAR(opline->op1, target->op1)) { /* convert JMPZ_EX(X,L1), L1: JMPNZ_EX(X,L2) to JMPZ_EX(X,L1+1) */ ZEND_SET_OP_JMP_ADDR(opline, opline->op2, target + 1); break; } else if (target->opcode == INV_EX_COND(opline->opcode) && SAME_VAR(target->op1, T)) { /* convert T=JMPZ_EX(X,L1), L1: JMPNZ(T,L2) to JMPZ_EX(X,L1+1) */ ZEND_SET_OP_JMP_ADDR(opline, opline->op2, target + 1); break; } else if (target->opcode == INV_EX_COND_EX(opline->opcode) && SAME_VAR(target->op1, T) && SAME_VAR(target->result, T)) { /* convert T=JMPZ_EX(X,L1), L1: T=JMPNZ_EX(T,L2) to JMPZ_EX(X,L1+1) */ ZEND_SET_OP_JMP_ADDR(opline, opline->op2, target + 1); break; } else if (target->opcode == ZEND_BOOL && SAME_VAR(opline->result, target->op1)) { /* convert Y = JMPZ_EX(X,L1), L1: Z = BOOL(Y) to Z = JMPZ_EX(X,L1+1) */ opline->result.var = target->result.var; ZEND_SET_OP_JMP_ADDR(opline, opline->op2, target + 1); break; } else { break; } } /* while */ continue_jmp_ex_optimization: break; #if 0 /* If Ti = JMPZ_EX(X, L) and Ti is not used, convert to JMPZ(X, L) */ { zend_op *op; for(op = opline+1; op<end; op++) { if(op->result_type == IS_TMP_VAR && op->result.var == opline->result.var) { break; /* can pass to part 2 */ } if(op->opcode == ZEND_JMP || op->opcode == ZEND_JMPZ || op->opcode == ZEND_JMPZ_EX || op->opcode == ZEND_JMPNZ || op->opcode == ZEND_JMPNZ_EX || op->opcode == ZEND_JMPZNZ || op->opcode == ZEND_CASE || op->opcode == ZEND_RETURN || op->opcode == ZEND_RETURN_BY_REF || op->opcode == ZEND_FAST_RET || op->opcode == ZEND_FE_FETCH_R || op->opcode == ZEND_FE_FETCH_RW || op->opcode == ZEND_EXIT) { break; } if(op->op1_type == IS_TMP_VAR && op->op1.var == opline->result.var) { goto done_jmp_optimization; } if(op->op2_type == IS_TMP_VAR && op->op2.var == opline->result.var) { goto done_jmp_optimization; } } /* for */ for(op = &op_array->opcodes[opline->op2.opline_num]; op<end; op++) { if(op->result_type == IS_TMP_VAR && op->result.var == opline->result.var) { break; /* can pass to optimization */ } if(op->opcode == ZEND_JMP || op->opcode == ZEND_JMPZ || op->opcode == ZEND_JMPZ_EX || op->opcode == ZEND_JMPNZ || op->opcode == ZEND_JMPNZ_EX || op->opcode == ZEND_JMPZNZ || op->opcode == ZEND_CASE || op->opcode == ZEND_RETURN || op->opcode == ZEND_RETURN_BY_REF || op->opcode == ZEND_FAST_RET || op->opcode == ZEND_FE_FETCH_R || op->opcode == ZEND_FE_FETCH_RW || op->opcode == ZEND_EXIT) { break; } if(op->op1_type == IS_TMP_VAR && op->op1.var == opline->result.var) { goto done_jmp_optimization; } if(op->op2_type == IS_TMP_VAR && op->op2.var == opline->result.var) { goto done_jmp_optimization; } } opline->opcode = opline->opcode-3; /* JMP_EX -> JMP */ SET_UNUSED(opline->result); break; } #endif } break; case ZEND_JMPZNZ: if (op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK) { break; } /* JMPZNZ(X,L1,L2), L1: JMP(L3) => JMPZNZ(X,L3,L2), L1: JMP(L3) */ while (ZEND_OP2_JMP_ADDR(opline) < end && ZEND_OP2_JMP_ADDR(opline)->opcode == ZEND_JMP) { zend_op *target = ZEND_OP2_JMP_ADDR(opline); CHECK_JMP(target, continue_jmpznz_optimization); ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP1_JMP_ADDR(target)); } continue_jmpznz_optimization: /* JMPZNZ(X,L1,L2), L2: JMP(L3) => JMPZNZ(X,L1,L3), L2: JMP(L3) */ while (ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value) < end && ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value)->opcode == ZEND_JMP) { zend_op *target = ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value); CHECK_JMP(target, done_jmp_optimization); opline->extended_value = ZEND_OPLINE_TO_OFFSET(opline, ZEND_OP1_JMP_ADDR(target)); } break; case ZEND_POST_INC: case ZEND_POST_DEC: { /* POST_INC, FREE => PRE_INC */ zend_op *next_op = opline + 1; if (next_op >= end) { break; } if (next_op->opcode == ZEND_FREE && next_op->op1.var == opline->result.var) { MAKE_NOP(next_op); opline->opcode -= 2; opline->result_type = IS_UNUSED; } } break; } done_jmp_optimization: opline++; opline_num++; } free_alloca(jmp_hitlist, use_heap); }
void zend_optimizer_nop_removal(zend_op_array *op_array) { zend_op *end, *opline; uint32_t new_count, i, shift; int j; uint32_t *shiftlist; ALLOCA_FLAG(use_heap); shiftlist = (uint32_t *)do_alloca(sizeof(uint32_t) * op_array->last, use_heap); i = new_count = shift = 0; end = op_array->opcodes + op_array->last; for (opline = op_array->opcodes; opline < end; opline++) { /* Kill JMP-over-NOP-s */ if (opline->opcode == ZEND_JMP && ZEND_OP1_JMP_ADDR(opline) > op_array->opcodes + i) { /* check if there are only NOPs under the branch */ zend_op *target = ZEND_OP1_JMP_ADDR(opline) - 1; while (target->opcode == ZEND_NOP) { target--; } if (target == opline) { /* only NOPs */ opline->opcode = ZEND_NOP; } } shiftlist[i++] = shift; if (opline->opcode == ZEND_NOP) { shift++; } else { if (shift) { zend_op *new_opline = op_array->opcodes + new_count; *new_opline = *opline; switch (new_opline->opcode) { case ZEND_JMP: case ZEND_FAST_CALL: ZEND_SET_OP_JMP_ADDR(new_opline, new_opline->op1, ZEND_OP1_JMP_ADDR(opline)); break; case ZEND_JMPZNZ: new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); /* break missing intentionally */ case ZEND_JMPZ: case ZEND_JMPNZ: case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: case ZEND_JMP_SET: case ZEND_COALESCE: case ZEND_ASSERT_CHECK: ZEND_SET_OP_JMP_ADDR(new_opline, new_opline->op2, ZEND_OP2_JMP_ADDR(opline)); break; case ZEND_CATCH: if (!opline->result.num) { new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); } break; case ZEND_DECLARE_ANON_CLASS: case ZEND_DECLARE_ANON_INHERITED_CLASS: case ZEND_FE_FETCH_R: case ZEND_FE_FETCH_RW: new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); break; } } new_count++; } } if (shift) { op_array->last = new_count; end = op_array->opcodes + op_array->last; /* update JMPs */ for (opline = op_array->opcodes; opline<end; opline++) { switch (opline->opcode) { case ZEND_JMP: case ZEND_FAST_CALL: ZEND_SET_OP_JMP_ADDR(opline, opline->op1, ZEND_OP1_JMP_ADDR(opline) - shiftlist[ZEND_OP1_JMP_ADDR(opline) - op_array->opcodes]); break; case ZEND_JMPZNZ: opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value) - shiftlist[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]); /* break missing intentionally */ case ZEND_JMPZ: case ZEND_JMPNZ: case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: case ZEND_JMP_SET: case ZEND_COALESCE: case ZEND_ASSERT_CHECK: ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP2_JMP_ADDR(opline) - shiftlist[ZEND_OP2_JMP_ADDR(opline) - op_array->opcodes]); break; case ZEND_DECLARE_ANON_CLASS: case ZEND_DECLARE_ANON_INHERITED_CLASS: case ZEND_FE_FETCH_R: case ZEND_FE_FETCH_RW: case ZEND_CATCH: opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value) - shiftlist[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]); break; } } /* update brk/cont array */ for (j = 0; j < op_array->last_live_range; j++) { op_array->live_range[j].start -= shiftlist[op_array->live_range[j].start]; op_array->live_range[j].end -= shiftlist[op_array->live_range[j].end]; } /* update try/catch array */ for (j = 0; j < op_array->last_try_catch; j++) { op_array->try_catch_array[j].try_op -= shiftlist[op_array->try_catch_array[j].try_op]; op_array->try_catch_array[j].catch_op -= shiftlist[op_array->try_catch_array[j].catch_op]; if (op_array->try_catch_array[j].finally_op) { op_array->try_catch_array[j].finally_op -= shiftlist[op_array->try_catch_array[j].finally_op]; op_array->try_catch_array[j].finally_end -= shiftlist[op_array->try_catch_array[j].finally_end]; } } /* update early binding list */ if (op_array->early_binding != (uint32_t)-1) { uint32_t *opline_num = &op_array->early_binding; do { *opline_num -= shiftlist[*opline_num]; opline_num = &ZEND_RESULT(&op_array->opcodes[*opline_num]).opline_num; } while (*opline_num != (uint32_t)-1); } } free_alloca(shiftlist, use_heap); }
static void zend_ssa_remove_nops(zend_op_array *op_array, zend_ssa *ssa) { zend_basic_block *blocks = ssa->cfg.blocks; zend_basic_block *end = blocks + ssa->cfg.blocks_count; zend_basic_block *b; zend_func_info *func_info; int j; uint32_t i; uint32_t target = 0; uint32_t *shiftlist; ALLOCA_FLAG(use_heap); shiftlist = (uint32_t *)do_alloca(sizeof(uint32_t) * op_array->last, use_heap); memset(shiftlist, 0, sizeof(uint32_t) * op_array->last); for (b = blocks; b < end; b++) { if (b->flags & (ZEND_BB_REACHABLE|ZEND_BB_UNREACHABLE_FREE)) { uint32_t end; if (b->flags & ZEND_BB_UNREACHABLE_FREE) { /* Only keep the FREE for the loop var */ ZEND_ASSERT(op_array->opcodes[b->start].opcode == ZEND_FREE || op_array->opcodes[b->start].opcode == ZEND_FE_FREE); b->len = 1; } end = b->start + b->len; i = b->start; b->start = target; while (i < end) { shiftlist[i] = i - target; if (EXPECTED(op_array->opcodes[i].opcode != ZEND_NOP) || /*keep NOP to support ZEND_VM_SMART_BRANCH */ (i > 0 && i + 1 < op_array->last && (op_array->opcodes[i+1].opcode == ZEND_JMPZ || op_array->opcodes[i+1].opcode == ZEND_JMPNZ) && zend_is_smart_branch(op_array->opcodes + i - 1))) { if (i != target) { op_array->opcodes[target] = op_array->opcodes[i]; ssa->ops[target] = ssa->ops[i]; } target++; } i++; } if (target != end && b->len != 0) { zend_op *opline; zend_op *new_opline; b->len = target - b->start; opline = op_array->opcodes + end - 1; if (opline->opcode == ZEND_NOP) { continue; } new_opline = op_array->opcodes + target - 1; zend_optimizer_migrate_jump(op_array, new_opline, opline); } } } if (target != op_array->last) { /* reset rest opcodes */ for (i = target; i < op_array->last; i++) { MAKE_NOP(op_array->opcodes + i); } /* update SSA variables */ for (j = 0; j < ssa->vars_count; j++) { if (ssa->vars[j].definition >= 0) { ssa->vars[j].definition -= shiftlist[ssa->vars[j].definition]; } if (ssa->vars[j].use_chain >= 0) { ssa->vars[j].use_chain -= shiftlist[ssa->vars[j].use_chain]; } } for (i = 0; i < op_array->last; i++) { if (ssa->ops[i].op1_use_chain >= 0) { ssa->ops[i].op1_use_chain -= shiftlist[ssa->ops[i].op1_use_chain]; } if (ssa->ops[i].op2_use_chain >= 0) { ssa->ops[i].op2_use_chain -= shiftlist[ssa->ops[i].op2_use_chain]; } if (ssa->ops[i].res_use_chain >= 0) { ssa->ops[i].res_use_chain -= shiftlist[ssa->ops[i].res_use_chain]; } } /* update branch targets */ for (b = blocks; b < end; b++) { if ((b->flags & ZEND_BB_REACHABLE) && b->len != 0) { zend_op *opline = op_array->opcodes + b->start + b->len - 1; zend_optimizer_shift_jump(op_array, opline, shiftlist); } } /* update brk/cont array */ for (j = 0; j < op_array->last_live_range; j++) { op_array->live_range[j].start -= shiftlist[op_array->live_range[j].start]; op_array->live_range[j].end -= shiftlist[op_array->live_range[j].end]; } /* update try/catch array */ for (j = 0; j < op_array->last_try_catch; j++) { op_array->try_catch_array[j].try_op -= shiftlist[op_array->try_catch_array[j].try_op]; op_array->try_catch_array[j].catch_op -= shiftlist[op_array->try_catch_array[j].catch_op]; if (op_array->try_catch_array[j].finally_op) { op_array->try_catch_array[j].finally_op -= shiftlist[op_array->try_catch_array[j].finally_op]; op_array->try_catch_array[j].finally_end -= shiftlist[op_array->try_catch_array[j].finally_end]; } } /* update early binding list */ if (op_array->early_binding != (uint32_t)-1) { uint32_t *opline_num = &op_array->early_binding; do { *opline_num -= shiftlist[*opline_num]; opline_num = &ZEND_RESULT(&op_array->opcodes[*opline_num]).opline_num; } while (*opline_num != (uint32_t)-1); } /* update call graph */ func_info = ZEND_FUNC_INFO(op_array); if (func_info) { zend_call_info *call_info = func_info->callee_info; while (call_info) { call_info->caller_init_opline -= shiftlist[call_info->caller_init_opline - op_array->opcodes]; call_info->caller_call_opline -= shiftlist[call_info->caller_call_opline - op_array->opcodes]; call_info = call_info->next_callee; } } op_array->last = target; } free_alloca(shiftlist, use_heap); }
static zend_string *browscap_convert_pattern(zend_string *pattern, int persistent) /* {{{ */ { size_t i, j=0; char *t; zend_string *res; char *lc_pattern; ALLOCA_FLAG(use_heap); res = zend_string_alloc(browscap_compute_regex_len(pattern), persistent); t = ZSTR_VAL(res); lc_pattern = do_alloca(ZSTR_LEN(pattern) + 1, use_heap); zend_str_tolower_copy(lc_pattern, ZSTR_VAL(pattern), ZSTR_LEN(pattern)); t[j++] = '~'; t[j++] = '^'; for (i = 0; i < ZSTR_LEN(pattern); i++, j++) { switch (lc_pattern[i]) { case '?': t[j] = '.'; break; case '*': t[j++] = '.'; t[j] = '*'; break; case '.': t[j++] = '\\'; t[j] = '.'; break; case '\\': t[j++] = '\\'; t[j] = '\\'; break; case '(': t[j++] = '\\'; t[j] = '('; break; case ')': t[j++] = '\\'; t[j] = ')'; break; case '~': t[j++] = '\\'; t[j] = '~'; break; case '+': t[j++] = '\\'; t[j] = '+'; break; default: t[j] = lc_pattern[i]; break; } } t[j++] = '$'; t[j++] = '~'; t[j]=0; ZSTR_LEN(res) = j; free_alloca(lc_pattern, use_heap); return res; }
ZEND_API zval *zend_get_constant_ex(zend_string *cname, zend_class_entry *scope, zend_ulong flags) { zend_constant *c; const char *colon; zend_class_entry *ce = NULL; zend_string *class_name; const char *name = cname->val; size_t name_len = cname->len; /* Skip leading \\ */ if (name[0] == '\\') { name += 1; name_len -= 1; cname = NULL; } if ((colon = zend_memrchr(name, ':', name_len)) && colon > name && (*(colon - 1) == ':')) { int class_name_len = colon - name - 1; size_t const_name_len = name_len - class_name_len - 2; zend_string *constant_name = zend_string_init(colon + 1, const_name_len, 0); char *lcname; zval *ret_constant = NULL; ALLOCA_FLAG(use_heap) class_name = zend_string_init(name, class_name_len, 0); lcname = do_alloca(class_name_len + 1, use_heap); zend_str_tolower_copy(lcname, name, class_name_len); if (!scope) { if (EG(current_execute_data)) { scope = EG(scope); } else { scope = CG(active_class_entry); } } if (class_name_len == sizeof("self")-1 && !memcmp(lcname, "self", sizeof("self")-1)) { if (UNEXPECTED(!scope)) { zend_error(E_EXCEPTION | E_ERROR, "Cannot access self:: when no class scope is active"); return NULL; } ce = scope; } else if (class_name_len == sizeof("parent")-1 && !memcmp(lcname, "parent", sizeof("parent")-1)) { if (UNEXPECTED(!scope)) { zend_error(E_EXCEPTION | E_ERROR, "Cannot access parent:: when no class scope is active"); return NULL; } else if (UNEXPECTED(!scope->parent)) { zend_error(E_EXCEPTION | E_ERROR, "Cannot access parent:: when current class scope has no parent"); return NULL; } else { ce = scope->parent; } } else if (class_name_len == sizeof("static")-1 && !memcmp(lcname, "static", sizeof("static")-1)) { ce = zend_get_called_scope(EG(current_execute_data)); if (UNEXPECTED(!ce)) { zend_error(E_EXCEPTION | E_ERROR, "Cannot access static:: when no class scope is active"); return NULL; } } else { ce = zend_fetch_class(class_name, flags); } free_alloca(lcname, use_heap); if (ce) { ret_constant = zend_hash_find(&ce->constants_table, constant_name); if (ret_constant == NULL) { if ((flags & ZEND_FETCH_CLASS_SILENT) == 0) { zend_error(E_EXCEPTION | E_ERROR, "Undefined class constant '%s::%s'", class_name->val, constant_name->val); zend_string_release(class_name); zend_string_free(constant_name); return NULL; } } else if (Z_ISREF_P(ret_constant)) { ret_constant = Z_REFVAL_P(ret_constant); } } zend_string_release(class_name); zend_string_free(constant_name); if (ret_constant && Z_CONSTANT_P(ret_constant)) { if (UNEXPECTED(zval_update_constant_ex(ret_constant, 1, ce) != SUCCESS)) { return NULL; } } return ret_constant; } /* non-class constant */ if ((colon = zend_memrchr(name, '\\', name_len)) != NULL) { /* compound constant name */ int prefix_len = colon - name; size_t const_name_len = name_len - prefix_len - 1; const char *constant_name = colon + 1; char *lcname; size_t lcname_len; ALLOCA_FLAG(use_heap) lcname_len = prefix_len + 1 + const_name_len; lcname = do_alloca(lcname_len + 1, use_heap); zend_str_tolower_copy(lcname, name, prefix_len); /* Check for namespace constant */ lcname[prefix_len] = '\\'; memcpy(lcname + prefix_len + 1, constant_name, const_name_len + 1); if ((c = zend_hash_str_find_ptr(EG(zend_constants), lcname, lcname_len)) == NULL) { /* try lowercase */ zend_str_tolower(lcname + prefix_len + 1, const_name_len); if ((c = zend_hash_str_find_ptr(EG(zend_constants), lcname, lcname_len)) != NULL) { if ((c->flags & CONST_CS) != 0) { c = NULL; } } } free_alloca(lcname, use_heap); if (c) { return &c->value; } /* name requires runtime resolution, need to check non-namespaced name */ if ((flags & IS_CONSTANT_UNQUALIFIED) != 0) { return zend_get_constant_str(constant_name, const_name_len); } return NULL; } if (cname) { return zend_get_constant(cname); } else { return zend_get_constant_str(name, name_len); } }
static void zend_ssa_remove_nops(zend_op_array *op_array, zend_ssa *ssa, zend_optimizer_ctx *ctx) { zend_basic_block *blocks = ssa->cfg.blocks; zend_basic_block *end = blocks + ssa->cfg.blocks_count; zend_basic_block *b; zend_func_info *func_info; int j; uint32_t i = 0; uint32_t target = 0; uint32_t *shiftlist; ALLOCA_FLAG(use_heap); shiftlist = (uint32_t *)do_alloca(sizeof(uint32_t) * op_array->last, use_heap); memset(shiftlist, 0, sizeof(uint32_t) * op_array->last); /* remove empty callee_info */ func_info = ZEND_FUNC_INFO(op_array); if (func_info) { zend_call_info **call_info = &func_info->callee_info; while ((*call_info)) { if ((*call_info)->caller_init_opline->opcode == ZEND_NOP) { *call_info = (*call_info)->next_callee; } else { call_info = &(*call_info)->next_callee; } } } for (b = blocks; b < end; b++) { if (b->flags & (ZEND_BB_REACHABLE|ZEND_BB_UNREACHABLE_FREE)) { uint32_t end; if (b->len) { while (i < b->start) { shiftlist[i] = i - target; i++; } if (b->flags & ZEND_BB_UNREACHABLE_FREE) { /* Only keep the FREE for the loop var */ ZEND_ASSERT(op_array->opcodes[b->start].opcode == ZEND_FREE || op_array->opcodes[b->start].opcode == ZEND_FE_FREE); b->len = 1; } end = b->start + b->len; b->start = target; while (i < end) { shiftlist[i] = i - target; if (EXPECTED(op_array->opcodes[i].opcode != ZEND_NOP) || /* Keep NOP to support ZEND_VM_SMART_BRANCH. Using "target-1" instead of * "i-1" here to check the last non-NOP instruction. */ (target > 0 && i + 1 < op_array->last && (op_array->opcodes[i+1].opcode == ZEND_JMPZ || op_array->opcodes[i+1].opcode == ZEND_JMPNZ) && zend_is_smart_branch(op_array->opcodes + target - 1))) { if (i != target) { op_array->opcodes[target] = op_array->opcodes[i]; ssa->ops[target] = ssa->ops[i]; ssa->cfg.map[target] = b - blocks; } target++; } i++; } if (target != end) { zend_op *opline; zend_op *new_opline; b->len = target - b->start; opline = op_array->opcodes + end - 1; if (opline->opcode == ZEND_NOP) { continue; } new_opline = op_array->opcodes + target - 1; zend_optimizer_migrate_jump(op_array, new_opline, opline); } } else { b->start = target; } } else { b->start = target; b->len = 0; } } if (target != op_array->last) { /* reset rest opcodes */ for (i = target; i < op_array->last; i++) { MAKE_NOP(op_array->opcodes + i); } /* update SSA variables */ for (j = 0; j < ssa->vars_count; j++) { if (ssa->vars[j].definition >= 0) { ssa->vars[j].definition -= shiftlist[ssa->vars[j].definition]; } if (ssa->vars[j].use_chain >= 0) { ssa->vars[j].use_chain -= shiftlist[ssa->vars[j].use_chain]; } } for (i = 0; i < op_array->last; i++) { if (ssa->ops[i].op1_use_chain >= 0) { ssa->ops[i].op1_use_chain -= shiftlist[ssa->ops[i].op1_use_chain]; } if (ssa->ops[i].op2_use_chain >= 0) { ssa->ops[i].op2_use_chain -= shiftlist[ssa->ops[i].op2_use_chain]; } if (ssa->ops[i].res_use_chain >= 0) { ssa->ops[i].res_use_chain -= shiftlist[ssa->ops[i].res_use_chain]; } } /* update branch targets */ for (b = blocks; b < end; b++) { if ((b->flags & ZEND_BB_REACHABLE) && b->len != 0) { zend_op *opline = op_array->opcodes + b->start + b->len - 1; zend_optimizer_shift_jump(op_array, opline, shiftlist); } } /* update try/catch array */ for (j = 0; j < op_array->last_try_catch; j++) { op_array->try_catch_array[j].try_op -= shiftlist[op_array->try_catch_array[j].try_op]; op_array->try_catch_array[j].catch_op -= shiftlist[op_array->try_catch_array[j].catch_op]; if (op_array->try_catch_array[j].finally_op) { op_array->try_catch_array[j].finally_op -= shiftlist[op_array->try_catch_array[j].finally_op]; op_array->try_catch_array[j].finally_end -= shiftlist[op_array->try_catch_array[j].finally_end]; } } /* update early binding list */ if (op_array->fn_flags & ZEND_ACC_EARLY_BINDING) { uint32_t *opline_num = &ctx->script->first_early_binding_opline; ZEND_ASSERT(op_array == &ctx->script->main_op_array); do { *opline_num -= shiftlist[*opline_num]; opline_num = &op_array->opcodes[*opline_num].result.opline_num; } while (*opline_num != (uint32_t)-1); } /* update call graph */ if (func_info) { zend_call_info *call_info = func_info->callee_info; while (call_info) { call_info->caller_init_opline -= shiftlist[call_info->caller_init_opline - op_array->opcodes]; call_info->caller_call_opline -= shiftlist[call_info->caller_call_opline - op_array->opcodes]; call_info = call_info->next_callee; } } op_array->last = target; } free_alloca(shiftlist, use_heap); }
static void zend_ssa_remove_nops(zend_op_array *op_array, zend_ssa *ssa) { zend_basic_block *blocks = ssa->cfg.blocks; zend_basic_block *end = blocks + ssa->cfg.blocks_count; zend_basic_block *b; zend_func_info *func_info; int j; uint32_t i; uint32_t target = 0; uint32_t *shiftlist; ALLOCA_FLAG(use_heap); shiftlist = (uint32_t *)do_alloca(sizeof(uint32_t) * op_array->last, use_heap); memset(shiftlist, 0, sizeof(uint32_t) * op_array->last); for (b = blocks; b < end; b++) { if (b->flags & ZEND_BB_REACHABLE) { i = b->start; b->start = target; while (i <= b->end) { if (EXPECTED(op_array->opcodes[i].opcode != ZEND_NOP) || /*keep NOP to support ZEND_VM_SMART_BRANCH */ (i > 0 && i + 1 < op_array->last && (op_array->opcodes[i+1].opcode == ZEND_JMPZ || op_array->opcodes[i+1].opcode == ZEND_JMPNZ) && (op_array->opcodes[i-1].opcode == ZEND_IS_IDENTICAL || op_array->opcodes[i-1].opcode == ZEND_IS_NOT_IDENTICAL || op_array->opcodes[i-1].opcode == ZEND_IS_EQUAL || op_array->opcodes[i-1].opcode == ZEND_IS_NOT_EQUAL || op_array->opcodes[i-1].opcode == ZEND_IS_SMALLER || op_array->opcodes[i-1].opcode == ZEND_IS_SMALLER_OR_EQUAL || op_array->opcodes[i-1].opcode == ZEND_CASE || op_array->opcodes[i-1].opcode == ZEND_ISSET_ISEMPTY_VAR || op_array->opcodes[i-1].opcode == ZEND_ISSET_ISEMPTY_STATIC_PROP || op_array->opcodes[i-1].opcode == ZEND_ISSET_ISEMPTY_DIM_OBJ || op_array->opcodes[i-1].opcode == ZEND_ISSET_ISEMPTY_PROP_OBJ || op_array->opcodes[i-1].opcode == ZEND_INSTANCEOF || op_array->opcodes[i-1].opcode == ZEND_TYPE_CHECK || op_array->opcodes[i-1].opcode == ZEND_DEFINED))) { if (i != target) { op_array->opcodes[target] = op_array->opcodes[i]; ssa->ops[target] = ssa->ops[i]; shiftlist[i] = i - target; } target++; } i++; } if (b->end != target - 1) { zend_op *opline; zend_op *new_opline; opline = op_array->opcodes + b->end; b->end = target - 1; new_opline = op_array->opcodes + b->end; switch (new_opline->opcode) { case ZEND_JMP: case ZEND_FAST_CALL: ZEND_SET_OP_JMP_ADDR(new_opline, new_opline->op1, ZEND_OP1_JMP_ADDR(opline)); break; case ZEND_JMPZNZ: new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); /* break missing intentionally */ case ZEND_JMPZ: case ZEND_JMPNZ: case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: case ZEND_JMP_SET: case ZEND_COALESCE: case ZEND_ASSERT_CHECK: ZEND_SET_OP_JMP_ADDR(new_opline, new_opline->op2, ZEND_OP2_JMP_ADDR(opline)); break; case ZEND_CATCH: if (!opline->result.num) { new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); } break; case ZEND_DECLARE_ANON_CLASS: case ZEND_DECLARE_ANON_INHERITED_CLASS: case ZEND_FE_FETCH_R: case ZEND_FE_FETCH_RW: new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); break; } } } } if (target != op_array->last) { /* reset rest opcodes */ for (i = target; i < op_array->last; i++) { MAKE_NOP(op_array->opcodes + i); } /* update SSA variables */ for (j = 0; j < ssa->vars_count; j++) { if (ssa->vars[j].definition >= 0) { ssa->vars[j].definition -= shiftlist[ssa->vars[j].definition]; } if (ssa->vars[j].use_chain >= 0) { ssa->vars[j].use_chain -= shiftlist[ssa->vars[j].use_chain]; } } for (i = 0; i < op_array->last; i++) { if (ssa->ops[i].op1_use_chain >= 0) { ssa->ops[i].op1_use_chain -= shiftlist[ssa->ops[i].op1_use_chain]; } if (ssa->ops[i].op2_use_chain >= 0) { ssa->ops[i].op2_use_chain -= shiftlist[ssa->ops[i].op2_use_chain]; } if (ssa->ops[i].res_use_chain >= 0) { ssa->ops[i].res_use_chain -= shiftlist[ssa->ops[i].res_use_chain]; } } /* update branch targets */ for (b = blocks; b < end; b++) { if (b->flags & ZEND_BB_REACHABLE) { zend_op *opline = op_array->opcodes + b->end; switch (opline->opcode) { case ZEND_JMP: case ZEND_FAST_CALL: ZEND_SET_OP_JMP_ADDR(opline, opline->op1, ZEND_OP1_JMP_ADDR(opline) - shiftlist[ZEND_OP1_JMP_ADDR(opline) - op_array->opcodes]); break; case ZEND_JMPZNZ: opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value) - shiftlist[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]); /* break missing intentionally */ case ZEND_JMPZ: case ZEND_JMPNZ: case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: case ZEND_JMP_SET: case ZEND_COALESCE: case ZEND_ASSERT_CHECK: ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP2_JMP_ADDR(opline) - shiftlist[ZEND_OP2_JMP_ADDR(opline) - op_array->opcodes]); break; case ZEND_DECLARE_ANON_CLASS: case ZEND_DECLARE_ANON_INHERITED_CLASS: case ZEND_FE_FETCH_R: case ZEND_FE_FETCH_RW: case ZEND_CATCH: opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value) - shiftlist[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]); break; } } } /* update brk/cont array */ for (j = 0; j < op_array->last_live_range; j++) { op_array->live_range[j].start -= shiftlist[op_array->live_range[j].start]; op_array->live_range[j].end -= shiftlist[op_array->live_range[j].end]; } /* update try/catch array */ for (j = 0; j < op_array->last_try_catch; j++) { op_array->try_catch_array[j].try_op -= shiftlist[op_array->try_catch_array[j].try_op]; op_array->try_catch_array[j].catch_op -= shiftlist[op_array->try_catch_array[j].catch_op]; if (op_array->try_catch_array[j].finally_op) { op_array->try_catch_array[j].finally_op -= shiftlist[op_array->try_catch_array[j].finally_op]; op_array->try_catch_array[j].finally_end -= shiftlist[op_array->try_catch_array[j].finally_end]; } } /* update early binding list */ if (op_array->early_binding != (uint32_t)-1) { uint32_t *opline_num = &op_array->early_binding; do { *opline_num -= shiftlist[*opline_num]; opline_num = &ZEND_RESULT(&op_array->opcodes[*opline_num]).opline_num; } while (*opline_num != (uint32_t)-1); } /* update call graph */ func_info = ZEND_FUNC_INFO(op_array); if (func_info) { zend_call_info *call_info = func_info->callee_info; while (call_info) { call_info->caller_init_opline -= shiftlist[call_info->caller_init_opline - op_array->opcodes]; call_info->caller_call_opline -= shiftlist[call_info->caller_call_opline - op_array->opcodes]; call_info = call_info->next_callee; } } op_array->last = target; } free_alloca(shiftlist, use_heap); }
int zend_cfg_identify_loops(const zend_op_array *op_array, zend_cfg *cfg, uint32_t *flags) /* {{{ */ { int i, j, k; int depth; zend_basic_block *blocks = cfg->blocks; int *dj_spanning_tree; zend_worklist work; int flag = ZEND_FUNC_NO_LOOPS; ALLOCA_FLAG(list_use_heap); ALLOCA_FLAG(tree_use_heap); ZEND_WORKLIST_ALLOCA(&work, cfg->blocks_count, list_use_heap); dj_spanning_tree = do_alloca(sizeof(int) * cfg->blocks_count, tree_use_heap); for (i = 0; i < cfg->blocks_count; i++) { dj_spanning_tree[i] = -1; } zend_worklist_push(&work, 0); while (zend_worklist_len(&work)) { next: i = zend_worklist_peek(&work); /* Visit blocks immediately dominated by i. */ for (j = blocks[i].children; j >= 0; j = blocks[j].next_child) { if (zend_worklist_push(&work, j)) { dj_spanning_tree[j] = i; goto next; } } /* Visit join edges. */ for (j = 0; j < 2; j++) { int succ = blocks[i].successors[j]; if (succ < 0) { continue; } else if (blocks[succ].idom == i) { continue; } else if (zend_worklist_push(&work, succ)) { dj_spanning_tree[succ] = i; goto next; } } zend_worklist_pop(&work); } /* Identify loops. See Sreedhar et al, "Identifying Loops Using DJ Graphs". */ for (i = 0, depth = 0; i < cfg->blocks_count; i++) { if (blocks[i].level > depth) { depth = blocks[i].level; } } for (; depth >= 0; depth--) { for (i = 0; i < cfg->blocks_count; i++) { if (blocks[i].level != depth) { continue; } zend_bitset_clear(work.visited, zend_bitset_len(cfg->blocks_count)); for (j = 0; j < blocks[i].predecessors_count; j++) { int pred = cfg->predecessors[blocks[i].predecessor_offset + j]; /* A join edge is one for which the predecessor does not immediately dominate the successor. */ if (blocks[i].idom == pred) { continue; } /* In a loop back-edge (back-join edge), the successor dominates the predecessor. */ if (dominates(blocks, i, pred)) { blocks[i].flags |= ZEND_BB_LOOP_HEADER; flag &= ~ZEND_FUNC_NO_LOOPS; zend_worklist_push(&work, pred); } else { /* Otherwise it's a cross-join edge. See if it's a branch to an ancestor on the dominator spanning tree. */ int dj_parent = pred; while (dj_parent >= 0) { if (dj_parent == i) { /* An sp-back edge: mark as irreducible. */ blocks[i].flags |= ZEND_BB_IRREDUCIBLE_LOOP; flag |= ZEND_FUNC_IRREDUCIBLE; flag &= ~ZEND_FUNC_NO_LOOPS; break; } else { dj_parent = dj_spanning_tree[dj_parent]; } } } } while (zend_worklist_len(&work)) { j = zend_worklist_pop(&work); if (blocks[j].loop_header < 0 && j != i) { blocks[j].loop_header = i; for (k = 0; k < blocks[j].predecessors_count; k++) { zend_worklist_push(&work, cfg->predecessors[blocks[j].predecessor_offset + k]); } } } } } free_alloca(dj_spanning_tree, tree_use_heap); ZEND_WORKLIST_FREE_ALLOCA(&work, list_use_heap); *flags |= flag; return SUCCESS; }
int zend_build_ssa(zend_arena **arena, const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa, uint32_t *func_flags) /* {{{ */ { zend_basic_block *blocks = ssa->cfg.blocks; zend_ssa_block *ssa_blocks; int blocks_count = ssa->cfg.blocks_count; uint32_t set_size; zend_bitset tmp, gen, in; int *var = NULL; int i, j, k, changed; zend_dfg dfg; ALLOCA_FLAG(dfg_use_heap); ALLOCA_FLAG(var_use_heap); ssa->rt_constants = (build_flags & ZEND_RT_CONSTANTS); ssa_blocks = zend_arena_calloc(arena, blocks_count, sizeof(zend_ssa_block)); if (!ssa_blocks) { return FAILURE; } ssa->blocks = ssa_blocks; /* Compute Variable Liveness */ dfg.vars = op_array->last_var + op_array->T; dfg.size = set_size = zend_bitset_len(dfg.vars); dfg.tmp = do_alloca((set_size * sizeof(zend_ulong)) * (blocks_count * 5 + 1), dfg_use_heap); memset(dfg.tmp, 0, (set_size * sizeof(zend_ulong)) * (blocks_count * 5 + 1)); dfg.gen = dfg.tmp + set_size; dfg.def = dfg.gen + set_size * blocks_count; dfg.use = dfg.def + set_size * blocks_count; dfg.in = dfg.use + set_size * blocks_count; dfg.out = dfg.in + set_size * blocks_count; if (zend_build_dfg(op_array, &ssa->cfg, &dfg, build_flags) != SUCCESS) { free_alloca(dfg.tmp, dfg_use_heap); return FAILURE; } if (build_flags & ZEND_SSA_DEBUG_LIVENESS) { zend_dump_dfg(op_array, &ssa->cfg, &dfg); } tmp = dfg.tmp; gen = dfg.gen; in = dfg.in; /* SSA construction, Step 1: Propagate "gen" sets in merge points */ do { changed = 0; for (j = 0; j < blocks_count; j++) { if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) { continue; } if (j >= 0 && (blocks[j].predecessors_count > 1 || j == 0)) { zend_bitset_copy(tmp, gen + (j * set_size), set_size); for (k = 0; k < blocks[j].predecessors_count; k++) { i = ssa->cfg.predecessors[blocks[j].predecessor_offset + k]; while (i != -1 && i != blocks[j].idom) { zend_bitset_union_with_intersection(tmp, tmp, gen + (i * set_size), in + (j * set_size), set_size); i = blocks[i].idom; } } if (!zend_bitset_equal(gen + (j * set_size), tmp, set_size)) { zend_bitset_copy(gen + (j * set_size), tmp, set_size); changed = 1; } } } } while (changed); /* SSA construction, Step 2: Phi placement based on Dominance Frontiers */ var = do_alloca(sizeof(int) * (op_array->last_var + op_array->T), var_use_heap); if (!var) { free_alloca(dfg.tmp, dfg_use_heap); return FAILURE; } zend_bitset_clear(tmp, set_size); for (j = 0; j < blocks_count; j++) { if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) { continue; } if (blocks[j].predecessors_count > 1) { zend_bitset_clear(tmp, set_size); if (blocks[j].flags & ZEND_BB_IRREDUCIBLE_LOOP) { /* Prevent any values from flowing into irreducible loops by replacing all incoming values with explicit phis. The register allocator depends on this property. */ zend_bitset_copy(tmp, in + (j * set_size), set_size); } else { for (k = 0; k < blocks[j].predecessors_count; k++) { i = ssa->cfg.predecessors[blocks[j].predecessor_offset + k]; while (i != -1 && i != blocks[j].idom) { zend_bitset_union_with_intersection(tmp, tmp, gen + (i * set_size), in + (j * set_size), set_size); i = blocks[i].idom; } } } if (!zend_bitset_empty(tmp, set_size)) { i = op_array->last_var + op_array->T; while (i > 0) { i--; if (zend_bitset_in(tmp, i)) { zend_ssa_phi *phi = zend_arena_calloc(arena, 1, sizeof(zend_ssa_phi) + sizeof(int) * blocks[j].predecessors_count + sizeof(void*) * blocks[j].predecessors_count); if (!phi) { goto failure; } phi->sources = (int*)(((char*)phi) + sizeof(zend_ssa_phi)); memset(phi->sources, 0xff, sizeof(int) * blocks[j].predecessors_count); phi->use_chains = (zend_ssa_phi**)(((char*)phi->sources) + sizeof(int) * ssa->cfg.blocks[j].predecessors_count); phi->pi = -1; phi->var = i; phi->ssa_var = -1; phi->next = ssa_blocks[j].phis; ssa_blocks[j].phis = phi; } } } } } place_essa_pis(arena, op_array, build_flags, ssa, &dfg); /* SSA construction, Step ?: Phi after Pi placement based on Dominance Frontiers */ for (j = 0; j < blocks_count; j++) { if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) { continue; } if (blocks[j].predecessors_count > 1) { zend_bitset_clear(tmp, set_size); if (blocks[j].flags & ZEND_BB_IRREDUCIBLE_LOOP) { /* Prevent any values from flowing into irreducible loops by replacing all incoming values with explicit phis. The register allocator depends on this property. */ zend_bitset_copy(tmp, in + (j * set_size), set_size); } else { for (k = 0; k < blocks[j].predecessors_count; k++) { i = ssa->cfg.predecessors[blocks[j].predecessor_offset + k]; while (i != -1 && i != blocks[j].idom) { zend_ssa_phi *p = ssa_blocks[i].phis; while (p) { if (p) { if (p->pi >= 0) { if (zend_bitset_in(in + (j * set_size), p->var) && !zend_bitset_in(gen + (i * set_size), p->var)) { zend_bitset_incl(tmp, p->var); } } else { zend_bitset_excl(tmp, p->var); } } p = p->next; } i = blocks[i].idom; } } } if (!zend_bitset_empty(tmp, set_size)) { i = op_array->last_var + op_array->T; while (i > 0) { i--; if (zend_bitset_in(tmp, i)) { zend_ssa_phi **pp = &ssa_blocks[j].phis; while (*pp) { if ((*pp)->pi <= 0 && (*pp)->var == i) { break; } pp = &(*pp)->next; } if (*pp == NULL) { zend_ssa_phi *phi = zend_arena_calloc(arena, 1, sizeof(zend_ssa_phi) + sizeof(int) * blocks[j].predecessors_count + sizeof(void*) * blocks[j].predecessors_count); if (!phi) { goto failure; } phi->sources = (int*)(((char*)phi) + sizeof(zend_ssa_phi)); memset(phi->sources, 0xff, sizeof(int) * blocks[j].predecessors_count); phi->use_chains = (zend_ssa_phi**)(((char*)phi->sources) + sizeof(int) * ssa->cfg.blocks[j].predecessors_count); phi->pi = -1; phi->var = i; phi->ssa_var = -1; phi->next = NULL; *pp = phi; } } } } } } if (build_flags & ZEND_SSA_DEBUG_PHI_PLACEMENT) { zend_dump_phi_placement(op_array, ssa); } /* SSA construction, Step 3: Renaming */ ssa->ops = zend_arena_calloc(arena, op_array->last, sizeof(zend_ssa_op)); memset(ssa->ops, 0xff, op_array->last * sizeof(zend_ssa_op)); memset(var, 0xff, (op_array->last_var + op_array->T) * sizeof(int)); /* Create uninitialized SSA variables for each CV */ for (j = 0; j < op_array->last_var; j++) { var[j] = j; } ssa->vars_count = op_array->last_var; if (zend_ssa_rename(op_array, build_flags, ssa, var, 0) != SUCCESS) { failure: free_alloca(var, var_use_heap); free_alloca(dfg.tmp, dfg_use_heap); return FAILURE; } free_alloca(var, var_use_heap); free_alloca(dfg.tmp, dfg_use_heap); return SUCCESS; }
void zend_optimizer_nop_removal(zend_op_array *op_array, zend_optimizer_ctx *ctx) { zend_op *end, *opline; uint32_t new_count, i, shift; int j; uint32_t *shiftlist; ALLOCA_FLAG(use_heap); shiftlist = (uint32_t *)do_alloca(sizeof(uint32_t) * op_array->last, use_heap); i = new_count = shift = 0; end = op_array->opcodes + op_array->last; for (opline = op_array->opcodes; opline < end; opline++) { /* Kill JMP-over-NOP-s */ if (opline->opcode == ZEND_JMP && ZEND_OP1_JMP_ADDR(opline) > op_array->opcodes + i) { /* check if there are only NOPs under the branch */ zend_op *target = ZEND_OP1_JMP_ADDR(opline) - 1; while (target->opcode == ZEND_NOP) { target--; } if (target == opline) { /* only NOPs */ opline->opcode = ZEND_NOP; } } shiftlist[i++] = shift; if (opline->opcode == ZEND_NOP) { shift++; } else { if (shift) { zend_op *new_opline = op_array->opcodes + new_count; *new_opline = *opline; zend_optimizer_migrate_jump(op_array, new_opline, opline); } new_count++; } } if (shift) { op_array->last = new_count; end = op_array->opcodes + op_array->last; /* update JMPs */ for (opline = op_array->opcodes; opline<end; opline++) { zend_optimizer_shift_jump(op_array, opline, shiftlist); } /* update brk/cont array */ for (j = 0; j < op_array->last_live_range; j++) { op_array->live_range[j].start -= shiftlist[op_array->live_range[j].start]; op_array->live_range[j].end -= shiftlist[op_array->live_range[j].end]; } /* update try/catch array */ for (j = 0; j < op_array->last_try_catch; j++) { op_array->try_catch_array[j].try_op -= shiftlist[op_array->try_catch_array[j].try_op]; op_array->try_catch_array[j].catch_op -= shiftlist[op_array->try_catch_array[j].catch_op]; if (op_array->try_catch_array[j].finally_op) { op_array->try_catch_array[j].finally_op -= shiftlist[op_array->try_catch_array[j].finally_op]; op_array->try_catch_array[j].finally_end -= shiftlist[op_array->try_catch_array[j].finally_end]; } } /* update early binding list */ if (op_array->fn_flags & ZEND_ACC_EARLY_BINDING) { uint32_t *opline_num = &ctx->script->first_early_binding_opline; ZEND_ASSERT(op_array == &ctx->script->main_op_array); do { *opline_num -= shiftlist[*opline_num]; opline_num = &op_array->opcodes[*opline_num].result.opline_num; } while (*opline_num != (uint32_t)-1); } } free_alloca(shiftlist, use_heap); }