From 017d65d74a239ebf7b49790dad1f6ae91a08aa04 Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Tue, 30 May 2017 12:25:46 +0300 Subject: [PATCH] Extend ZEND_SEND_ARRAY to eliminate array_slice() call for "call_user_func_array(_, array_slice(_, LONG, _))" pattern. --- Zend/zend_compile.c | 27 ++++++++++ Zend/zend_vm_def.h | 112 ++++++++++++++++++++++++++++++----------- Zend/zend_vm_execute.h | 112 ++++++++++++++++++++++++++++++----------- 3 files changed, 193 insertions(+), 58 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 75ceb36a48..e210a26e43 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -3554,6 +3554,33 @@ int zend_compile_func_cufa(znode *result, zend_ast_list *args, zend_string *lcna } zend_compile_init_user_func(args->child[0], 0, lcname); + if (args->child[1]->kind == ZEND_AST_CALL + && args->child[1]->child[0]->kind == ZEND_AST_ZVAL + && args->child[1]->child[1]->kind == ZEND_AST_ARG_LIST) { + zval *name = zend_ast_get_zval(args->child[1]->child[0]); + zend_ast_list *list = zend_ast_get_list(args->child[1]->child[1]); + + if (Z_TYPE_P(name) == IS_STRING + && zend_string_equals_literal_ci(Z_STR_P(name), "array_slice") + && list->children == 3 + && list->child[1]->kind == ZEND_AST_ZVAL) { + zval *zv = zend_ast_get_zval(list->child[1]); + + if (Z_TYPE_P(zv) == IS_LONG + && Z_LVAL_P(zv) >= 0 + && Z_LVAL_P(zv) <= 0x7fffffff) { + zend_op *opline; + znode len_node; + + zend_compile_expr(&arg_node, list->child[0]); + zend_compile_expr(&len_node, list->child[2]); + opline = zend_emit_op(NULL, ZEND_SEND_ARRAY, &arg_node, &len_node); + opline->extended_value = Z_LVAL_P(zv); + zend_emit_op(result, ZEND_DO_FCALL, NULL, NULL); + return SUCCESS; + } + } + } zend_compile_expr(&arg_node, args->child[1]); zend_emit_op(NULL, ZEND_SEND_ARRAY, &arg_node, NULL); zend_emit_op(result, ZEND_DO_FCALL, NULL, NULL); diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 9b8d0670bb..d26f9e78cb 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -4465,8 +4465,8 @@ ZEND_VM_C_LABEL(send_again): ZEND_VM_HANDLER(119, ZEND_SEND_ARRAY, ANY, ANY) { USE_OPLINE - zend_free_op free_op1; - zval *args; + zend_free_op free_op1, free_op2; + zval *args, *op2; SAVE_OPLINE(); args = GET_OP1_ZVAL_PTR(BP_VAR_R); @@ -4488,44 +4488,98 @@ ZEND_VM_HANDLER(119, ZEND_SEND_ARRAY, ANY, ANY) EX(call)->func = (zend_function*)&zend_pass_function; Z_OBJ(EX(call)->This) = NULL; ZEND_SET_CALL_INFO(EX(call), 0, ZEND_CALL_INFO(EX(call)) & ~ZEND_CALL_RELEASE_THIS); + FREE_UNFETCHED_OP2(); } else { uint32_t arg_num; HashTable *ht; zval *arg, *param; + ZEND_VM_C_LABEL(send_array): ht = Z_ARRVAL_P(args); - zend_vm_stack_extend_call_frame(&EX(call), 0, zend_hash_num_elements(ht)); + if (OP2_TYPE != IS_UNUSED) { + zend_free_op free_op2; + zval *op2 = GET_OP2_ZVAL_PTR_DEREF(BP_VAR_R); + uint32_t skip = opline->extended_value; + uint32_t count = zend_hash_num_elements(ht); + zend_long len = zval_get_long(op2); + + if (len < 0) { + len += (zend_long)(count - skip); + } + if (skip < count && len > 0) { + if (len > (zend_long)(count - skip)) { + len = (zend_long)(count - skip); + } + zend_vm_stack_extend_call_frame(&EX(call), 0, len); + arg_num = 1; + param = ZEND_CALL_ARG(EX(call), 1); + ZEND_HASH_FOREACH_VAL(ht, arg) { + if (skip > 0) { + skip--; + continue; + } else if ((zend_long)(arg_num - 1) >= len) { + break; + } else if (ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, arg_num)) { + if (UNEXPECTED(!Z_ISREF_P(arg))) { + if (!ARG_MAY_BE_SENT_BY_REF(EX(call)->func, arg_num)) { + /* By-value send is not allowed -- emit a warning, + * but still perform the call. */ + zend_error(E_WARNING, + "Parameter %d to %s%s%s() expected to be a reference, value given", + arg_num, + EX(call)->func->common.scope ? ZSTR_VAL(EX(call)->func->common.scope->name) : "", + EX(call)->func->common.scope ? "::" : "", + ZSTR_VAL(EX(call)->func->common.function_name)); - arg_num = 1; - param = ZEND_CALL_ARG(EX(call), 1); - ZEND_HASH_FOREACH_VAL(ht, arg) { - if (ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, arg_num)) { - if (UNEXPECTED(!Z_ISREF_P(arg))) { - if (!ARG_MAY_BE_SENT_BY_REF(EX(call)->func, arg_num)) { - /* By-value send is not allowed -- emit a warning, - * but still perform the call. */ - zend_error(E_WARNING, - "Parameter %d to %s%s%s() expected to be a reference, value given", - arg_num, - EX(call)->func->common.scope ? ZSTR_VAL(EX(call)->func->common.scope->name) : "", - EX(call)->func->common.scope ? "::" : "", - ZSTR_VAL(EX(call)->func->common.function_name)); + } + } + } else { + if (Z_ISREF_P(arg) && + !(EX(call)->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { + /* don't separate references for __call */ + arg = Z_REFVAL_P(arg); + } + } + ZVAL_COPY(param, arg); + ZEND_CALL_NUM_ARGS(EX(call))++; + arg_num++; + param++; + } ZEND_HASH_FOREACH_END(); + } + FREE_OP2(); + } else { + zend_vm_stack_extend_call_frame(&EX(call), 0, zend_hash_num_elements(ht)); + arg_num = 1; + param = ZEND_CALL_ARG(EX(call), 1); + ZEND_HASH_FOREACH_VAL(ht, arg) { + if (ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, arg_num)) { + if (UNEXPECTED(!Z_ISREF_P(arg))) { + if (!ARG_MAY_BE_SENT_BY_REF(EX(call)->func, arg_num)) { + /* By-value send is not allowed -- emit a warning, + * but still perform the call. */ + zend_error(E_WARNING, + "Parameter %d to %s%s%s() expected to be a reference, value given", + arg_num, + EX(call)->func->common.scope ? ZSTR_VAL(EX(call)->func->common.scope->name) : "", + EX(call)->func->common.scope ? "::" : "", + ZSTR_VAL(EX(call)->func->common.function_name)); + } + } + } else { + if (Z_ISREF_P(arg) && + !(EX(call)->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { + /* don't separate references for __call */ + arg = Z_REFVAL_P(arg); } } - } else { - if (Z_ISREF_P(arg) && - !(EX(call)->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { - /* don't separate references for __call */ - arg = Z_REFVAL_P(arg); - } - } - ZVAL_COPY(param, arg); - ZEND_CALL_NUM_ARGS(EX(call))++; - arg_num++; - param++; - } ZEND_HASH_FOREACH_END(); + ZVAL_COPY(param, arg); + ZEND_CALL_NUM_ARGS(EX(call))++; + arg_num++; + param++; + } ZEND_HASH_FOREACH_END(); + } } FREE_OP1(); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index aa2175e4c9..2427327af6 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -1322,8 +1322,8 @@ send_again: static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_SEND_ARRAY_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE - zend_free_op free_op1; - zval *args; + zend_free_op free_op1, free_op2; + zval *args, *op2; SAVE_OPLINE(); args = get_zval_ptr(opline->op1_type, opline->op1, execute_data, &free_op1, BP_VAR_R); @@ -1345,44 +1345,98 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_SEND_ARRAY_SPEC_HANDLER(ZEND_O EX(call)->func = (zend_function*)&zend_pass_function; Z_OBJ(EX(call)->This) = NULL; ZEND_SET_CALL_INFO(EX(call), 0, ZEND_CALL_INFO(EX(call)) & ~ZEND_CALL_RELEASE_THIS); + FREE_UNFETCHED_OP(opline->op2_type, opline->op2.var); } else { uint32_t arg_num; HashTable *ht; zval *arg, *param; + send_array: ht = Z_ARRVAL_P(args); - zend_vm_stack_extend_call_frame(&EX(call), 0, zend_hash_num_elements(ht)); + if (opline->op2_type != IS_UNUSED) { + zend_free_op free_op2; + zval *op2 = get_zval_ptr_deref(opline->op2_type, opline->op2, execute_data, &free_op2, BP_VAR_R); + uint32_t skip = opline->extended_value; + uint32_t count = zend_hash_num_elements(ht); + zend_long len = zval_get_long(op2); + + if (len < 0) { + len += (zend_long)(count - skip); + } + if (skip < count && len > 0) { + if (len > (zend_long)(count - skip)) { + len = (zend_long)(count - skip); + } + zend_vm_stack_extend_call_frame(&EX(call), 0, len); + arg_num = 1; + param = ZEND_CALL_ARG(EX(call), 1); + ZEND_HASH_FOREACH_VAL(ht, arg) { + if (skip > 0) { + skip--; + continue; + } else if ((zend_long)(arg_num - 1) >= len) { + break; + } else if (ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, arg_num)) { + if (UNEXPECTED(!Z_ISREF_P(arg))) { + if (!ARG_MAY_BE_SENT_BY_REF(EX(call)->func, arg_num)) { + /* By-value send is not allowed -- emit a warning, + * but still perform the call. */ + zend_error(E_WARNING, + "Parameter %d to %s%s%s() expected to be a reference, value given", + arg_num, + EX(call)->func->common.scope ? ZSTR_VAL(EX(call)->func->common.scope->name) : "", + EX(call)->func->common.scope ? "::" : "", + ZSTR_VAL(EX(call)->func->common.function_name)); - arg_num = 1; - param = ZEND_CALL_ARG(EX(call), 1); - ZEND_HASH_FOREACH_VAL(ht, arg) { - if (ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, arg_num)) { - if (UNEXPECTED(!Z_ISREF_P(arg))) { - if (!ARG_MAY_BE_SENT_BY_REF(EX(call)->func, arg_num)) { - /* By-value send is not allowed -- emit a warning, - * but still perform the call. */ - zend_error(E_WARNING, - "Parameter %d to %s%s%s() expected to be a reference, value given", - arg_num, - EX(call)->func->common.scope ? ZSTR_VAL(EX(call)->func->common.scope->name) : "", - EX(call)->func->common.scope ? "::" : "", - ZSTR_VAL(EX(call)->func->common.function_name)); + } + } + } else { + if (Z_ISREF_P(arg) && + !(EX(call)->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { + /* don't separate references for __call */ + arg = Z_REFVAL_P(arg); + } + } + ZVAL_COPY(param, arg); + ZEND_CALL_NUM_ARGS(EX(call))++; + arg_num++; + param++; + } ZEND_HASH_FOREACH_END(); + } + FREE_OP(free_op2); + } else { + zend_vm_stack_extend_call_frame(&EX(call), 0, zend_hash_num_elements(ht)); + arg_num = 1; + param = ZEND_CALL_ARG(EX(call), 1); + ZEND_HASH_FOREACH_VAL(ht, arg) { + if (ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, arg_num)) { + if (UNEXPECTED(!Z_ISREF_P(arg))) { + if (!ARG_MAY_BE_SENT_BY_REF(EX(call)->func, arg_num)) { + /* By-value send is not allowed -- emit a warning, + * but still perform the call. */ + zend_error(E_WARNING, + "Parameter %d to %s%s%s() expected to be a reference, value given", + arg_num, + EX(call)->func->common.scope ? ZSTR_VAL(EX(call)->func->common.scope->name) : "", + EX(call)->func->common.scope ? "::" : "", + ZSTR_VAL(EX(call)->func->common.function_name)); + } + } + } else { + if (Z_ISREF_P(arg) && + !(EX(call)->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { + /* don't separate references for __call */ + arg = Z_REFVAL_P(arg); } } - } else { - if (Z_ISREF_P(arg) && - !(EX(call)->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { - /* don't separate references for __call */ - arg = Z_REFVAL_P(arg); - } - } - ZVAL_COPY(param, arg); - ZEND_CALL_NUM_ARGS(EX(call))++; - arg_num++; - param++; - } ZEND_HASH_FOREACH_END(); + ZVAL_COPY(param, arg); + ZEND_CALL_NUM_ARGS(EX(call))++; + arg_num++; + param++; + } ZEND_HASH_FOREACH_END(); + } } FREE_OP(free_op1); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); -- 2.50.1