summaryrefslogtreecommitdiff
path: root/Zend/zend_compile.c
diff options
context:
space:
mode:
Diffstat (limited to 'Zend/zend_compile.c')
-rw-r--r--Zend/zend_compile.c100
1 files changed, 99 insertions, 1 deletions
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index ab6e23d7df..e760676e6f 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -2081,6 +2081,8 @@ static void zend_check_live_ranges(zend_op *opline) /* {{{ */
} else if (opline->opcode == ZEND_FAST_RET) {
/* fast_calls don't have to be destroyed */
} else if (opline->opcode == ZEND_CASE ||
+ opline->opcode == ZEND_SWITCH_LONG ||
+ opline->opcode == ZEND_SWITCH_STRING ||
opline->opcode == ZEND_FE_FETCH_R ||
opline->opcode == ZEND_FE_FETCH_RW ||
opline->opcode == ZEND_FE_FREE ||
@@ -4603,6 +4605,58 @@ void zend_compile_if(zend_ast *ast) /* {{{ */
}
/* }}} */
+static zend_uchar determine_switch_jumptable_type(zend_ast_list *cases) {
+ uint32_t i;
+ zend_uchar common_type = IS_UNDEF;
+ for (i = 0; i < cases->children; i++) {
+ zend_ast *case_ast = cases->child[i];
+ zend_ast **cond_ast = &case_ast->child[0];
+ zval *cond_zv;
+ if (!case_ast->child[0]) {
+ /* Skip default clause */
+ continue;
+ }
+
+ zend_eval_const_expr(cond_ast);
+ if ((*cond_ast)->kind != ZEND_AST_ZVAL) {
+ /* Non-constant case */
+ return IS_UNDEF;
+ }
+
+ cond_zv = zend_ast_get_zval(case_ast->child[0]);
+ if (Z_TYPE_P(cond_zv) != IS_LONG && Z_TYPE_P(cond_zv) != IS_STRING) {
+ /* We only optimize switched on integers and strings */
+ return IS_UNDEF;
+ }
+
+ if (common_type == IS_UNDEF) {
+ common_type = Z_TYPE_P(cond_zv);
+ } else if (common_type != Z_TYPE_P(cond_zv)) {
+ /* Non-uniform case types */
+ return IS_UNDEF;
+ }
+
+ if (Z_TYPE_P(cond_zv) == IS_STRING
+ && is_numeric_string(Z_STRVAL_P(cond_zv), Z_STRLEN_P(cond_zv), NULL, NULL, 0)) {
+ /* Numeric strings cannot be compared with a simple hash lookup */
+ return IS_UNDEF;
+ }
+ }
+
+ return common_type;
+}
+
+static zend_bool should_use_jumptable(zend_ast_list *cases, zend_uchar jumptable_type) {
+ /* Thresholds are chosen based on when the average switch time for equidistributed
+ * input becomes smaller when using the jumptable optimization. */
+ if (jumptable_type == IS_LONG) {
+ return cases->children >= 5;
+ } else {
+ ZEND_ASSERT(jumptable_type == IS_STRING);
+ return cases->children >= 2;
+ }
+}
+
void zend_compile_switch(zend_ast *ast) /* {{{ */
{
zend_ast *expr_ast = ast->child[0];
@@ -4613,7 +4667,9 @@ void zend_compile_switch(zend_ast *ast) /* {{{ */
znode expr_node, case_node;
zend_op *opline;
- uint32_t *jmpnz_opnums, opnum_default_jmp;
+ uint32_t *jmpnz_opnums, opnum_default_jmp, opnum_switch;
+ zend_uchar jumptable_type;
+ HashTable *jumptable = NULL;
zend_compile_expr(&expr_node, expr_ast);
@@ -4622,6 +4678,24 @@ void zend_compile_switch(zend_ast *ast) /* {{{ */
case_node.op_type = IS_TMP_VAR;
case_node.u.op.var = get_temporary_variable(CG(active_op_array));
+ jumptable_type = determine_switch_jumptable_type(cases);
+ if (jumptable_type != IS_UNDEF && should_use_jumptable(cases, jumptable_type)) {
+ znode jumptable_op;
+
+ ALLOC_HASHTABLE(jumptable);
+ zend_hash_init(jumptable, cases->children, NULL, NULL, 0);
+ jumptable_op.op_type = IS_CONST;
+ ZVAL_ARR(&jumptable_op.u.constant, jumptable);
+
+ opline = zend_emit_op(NULL,
+ jumptable_type == IS_LONG ? ZEND_SWITCH_LONG : ZEND_SWITCH_STRING,
+ &expr_node, &jumptable_op);
+ if (opline->op1_type == IS_CONST) {
+ zval_copy_ctor(CT_CONSTANT(opline->op1));
+ }
+ opnum_switch = opline - CG(active_op_array)->opcodes;
+ }
+
jmpnz_opnums = safe_emalloc(sizeof(uint32_t), cases->children, 0);
for (i = 0; i < cases->children; ++i) {
zend_ast *case_ast = cases->child[i];
@@ -4666,8 +4740,27 @@ void zend_compile_switch(zend_ast *ast) /* {{{ */
if (cond_ast) {
zend_update_jump_target_to_next(jmpnz_opnums[i]);
+
+ if (jumptable) {
+ zval *cond_zv = zend_ast_get_zval(cond_ast);
+ zval jmp_target;
+ ZVAL_LONG(&jmp_target, get_next_op_number(CG(active_op_array)));
+
+ ZEND_ASSERT(Z_TYPE_P(cond_zv) == jumptable_type);
+ if (Z_TYPE_P(cond_zv) == IS_LONG) {
+ zend_hash_index_add(jumptable, Z_LVAL_P(cond_zv), &jmp_target);
+ } else {
+ ZEND_ASSERT(Z_TYPE_P(cond_zv) == IS_STRING);
+ zend_hash_add(jumptable, Z_STR_P(cond_zv), &jmp_target);
+ }
+ }
} else {
zend_update_jump_target_to_next(opnum_default_jmp);
+
+ if (jumptable) {
+ opline = &CG(active_op_array)->opcodes[opnum_switch];
+ opline->extended_value = get_next_op_number(CG(active_op_array));
+ }
}
zend_compile_stmt(stmt_ast);
@@ -4675,6 +4768,11 @@ void zend_compile_switch(zend_ast *ast) /* {{{ */
if (!has_default_case) {
zend_update_jump_target_to_next(opnum_default_jmp);
+
+ if (jumptable) {
+ opline = &CG(active_op_array)->opcodes[opnum_switch];
+ opline->extended_value = get_next_op_number(CG(active_op_array));
+ }
}
zend_end_loop(get_next_op_number(CG(active_op_array)), &expr_node);