Extend ZEND_SEND_ARRAY to eliminate array_slice() call for "call_user_func_array(_, array_slice(_, LONG, _))" pattern.

This commit is contained in:
Dmitry Stogov 2017-05-30 12:25:46 +03:00
parent c45e3632a2
commit 017d65d74a
3 changed files with 193 additions and 58 deletions

View File

@ -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);

View File

@ -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);
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));
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));
}
}
} 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();

View File

@ -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);
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));
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));
}
}
} 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();