Skip to content

Commit 8a9d933

Browse files
committed
Implement stackless internal function calls
1 parent f91833d commit 8a9d933

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+3137
-827
lines changed

Zend/Optimizer/block_pass.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,6 +1017,7 @@ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array, zend_op
10171017
case ZEND_ASSERT_CHECK:
10181018
case ZEND_JMP_NULL:
10191019
case ZEND_BIND_INIT_STATIC_OR_JMP:
1020+
case ZEND_JMP_FRAMELESS:
10201021
ZEND_SET_OP_JMP_ADDR(opline, opline->op2, new_opcodes + blocks[b->successors[0]].start);
10211022
break;
10221023
case ZEND_CATCH:

Zend/Optimizer/compact_literals.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,7 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
770770
break;
771771
case ZEND_DECLARE_ANON_CLASS:
772772
case ZEND_DECLARE_CLASS_DELAYED:
773+
case ZEND_JMP_FRAMELESS:
773774
opline->extended_value = cache_size;
774775
cache_size += sizeof(void *);
775776
break;

Zend/Optimizer/dce.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ static inline bool may_have_side_effects(
146146
case ZEND_ASSERT_CHECK:
147147
case ZEND_JMP_NULL:
148148
case ZEND_BIND_INIT_STATIC_OR_JMP:
149+
case ZEND_JMP_FRAMELESS:
149150
/* For our purposes a jumps and branches are side effects. */
150151
return 1;
151152
case ZEND_BEGIN_SILENCE:
@@ -167,6 +168,10 @@ static inline bool may_have_side_effects(
167168
case ZEND_DO_FCALL_BY_NAME:
168169
case ZEND_DO_ICALL:
169170
case ZEND_DO_UCALL:
171+
case ZEND_FRAMELESS_ICALL_0:
172+
case ZEND_FRAMELESS_ICALL_1:
173+
case ZEND_FRAMELESS_ICALL_2:
174+
case ZEND_FRAMELESS_ICALL_3:
170175
/* For now assume all calls have side effects */
171176
return 1;
172177
case ZEND_RECV:

Zend/Optimizer/dfa_pass.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,7 @@ static void zend_ssa_replace_control_link(zend_op_array *op_array, zend_ssa *ssa
653653
case ZEND_ASSERT_CHECK:
654654
case ZEND_JMP_NULL:
655655
case ZEND_BIND_INIT_STATIC_OR_JMP:
656+
case ZEND_JMP_FRAMELESS:
656657
if (ZEND_OP2_JMP_ADDR(opline) == op_array->opcodes + old->start) {
657658
ZEND_SET_OP_JMP_ADDR(opline, opline->op2, op_array->opcodes + dst->start);
658659
}

Zend/Optimizer/pass1.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx)
355355
case ZEND_JMP_NULL:
356356
case ZEND_VERIFY_NEVER_TYPE:
357357
case ZEND_BIND_INIT_STATIC_OR_JMP:
358+
case ZEND_JMP_FRAMELESS:
358359
collect_constants = 0;
359360
break;
360361
}

Zend/Optimizer/sccp.c

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -789,14 +789,10 @@ static bool can_ct_eval_func_call(zend_function *func, zend_string *name, uint32
789789
/* The functions chosen here are simple to implement and either likely to affect a branch,
790790
* or just happened to be commonly used with constant operands in WP (need to test other
791791
* applications as well, of course). */
792-
static inline zend_result ct_eval_func_call(
793-
zend_op_array *op_array, zval *result, zend_string *name, uint32_t num_args, zval **args) {
792+
static inline zend_result ct_eval_func_call_ex(
793+
zend_op_array *op_array, zval *result, zend_function *func, uint32_t num_args, zval **args) {
794794
uint32_t i;
795-
zend_function *func = zend_hash_find_ptr(CG(function_table), name);
796-
if (!func || func->type != ZEND_INTERNAL_FUNCTION) {
797-
return FAILURE;
798-
}
799-
795+
zend_string *name = func->common.function_name;
800796
if (num_args == 1 && Z_TYPE_P(args[0]) == IS_STRING &&
801797
zend_optimizer_eval_special_func_call(result, name, Z_STR_P(args[0])) == SUCCESS) {
802798
return SUCCESS;
@@ -855,6 +851,15 @@ static inline zend_result ct_eval_func_call(
855851
return retval;
856852
}
857853

854+
static inline zend_result ct_eval_func_call(
855+
zend_op_array *op_array, zval *result, zend_string *name, uint32_t num_args, zval **args) {
856+
zend_function *func = zend_hash_find_ptr(CG(function_table), name);
857+
if (!func || func->type != ZEND_INTERNAL_FUNCTION) {
858+
return FAILURE;
859+
}
860+
return ct_eval_func_call_ex(op_array, result, func, num_args, args);
861+
}
862+
858863
#define SET_RESULT(op, zv) do { \
859864
if (ssa_op->op##_def >= 0) { \
860865
set_value(scdf, ctx, ssa_op->op##_def, zv); \
@@ -1708,6 +1713,51 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o
17081713
SET_RESULT_BOT(result);
17091714
break;
17101715
}
1716+
case ZEND_FRAMELESS_ICALL_0:
1717+
case ZEND_FRAMELESS_ICALL_1:
1718+
case ZEND_FRAMELESS_ICALL_2:
1719+
case ZEND_FRAMELESS_ICALL_3: {
1720+
/* We already know it can't be evaluated, don't bother checking again */
1721+
if (ssa_op->result_def < 0 || IS_BOT(&ctx->values[ssa_op->result_def])) {
1722+
break;
1723+
}
1724+
1725+
zval *args[3] = {NULL};
1726+
zend_function *func = ZEND_FLF_FUNC(opline);
1727+
uint32_t num_args = ZEND_FLF_NUM_ARGS(opline->opcode);
1728+
1729+
switch (num_args) {
1730+
case 3: {
1731+
zend_op *op_data = opline + 1;
1732+
args[2] = get_op1_value(ctx, op_data, &ctx->scdf.ssa->ops[op_data - ctx->scdf.op_array->opcodes]);
1733+
ZEND_FALLTHROUGH;
1734+
}
1735+
case 2:
1736+
args[1] = get_op2_value(ctx, opline, &ctx->scdf.ssa->ops[opline - ctx->scdf.op_array->opcodes]);
1737+
ZEND_FALLTHROUGH;
1738+
case 1:
1739+
args[0] = get_op1_value(ctx, opline, &ctx->scdf.ssa->ops[opline - ctx->scdf.op_array->opcodes]);
1740+
break;
1741+
}
1742+
for (uint32_t i = 0; i < num_args; i++) {
1743+
if (!args[i]) {
1744+
SET_RESULT_BOT(result);
1745+
return;
1746+
} else if (IS_BOT(args[i]) || IS_PARTIAL_ARRAY(args[i])) {
1747+
SET_RESULT_BOT(result);
1748+
return;
1749+
} else if (IS_TOP(args[i])) {
1750+
return;
1751+
}
1752+
}
1753+
if (ct_eval_func_call_ex(scdf->op_array, &zv, func, num_args, args) == SUCCESS) {
1754+
SET_RESULT(result, &zv);
1755+
zval_ptr_dtor_nogc(&zv);
1756+
break;
1757+
}
1758+
SET_RESULT_BOT(result);
1759+
break;
1760+
}
17111761
default:
17121762
{
17131763
/* If we have no explicit implementation return BOT */
@@ -2155,7 +2205,13 @@ static int try_remove_definition(sccp_ctx *ctx, int var_num, zend_ssa_var *var,
21552205
if (opline->opcode == ZEND_DO_ICALL) {
21562206
removed_ops = remove_call(ctx, opline, ssa_op) - 1;
21572207
} else {
2208+
bool has_op_data = opline->opcode == ZEND_FRAMELESS_ICALL_3;
21582209
zend_ssa_remove_instr(ssa, opline, ssa_op);
2210+
removed_ops++;
2211+
if (has_op_data) {
2212+
zend_ssa_remove_instr(ssa, opline + 1, ssa_op + 1);
2213+
removed_ops++;
2214+
}
21592215
}
21602216
ssa_op->result_def = var_num;
21612217
opline->opcode = ZEND_QM_ASSIGN;
@@ -2191,8 +2247,13 @@ static int try_remove_definition(sccp_ctx *ctx, int var_num, zend_ssa_var *var,
21912247
if (opline->opcode == ZEND_DO_ICALL) {
21922248
removed_ops = remove_call(ctx, opline, ssa_op);
21932249
} else {
2250+
bool has_op_data = opline->opcode == ZEND_FRAMELESS_ICALL_3;
21942251
zend_ssa_remove_instr(ssa, opline, ssa_op);
21952252
removed_ops++;
2253+
if (has_op_data) {
2254+
zend_ssa_remove_instr(ssa, opline + 1, ssa_op + 1);
2255+
removed_ops++;
2256+
}
21962257
}
21972258
}
21982259
} else if (ssa_op->op1_def == var_num) {

Zend/Optimizer/zend_call_graph.c

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ ZEND_API void zend_analyze_calls(zend_arena **arena, zend_script *script, uint32
7373
call_info->num_args = opline->extended_value;
7474
call_info->next_callee = func_info->callee_info;
7575
call_info->is_prototype = is_prototype;
76+
call_info->is_frameless = false;
7677
func_info->callee_info = call_info;
7778

7879
if (build_flags & ZEND_CALL_TREE) {
@@ -102,6 +103,24 @@ ZEND_API void zend_analyze_calls(zend_arena **arena, zend_script *script, uint32
102103
call_info = NULL;
103104
call++;
104105
break;
106+
case ZEND_FRAMELESS_ICALL_0:
107+
case ZEND_FRAMELESS_ICALL_1:
108+
case ZEND_FRAMELESS_ICALL_2:
109+
case ZEND_FRAMELESS_ICALL_3: {
110+
func = ZEND_FLF_FUNC(opline);
111+
zend_call_info *call_info = zend_arena_calloc(arena, 1, sizeof(zend_call_info));
112+
call_info->caller_op_array = op_array;
113+
call_info->caller_init_opline = opline;
114+
call_info->caller_call_opline = NULL;
115+
call_info->callee_func = func;
116+
call_info->num_args = ZEND_FLF_NUM_ARGS(opline->opcode);
117+
call_info->next_callee = func_info->callee_info;
118+
call_info->is_prototype = false;
119+
call_info->is_frameless = true;
120+
call_info->next_caller = NULL;
121+
func_info->callee_info = call_info;
122+
break;
123+
}
105124
case ZEND_DO_FCALL:
106125
case ZEND_DO_ICALL:
107126
case ZEND_DO_UCALL:
@@ -260,9 +279,11 @@ ZEND_API zend_call_info **zend_build_call_map(zend_arena **arena, zend_func_info
260279
if (call->caller_call_opline) {
261280
map[call->caller_call_opline - op_array->opcodes] = call;
262281
}
263-
for (i = 0; i < call->num_args; i++) {
264-
if (call->arg_info[i].opline) {
265-
map[call->arg_info[i].opline - op_array->opcodes] = call;
282+
if (!call->is_frameless) {
283+
for (i = 0; i < call->num_args; i++) {
284+
if (call->arg_info[i].opline) {
285+
map[call->arg_info[i].opline - op_array->opcodes] = call;
286+
}
266287
}
267288
}
268289
}

Zend/Optimizer/zend_call_graph.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ struct _zend_call_info {
3838
bool send_unpack; /* Parameters passed by SEND_UNPACK or SEND_ARRAY */
3939
bool named_args; /* Function has named arguments */
4040
bool is_prototype; /* An overridden child method may be called */
41+
bool is_frameless; /* A frameless function sends arguments through operands */
4142
int num_args; /* Number of arguments, excluding named and variadic arguments */
4243
zend_send_arg_info arg_info[1];
4344
};

Zend/Optimizer/zend_cfg.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@ ZEND_API void zend_build_cfg(zend_arena **arena, const zend_op_array *op_array,
370370
case ZEND_ASSERT_CHECK:
371371
case ZEND_JMP_NULL:
372372
case ZEND_BIND_INIT_STATIC_OR_JMP:
373+
case ZEND_JMP_FRAMELESS:
373374
BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes);
374375
BB_START(i + 1);
375376
break;
@@ -524,6 +525,7 @@ ZEND_API void zend_build_cfg(zend_arena **arena, const zend_op_array *op_array,
524525
case ZEND_ASSERT_CHECK:
525526
case ZEND_JMP_NULL:
526527
case ZEND_BIND_INIT_STATIC_OR_JMP:
528+
case ZEND_JMP_FRAMELESS:
527529
block->successors_count = 2;
528530
block->successors[0] = block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes];
529531
block->successors[1] = j + 1;

Zend/Optimizer/zend_dfg.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ static zend_always_inline void _zend_dfg_add_use_def_op(const zend_op_array *op_
122122
}
123123
break;
124124
case ZEND_ASSIGN_STATIC_PROP_OP:
125+
case ZEND_FRAMELESS_ICALL_3:
125126
next = opline + 1;
126127
if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
127128
var_num = EX_VAR_TO_NUM(next->op1.var);

0 commit comments

Comments
 (0)