Skip to content

Shadow the function call, argument and fetch handlers natively - #6530

Merged
ondrejmirtes merged 12 commits into
2.3.xfrom
turbo-native-function-and-fetch-handlers
Sep 22, 2026
Merged

ondrejmirtes merged 12 commits into
2.3.xfrom
turbo-native-function-and-fetch-handlers

Conversation

@ondrejmirtes

@ondrejmirtes ondrejmirtes commented Sep 22, 2026 •

Copy link
Copy Markdown
Member

Shadows the function-call path, the argument machinery and the property, offset and constant fetch handlers natively. Apart from the #[ShadowedByTurboExtension] / #[ReferencedByTurboExtension] attributes, the PHP twins are unchanged.

Arguments

  • ArgumentsHandler: processArgs() / processDroppedArgs(). Walk state, argument records and per-argument locals live in one allocated block, and the argument loop runs under pt_engine_with_stack().
  • PassedByReference, DummyParameter and ExtendedDummyParameter, read in place through ParameterValues.h.
  • ArgumentsNormalizer: named-argument reordering, including the call_user_func*() variants. A call without named arguments returns the call itself, with no copy.
  • ParametersAcceptorSelector: variant selection and the intrinsic argument overrides (array_map/filter/walk/find, curl_setopt(_array) with its option type table, implode, Closure::bind), plus variant combination.

Function calls

  • FuncCallHandler. processExpr() covers dynamic and named callees, immediately invoked closures, clone-with, array_walk's by-reference gatherer and __invoke. Its callbacks are native closures, and return-type resolution, narrowing, throw points and compact()'s variable flow are native too.
  • FuncCallScopeEffectsHelper and OutputBufferHelper: by-reference array-function effects, paired-read invalidations, extract(), output-buffer level tracking and volatile-expression invalidation.
  • Native function reflections are read from their slots, and hasFunction()/getFunction() of a memoizing provider over BetterReflection are answered from the provider's memos. A miss or any other provider calls the method as before.

Fetch handlers

  • PropertyFetchHandler, StaticPropertyFetchHandler, NullsafePropertyFetchHandler and PropertyHookThrowPointsResolver. The per-fetch property reflection getters read the slots of the final reflection chain in place.
  • ArrayDimFetchHandler, including the ArrayAccess offsetGet throw points and return type, and VariableWriteOffset.
  • ConstFetchHandler and ClassConstFetchHandler. The true/false/null literals and tracked constant fetches resolve natively; ConstantResolver and the initializer resolver stay PHP behind cached method sites.

Declarations

AttributesHandler and ParametersProcessor, so the attribute-argument closure recursion stays native. An empty attribute or parameter list returns without a call.

Verification

With the extension loaded and active:

  • side-by-side.php, signature-parity.php and smoke.php pass, and walk-trace.php reports identical traces (612,287 lines).
  • make tests, make phpstan, make cs, make lint, make lint-turbo and make sanitize-turbo are clean.
  • make pgo with 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

@ondrejmirtes
ondrejmirtes force-pushed the turbo-native-function-and-fetch-handlers branch from ede92ae to 28754fe Compare September 22, 2026 12:09
ondrejmirtes and others added 12 commits September 22, 2026 14:28
Ports processArgs()/processDroppedArgs() with the twin's DI arginfo, exported as
pt_arguments_handler_process_args() / _process_dropped_args(); the call handlers'
CallHandlerSupport sites use them. Walk state, arg records and per-arg locals live in
one emalloc'ed block, the argument loop runs under pt_engine_with_stack(). NodeScopeResolver,
AssignHandler, the method reflections and SimpleImpurePoint are called through their
direct entries (adds pt_node_scope_resolver_look_for_unset_allowed_undefined_expressions /
_is_returning/_is_consuming_stored_expression_results), ClosureProcessor & co. through
cached sites. Adds MutatingScope entries (push/popInFunctionCall, restoreThis, ...) and
pt_type_utils_find_callable_type(). Class-map key added: closureHandler.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q3SmhnNkB3pEeVRTxqbdbu
…atively

Ports the by-reference mode singletons (kept in the twin's static $registry) and
the dummy parameter value classes. Entries: pt_passed_by_reference_create_*(),
_mode(), _combine(), pt_dummy_parameter_new()/_construct(),
pt_extended_dummy_parameter_new(); ParameterValues.h reads their slots in place.
ArgumentsHandler reads parameters through those slots, TypeTraits and
NativeParameterReflection use the entries. Class-map keys removed: passedByReference,
dummyParameter, extendedDummyParameter; added: allowedConstantsResult.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q3SmhnNkB3pEeVRTxqbdbu
Ports the named-argument reordering: reorderArgs(), the four reorder*Arguments()
and the call_user_func*() variants, exported as pt_arguments_normalizer_reorder_*().
A call without named arguments answers with the call itself without the
array_values() copy; parameters are read through their DummyParameter slots, call
arguments through pt_call_like_args(). CallHandlerSupport, MethodCallReturnTypeHelper
and ClassReflection call the entries. Differential: reordering observations in
tests/type-family.php. Class-map key removed: argumentsNormalizer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q3SmhnNkB3pEeVRTxqbdbu
Ports variant selection, the intrinsic argument overrides (array_map/filter/walk/find,
curl_setopt(_array) incl. the option type table, implode, Closure::bind), the template
predicates and variant combination; exported as pt_parameters_acceptor_selector_*().
applyIntrinsicArgOverrides() reads through callables or, for selectFromArgs(), the scope
directly; ArgumentsHandler passes its native closure holders without \Closure wrappers.
The Type, helper, call-handler and scope callers use the entries. Class-map key removed:
parametersAcceptorSelector; added: genericParametersAcceptorResolver, functionVariant,
extendedCallableFunctionVariant.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q3SmhnNkB3pEeVRTxqbdbu
FuncCallScopeEffectsHelper's by-ref array function effects, paired-read
invalidations, possibly-impure remembering, extract(), output-buffer and
volatile-expression invalidation run natively; applyCallScopeEffects() and
applyArrayWalkResult() are exported as pt_func_call_scope_effects_helper_*,
OutputBufferHelper as pt_output_buffer_helper_get_level_delta/_apply_level_delta.
FunctionReflectionAccess.cpp reads NativeFunctionReflection's name/variants/
throw type/asserts/hasSideEffects/isBuiltin, ExtendedFunctionVariant's return
type and parameters, ExtendedNativeParameterReflection::isOptional() and
Assertions::getAll() from their slots (the method for any other class).
New MutatingScope entries: after_extract_call, after_clearstatcache_call,
after_open_ssl_call. AssignHandler and the CallLike arguments are reached
through their direct entries; GeneralizePrecision stays PHP behind a cached
site. Class map: nativeFunctionReflection.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q3SmhnNkB3pEeVRTxqbdbu
processExpr() (dynamic and named callees, immediately invoked closures,
clone-with, array_walk's by-ref gatherer, __invoke) runs natively with the
typeCallback / specifyTypesCallback / createTypesCallback / gatherer as native
closures (the typeCallback keeps its real WeakReference to the storage);
resolveReturnType(), specifyTypes(), isFuncCallNarrowable(),
getFunctionThrowPoint(), getDynamicFunctionReturnType() and the compact()
variable flow are native. New entries:
pt_type_specifier_get_function_type_specifying_extensions,
pt_early_terminating_call_helper_is_early_terminating_function_call,
pt_args_result_with_resolved_parameters_acceptor,
pt_arguments_normalizer_reorder_call_user_func_arguments/_array_arguments,
AnalyserValues readers pt_statement_result_exit_points and
pt_simple_impure_point_identifier/_description; reorderFuncArguments() and the
call_user_func reorders join CallHandlerSupport.h. ArgumentsHandler,
ArgumentsNormalizer, ParametersAcceptorSelector, the storage primer,
SimpleImpurePoint, AssignHandler and the CallLike arguments are called through
their direct entries; extensions through per-class direct-mapped sites.
Class map: cloneHandler, cloneExpr, closureReturnStatementsNode.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q3SmhnNkB3pEeVRTxqbdbu
pt_reflection_provider_has_function() / _get_function() answer
hasFunction() / getFunction() of a MemoizingReflectionProvider over a
BetterReflectionProvider from its resolvedFunctionNames and
functionReflections memos (nameResolutionCacheKey() rebuilt from the scope's
namespace and the Name's slot, exit/die answering themselves); a miss, any
other provider, name class or answerer calls the method, which resolves and
memoizes. FuncCallHandler, DefaultNarrowingHelper, TypeSpecifier's
function-call impurity gate and ConstantStringType's callable checks use
them. Class map key betterReflectionProvider.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q3SmhnNkB3pEeVRTxqbdbu
…ertyFetchHandler and PropertyHookThrowPointsResolver natively

The three property fetch handlers with their type, specify-types, create-types and
issetability reflection-resolver callbacks as native closures (the nullsafe
left-falsey-scope memo a shared reference capture), registered as handler entries;
AssignHandler's composeResult(), set-hook and PhpVersion sites switched to the new
pt_property_fetch_handler_compose_result / pt_static_property_fetch_handler_compose_result /
pt_property_hook_throw_points_resolver_get_throw_points_from_property_hook entries.
The per-fetch property reflection getters read the slots of the final
Resolved/ChangedType/PhpPropertyReflection chain, PhpMethodFromParserNodeReflection and
PhpVersion in place (pt_property_reflection_*, pt_php_version_supports_property_hooks).
New entries: pt_mutating_scope_get_instance_property_reflection / _is_in_write_expression_assign,
pt_class_reflection_has_native_property / _get_native_property,
pt_non_nullability_helper_get_active_ensured_original_type / _ensure_shallow_non_nullability /
_revert_non_nullability, pt_node_scope_resolver_process_expr_node_consuming_stored.
Class-map keys nullsafePropertyFetchExpressionNode and phpVersion. Twin quirk mirrored:
StaticPropertyFetchHandler::composeResult() drops its staticPropertyAccess impure point
when the class is an expression.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q3SmhnNkB3pEeVRTxqbdbu
The offset fetch handler with its `$arr[]` and offset type callbacks and both
specify-types callbacks as native closures, registered as the class's handler entry;
the ArrayAccess offsetGet throw points and return type (MethodThrowPointHelper,
MethodCallReturnTypeHelper) and the offset-read variable flow run natively.
AssignHandler's composeResult() site uses the new
pt_array_dim_fetch_handler_compose_result entry. VariableWriteOffset::fromType() is
native too (pt_variable_write_offset_from_type, with a smoke differential), and
VariableFlowBuilder calls it directly. Class-map key removed: variableWriteOffset.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q3SmhnNkB3pEeVRTxqbdbu
The constant and class constant fetch handlers with their type and specify-types
callbacks as native closures (the class-type callback ClassConstFetchHandler hands
to InitializerExprTypeResolver too), registered as handler entries. The true /
false / null literals, the namespaced and global tracked constant fetches
(MutatingScope::hasExpressionType() / getTrackedExpressionType()) resolve natively;
ConstantResolver::resolveConstant() / resolveConstantType() and
InitializerExprTypeResolver::getClassConstFetchTypeByReflection() stay PHP behind
cached method sites.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q3SmhnNkB3pEeVRTxqbdbu
Ports processAttributeGroups() and processParams() with the twins' DI arginfo,
exported as pt_attributes_handler_process_attribute_groups() /
pt_parameters_processor_process_params(); StmtHandlerCalls.h's ptsh helpers (the
declaration handlers' calls) now use them. An empty attribute or parameter list
answers without a call. MutatingScope::resolveName(), the reflection provider and
ClassReflection, the method reflection's variants, ParametersAcceptorSelector,
ArgumentsNormalizer, ArgumentsHandler and NodeScopeResolver are called through
their direct entries, so the attribute-argument closure recursion stays native.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q3SmhnNkB3pEeVRTxqbdbu
@ondrejmirtes
ondrejmirtes force-pushed the turbo-native-function-and-fetch-handlers branch from 28754fe to 3a093d3 Compare September 22, 2026 12:28
@ondrejmirtes
ondrejmirtes merged commit 3a093d3 into 2.3.x Sep 22, 2026
1 check passed
@ondrejmirtes
ondrejmirtes deleted the turbo-native-function-and-fetch-handlers branch September 22, 2026 12:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant