Skip to content

Commit a159bed

Browse files
committed
Add consumed_args support for fcall arguments
1 parent 00c0a9b commit a159bed

15 files changed

+239
-6
lines changed

UPGRADING.INTERNALS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ PHP 8.6 INTERNALS UPGRADE NOTES
7474
longer is a pointer, but a directly embedded HashTable struct.
7575
. Added a C23_ENUM() helper macro to define forward-compatible fixed-size
7676
enums.
77+
. Added zend_fcall_info.consumed_args together with
78+
zend_fci_consumed_arg(), which allows moving a selected callback argument
79+
instead of copying it in zend_call_function(). Currently only a single
80+
consumed argument is supported.
7781

7882
========================
7983
2. Build system changes

Zend/zend_API.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4239,6 +4239,7 @@ ZEND_API zend_result zend_fcall_info_init(zval *callable, uint32_t check_flags,
42394239
fci->param_count = 0;
42404240
fci->params = NULL;
42414241
fci->named_params = NULL;
4242+
fci->consumed_args = 0;
42424243

42434244
return SUCCESS;
42444245
}

Zend/zend_API.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ typedef struct _zend_fcall_info {
4949
zval *params;
5050
zend_object *object;
5151
uint32_t param_count;
52+
uint32_t consumed_args;
5253
/* This hashtable can also contain positional arguments (with integer keys),
5354
* which will be appended to the normal params[]. This makes it easier to
5455
* integrate APIs like call_user_func_array(). The usual restriction that
@@ -340,6 +341,13 @@ typedef struct _zend_fcall_info_cache {
340341
#define ZEND_FCI_INITIALIZED(fci) ((fci).size != 0)
341342
#define ZEND_FCC_INITIALIZED(fcc) ((fcc).function_handler != NULL)
342343

344+
static zend_always_inline uint32_t zend_fci_consumed_arg(uint32_t arg_index) {
345+
return arg_index < 32 ? (UINT32_C(1) << arg_index) : UINT32_C(0);
346+
}
347+
static zend_always_inline bool zend_fci_is_consumed_arg(uint32_t consumed_args, uint32_t arg_index) {
348+
return arg_index < 32 && (consumed_args & (UINT32_C(1) << arg_index));
349+
}
350+
343351
ZEND_API int zend_next_free_module(void);
344352

345353
BEGIN_EXTERN_C()

Zend/zend_closures.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ ZEND_METHOD(Closure, call)
155155
fci_cache.object = fci.object = newobj;
156156

157157
fci.size = sizeof(fci);
158+
fci.consumed_args = 0;
158159
ZVAL_OBJ(&fci.function_name, &closure->std);
159160
ZVAL_UNDEF(&closure_result);
160161
fci.retval = &closure_result;

Zend/zend_execute_API.c

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,7 @@ zend_result _call_user_function_impl(zval *object, zval *function_name, zval *re
797797
fci.param_count = param_count;
798798
fci.params = params;
799799
fci.named_params = named_params;
800+
fci.consumed_args = 0;
800801

801802
return zend_call_function(&fci, NULL);
802803
}
@@ -864,6 +865,9 @@ zend_result zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_
864865

865866
call = zend_vm_stack_push_call_frame(call_info,
866867
func, fci->param_count, object_or_called_scope);
868+
uint32_t consumed_args = fci->param_count ? fci->consumed_args : 0;
869+
870+
ZEND_ASSERT((consumed_args & (consumed_args - 1)) == 0);
867871

868872
for (uint32_t i = 0; i < fci->param_count; i++) {
869873
zval *param = ZEND_CALL_ARG(call, i+1);
@@ -905,7 +909,15 @@ zend_result zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_
905909
}
906910

907911
if (EXPECTED(!must_wrap)) {
908-
ZVAL_COPY(param, arg);
912+
if (EXPECTED(consumed_args == 0)
913+
|| !zend_fci_is_consumed_arg(consumed_args, i)
914+
|| Z_ISREF_P(arg)
915+
|| arg != &fci->params[i]) {
916+
ZVAL_COPY(param, arg);
917+
} else {
918+
ZVAL_COPY_VALUE(param, arg);
919+
ZVAL_UNDEF(arg);
920+
}
909921
} else {
910922
Z_TRY_ADDREF_P(arg);
911923
ZVAL_NEW_REF(param, arg);
@@ -1091,6 +1103,7 @@ ZEND_API void zend_call_known_function(
10911103
fci.param_count = param_count;
10921104
fci.params = params;
10931105
fci.named_params = named_params;
1106+
fci.consumed_args = 0;
10941107
ZVAL_UNDEF(&fci.function_name); /* Unused */
10951108

10961109
fcic.function_handler = fn;

ext/dom/xpath_callbacks.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@ static zend_result php_dom_xpath_callback_dispatch(php_dom_xpath_callbacks *xpat
408408
fci.param_count = param_count;
409409
fci.params = params;
410410
fci.named_params = NULL;
411+
fci.consumed_args = 0;
411412
ZVAL_STRINGL(&fci.function_name, function_name, function_name_length);
412413

413414
zend_call_function(&fci, NULL);

ext/ffi/ffi.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,7 @@ static void zend_ffi_callback_trampoline(ffi_cif* cif, void* ret, void** args, v
950950
fci.object = NULL;
951951
fci.param_count = callback_data->arg_count;
952952
fci.named_params = NULL;
953+
fci.consumed_args = 0;
953954

954955
if (callback_data->type->func.args) {
955956
int n = 0;

ext/zend_test/test.c

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,61 @@ static ZEND_FUNCTION(zend_call_method_if_exists)
548548
}
549549
}
550550

551+
static ZEND_FUNCTION(zend_test_call_with_consumed_args)
552+
{
553+
zend_fcall_info fci = empty_fcall_info;
554+
zend_fcall_info_cache fcc = empty_fcall_info_cache;
555+
zval *args;
556+
zend_long consumed_args;
557+
zval retval;
558+
uint32_t actual_consumed_args = 0;
559+
uint32_t i;
560+
zend_result call_result;
561+
562+
ZEND_PARSE_PARAMETERS_START(3, 3)
563+
Z_PARAM_FUNC(fci, fcc)
564+
Z_PARAM_ARRAY(args)
565+
Z_PARAM_LONG(consumed_args)
566+
ZEND_PARSE_PARAMETERS_END();
567+
568+
if (UNEXPECTED(consumed_args < 0 || consumed_args > UINT32_MAX)) {
569+
zend_argument_value_error(3, "must be between 0 and 4294967295");
570+
RETURN_THROWS();
571+
}
572+
573+
zend_fcall_info_args(&fci, args);
574+
575+
ZVAL_UNDEF(&retval);
576+
fci.retval = &retval;
577+
fci.consumed_args = (uint32_t) consumed_args;
578+
579+
call_result = zend_call_function(&fci, &fcc);
580+
581+
for (i = 0; i < fci.param_count && i < 32; i++) {
582+
if (Z_ISUNDEF(fci.params[i])) {
583+
actual_consumed_args |= (1u << i);
584+
}
585+
}
586+
587+
zend_fcall_info_args_clear(&fci, true);
588+
589+
if (call_result == FAILURE || EG(exception)) {
590+
if (!Z_ISUNDEF(retval)) {
591+
zval_ptr_dtor(&retval);
592+
}
593+
RETURN_THROWS();
594+
}
595+
596+
array_init(return_value);
597+
add_assoc_long(return_value, "consumed_args", actual_consumed_args);
598+
599+
if (Z_ISUNDEF(retval)) {
600+
add_assoc_null(return_value, "retval");
601+
} else {
602+
add_assoc_zval(return_value, "retval", &retval);
603+
}
604+
}
605+
551606
static ZEND_FUNCTION(zend_get_unit_enum)
552607
{
553608
ZEND_PARSE_PARAMETERS_NONE();

ext/zend_test/test.stub.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,8 @@ function zend_object_init_with_constructor(string $class, mixed ...$args): mixed
303303

304304
function zend_call_method_if_exists(object $obj, string $method, mixed ...$args): mixed {}
305305

306+
function zend_test_call_with_consumed_args(callable $cb, array $args, int $consumed_args): array {}
307+
306308
function zend_test_zend_ini_parse_quantity(string $str): int {}
307309
function zend_test_zend_ini_parse_uquantity(string $str): int {}
308310

ext/zend_test/test_arginfo.h

Lines changed: 9 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)