Skip to content

Use the same zend_arg_info struct for internal and user functions - #19022

Closed
arnaud-lb wants to merge 1 commit into
php:masterfrom
arnaud-lb:zstr-arg-info
Closed

Use the same zend_arg_info struct for internal and user functions#19022
arnaud-lb wants to merge 1 commit into
php:masterfrom
arnaud-lb:zstr-arg-info

Conversation

@arnaud-lb

@arnaud-lbarnaud-lb commented Jul 3, 2025

Copy link
Copy Markdown
Member

Internal functions use char* strings to represent arg names and default values. This differs from user functions, which use zend strings.

Here I unify this by using the same struct, zend_arg_info in both function types.

This simplifies accesses to arg infos. Also, in the PFAs RFC this avoids converting internal arg infos at runtime when applying an internal function.

@GirgiasGirgias left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for having a look at this, I'm overall in favour of unifying those, as it makes it easier to reason about.

See some of my comments, which are mostly nits. :)

Comment threadZend/zend_API.c
Comment threadZend/zend_API.c Outdated
Comment threadZend/zend_API.c Outdated
Comment threadZend/zend_API.c Outdated
Comment threadZend/zend_API.c Outdated
Comment threadZend/zend_API.c Outdated
Comment threadZend/zend_inheritance.c Outdated
@@ -790,17 +785,15 @@ static void _parameter_string(smart_str *str, zend_function *fptr, struct _zend_
if (ZEND_ARG_IS_VARIADIC(arg_info)) {
smart_str_appends(str, "...");
}
smart_str_append_printf(str, "$%s", has_internal_arg_info(fptr)
? ((zend_internal_arg_info*)arg_info)->name : ZSTR_VAL(arg_info->name));
smart_str_append_printf(str, "$%s", ZSTR_VAL(arg_info->name));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to do this with a printf? Can't we just append a char and then the zend_string?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, but I want to avoid changes that are not directly related

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK, let's keep this for a follow-up then :)

Comment threadext/reflection/php_reflection.c Outdated
Comment on lines 36 to 39
if (arginfo) {
if (func->type == ZEND_INTERNAL_FUNCTION) {
arg_name = (char *) ((zend_internal_arg_info *) &arginfo[i])->name;
} else {
arg_name = ZSTR_VAL(arginfo[i].name);
}
arg_name = ZSTR_VAL(arginfo[i].name);
}
smart_str_appends(s, arg_name ? arg_name : "?");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be improved to prevent a strlen computation within the smart_str_appends.

Comment threadZend/zend_API.c Outdated
@ndossche

Copy link
Copy Markdown
Member

You can probably get rid of ZEND_ACC_USER_ARG_INFO

@arnaud-lb

Copy link
Copy Markdown
MemberAuthor

Right :) I wanted to do it, but it's not trivial because the flag is also used to signal how arg default values should be fetched. I may try to remove it later.

@arnaud-lb
arnaud-lb marked this pull request as ready for review July 3, 2025 16:16
Comment threadZend/zend_API.c
#endif
return get_default_via_ast(default_value_zval, default_value);
return get_default_via_ast(default_value_zval, ZSTR_VAL(default_value));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a follow-up, the get_default_via_ast() function should be changed to accept a zend_string to get rid of an unnecessary strlen() computation :)

@ndossche

Copy link
Copy Markdown
Member

This does seem to have a negative impact on the Valgrind instruction counts though. Probably because of the slightly higher memory requirement if you have the zend_string header too.

@@ -790,17 +785,15 @@ static void _parameter_string(smart_str *str, zend_function *fptr, struct _zend_
if (ZEND_ARG_IS_VARIADIC(arg_info)) {
smart_str_appends(str, "...");
}
smart_str_append_printf(str, "$%s", has_internal_arg_info(fptr)
? ((zend_internal_arg_info*)arg_info)->name : ZSTR_VAL(arg_info->name));
smart_str_append_printf(str, "$%s", ZSTR_VAL(arg_info->name));

if (!required && !ZEND_ARG_IS_VARIADIC(arg_info)) {
if (fptr->type == ZEND_INTERNAL_FUNCTION) {
smart_str_appends(str, " = ");
/* TODO: We don't have a way to fetch the default value for an internal function

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment still correct?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this is still correct. This is also related to why we can not remove ZEND_ACC_USER_ARG_INFO immediately.

Comment threadZend/zend_API.c Outdated
Comment threadZend/zend_closures.c Outdated
@arnaud-lb

Copy link
Copy Markdown
MemberAuthor

@nielsdos I believe this is purely startup/shutdown overhead. When ignoring startup/shutdown, I see absolutely zero degradation under valgrind.

@dstogovdstogov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't object.
This wasn't made before to avoid the waste of memory.
Now names are going to be duplicated from .text to heap and shm.
This is not a big problem, but it's better to measure this in some way.

@arnaud-lb

Copy link
Copy Markdown
MemberAuthor

I've measured zero regression on wall time or valgrind icount on the symfony benchmark. I will measure the memory impact once I get back from vacations.

NB: I've also tried declaring zend strings in static storage, but there are a number of complications that make the current solution more practicable. The most annoying one is that we can't initialize a zend string directly due to the variable size member.

@dstogov

Copy link
Copy Markdown
Member

NB: I've also tried declaring zend strings in static storage, but there are a number of complications that make the current solution more practicable. The most annoying one is that we can't initialize a zend string directly due to the variable size member.

I know. I tried this years ago and gave up :)

@arnaud-lb

Copy link
Copy Markdown
MemberAuthor

I don't object. This wasn't made before to avoid the waste of memory. Now names are going to be duplicated from .text to heap and shm. This is not a big problem, but it's better to measure this in some way.

With a realistic set of extensions: Core,ctype,date,dom,FFI,fileinfo,filter,gmp,hash,iconv,intl,json,lexbor,libxml,mbstring,mysqli,mysqlnd,openssl,pcre,PDO,pdo_mysql,pdo_sqlite,Phar,posix,random,Reflection,session,SimpleXML,sockets,SPL,sqlite3,standard,tokenizer,uri,xml,xmlreader,xmlwriter,Zend OPcache,zend_test,zlib

I measured the memory usage and interned strings usage when running the following script:

readfile("/proc/self/status");
var_dump(opcache_get_status()["interned_strings_usage"]);
MetricBeforeAfterChange
VmPeak514684 kB515032 kB+348 kB (+0.07%)
VmSize514684 kB515032 kB+348 kB (+0.07%)
VmHWM26076 kB26284 kB+208 kB (+0.80%)
VmRSS26076 kB26284 kB+208 kB (+0.80%)
VmData7584 kB7936 kB+352 kB (+4.64%)
VmStk132 kB132 kB+0 kB (+0.00%)
VmExe5772 kB5768 kB-4 kB (-0.07%)
VmLib15720 kB15720 kB+0 kB (+0.00%)
VmPTE168 kB172 kB+4 kB (+2.38%)

Opcache interned strings:

MetricBeforeAfterChange
buffer_size8192 kB8192 kB+0 kB (+0.00%)
used_memory2403.03 kB2428.09 kB+25 kB (+1.04%)
free_memory5788.97 kB5763.91 kB-25 kB (-0.43%)
number_of_strings76938286+593 (+7.71%)

The relative increase of VmData is not negligible, but the absolute difference (352 KiB) is not really high when considering this is likely to be shared between processes. And this is a "fixed" overhead: This does not grow proportionally to the program size or its own memory usage.

Given there is zero time or instruction overhead in the symfony benchmark, this seems acceptable to me.

Comment threadZend/zend_API.h Outdated
Comment threadZend/zend_API.c Outdated
Comment threadZend/zend_API.c Outdated
Comment threadZend/zend_API.c Outdated

@iluuu1994iluuu1994 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the unification, and it looks reasonable to me. Should probably be delayed for 8.6.

@github-actions

Copy link
Copy Markdown

AWS x86_64 (c7i.24xl)

AttributeValue
Environmentaws
Runnerhost
Instance typec7i.metal-24xl (dedicated)
Architecturex86_64
CPU48 cores
CPU settingsdisabled deeper C-states, disabled turbo boost, disabled hyper-threading
RAM188 GB
Kernel6.1.147-172.266.amzn2023.x86_64
OSAmazon Linux 2023.8.20250818
GCC14.2.1
Time2025-09-24 08:25:16 UTC

Laravel 12.2.0 demo app - 100 consecutive runs, 50 warmups, 100 requests (sec)

PHPMinMaxStd devRel std dev %MeanMean diff %MedianMedian diff %SkewP-valueInstr countMemory
PHP - baseline@0d4f0.468830.474520.000650.14%0.469860.00%0.469750.00%3.9610.99917618985144.31 MB
PHP - zstr-arg-info0.459620.468430.001120.24%0.46688-0.63%0.46696-0.59%-5.2110.00017617812344.35 MB

Symfony 2.7.0 demo app - 100 consecutive runs, 50 warmups, 100 requests (sec)

PHPMinMaxStd devRel std dev %MeanMean diff %MedianMedian diff %SkewP-valueInstr countMemory
PHP - baseline@0d4f0.722830.747570.002340.32%0.734460.00%0.734000.00%1.4930.99928732639440.57 MB
PHP - zstr-arg-info0.730160.738810.001540.21%0.73213-0.32%0.73181-0.30%1.9030.00028733410140.66 MB

Wordpress 6.2 main page - 100 consecutive runs, 20 warmups, 20 requests (sec)

PHPMinMaxStd devRel std dev %MeanMean diff %MedianMedian diff %SkewP-valueInstr countMemory
PHP - baseline@0d4f0.579290.582500.000700.12%0.580670.00%0.580470.00%0.6090.999112021465643.92 MB
PHP - zstr-arg-info0.578050.581030.000570.10%0.57930-0.23%0.57921-0.22%0.6490.000112007004843.98 MB

bench.php - 100 consecutive runs, 10 warmups, 2 requests (sec)

PHPMinMaxStd devRel std dev %MeanMean diff %MedianMedian diff %SkewP-valueInstr countMemory
PHP - baseline@0d4f0.428600.538280.025375.78%0.438990.00%0.431700.00%3.4140.999202059503626.96 MB
PHP - zstr-arg-info0.426820.596480.030647.00%0.43790-0.25%0.43008-0.37%4.0340.000202059505927.02 MB

@arnaud-lb

Copy link
Copy Markdown
MemberAuthor

Updated the branch to fix two things:

  • bb512d08106a1d84c5da7aba0ba7946a745015eb: Calling zend_convert_internal_arg() at runtime would break ZEND_RC_MOD_CHECK(), as it allocated persistent strings. This would break pdo_hash_methods().
  • 9ccfb2fcd2335edc7875ee10f5e5fc08ab7297fe: Non-persistent arg infos break zend_get_arg_offset_by_name() again. Updating zend_get_arg_offset_by_name() to look at ZEND_ACC_NEVER_CACHE instead of ZEND_ACC_USER_ARG_INFO fixed that.

@SakiTakamachiSakiTakamachi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[PDO part] LGTM :)

The arg_info member of zend_function is now always a zend_arg_info*. Before,
it was a zend_internal_arg_info* on internal functions, unless the
ZEND_ACC_USER_ARG_INFO flag was set.
ClosesphpGH-19022
This was referenced Jan 16, 2026
arnaud-lb added a commit that referenced this pull request Jan 16, 2026
* Fix zend_call_trampoline_arginfo arg name
Name is "arguments" in documentation:
https://www.php.net/__call#language.oop5.overloading.methods
* Use zend_call_trampoline_arginfo in zend_get_call_trampoline_func()
* Copy the original arg_info in zend_closure_from_frame
None of these changes are observable, but this is cleaner, and this becomes observable in GH-20848.
ClosesGH-20951
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@arnaud-lb@ndossche@dstogov@TimWolla@iluuu1994@Girgias@SakiTakamachi@DanielEScherzer