Skip to content

Commit dba894d

Browse files
committed
Fix IN_ARRAY optimization
in_array() calls are compiled to frameless calls. Adjust the optimization appropriately. Luckily, frameless opcodes simplify the optimization quite a bit. Fixes GH-18050
1 parent 4b9c72f commit dba894d

File tree

2 files changed

+86
-74
lines changed

2 files changed

+86
-74
lines changed

Zend/Optimizer/dfa_pass.c

+19-74
Original file line numberDiff line numberDiff line change
@@ -407,40 +407,28 @@ int zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa)
407407
zend_call_info *call_info = func_info->callee_info;
408408

409409
do {
410-
if (call_info->caller_call_opline
411-
&& call_info->caller_call_opline->opcode == ZEND_DO_ICALL
410+
zend_op *op = call_info->caller_init_opline;
411+
412+
if ((op->opcode == ZEND_FRAMELESS_ICALL_2
413+
|| (op->opcode == ZEND_FRAMELESS_ICALL_3 && (op + 1)->op1_type == IS_CONST))
412414
&& call_info->callee_func
413-
&& zend_string_equals_literal(call_info->callee_func->common.function_name, "in_array")
414-
&& (call_info->caller_init_opline->extended_value == 2
415-
|| (call_info->caller_init_opline->extended_value == 3
416-
&& (call_info->caller_call_opline - 1)->opcode == ZEND_SEND_VAL
417-
&& (call_info->caller_call_opline - 1)->op1_type == IS_CONST))) {
418-
419-
zend_op *send_array;
420-
zend_op *send_needly;
415+
&& zend_string_equals_literal(call_info->callee_func->common.function_name, "in_array")) {
416+
421417
bool strict = 0;
418+
bool has_opdata = op->opcode == ZEND_FRAMELESS_ICALL_3;
422419
ZEND_ASSERT(!call_info->is_prototype);
423420

424-
if (call_info->caller_init_opline->extended_value == 2) {
425-
send_array = call_info->caller_call_opline - 1;
426-
send_needly = call_info->caller_call_opline - 2;
427-
} else {
428-
if (zend_is_true(CT_CONSTANT_EX(op_array, (call_info->caller_call_opline - 1)->op1.constant))) {
421+
if (has_opdata) {
422+
if (zend_is_true(CT_CONSTANT_EX(op_array, (op + 1)->op1.constant))) {
429423
strict = 1;
430424
}
431-
send_array = call_info->caller_call_opline - 2;
432-
send_needly = call_info->caller_call_opline - 3;
433425
}
434426

435-
if (send_array->opcode == ZEND_SEND_VAL
436-
&& send_array->op1_type == IS_CONST
437-
&& Z_TYPE_P(CT_CONSTANT_EX(op_array, send_array->op1.constant)) == IS_ARRAY
438-
&& (send_needly->opcode == ZEND_SEND_VAL
439-
|| send_needly->opcode == ZEND_SEND_VAR)
440-
) {
427+
if (op->op2_type == IS_CONST
428+
&& Z_TYPE_P(CT_CONSTANT_EX(op_array, op->op2.constant)) == IS_ARRAY) {
441429
bool ok = 1;
442430

443-
HashTable *src = Z_ARRVAL_P(CT_CONSTANT_EX(op_array, send_array->op1.constant));
431+
HashTable *src = Z_ARRVAL_P(CT_CONSTANT_EX(op_array, op->op2.constant));
444432
HashTable *dst;
445433
zval *val, tmp;
446434
zend_ulong idx;
@@ -471,59 +459,16 @@ int zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa)
471459
}
472460

473461
if (ok) {
474-
uint32_t op_num = send_needly - op_array->opcodes;
475-
zend_ssa_op *ssa_op = ssa->ops + op_num;
476-
477-
if (ssa_op->op1_use >= 0) {
478-
/* Reconstruct SSA */
479-
int var_num = ssa_op->op1_use;
480-
zend_ssa_var *var = ssa->vars + var_num;
481-
482-
ZEND_ASSERT(ssa_op->op1_def < 0);
483-
zend_ssa_unlink_use_chain(ssa, op_num, ssa_op->op1_use);
484-
ssa_op->op1_use = -1;
485-
ssa_op->op1_use_chain = -1;
486-
op_num = call_info->caller_call_opline - op_array->opcodes;
487-
ssa_op = ssa->ops + op_num;
488-
ssa_op->op1_use = var_num;
489-
ssa_op->op1_use_chain = var->use_chain;
490-
var->use_chain = op_num;
491-
}
492-
493462
ZVAL_ARR(&tmp, dst);
494463

495464
/* Update opcode */
496-
call_info->caller_call_opline->opcode = ZEND_IN_ARRAY;
497-
call_info->caller_call_opline->extended_value = strict;
498-
call_info->caller_call_opline->op1_type = send_needly->op1_type;
499-
call_info->caller_call_opline->op1.num = send_needly->op1.num;
500-
call_info->caller_call_opline->op2_type = IS_CONST;
501-
call_info->caller_call_opline->op2.constant = zend_optimizer_add_literal(op_array, &tmp);
502-
if (call_info->caller_init_opline->extended_value == 3) {
503-
MAKE_NOP(call_info->caller_call_opline - 1);
504-
}
505-
MAKE_NOP(call_info->caller_init_opline);
506-
MAKE_NOP(send_needly);
507-
MAKE_NOP(send_array);
508-
removed_ops++;
509-
510-
op_num = call_info->caller_call_opline - op_array->opcodes;
511-
ssa_op = ssa->ops + op_num;
512-
if (ssa_op->result_def >= 0) {
513-
int var = ssa_op->result_def;
514-
int use = ssa->vars[var].use_chain;
515-
516-
/* If the result is used only in a JMPZ/JMPNZ, replace result type with
517-
* IS_TMP_VAR, which will enable use of smart branches. Don't do this
518-
* in other cases, as not all opcodes support both VAR and TMP. */
519-
if (ssa->vars[var].phi_use_chain == NULL
520-
&& ssa->ops[use].op1_use == var
521-
&& ssa->ops[use].op1_use_chain == -1
522-
&& (op_array->opcodes[use].opcode == ZEND_JMPZ
523-
|| op_array->opcodes[use].opcode == ZEND_JMPNZ)) {
524-
call_info->caller_call_opline->result_type = IS_TMP_VAR;
525-
op_array->opcodes[use].op1_type = IS_TMP_VAR;
526-
}
465+
op->opcode = ZEND_IN_ARRAY;
466+
op->extended_value = strict;
467+
op->op2_type = IS_CONST;
468+
op->op2.constant = zend_optimizer_add_literal(op_array, &tmp);
469+
if (has_opdata) {
470+
MAKE_NOP(op + 1);
471+
removed_ops++;
527472
}
528473
}
529474
}

ext/opcache/tests/gh18050.phpt

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
--TEST--
2+
GH-18050: Frameless calls break IN_ARRAY optimization
3+
--EXTENSIONS--
4+
opcache
5+
--INI--
6+
opcache.enable_cli=1
7+
opcache.optimization_level=-1
8+
opcache.opt_debug_level=0x20000
9+
--FILE--
10+
<?php
11+
12+
function test($v) {
13+
$ary = ['x', 'y'];
14+
var_dump(in_array($v, $ary));
15+
var_dump(in_array($v, $ary, false));
16+
var_dump(in_array($v, $ary, true));
17+
18+
if (in_array($v, $ary, true)) {
19+
echo "True\n";
20+
}
21+
}
22+
test('x');
23+
test('z');
24+
25+
?>
26+
--EXPECTF--
27+
$_main:
28+
; (lines=%d, args=%d, vars=%d, tmps=%d)
29+
; (after optimizer)
30+
; %sgh18050.php:%s
31+
0000 INIT_FCALL 1 %d string("test")
32+
0001 SEND_VAL string("x") 1
33+
0002 DO_UCALL
34+
0003 INIT_FCALL 1 %d string("test")
35+
0004 SEND_VAL string("z") 1
36+
0005 DO_UCALL
37+
0006 RETURN int(1)
38+
39+
test:
40+
; (lines=%d, args=%d, vars=%d, tmps=%d)
41+
; (after optimizer)
42+
; %sgh18050.php:%s
43+
0000 CV0($v) = RECV 1
44+
0001 INIT_FCALL 1 %d string("var_dump")
45+
0002 T1 = IN_ARRAY 0 CV0($v) array(...)
46+
0003 SEND_VAL T1 1
47+
0004 DO_ICALL
48+
0005 INIT_FCALL 1 %d string("var_dump")
49+
0006 T1 = IN_ARRAY 0 CV0($v) array(...)
50+
0007 SEND_VAL T1 1
51+
0008 DO_ICALL
52+
0009 INIT_FCALL 1 %d string("var_dump")
53+
0010 T1 = IN_ARRAY 1 CV0($v) array(...)
54+
0011 SEND_VAL T1 1
55+
0012 DO_ICALL
56+
0013 T1 = IN_ARRAY 1 CV0($v) array(...)
57+
0014 JMPZ T1 0016
58+
0015 ECHO string("True
59+
")
60+
0016 RETURN null
61+
bool(true)
62+
bool(true)
63+
bool(true)
64+
True
65+
bool(false)
66+
bool(false)
67+
bool(false)

0 commit comments

Comments
 (0)