Repository navigation
Zend: Read string arguments of #[\Deprecated] without constructing it - #24198
Open
ArtUkrainskiy wants to merge 1 commit into
Open
ArtUkrainskiy wants to merge 1 commit into
ArtUkrainskiy wants to merge 1 commit into
Conversation
Emitting the deprecation of a function constructed a \Deprecated object on every call to read its message and since arguments: an object, a constructor call with named arguments, two property writes, two reads and the destruction, about 2,900 of the 5,000 instructions a deprecated call costs, paid whether or not E_DEPRECATED is reported. The object was constructed for the parameter handling of the constructor, named arguments, defaults, type errors, constant expressions, and nearly every attribute has only string literals, which need none of it. Read message and since straight from the arguments when they are strings or null, positional or named, each at most once; everything else, a constant expression, another type, a duplicate or unknown name, goes through the constructor as before, and so does a call with an exception pending, where the constructor is not run and nothing is emitted. The same for #[\NoDiscard]. The message is byte for byte the same; what changes is that the constructor is no longer called for literal arguments, so an execute_internal observer no longer sees it. A call to ReflectionProperty::setAccessible() goes from 5,010 to 2,770 instructions, a deprecated user function with literal arguments from 4,640 to 2,520.
ArtUkrainskiy
force-pushed
the
deprecated-suffix-cache
branch
from
October 8, 2026 17:35
c3c9372 to
bd47375
Compare
ArtUkrainskiy
marked this pull request as ready for review
October 8, 2026 20:14
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First of the two steps in GH-24191.
Emitting the deprecation of a function constructs a
\Deprecatedobject on every call to read itsmessageandsince:zend_get_attribute_object(), the constructor throughzend_call_function()with named arguments, two property writes, two reads, destruction — about 2,900 of the 5,000 instructions a deprecated call costs, paid whether or notE_DEPRECATEDis reported. The constructor is there for parameter handling (named arguments, defaults, type errors, constant expressions), and nearly every attribute has only string literals, which need none of it; this is the string bypass discussed in #11293 that didn't make it into the merge.This reads
message/sincestraight from the arguments when they are strings or null, positional or named, each at most once; anything else goes through the constructor as before and gets its errors from it, and so does a call with an exception pending (preloading relies on the constructor not running there). Same for#[\NoDiscard]. The message is byte for byte the same.Instructions per call, release build:
ReflectionProperty::setAccessible()5,010 → 2,772; a deprecated user function with literal arguments 4,641 → 2,516; with a runtime constant as argument (constructor path) 4,831 → 4,976, the cost of trying the fast path first.What observers see:
Deprecated::__construct()/NoDiscard::__construct()are no longer called for literal arguments, sozend_execute_internalhooks don't see them any more;nodiscard/007.phptandobserver_sqlite_create_function.phptrecorded those frames and are updated, 007 now shows both paths. Tests added for the argument forms the fast path accepts and refuses (positional, named, null, duplicate and unknown names, too many arguments), expectations generated on master, and for repeated internal calls through closures, reflection and an error handler. Full test suite on a debug ZTS build with 45 extensions passes; measurements and the differential runs: https://github.com/ArtUkrainskiy/php-src-bench/tree/main/reports/deprecation-cost