Skip to content

Commit ad86528

Browse files
committed
Implement jumptable optimization
1 parent d1a012b commit ad86528

17 files changed

+719
-169
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
Switch on numeric strings
3+
--FILE--
4+
<?php
5+
6+
function test($value) {
7+
switch ($value) {
8+
case "01": return "01";
9+
case "1": return "1";
10+
11+
case " 2": return " 2";
12+
case "2": return "2";
13+
14+
case "10.0": return "10.0";
15+
case "1e1": return "1e1";
16+
17+
default: return "default";
18+
}
19+
}
20+
21+
var_dump(test("1"));
22+
var_dump(test("2"));
23+
var_dump(test("1e1"));
24+
25+
?>
26+
--EXPECT--
27+
string(2) "01"
28+
string(2) " 2"
29+
string(4) "10.0"

Zend/zend_compile.c

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2081,6 +2081,8 @@ static void zend_check_live_ranges(zend_op *opline) /* {{{ */
20812081
} else if (opline->opcode == ZEND_FAST_RET) {
20822082
/* fast_calls don't have to be destroyed */
20832083
} else if (opline->opcode == ZEND_CASE ||
2084+
opline->opcode == ZEND_SWITCH_LONG ||
2085+
opline->opcode == ZEND_SWITCH_STRING ||
20842086
opline->opcode == ZEND_FE_FETCH_R ||
20852087
opline->opcode == ZEND_FE_FETCH_RW ||
20862088
opline->opcode == ZEND_FE_FREE ||
@@ -4603,6 +4605,58 @@ void zend_compile_if(zend_ast *ast) /* {{{ */
46034605
}
46044606
/* }}} */
46054607

4608+
static zend_uchar determine_switch_jumptable_type(zend_ast_list *cases) {
4609+
uint32_t i;
4610+
zend_uchar common_type = IS_UNDEF;
4611+
for (i = 0; i < cases->children; i++) {
4612+
zend_ast *case_ast = cases->child[i];
4613+
zend_ast **cond_ast = &case_ast->child[0];
4614+
zval *cond_zv;
4615+
if (!case_ast->child[0]) {
4616+
/* Skip default clause */
4617+
continue;
4618+
}
4619+
4620+
zend_eval_const_expr(cond_ast);
4621+
if ((*cond_ast)->kind != ZEND_AST_ZVAL) {
4622+
/* Non-constant case */
4623+
return IS_UNDEF;
4624+
}
4625+
4626+
cond_zv = zend_ast_get_zval(case_ast->child[0]);
4627+
if (Z_TYPE_P(cond_zv) != IS_LONG && Z_TYPE_P(cond_zv) != IS_STRING) {
4628+
/* We only optimize switched on integers and strings */
4629+
return IS_UNDEF;
4630+
}
4631+
4632+
if (common_type == IS_UNDEF) {
4633+
common_type = Z_TYPE_P(cond_zv);
4634+
} else if (common_type != Z_TYPE_P(cond_zv)) {
4635+
/* Non-uniform case types */
4636+
return IS_UNDEF;
4637+
}
4638+
4639+
if (Z_TYPE_P(cond_zv) == IS_STRING
4640+
&& is_numeric_string(Z_STRVAL_P(cond_zv), Z_STRLEN_P(cond_zv), NULL, NULL, 0)) {
4641+
/* Numeric strings cannot be compared with a simple hash lookup */
4642+
return IS_UNDEF;
4643+
}
4644+
}
4645+
4646+
return common_type;
4647+
}
4648+
4649+
static zend_bool should_use_jumptable(zend_ast_list *cases, zend_uchar jumptable_type) {
4650+
/* Thresholds are chosen based on when the average switch time for equidistributed
4651+
* input becomes smaller when using the jumptable optimization. */
4652+
if (jumptable_type == IS_LONG) {
4653+
return cases->children >= 5;
4654+
} else {
4655+
ZEND_ASSERT(jumptable_type == IS_STRING);
4656+
return cases->children >= 2;
4657+
}
4658+
}
4659+
46064660
void zend_compile_switch(zend_ast *ast) /* {{{ */
46074661
{
46084662
zend_ast *expr_ast = ast->child[0];
@@ -4613,7 +4667,9 @@ void zend_compile_switch(zend_ast *ast) /* {{{ */
46134667

46144668
znode expr_node, case_node;
46154669
zend_op *opline;
4616-
uint32_t *jmpnz_opnums, opnum_default_jmp;
4670+
uint32_t *jmpnz_opnums, opnum_default_jmp, opnum_switch;
4671+
zend_uchar jumptable_type;
4672+
HashTable *jumptable = NULL;
46174673

46184674
zend_compile_expr(&expr_node, expr_ast);
46194675

@@ -4622,6 +4678,24 @@ void zend_compile_switch(zend_ast *ast) /* {{{ */
46224678
case_node.op_type = IS_TMP_VAR;
46234679
case_node.u.op.var = get_temporary_variable(CG(active_op_array));
46244680

4681+
jumptable_type = determine_switch_jumptable_type(cases);
4682+
if (jumptable_type != IS_UNDEF && should_use_jumptable(cases, jumptable_type)) {
4683+
znode jumptable_op;
4684+
4685+
ALLOC_HASHTABLE(jumptable);
4686+
zend_hash_init(jumptable, cases->children, NULL, NULL, 0);
4687+
jumptable_op.op_type = IS_CONST;
4688+
ZVAL_ARR(&jumptable_op.u.constant, jumptable);
4689+
4690+
opline = zend_emit_op(NULL,
4691+
jumptable_type == IS_LONG ? ZEND_SWITCH_LONG : ZEND_SWITCH_STRING,
4692+
&expr_node, &jumptable_op);
4693+
if (opline->op1_type == IS_CONST) {
4694+
zval_copy_ctor(CT_CONSTANT(opline->op1));
4695+
}
4696+
opnum_switch = opline - CG(active_op_array)->opcodes;
4697+
}
4698+
46254699
jmpnz_opnums = safe_emalloc(sizeof(uint32_t), cases->children, 0);
46264700
for (i = 0; i < cases->children; ++i) {
46274701
zend_ast *case_ast = cases->child[i];
@@ -4666,15 +4740,39 @@ void zend_compile_switch(zend_ast *ast) /* {{{ */
46664740

46674741
if (cond_ast) {
46684742
zend_update_jump_target_to_next(jmpnz_opnums[i]);
4743+
4744+
if (jumptable) {
4745+
zval *cond_zv = zend_ast_get_zval(cond_ast);
4746+
zval jmp_target;
4747+
ZVAL_LONG(&jmp_target, get_next_op_number(CG(active_op_array)));
4748+
4749+
ZEND_ASSERT(Z_TYPE_P(cond_zv) == jumptable_type);
4750+
if (Z_TYPE_P(cond_zv) == IS_LONG) {
4751+
zend_hash_index_add(jumptable, Z_LVAL_P(cond_zv), &jmp_target);
4752+
} else {
4753+
ZEND_ASSERT(Z_TYPE_P(cond_zv) == IS_STRING);
4754+
zend_hash_add(jumptable, Z_STR_P(cond_zv), &jmp_target);
4755+
}
4756+
}
46694757
} else {
46704758
zend_update_jump_target_to_next(opnum_default_jmp);
4759+
4760+
if (jumptable) {
4761+
opline = &CG(active_op_array)->opcodes[opnum_switch];
4762+
opline->extended_value = get_next_op_number(CG(active_op_array));
4763+
}
46714764
}
46724765

46734766
zend_compile_stmt(stmt_ast);
46744767
}
46754768

46764769
if (!has_default_case) {
46774770
zend_update_jump_target_to_next(opnum_default_jmp);
4771+
4772+
if (jumptable) {
4773+
opline = &CG(active_op_array)->opcodes[opnum_switch];
4774+
opline->extended_value = get_next_op_number(CG(active_op_array));
4775+
}
46784776
}
46794777

46804778
zend_end_loop(get_next_op_number(CG(active_op_array)), &expr_node);

Zend/zend_opcode.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,19 @@ ZEND_API int pass_two(zend_op_array *op_array)
670670
opline->opcode = ZEND_GENERATOR_RETURN;
671671
}
672672
break;
673+
case ZEND_SWITCH_LONG:
674+
case ZEND_SWITCH_STRING:
675+
{
676+
/* absolute indexes to relative offsets */
677+
HashTable *jumptable = Z_ARRVAL_P(CT_CONSTANT(opline->op2));
678+
zval *zv;
679+
ZEND_HASH_FOREACH_VAL(jumptable, zv) {
680+
Z_LVAL_P(zv) = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, Z_LVAL_P(zv));
681+
} ZEND_HASH_FOREACH_END();
682+
683+
opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, opline->extended_value);
684+
break;
685+
}
673686
}
674687
if (opline->op1_type == IS_CONST) {
675688
ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, opline->op1);

Zend/zend_vm_def.h

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8043,6 +8043,64 @@ ZEND_VM_HANDLER(51, ZEND_MAKE_REF, VAR|CV, UNUSED)
80438043
ZEND_VM_NEXT_OPCODE();
80448044
}
80458045

8046+
ZEND_VM_HANDLER(187, ZEND_SWITCH_LONG, CONST|TMPVAR|CV, CONST, JMP_ADDR)
8047+
{
8048+
USE_OPLINE
8049+
zend_free_op free_op1, free_op2;
8050+
zval *op, *jump_zv;
8051+
HashTable *jumptable;
8052+
8053+
op = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
8054+
jumptable = Z_ARRVAL_P(GET_OP2_ZVAL_PTR(BP_VAR_R));
8055+
8056+
if (Z_TYPE_P(op) != IS_LONG) {
8057+
ZVAL_DEREF(op);
8058+
if (Z_TYPE_P(op) != IS_LONG) {
8059+
/* Wrong type, fall back to ZEND_CASE chain */
8060+
ZEND_VM_NEXT_OPCODE();
8061+
}
8062+
}
8063+
8064+
jump_zv = zend_hash_index_find(jumptable, Z_LVAL_P(op));
8065+
if (jump_zv != NULL) {
8066+
ZEND_VM_SET_RELATIVE_OPCODE(opline, Z_LVAL_P(jump_zv));
8067+
ZEND_VM_CONTINUE();
8068+
} else {
8069+
/* default */
8070+
ZEND_VM_SET_RELATIVE_OPCODE(opline, opline->extended_value);
8071+
ZEND_VM_CONTINUE();
8072+
}
8073+
}
8074+
8075+
ZEND_VM_HANDLER(188, ZEND_SWITCH_STRING, CONST|TMPVAR|CV, CONST, JMP_ADDR)
8076+
{
8077+
USE_OPLINE
8078+
zend_free_op free_op1, free_op2;
8079+
zval *op, *jump_zv;
8080+
HashTable *jumptable;
8081+
8082+
op = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
8083+
jumptable = Z_ARRVAL_P(GET_OP2_ZVAL_PTR(BP_VAR_R));
8084+
8085+
if (Z_TYPE_P(op) != IS_STRING) {
8086+
ZVAL_DEREF(op);
8087+
if (Z_TYPE_P(op) != IS_STRING) {
8088+
/* Wrong type, fall back to ZEND_CASE chain */
8089+
ZEND_VM_NEXT_OPCODE();
8090+
}
8091+
}
8092+
8093+
jump_zv = zend_hash_find(jumptable, Z_STR_P(op));
8094+
if (jump_zv != NULL) {
8095+
ZEND_VM_SET_RELATIVE_OPCODE(opline, Z_LVAL_P(jump_zv));
8096+
ZEND_VM_CONTINUE();
8097+
} else {
8098+
/* default */
8099+
ZEND_VM_SET_RELATIVE_OPCODE(opline, opline->extended_value);
8100+
ZEND_VM_CONTINUE();
8101+
}
8102+
}
8103+
80468104
ZEND_VM_TYPE_SPEC_HANDLER(ZEND_ADD, (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG && op2_info == MAY_BE_LONG), ZEND_ADD_LONG_NO_OVERFLOW, CONST|TMPVARCV, CONST|TMPVARCV, SPEC(NO_CONST_CONST,COMMUTATIVE))
80478105
{
80488106
USE_OPLINE

0 commit comments

Comments
 (0)