Skip to content

Commit 574c566

Browse files
committed
Forward superfluous args
1 parent 6c5bdf7 commit 574c566

File tree

4 files changed

+235
-74
lines changed

4 files changed

+235
-74
lines changed

Zend/tests/partial_application/pipe_optimization_008.phpt

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,26 @@ LIVE RANGES:
7474
1: 0001 - 0002 (tmp/var)
7575

7676
{closure:%s:%d}:
77-
; (lines=7, args=1, vars=2, tmps=1)
77+
; (lines=18, args=1, vars=2, tmps=2)
7878
; (after optimizer)
7979
; %s:10-10
8080
0000 CV0($b) = RECV 1
8181
0001 BIND_STATIC CV1($a)
82-
0002 INIT_FCALL 2 112 string("foo")
83-
0003 SEND_VAR CV1($a) 1
84-
0004 SEND_VAR CV0($b) 2
85-
0005 V2 = DO_UCALL
86-
0006 RETURN V2
82+
0002 T3 = FUNC_NUM_ARGS
83+
0003 T2 = IS_SMALLER_OR_EQUAL T3 int(1)
84+
0004 JMPZ T2 0010
85+
0005 INIT_FCALL 2 112 string("foo")
86+
0006 SEND_VAR CV1($a) 1
87+
0007 SEND_VAR CV0($b) 2
88+
0008 V2 = DO_UCALL
89+
0009 RETURN V2
90+
0010 INIT_FCALL 2 112 string("foo")
91+
0011 SEND_VAR CV1($a) 1
92+
0012 SEND_VAR CV0($b) 2
93+
0013 T2 = FUNC_GET_ARGS int(1)
94+
0014 SEND_UNPACK T2
95+
0015 CHECK_UNDEF_ARGS
96+
0016 V2 = DO_UCALL
97+
0017 RETURN V2
8798
int(1)
8899
int(2)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
--TEST--
2+
PFAs forwarded superfluous args iff a '...' placeholder is specified
3+
--FILE--
4+
<?php
5+
6+
function f($a) {
7+
var_dump(func_get_args());
8+
}
9+
$f = f(?, ...);
10+
$f(1, 2, 3);
11+
12+
function g($a, ...$args) {
13+
var_dump(func_get_args());
14+
}
15+
16+
$g = f(?, ...);
17+
$g(1, 2, 3);
18+
19+
function h($a) {
20+
}
21+
22+
$h = f(?);
23+
$h(1, 2, 3);
24+
25+
?>
26+
--EXPECT--
27+
array(3) {
28+
[0]=>
29+
int(1)
30+
[1]=>
31+
int(2)
32+
[2]=>
33+
int(3)
34+
}
35+
array(3) {
36+
[0]=>
37+
int(1)
38+
[1]=>
39+
int(2)
40+
[2]=>
41+
int(3)
42+
}
43+
array(1) {
44+
[0]=>
45+
int(1)
46+
}

Zend/zend_partial.c

Lines changed: 169 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,113 @@ static zend_ast *zp_param_attributes_to_ast(zend_function *function,
452452
return attributes_ast;
453453
}
454454

455+
zend_ast *zp_compile_forwarding_call(
456+
zval *this_ptr, zend_function *function,
457+
uint32_t argc, zval *argv, zend_array *extra_named_params,
458+
zend_string **param_names, bool variadic_partial, uint32_t num_args,
459+
zend_class_entry *called_scope, zend_type return_type,
460+
bool forward_superfluous_args,
461+
zend_ast *stmts_ast)
462+
{
463+
/* Generate function body */
464+
zend_ast *args_ast = zend_ast_create_list(0, ZEND_AST_ARG_LIST);
465+
for (uint32_t offset = 0; offset < argc; offset++) {
466+
if (Z_ISUNDEF(argv[offset])) {
467+
/* Argument was not passed. Pass its default value. */
468+
if (offset < function->common.required_num_args) {
469+
zend_argument_error(zend_ce_argument_count_error, offset + 1, "not passed");
470+
/* TODO: cleanup */
471+
return NULL;
472+
}
473+
zval default_value;
474+
if (zp_get_param_default_value(&default_value, function, offset) == FAILURE) {
475+
/* TODO: cleanup */
476+
return NULL;
477+
}
478+
zend_ast *default_value_ast;
479+
if (Z_TYPE(default_value) == IS_CONSTANT_AST) {
480+
default_value_ast = zend_ast_dup(Z_ASTVAL(default_value));
481+
} else {
482+
default_value_ast = zend_ast_create_zval(&default_value);
483+
}
484+
args_ast = zend_ast_list_add(args_ast, default_value_ast);
485+
} else {
486+
args_ast = zend_ast_list_add(args_ast, zend_ast_create(ZEND_AST_VAR,
487+
zend_ast_create_zval_from_str(zend_string_copy(param_names[offset]))));
488+
}
489+
}
490+
if (extra_named_params) {
491+
args_ast = zend_ast_list_add(args_ast, zend_ast_create(ZEND_AST_UNPACK,
492+
zend_ast_create(ZEND_AST_VAR,
493+
zend_ast_create_zval_from_str(zend_string_copy(param_names[argc + variadic_partial])))));
494+
}
495+
if (variadic_partial) {
496+
if (function->common.fn_flags & ZEND_ACC_VARIADIC) {
497+
args_ast = zend_ast_list_add(args_ast, zend_ast_create(ZEND_AST_UNPACK,
498+
zend_ast_create(ZEND_AST_VAR,
499+
zend_ast_create_zval_from_str(zend_string_copy(param_names[argc])))));
500+
} else if (forward_superfluous_args) {
501+
/* When a '...' placeholder is used, and the underlying function is
502+
* not variadic, superfluous arguments are forwarded.
503+
* Add a ...array_slice(func_get_args(), n) argument, which should
504+
* be compiled as ZEND_AST_UNPACK + ZEND_FUNC_GET_ARGS. */
505+
506+
zend_ast *func_get_args_name_ast = zend_ast_create_zval_from_str(
507+
zend_string_copy(ZSTR_KNOWN(ZEND_STR_FUNC_GET_ARGS)));
508+
func_get_args_name_ast->attr = ZEND_NAME_FQ;
509+
510+
zend_ast *array_slice_name_ast = zend_ast_create_zval_from_str(
511+
zend_string_copy(ZSTR_KNOWN(ZEND_STR_ARRAY_SLICE)));
512+
array_slice_name_ast->attr = ZEND_NAME_FQ;
513+
514+
args_ast = zend_ast_list_add(args_ast,
515+
zend_ast_create(ZEND_AST_UNPACK,
516+
zend_ast_create(ZEND_AST_CALL,
517+
array_slice_name_ast,
518+
zend_ast_create_list(2, ZEND_AST_ARG_LIST,
519+
zend_ast_create(ZEND_AST_CALL,
520+
func_get_args_name_ast,
521+
zend_ast_create_list(0, ZEND_AST_ARG_LIST)),
522+
zend_ast_create_zval_from_long(num_args)))));
523+
}
524+
}
525+
526+
zend_ast *call_ast;
527+
if (function->common.fn_flags & ZEND_ACC_CLOSURE) {
528+
zend_ast *fn_ast = zend_ast_create(ZEND_AST_VAR,
529+
zend_ast_create_zval_from_str(zend_string_copy(param_names[argc + variadic_partial + (extra_named_params != NULL)])));
530+
call_ast = zend_ast_create(ZEND_AST_CALL, fn_ast, args_ast);
531+
} else if (Z_TYPE_P(this_ptr) == IS_OBJECT) {
532+
zend_ast *this_ast = zend_ast_create(ZEND_AST_VAR,
533+
zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_THIS)));
534+
zend_ast *method_name_ast = zend_ast_create_zval_from_str(
535+
zend_string_copy(function->common.function_name));
536+
call_ast = zend_ast_create(ZEND_AST_METHOD_CALL, this_ast,
537+
method_name_ast, args_ast);
538+
} else if (called_scope) {
539+
zend_ast *class_name_ast = zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_STATIC));
540+
class_name_ast->attr = ZEND_NAME_NOT_FQ;
541+
zend_ast *method_name_ast = zend_ast_create_zval_from_str(
542+
zend_string_copy(function->common.function_name));
543+
call_ast = zend_ast_create(ZEND_AST_STATIC_CALL, class_name_ast,
544+
method_name_ast, args_ast);
545+
} else {
546+
zend_ast *func_name_ast = zend_ast_create_zval_from_str(zend_string_copy(function->common.function_name));
547+
func_name_ast->attr = ZEND_NAME_FQ;
548+
call_ast = zend_ast_create(ZEND_AST_CALL, func_name_ast, args_ast);
549+
}
550+
551+
/* Void functions can not 'return $expr' */
552+
if (ZEND_TYPE_FULL_MASK(return_type) & MAY_BE_VOID) {
553+
stmts_ast = zend_ast_list_add(stmts_ast, call_ast);
554+
} else {
555+
zend_ast *return_ast = zend_ast_create(ZEND_AST_RETURN, call_ast);
556+
stmts_ast = zend_ast_list_add(stmts_ast, return_ast);
557+
}
558+
559+
return stmts_ast;
560+
}
561+
455562
zend_op_array *zp_compile(zval *this_ptr, zend_function *function,
456563
uint32_t argc, zval *argv, zend_array *extra_named_params,
457564
const zend_op_array *declaring_op_array,
@@ -621,82 +728,76 @@ zend_op_array *zp_compile(zval *this_ptr, zend_function *function,
621728
NULL, NULL, NULL, NULL));
622729
}
623730

624-
/* Generate function body */
625-
zend_ast *args_ast = zend_ast_create_list(0, ZEND_AST_ARG_LIST);
626-
for (uint32_t offset = 0; offset < argc; offset++) {
627-
if (Z_ISUNDEF(argv[offset])) {
628-
/* Argument was not passed. Pass its default value. */
629-
if (offset < function->common.required_num_args) {
630-
zend_argument_error(zend_ce_argument_count_error, offset + 1, "not passed");
631-
// TODO: proper cleanup
632-
goto clean_argv;
633-
}
634-
zval default_value;
635-
if (zp_get_param_default_value(&default_value, function, offset) == FAILURE) {
636-
// TODO: proper cleanup
637-
goto clean_argv;
638-
}
639-
zend_ast *default_value_ast;
640-
if (Z_TYPE(default_value) == IS_CONSTANT_AST) {
641-
default_value_ast = zend_ast_dup(Z_ASTVAL(default_value));
642-
} else {
643-
default_value_ast = zend_ast_create_zval(&default_value);
644-
}
645-
args_ast = zend_ast_list_add(args_ast, default_value_ast);
646-
} else {
647-
args_ast = zend_ast_list_add(args_ast, zend_ast_create(ZEND_AST_VAR,
648-
zend_ast_create_zval_from_str(zend_string_copy(param_names[offset]))));
649-
}
650-
}
651-
if (extra_named_params) {
652-
args_ast = zend_ast_list_add(args_ast, zend_ast_create(ZEND_AST_UNPACK,
653-
zend_ast_create(ZEND_AST_VAR,
654-
zend_ast_create_zval_from_str(zend_string_copy(param_names[argc + variadic_partial])))));
655-
}
656-
if (variadic_partial && (function->common.fn_flags & ZEND_ACC_VARIADIC)) {
657-
args_ast = zend_ast_list_add(args_ast, zend_ast_create(ZEND_AST_UNPACK,
658-
zend_ast_create(ZEND_AST_VAR,
659-
zend_ast_create_zval_from_str(zend_string_copy(param_names[argc])))));
660-
}
661-
662-
zend_ast *call_ast;
663-
if (function->common.fn_flags & ZEND_ACC_CLOSURE) {
664-
zend_ast *fn_ast = zend_ast_create(ZEND_AST_VAR,
665-
zend_ast_create_zval_from_str(zend_string_copy(param_names[argc + variadic_partial + (extra_named_params != NULL)])));
666-
call_ast = zend_ast_create(ZEND_AST_CALL, fn_ast, args_ast);
667-
} else if (Z_TYPE_P(this_ptr) == IS_OBJECT) {
668-
zend_ast *this_ast = zend_ast_create(ZEND_AST_VAR,
669-
zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_THIS)));
670-
zend_ast *method_name_ast = zend_ast_create_zval_from_str(
671-
zend_string_copy(function->common.function_name));
672-
call_ast = zend_ast_create(ZEND_AST_METHOD_CALL, this_ast,
673-
method_name_ast, args_ast);
674-
} else if (called_scope) {
675-
zend_ast *class_name_ast = zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_STATIC));
676-
class_name_ast->attr = ZEND_NAME_NOT_FQ;
677-
zend_ast *method_name_ast = zend_ast_create_zval_from_str(
678-
zend_string_copy(function->common.function_name));
679-
call_ast = zend_ast_create(ZEND_AST_STATIC_CALL, class_name_ast,
680-
method_name_ast, args_ast);
681-
} else {
682-
zend_ast *func_name_ast = zend_ast_create_zval_from_str(zend_string_copy(function->common.function_name));
683-
func_name_ast->attr = ZEND_NAME_FQ;
684-
call_ast = zend_ast_create(ZEND_AST_CALL, func_name_ast, args_ast);
685-
}
686-
687731
zend_ast *return_type_ast = NULL;
688732
zend_type return_type = {0};
689733
if (function->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
690734
return_type = (function->common.arg_info-1)->type;
691735
return_type_ast = zp_type_to_ast(return_type);
692736
}
693737

694-
/* Void functions can not 'return $expr' */
695-
if (ZEND_TYPE_FULL_MASK(return_type) & MAY_BE_VOID) {
696-
stmts_ast = zend_ast_list_add(stmts_ast, call_ast);
738+
/**
739+
* Generate function body.
740+
*
741+
* If we may need to forward superflous arguments, do that conditionally, as
742+
* it's faster:
743+
*
744+
* if (func_num_args() <= n) {
745+
* // normal call
746+
* } else {
747+
* // call with superflous arg forwarding
748+
* }
749+
*
750+
* The func_num_args() call should be compiled to a single FUNC_NUM_ARGS op.
751+
*/
752+
if (variadic_partial && !(function->common.fn_flags & ZEND_ACC_VARIADIC)) {
753+
zend_ast *no_forwarding_ast = zend_ast_create_list(0, ZEND_AST_STMT_LIST);
754+
zend_ast *forwarding_ast = zend_ast_create_list(0, ZEND_AST_STMT_LIST);
755+
756+
no_forwarding_ast = zp_compile_forwarding_call(this_ptr, function,
757+
argc, argv, extra_named_params,
758+
param_names, variadic_partial, num_args,
759+
called_scope, return_type, false, no_forwarding_ast);
760+
761+
if (!no_forwarding_ast) {
762+
/* TODO: proper cleanup */
763+
goto clean_argv;
764+
}
765+
766+
forwarding_ast = zp_compile_forwarding_call(this_ptr, function,
767+
argc, argv, extra_named_params,
768+
param_names, variadic_partial, num_args,
769+
called_scope, return_type, true, forwarding_ast);
770+
771+
if (!forwarding_ast) {
772+
/* TODO: proper cleanup */
773+
goto clean_argv;
774+
}
775+
776+
zend_ast *func_num_args_name_ast = zend_ast_create_zval_from_str(
777+
zend_string_copy(ZSTR_KNOWN(ZEND_STR_FUNC_NUM_ARGS)));
778+
func_num_args_name_ast->attr = ZEND_NAME_FQ;
779+
780+
stmts_ast = zend_ast_list_add(stmts_ast,
781+
zend_ast_create_list(2, ZEND_AST_IF,
782+
zend_ast_create(ZEND_AST_IF_ELEM,
783+
zend_ast_create_binary_op(ZEND_IS_SMALLER_OR_EQUAL,
784+
zend_ast_create(ZEND_AST_CALL, func_num_args_name_ast,
785+
zend_ast_create_list(0, ZEND_AST_ARG_LIST)),
786+
zend_ast_create_zval_from_long(num_args)),
787+
no_forwarding_ast),
788+
zend_ast_create(ZEND_AST_IF_ELEM,
789+
NULL,
790+
forwarding_ast)));
697791
} else {
698-
zend_ast *return_ast = zend_ast_create(ZEND_AST_RETURN, call_ast);
699-
stmts_ast = zend_ast_list_add(stmts_ast, return_ast);
792+
stmts_ast = zp_compile_forwarding_call(this_ptr, function,
793+
argc, argv, extra_named_params,
794+
param_names, variadic_partial, num_args,
795+
called_scope, return_type, false, stmts_ast);
796+
797+
if (!stmts_ast) {
798+
/* TODO: proper cleanup */
799+
goto clean_argv;
800+
}
700801
}
701802

702803
/* Inherit the NoDiscard attribute */

Zend/zend_string.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,9 @@ EMPTY_SWITCH_DEFAULT_CASE()
630630
_(ZEND_STR_AUTOGLOBAL_ENV, "_ENV") \
631631
_(ZEND_STR_AUTOGLOBAL_REQUEST, "_REQUEST") \
632632
_(ZEND_STR_COUNT, "count") \
633+
_(ZEND_STR_FUNC_NUM_ARGS, "func_num_args") \
634+
_(ZEND_STR_FUNC_GET_ARGS, "func_get_args") \
635+
_(ZEND_STR_ARRAY_SLICE, "array_slice") \
633636
_(ZEND_STR_SENSITIVEPARAMETER, "SensitiveParameter") \
634637
_(ZEND_STR_CONST_EXPR_PLACEHOLDER, "[constant expression]") \
635638
_(ZEND_STR_DEPRECATED_CAPITALIZED, "Deprecated") \

0 commit comments

Comments
 (0)