Repository navigation
Shadow the call, assignment and core statement handlers natively - #6523
Merged
Merged
Conversation
The method reflection $scope->getMethodReflection() returns for nearly every method call: ResolvedMethodReflection over ChangedTypeMethodReflection over the PHP reflection. Both keep the twins' slots and memoization; a delegation to a native ChangedTypeMethodReflection takes its body directly, any other wrapped reflection one cached method site per member. pt_extended_method_reflection_call() is that dispatch for any method reflection (the handlers' entry), next to pt_resolved_method_reflection_new() / pt_changed_type_method_reflection_new(), which the prototypes in TypeTraits.cpp now use. Class-map keys resolvedMethodReflection / changedTypeMethodReflection are gone, resolvedFunctionVariantWithOriginal is new. Differential in type-family.php. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q3SmhnNkB3pEeVRTxqbdbu
The impure point every call handler derives from the callee's reflection: the value class plus createFromVariant() and resolvePureUnlessCallableIsImpureVerdict(), asking the method reflections through pt_extended_method_reflection_call(), the scope through its getType() entry and the Types through their ops. pt_simple_impure_point_resolve() answers createFromVariant() without the intermediate object for native handlers; pt_simple_impure_point_new() replaces the class-map instantiation in TypeTraits.cpp (key simpleImpurePoint removed) and ClosureType::isPure() reads isCertain() from the slot. New ClassReflection entries getDisplayName() / isBuiltin(). Differential in type-family.php. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q3SmhnNkB3pEeVRTxqbdbu
The method call handler with its type, specify-types, create-types and assert-mapping callbacks as native closures, registered as the class's handler entry. NodeScopeResolver, DefaultNarrowingHelper, TypeSpecifier and the early-terminating / return-type / throw-point helpers are reached through direct entries (new: pt_early_terminating_call_helper_is_early_terminating_method_call, pt_method_call_return_type_helper_method_call_return_type, pt_method_throw_point_helper_get_throw_point, pt_type_specifier_get_method_type_specifying_extensions_for_class), as are the method reflections, SimpleImpurePoint, the primer (push/pop without closures, also for MethodCallReturnTypeHelper) and new MutatingScope / ExpressionResult / VariableFlow(Builder) / TypeUtils entries; ArgumentsHandler stays a cached site. The inline containsNullsafe() reader replaces the out-of-line entry. Class-map key invalidateExprNode. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q3SmhnNkB3pEeVRTxqbdbu
The whole twin in one port: processExpr(), processVirtualAssign(), prepareTarget() with every target kind and applyWrite() with every write kind, the conditional-expression machinery (falsey sentinels, ternary arms, match, in_array, derived holders), the offset-chain composition and the narrowing closures as native closures; AssignOpHandler calls the native prepareTarget()/applyWrite() directly, and both split processExpr() so only the walk state sits on the recursion path. Exports pt_assign_handler_prepare_target / _apply_write / _process_virtual_assign, plus direct entries on MutatingScope (8), NodeScopeResolver (3), VariableFlowBuilder (4), VariableFlow (inputs, choice), NonNullabilityHelper and MethodThrowPointHelper. TemplateArgumentObserver, VarAnnotationProcessor, the other handlers' composers and the reflections stay PHP behind one helper each. Twin oddities are mirrored (the reversed-index dim-fetch pairing in produceArrayDimFetchAssignValueToWrite() among them). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q3SmhnNkB3pEeVRTxqbdbu
…d NopHandler natively The hottest statement handlers register their processStmt() bodies as statement-handler entries. ExpressionHandler's gatherer frame is a native closure capturing $currentScope and &$hasAssign; NodeScopeResolver and StatementsHandler are called through their direct entries, the twins' try/finally through pt_finally(). ImplicitToStringCallHelper stays PHP behind EchoHandler's cached site. New direct entries: pt_expression_result_get_truthy_scope / _get_falsey_scope, pt_variable_flow_exit / _conditional, pt_mutating_scope_get_anonymous_function_reflection, pt_node_scope_resolver_push_node_gatherer / _pop_node_gatherer / _collect_return_send. Mirrors the twin's BlockHandler dropping the inner variableFlow when not polluting the scope. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q3SmhnNkB3pEeVRTxqbdbu
The declaration handlers register statement-handler entries. The body gatherers (called for every node a method or function body emits) are native closures with the five gathered lists captured by reference; ClassLikeHandler sorts the class members with zend_hash_sort() and the twin's comparator plus usort()'s stable fallback. New direct entries: MutatingScope enterClassMethod/enterFunction/enterClass, rememberConstructorScope, invalidateExistenceCheckExpressions, getNamespace; ClassReflection hasConstructor/getConstructor/isReadOnly/getFileName/evictPrivateSymbols; VariableLivenessResolver::resolve; ClassStatementsGatherer new/getters; StatementResult slot readers. The declaration processors (AttributesHandler, PhpDocsResolver, ParametersProcessor, DeprecatedAttributeResolver) stay PHP behind shared cached sites in StmtHandlerCalls.h. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q3SmhnNkB3pEeVRTxqbdbu
Registers the if/elseif/else handler as a statement-handler entry. The condition results, branch scopes and flows go through the ExpressionResult, InternalStatementResult, MutatingScope::mergeWith(), VariableFlow::conditional() and NodeScopeResolver direct entries; the condition type's toBoolean()/isTrue()/isFalse() through the native type dispatch, isTrue() and isFalse() asked once per condition type (the twin asks them repeatedly; they are pure). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q3SmhnNkB3pEeVRTxqbdbu
The static call handler with its Closure::bind() scope factory, type, specify-types, create-types and assert-mapping callbacks as native closures, registered as the class's handler entry. The PHP collaborator sites and value helpers it shares with MethodCallHandler (ArgumentsHandler, ArgumentsNormalizer, ParametersAcceptorSelector, acceptors, Assertions, array_merge(), the explicit never check, template resolution against an acceptor) move into CallHandlerSupport.h; its array_merge() now also keeps the engine's shortcut for an empty side over a string-keyed map ([] + [] stays the shared empty array). New entries: pt_mutating_scope_resolve_name / _enter_closure_bind, pt_class_reflection_is / _is_subclass_of_class, pt_type_specifier_get_static_method_type_specifying_extensions_for_class. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q3SmhnNkB3pEeVRTxqbdbu
The new handler with its anonymous-class constructor gatherer (the by-reference capture a reference zval), type, specify-types, template-map and assert-mapping callbacks as native closures, registered as the class's handler entry; the exact-instantiation return type (dynamic static-method return type extensions, the assigned-property and inherited generic constructor paths) runs natively, sharing CallHandlerSupport.h with the other call handlers. New entries: pt_class_reflection_is_final / _as_final / _get_template_type_map / _get_active_template_type_map / _type_map_to_list / _with_types, pt_template_type_map_get_types / _get_type / _resolve_to_bounds / _map, pt_simple_impure_point_resolve_verdict and the inline pt_statement_result_throw_points() reader. Class-map keys dummyConstructorReflection and genericTypeTemplateTraverser. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q3SmhnNkB3pEeVRTxqbdbu
…d VariableControlFlow natively The four final flow classes become native subclasses of the native VariableFlow, their state in the twin's promoted readonly slots. The VariableFlow factories construct them directly (pt_variable_access_flow_new, pt_variable_sequence_flow_new, pt_variable_input_flow_new, pt_variable_control_flow_new) instead of calling the PHP constructors, and VariableLivenessResolver / VariableFlowBuilder read the slots by constant offset after a class-entry compare. Constructors called from PHP keep the twin's assignment order and readonly errors (pt_variable_flow_init_readonly, which also names the declaring class for VariableFlow::$kind now). Drops the class-map keys variableAccessFlow, variableSequenceFlow, variableControlFlow and variableInputFlow. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q3SmhnNkB3pEeVRTxqbdbu
getRawArgs(), isFirstClassCallable() and getArgs() of a FuncCall, MethodCall, NullsafeMethodCall, StaticCall or New_ are answered from the `args` slot by pt_call_like_raw_args(), pt_call_like_is_first_class_callable() and pt_call_like_args() (support.cpp), after a per-class-entry check (an 8-entry per-request table) that the three methods are php-parser's own; an overriding class, an uninitialized slot and getArgs() of a first-class callable (whose assert() throws under zend.assertions=1) go through the methods. MutatingScope (getKeepVoidType, expressionTypeIsUnchangeable, ...), ClassStatementsGatherer, ClassReflection, MethodThrowPointHelper, MethodCallReturnTypeHelper and VariableFlowBuilder switch to them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q3SmhnNkB3pEeVRTxqbdbu
The DI service keeps the twin's constructor arginfo. A statement without a doc comment - the common case - is answered from the scope's function and the node's comments (pt_engine_node_get_comments(), which now leaves a non-array `comments` attribute to the method's return-type TypeError, as the twin does) without a call; with a doc comment the scope is asked through the MutatingScope direct entries (getFile, isInClass, getClassReflection, isInTrait, getTraitReflection, assignVariable) and FileTypeMapper, ResolvedPhpDocBlock, VarTag, the function reflection and Comment::getText() through cached method sites. Native callers use pt_var_annotation_processor_process_var_annotation(); AssignHandler does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q3SmhnNkB3pEeVRTxqbdbu
… low VariableLivenessResolver's collect() and liveBefore() recurse as deep as the flow tree nests - a 10000-operand `$a + $a + ...` is a 10000-deep sequence - where the twin recursed on the VM stack, so the native walk overflowed the C stack (a segfault analysing such a function with the extension loaded). They, and VariableFlowBuilder's targetRead(), targetWrite() and the writes() recursion, now continue through pt_engine_with_stack() when the current stack runs low. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q3SmhnNkB3pEeVRTxqbdbu
ondrejmirtes
force-pushed
the
turbo-native-call-and-statement-handlers
branch
from
September 22, 2026 11:06
dc1125f to
1b3a190
Compare
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.
Shadows the call handlers, the assignment handlers and the hottest statement handlers natively, together with the method reflections, impure points and variable-flow classes they create. Apart from the
#[ShadowedByTurboExtension]/#[ReferencedByTurboExtension]attributes, the PHP twins are unchanged.Expression handlers
MethodCallHandler,StaticCallHandlerandNewHandler. Their type, specify-types, create-types and assert-mapping callbacks are native closures, and each handler is registered as its class's handler entry. The PHP collaborators and value helpers the three share (argument handling, acceptor selection, template resolution against an acceptor) live inCallHandlerSupport.h.DynamicReturnTypeStoragePrimer, which primes the storage for dynamic return-type extensions.AssignHandlerandAssignOpHandler, each as one port:prepareTarget()and every write kind inapplyWrite();Both split
processExpr(), so only the walk state sits on the recursion path.PhpParser call arguments (
getRawArgs(),isFirstClassCallable(),getArgs()) are read from theargsslot. A class that overrides those methods still gets its methods called.Statement handlers
ExpressionHandler,ReturnHandler,EchoHandler,BlockHandlerandNopHandler.ClassMethodHandler,FunctionHandlerandClassLikeHandler. The body gatherers, which run for every node a method or function body emits, are native closures. Class members are sorted with the twin's comparator andusort()'s stable fallback.IfHandler.Values and helpers
ResolvedMethodReflectionandChangedTypeMethodReflection, the wrapper pair$scope->getMethodReflection()returns for nearly every method call.pt_extended_method_reflection_call()dispatches to the native body when it can.SimpleImpurePoint, the impure point every call handler derives from the callee's reflection.VariableFlow.VarAnnotationProcessor. A statement without a doc comment, the common case, is answered without a call.Deep nesting
VariableLivenessResolverandVariableFlowBuilderrecurse as deep as the flow tree nests: a 10000-operand$a + $a + ...is a 10000-deep sequence. The PHP twin recursed on the VM stack, so the native walk could overflow the C stack. They now continue on a fresh stack throughpt_engine_with_stack()when the current one runs low.Verification
With the extension loaded and active:
side-by-side.php,signature-parity.phpandsmoke.phppass, andwalk-trace.phpreports identical traces (612,167 lines).make tests,make phpstan,make cs,make lint,make lint-turboandmake sanitize-turboare clean.make pgowith the strict flags passes under GCC 11.4 (CI's gnu image) against PHP 8.4 and PHP 8.6.🤖 Generated with Claude Code
https://claude.ai/code/session_017MvPby652L7wUqGAHEiEcN