Skip to content
Open
Show file tree
Hide file tree
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
2 changes: 2 additions & 0 deletions Zend/Optimizer/block_pass.c
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,8 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
src->opcode != ZEND_ADD_ARRAY_ELEMENT &&
src->opcode != ZEND_ADD_ARRAY_UNPACK &&
(src->opcode != ZEND_DECLARE_LAMBDA_FUNCTION ||
src == opline -1) &&
(src->opcode != ZEND_DECLARE_SCOPE_FUNC ||
src == opline -1)) {
src->result.var = opline->result.var;
VAR_SOURCE(opline->op1) = NULL;
Expand Down
23 changes: 22 additions & 1 deletion Zend/Optimizer/compact_literals.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,18 @@

typedef struct _literal_info {
uint8_t num_related;
bool pinned;
} literal_info;

#define LITERAL_INFO(n, related) do { \
info[n].num_related = (related); \
} while (0)

#define LITERAL_INFO_PIN(n) do { \
info[n].num_related = 1; \
info[n].pinned = true; \
} while (0)

static uint32_t add_static_slot(HashTable *hash,
zend_op_array *op_array,
uint32_t op1,
Expand Down Expand Up @@ -239,6 +245,14 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
}
}
break;
case ZEND_ENTER_SCOPE_FUNC: {
/* We must preserve the precise ordering of the scope fn literals mapping to CV offsets. */
uint32_t num_params = opline->op1.num;
for (uint32_t k = 0; k < num_params; k++) {
LITERAL_INFO_PIN(k);
}
break;
}
default:
if (opline->op1_type == IS_CONST) {
LITERAL_INFO(opline->op1.constant, 1);
Expand Down Expand Up @@ -315,7 +329,14 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
map[i] = l_true;
break;
case IS_LONG:
if (info[i].num_related == 1) {
if (info[i].pinned) {
map[i] = j;
if (i != j) {
op_array->literals[j] = op_array->literals[i];
info[j] = info[i];
}
j++;
} else if (info[i].num_related == 1) {
if ((pos = zend_hash_index_find(&hash, Z_LVAL(op_array->literals[i]))) != NULL) {
map[i] = Z_LVAL_P(pos);
} else {
Expand Down
61 changes: 61 additions & 0 deletions Zend/Optimizer/compact_vars.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,58 @@
#include "zend_bitset.h"
#include "zend_observer.h"

/* Recursively include scope fns for shared CVs; apply changes with vars_map, otherwise fill used_vars */
static void scope_fn_visit_parent_cvs(zend_op_array *scope_op, zend_bitset used_vars, const uint32_t *vars_map)
{
#define VISIT_CV(slot) if (slot##_type == IS_CV) { \
uint32_t old = VAR_NUM(slot.var); \
if (vars_map) slot.var = NUM_VAR(vars_map[old]); \
else zend_bitset_incl(used_vars, old); \
}
bool in_body = false;
for (uint32_t i = 0; i < scope_op->last; i++) {
zend_op *opline = &scope_op->opcodes[i];
if (opline->opcode == ZEND_ENTER_SCOPE_FUNC) {
in_body = true;
/* Mapping is pinned at literals[0..num_params). */
uint32_t num_params = opline->op1.num;
for (uint32_t j = 0; j < num_params; j++) {
zval *zv = &scope_op->literals[j];
uint32_t off = (uint32_t)Z_LVAL_P(zv);
if (vars_map) {
ZVAL_LONG(zv, NUM_VAR(vars_map[VAR_NUM(off)]));
} else {
zend_bitset_incl(used_vars, VAR_NUM(off));
}
}
continue;
}
if (!in_body) {
continue;
}
VISIT_CV(opline->op1);
VISIT_CV(opline->op2);
VISIT_CV(opline->result);
}
for (uint32_t i = 0; i < scope_op->num_dynamic_func_defs; i++) {
zend_op_array *nested = scope_op->dynamic_func_defs[i];
if (nested->fn_flags2 & ZEND_ACC2_SCOPE_FUNC) {
scope_fn_visit_parent_cvs(nested, used_vars, vars_map);
}
}
#undef VISIT_CV
}

/* 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;

/* Scope fn CVs are handled by the ancestor. Just skip this here. */
if (op_array->fn_flags2 & ZEND_ACC2_SCOPE_FUNC) {
return;
}

ALLOCA_FLAG(use_heap1);
ALLOCA_FLAG(use_heap2);
uint32_t used_vars_len = zend_bitset_len(op_array->last_var + op_array->T);
Expand Down Expand Up @@ -52,6 +99,13 @@ void zend_optimizer_compact_vars(zend_op_array *op_array) {
}
}

for (i = 0; i < (int)op_array->num_dynamic_func_defs; i++) {
zend_op_array *child = op_array->dynamic_func_defs[i];
if (child->fn_flags2 & ZEND_ACC2_SCOPE_FUNC) {
scope_fn_visit_parent_cvs(child, used_vars, NULL);
}
}

num_cvs = 0;
for (i = 0; i < op_array->last_var; i++) {
if (zend_bitset_in(used_vars, i)) {
Expand Down Expand Up @@ -93,6 +147,13 @@ void zend_optimizer_compact_vars(zend_op_array *op_array) {
}
}

for (i = 0; i < (int)op_array->num_dynamic_func_defs; i++) {
zend_op_array *child = op_array->dynamic_func_defs[i];
if (child->fn_flags2 & ZEND_ACC2_SCOPE_FUNC) {
scope_fn_visit_parent_cvs(child, NULL, vars_map);
}
}

/* Update CV name table */
if (num_cvs != op_array->last_var) {
if (num_cvs) {
Expand Down
5 changes: 5 additions & 0 deletions Zend/Optimizer/optimize_temp_vars_5.c
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ void zend_optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_c
}
}
}
/* DECLARE_SCOPE_FUNC op1 is never consumed, just used for itself.
* It has to simply exist for the whole function, so it gets its own TMP. */
if (opline->opcode == ZEND_DECLARE_SCOPE_FUNC) {
use_new_var = 1;
}
if (use_new_var) {
i = ++max;
zend_bitset_incl(taken_T, i);
Expand Down
6 changes: 6 additions & 0 deletions Zend/Optimizer/zend_call_graph.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,18 @@
static void zend_op_array_calc(zend_op_array *op_array, void *context)
{
zend_call_graph *call_graph = context;
if (op_array->fn_flags2 & ZEND_ACC2_SCOPE_FUNC) {
return;
}
call_graph->op_arrays_count++;
}

static void zend_op_array_collect(zend_op_array *op_array, void *context)
{
zend_call_graph *call_graph = context;
if (op_array->fn_flags2 & ZEND_ACC2_SCOPE_FUNC) {
return;
}
zend_func_info *func_info = call_graph->func_infos + call_graph->op_arrays_count;

ZEND_SET_FUNC_INFO(op_array, func_info);
Expand Down
3 changes: 3 additions & 0 deletions Zend/Optimizer/zend_inference.c
Original file line number Diff line number Diff line change
Expand Up @@ -4010,9 +4010,12 @@ static zend_always_inline zend_result _zend_update_type_info(
UPDATE_SSA_TYPE(MAY_BE_BOOL, ssa_op->result_def);
break;
case ZEND_DECLARE_LAMBDA_FUNCTION:
case ZEND_DECLARE_SCOPE_FUNC:
UPDATE_SSA_TYPE(MAY_BE_OBJECT|MAY_BE_RC1|MAY_BE_RCN, ssa_op->result_def);
UPDATE_SSA_OBJ_TYPE(zend_ce_closure, /* is_instanceof */ false, ssa_op->result_def);
break;
case ZEND_ENTER_SCOPE_FUNC:
break;
case ZEND_PRE_DEC_STATIC_PROP:
case ZEND_PRE_INC_STATIC_PROP:
case ZEND_POST_DEC_STATIC_PROP:
Expand Down
85 changes: 73 additions & 12 deletions Zend/Optimizer/zend_optimizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -1048,13 +1048,19 @@ zend_op *zend_optimizer_get_loop_var_def(const zend_op_array *op_array, zend_op
return NULL;
}

static zend_always_inline bool zend_op_array_is_scope_fn(const zend_op_array *op_array) {
return (op_array->fn_flags2 & ZEND_ACC2_SCOPE_FUNC) != 0;
}

static void zend_optimize(zend_op_array *op_array,
zend_optimizer_ctx *ctx)
{
if (op_array->type == ZEND_EVAL_CODE) {
return;
}

bool is_scope_fn = zend_op_array_is_scope_fn(op_array);

if (ctx->debug_level & ZEND_DUMP_BEFORE_OPTIMIZER) {
zend_dump_op_array(op_array, ZEND_DUMP_LIVE_RANGES, "before optimizer", NULL);
}
Expand Down Expand Up @@ -1106,9 +1112,13 @@ static void zend_optimize(zend_op_array *op_array,

/* pass 6:
* - DFA optimization
*/
*
* Skipped for scope-fn op_arrays: DCE on a body's CV write would
* eliminate a store the parent later reads, but single-op_array SSA
* cannot see that. Re-enabling needs cross-op-array DCE protection
* (treat every body CV write as live-out). */
if ((ZEND_OPTIMIZER_PASS_6 & ctx->optimization_level) &&
!(ZEND_OPTIMIZER_PASS_7 & ctx->optimization_level)) {
!(ZEND_OPTIMIZER_PASS_7 & ctx->optimization_level) && !is_scope_fn) {
zend_optimize_dfa(op_array, ctx);
if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_6) {
zend_dump_op_array(op_array, 0, "after pass 6", NULL);
Expand Down Expand Up @@ -1141,7 +1151,8 @@ static void zend_optimize(zend_op_array *op_array,
*/
if ((ZEND_OPTIMIZER_PASS_11 & ctx->optimization_level) &&
(!(ZEND_OPTIMIZER_PASS_6 & ctx->optimization_level) ||
!(ZEND_OPTIMIZER_PASS_7 & ctx->optimization_level))) {
!(ZEND_OPTIMIZER_PASS_7 & ctx->optimization_level) ||
is_scope_fn /* normally passes 6/7 would handle it, but scope fns are exempt */)) {
zend_optimizer_compact_literals(op_array, ctx);
if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_11) {
zend_dump_op_array(op_array, 0, "after pass 11", NULL);
Expand Down Expand Up @@ -1172,6 +1183,8 @@ static void zend_revert_pass_two(zend_op_array *op_array)

ZEND_ASSERT((op_array->fn_flags & ZEND_ACC_DONE_PASS_TWO) != 0);

zend_pass_two_revert_scope_fn_reservations(op_array);

opline = op_array->opcodes;
const zend_op *end = opline + op_array->last;
while (opline < end) {
Expand Down Expand Up @@ -1304,6 +1317,7 @@ static void zend_redo_pass_two(zend_op_array *op_array)
}

op_array->fn_flags |= ZEND_ACC_DONE_PASS_TWO;
zend_pass_two_install_scope_fn_reservations(op_array);
}

static void zend_redo_pass_two_ex(zend_op_array *op_array, const zend_ssa *ssa)
Expand Down Expand Up @@ -1446,23 +1460,35 @@ static void zend_redo_pass_two_ex(zend_op_array *op_array, const zend_ssa *ssa)
}

op_array->fn_flags |= ZEND_ACC_DONE_PASS_TWO;
zend_pass_two_install_scope_fn_reservations(op_array);
}

static void zend_optimize_op_array(zend_op_array *op_array,
zend_optimizer_ctx *ctx)
{
bool is_scope_fn = zend_op_array_is_scope_fn(op_array);

/* Make scope fns individually optimizeable first, before running optimizer. */
uint32_t saved_scope_T = is_scope_fn ? op_array->T : 0;
uint32_t saved_scope_ex_offset = is_scope_fn ? zend_unfixup_scope_func_self(op_array, saved_scope_T) : 0;

/* Revert pass_two() */
zend_revert_pass_two(op_array);

/* Do actual optimizations */
zend_optimize(op_array, ctx);

/* Redo pass_two() */
zend_redo_pass_two(op_array);

/* Live ranges must be recalculated before scope fn fixup. */
if (op_array->live_range) {
zend_recalc_live_ranges(op_array, NULL);
}

if (is_scope_fn) {
zend_refixup_scope_func_self(op_array, saved_scope_ex_offset, saved_scope_T);
}

/* Redo pass_two() */
zend_redo_pass_two(op_array);
}

static void zend_adjust_fcall_stack_size(const zend_op_array *op_array, const zend_optimizer_ctx *ctx)
Expand Down Expand Up @@ -1576,6 +1602,12 @@ static void step_optimize_op_array(zend_op_array *op_array, void *context) {
zend_optimize_op_array(op_array, (zend_optimizer_ctx *) context);
}

static void step_optimize_scope_fn_op_array(zend_op_array *op_array, void *context) {
if (zend_op_array_is_scope_fn(op_array)) {
zend_optimize_op_array(op_array, (zend_optimizer_ctx *) context);
}
}

static void step_adjust_fcall_stack_size(zend_op_array *op_array, void *context) {
zend_adjust_fcall_stack_size(op_array, (zend_optimizer_ctx *) context);
}
Expand Down Expand Up @@ -1634,6 +1666,12 @@ ZEND_API void zend_optimize_script(zend_script *script, zend_long optimization_l
}

for (i = 0; i < call_graph.op_arrays_count; i++) {
/* Single-op_array SSA can't see scope-fn children's reads of
* parent CVs. Subsequent DFA-based passes (DCE, SCCP) would
* treat parent CVs only used by a child as dead. */
if (call_graph.op_arrays[i]->fn_flags2 & ZEND_ACC2_HAS_TRACKED_TEMPORARIES) {
continue;
}
func_info = ZEND_FUNC_INFO(call_graph.op_arrays[i]);
if (func_info) {
if (zend_dfa_analyze_op_array(call_graph.op_arrays[i], &ctx, &func_info->ssa) == SUCCESS) {
Expand All @@ -1646,6 +1684,9 @@ ZEND_API void zend_optimize_script(zend_script *script, zend_long optimization_l

//TODO: perform inner-script inference???
for (i = 0; i < call_graph.op_arrays_count; i++) {
if (call_graph.op_arrays[i]->fn_flags2 & ZEND_ACC2_HAS_TRACKED_TEMPORARIES) {
continue;
}
func_info = ZEND_FUNC_INFO(call_graph.op_arrays[i]);
if (func_info) {
zend_dfa_optimize_op_array(call_graph.op_arrays[i], &ctx, &func_info->ssa, func_info->call_map);
Expand Down Expand Up @@ -1685,14 +1726,9 @@ ZEND_API void zend_optimize_script(zend_script *script, zend_long optimization_l
}
}

if (ZEND_OPTIMIZER_PASS_12 & optimization_level) {
for (i = 0; i < call_graph.op_arrays_count; i++) {
zend_adjust_fcall_stack_size_graph(call_graph.op_arrays[i]);
}
}

for (i = 0; i < call_graph.op_arrays_count; i++) {
op_array = call_graph.op_arrays[i];
zend_op *old_opcodes = op_array->opcodes;
func_info = ZEND_FUNC_INFO(op_array);
if (func_info && func_info->ssa.var_info) {
zend_redo_pass_two_ex(op_array, &func_info->ssa);
Expand All @@ -1705,6 +1741,31 @@ ZEND_API void zend_optimize_script(zend_script *script, zend_long optimization_l
zend_recalc_live_ranges(op_array, NULL);
}
}
/* Update offsets for pass 12, as zend_redo_pass_two may reallocate */
if (func_info && op_array->opcodes != old_opcodes) {
ptrdiff_t delta = op_array->opcodes - old_opcodes;
zend_call_info *call = func_info->callee_info;
while (call) {
if (call->caller_init_opline) {
call->caller_init_opline += delta;
}
if (call->caller_call_opline) {
call->caller_call_opline += delta;
}
call = call->next_callee;
}
}
}

/* zend_build_call_graph ignores scope fns, so explicitely optimize them here. */
zend_foreach_op_array(script, step_optimize_scope_fn_op_array, &ctx);

/* PASS_12 requires knowledge of the final callee->T to compute INIT_FCALL stack sizes;
* for scope fns these change during zend_redo_pass_two, hence we do this late. */
if (ZEND_OPTIMIZER_PASS_12 & optimization_level) {
for (i = 0; i < call_graph.op_arrays_count; i++) {
zend_adjust_fcall_stack_size_graph(call_graph.op_arrays[i]);
}
}

for (i = 0; i < call_graph.op_arrays_count; i++) {
Expand Down
2 changes: 1 addition & 1 deletion Zend/tests/function_arguments/gh20435_2.phpt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
--TEST--
GH-20435: ZEND_CALL_HAS_EXTRA_NAMED_PARAMS & magic methods in debug_backtrace_get_args()
GH-20435: ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS & magic methods in debug_backtrace_get_args()
--FILE--
<?php

Expand Down
Loading
Loading