@@ -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+
455562zend_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 */
0 commit comments