Skip to content

Commit 4dfd4df

Browse files
committed
Implement frameless type inference
1 parent 4cbfa32 commit 4dfd4df

10 files changed

+80
-12
lines changed

Zend/Optimizer/sccp.c

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1717,21 +1717,29 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o
17171717
case ZEND_FRAMELESS_ICALL_1:
17181718
case ZEND_FRAMELESS_ICALL_2:
17191719
case ZEND_FRAMELESS_ICALL_3: {
1720+
if (!ctx->call_map) {
1721+
SET_RESULT_BOT(result);
1722+
break;
1723+
}
1724+
17201725
zval *args[3] = {NULL};
1721-
switch (opline->opcode) {
1722-
case ZEND_FRAMELESS_ICALL_3: {
1726+
zend_call_info *call = ctx->call_map[opline - ctx->scdf.op_array->opcodes];
1727+
zend_function *func = call->callee_func;
1728+
uint32_t num_args = ZEND_FLF_NUM_ARGS(opline->opcode);
1729+
1730+
switch (num_args) {
1731+
case 3: {
17231732
zend_op *op_data = opline + 1;
17241733
args[2] = get_op1_value(ctx, op_data, &ctx->scdf.ssa->ops[op_data - ctx->scdf.op_array->opcodes]);
17251734
ZEND_FALLTHROUGH;
17261735
}
1727-
case ZEND_FRAMELESS_ICALL_2:
1736+
case 2:
17281737
args[1] = get_op2_value(ctx, opline, &ctx->scdf.ssa->ops[opline - ctx->scdf.op_array->opcodes]);
17291738
ZEND_FALLTHROUGH;
1730-
case ZEND_FRAMELESS_ICALL_1:
1739+
case 1:
17311740
args[0] = get_op1_value(ctx, opline, &ctx->scdf.ssa->ops[opline - ctx->scdf.op_array->opcodes]);
17321741
break;
17331742
}
1734-
uint32_t num_args = ZEND_FLF_NUM_ARGS(opline->opcode);
17351743
for (uint32_t i = 0; i < num_args; i++) {
17361744
if (!args[i]) {
17371745
SET_RESULT_BOT(result);
@@ -1743,7 +1751,6 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o
17431751
return;
17441752
}
17451753
}
1746-
zend_function *func = (*zend_frameless_function_0_functions_lists[num_args])[opline->extended_value];
17471754
if (ct_eval_func_call_ex(scdf->op_array, &zv, func, num_args, args) == SUCCESS) {
17481755
SET_RESULT(result, &zv);
17491756
zval_ptr_dtor_nogc(&zv);

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_dump.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,11 @@ ZEND_API void zend_dump_op(const zend_op_array *op_array, const zend_basic_block
449449
} else {
450450
fprintf(stderr, "OP_%d", (int)opline->opcode);
451451
}
452+
453+
if (ZEND_OP_IS_FRAMELESS_ICALL(opline->opcode)) {
454+
zend_function *func = ZEND_FLF_FUNC(opline);
455+
fprintf(stderr, "(%s)", ZSTR_VAL(func->common.function_name));
456+
}
452457

453458
if (ZEND_VM_EXT_NUM == (flags & ZEND_VM_EXT_MASK)) {
454459
fprintf(stderr, " %u", opline->extended_value);

Zend/Optimizer/zend_func_info.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ typedef struct _func_info_t {
5151

5252
static uint32_t zend_range_info(const zend_call_info *call_info, const zend_ssa *ssa)
5353
{
54+
ZEND_ASSERT(!call_info->is_frameless);
55+
5456
if (!call_info->send_unpack
5557
&& (call_info->num_args == 2 || call_info->num_args == 3)
5658
&& ssa

Zend/Optimizer/zend_inference.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3793,6 +3793,10 @@ static zend_always_inline zend_result _zend_update_type_info(
37933793
case ZEND_DO_ICALL:
37943794
case ZEND_DO_UCALL:
37953795
case ZEND_DO_FCALL_BY_NAME:
3796+
case ZEND_FRAMELESS_ICALL_0:
3797+
case ZEND_FRAMELESS_ICALL_1:
3798+
case ZEND_FRAMELESS_ICALL_2:
3799+
case ZEND_FRAMELESS_ICALL_3:
37963800
if (ssa_op->result_def >= 0) {
37973801
zend_func_info *func_info = ZEND_FUNC_INFO(op_array);
37983802
zend_call_info *call_info;

Zend/zend_builtin_functions.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1825,7 +1825,7 @@ ZEND_API void zend_fetch_debug_backtrace(zval *return_value, int skip_last, int
18251825
int num_args = ZEND_FLF_NUM_ARGS(opline->opcode);
18261826
stack_frame = zend_new_array(8);
18271827
zend_hash_real_init_mixed(stack_frame);
1828-
zend_function *func = (*zend_frameless_function_0_functions_lists[num_args])[opline->extended_value];
1828+
zend_function *func = ZEND_FLF_FUNC(opline);
18291829
zend_string *name = func->common.function_name;
18301830
ZVAL_STRINGL(&tmp, ZSTR_VAL(name), ZSTR_LEN(name));
18311831
_zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_FUNCTION), &tmp, 1);

Zend/zend_execute_API.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -569,8 +569,7 @@ ZEND_API zend_function *zend_active_function_ex(zend_execute_data *execute_data)
569569
if (ZEND_USER_CODE(func->type)) {
570570
const zend_op *op = EX(opline);
571571
if (ZEND_OP_IS_FRAMELESS_ICALL(op->opcode)) {
572-
uint32_t num_args = ZEND_FLF_NUM_ARGS(op->opcode);
573-
func = (*zend_frameless_function_0_functions_lists[num_args])[op->extended_value];
572+
func = ZEND_FLF_FUNC(op);
574573
}
575574
}
576575

Zend/zend_frameless_function.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#define ZEND_FRAMELESS_FUNCTION_NAME(name, arity) zdc_##name##_##arity
2929
#define ZEND_OP_IS_FRAMELESS_ICALL(opcode) ((opcode) >= ZEND_FRAMELESS_ICALL_0 && (opcode) <= ZEND_FRAMELESS_ICALL_3)
3030
#define ZEND_FLF_NUM_ARGS(opcode) ((opcode) - ZEND_FRAMELESS_ICALL_0)
31+
#define ZEND_FLF_FUNC(opline) ((*zend_frameless_function_0_functions_lists[ZEND_FLF_NUM_ARGS((opline)->opcode)])[(opline)->extended_value])
3132

3233
#define ZEND_FRAMELESS_FUNCTION(name, arity) \
3334
void ZEND_FRAMELESS_FUNCTION_NAME(name, arity)(DIRECT_FUNCTION_PARAMETERS_##arity)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
Type inference of frameless functions
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.optimization_level=-1
7+
opcache.opt_debug_level=0x20000
8+
--FILE--
9+
<?php
10+
function _strpos(string $str): int|false {
11+
return \strpos($str, 1, 1);
12+
}
13+
?>
14+
--EXPECTF--
15+
$_main:
16+
; (lines=1, args=0, vars=0, tmps=0)
17+
; (after optimizer)
18+
; %sinference_frameless.php:1-6
19+
0000 RETURN int(1)
20+
21+
_strpos:
22+
; (lines=4, args=1, vars=1, tmps=1)
23+
; (after optimizer)
24+
; %sinference_frameless.php:2-4
25+
0000 CV0($str) = RECV 1
26+
0001 T1 = FRAMELESS_ICALL_3(strpos) (SUB) CV0($str) int(1)
27+
0002 OP_DATA int(1)
28+
0003 RETURN T1

0 commit comments

Comments
 (0)