diff --git a/src/Php/Optimizer/SsaPropOptimizer.php b/src/Php/Optimizer/SsaPropOptimizer.php index a23ad5d5..a3ccceb3 100644 --- a/src/Php/Optimizer/SsaPropOptimizer.php +++ b/src/Php/Optimizer/SsaPropOptimizer.php @@ -1,6 +1,6 @@ intProp optimization to any SSA-proven stable * object. When an object variable has a single definition, no escape/reference/ @@ -33,7 +33,8 @@ use PhpParser\Node\Expr; trait SsaPropOptimizer { /** - * Analyze object stability and identify safe property accesses. + * Analyze object stability for method dispatch and other non-native + * optimizations. * Called after SSA build and var type optimization in parseFunction(). * * Scans the function body AST to find object assignments (e.g. $o = new Foo()), @@ -41,12 +42,28 @@ trait SsaPropOptimizer * This must be done during analysis because $this->context->objects is only * populated during code generation (after analysis). */ - protected function optimizeObjectProps(SsaBuilder $ssa): void + protected function analyzeStableObjects(SsaBuilder $ssa): void { - if (!$this->nativeTypes) { - return; + [$objectAssigns] = $this->collectStableObjectCandidates($ssa); + + foreach ($objectAssigns as $objName => $className) { + if (!$className || $className === 'stdClass' || !$this->hasClass($className)) { + continue; + } + + if (!$this->isObjectSsaStable($ssa, $objName, $objectAssigns)) { + continue; + } + + $this->context->stableObjects[$objName] = $className; } + } + /** + * Identify safe property accesses for native typed property hoisting. + */ + protected function optimizeObjectProps(SsaBuilder $ssa): void + { if ($this->classDef && !$this->classDef->trait) { if ($this->isClassSafeForPropHoisting($this->getFullClassName())) { $unsafeProps = $this->collectDangerousPropOps('this_', $ssa->getStmts()); @@ -58,36 +75,11 @@ trait SsaPropOptimizer } } - if (empty($ssa->ssaVars)) { - return; - } - - // Seed with function parameters that are typed objects, then propagate - // simple object aliases such as `$next = $right`. - $objectAssigns = []; - foreach ($this->context->objects as $objName => $className) { - if ($objName === 'this_') { - continue; - } - $objectAssigns[$objName] = $className; - } - $objectAliases = []; - $objectAssigns = $this->collectObjectAssignments($ssa->getStmts(), $objectAssigns, $objectAliases); - - foreach ($objectAssigns as $objName => $className) { - if ($objName === 'this_') { - continue; - } - - if (!$className || $className === 'stdClass' || !$this->hasClass($className)) { - continue; - } - - if (!$this->isObjectSsaStable($ssa, $objName, $objectAssigns)) { - continue; - } + [, $objectAliases] = $this->collectStableObjectCandidates($ssa); + foreach ($this->context->stableObjects as $objName => $className) { if (!$this->isClassSafeForPropHoisting($className)) { + $this->context->unsafeObjectProps[$objName] = ['*' => true]; continue; } @@ -98,9 +90,31 @@ trait SsaPropOptimizer if ($unsafeProps) { $this->context->unsafeObjectProps[$objName] = $unsafeProps; } + } + } - $this->context->stableObjects[$objName] = $className; + /** + * @return array{0: array, 1: array} + */ + protected function collectStableObjectCandidates(SsaBuilder $ssa): array + { + if (empty($ssa->ssaVars)) { + return [[], []]; } + + $objectAssigns = []; + foreach ($this->context->objects as $objName => $className) { + if ($objName === 'this_') { + continue; + } + $objectAssigns[$objName] = $className; + } + $objectAliases = []; + + return [ + $this->collectObjectAssignments($ssa->getStmts(), $objectAssigns, $objectAliases), + $objectAliases, + ]; } /** diff --git a/src/Php/Translator.php b/src/Php/Translator.php index 1c577659..3577e509 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -2835,12 +2835,14 @@ CODE; $ssaBuilder = new SsaBuilder($v->stmts, $this->functionDef->argInfoList); $ssaBuilder->build(); $this->context->ssaBuilder = $ssaBuilder; - // Narrow local variable types based on SSA analysis - $this->optimizeVarTypes($ssaBuilder); - // Narrow range-proven loop counters independent of native_types - $this->optimizeLoopVars($ssaBuilder); - // Analyze object stability for property reference hoisting - $this->optimizeObjectProps($ssaBuilder); + $this->analyzeStableObjects($ssaBuilder); + if ($this->nativeTypes) { + // Narrow local variable types based on SSA analysis. + $this->optimizeVarTypes($ssaBuilder); + // Narrow range-proven loop counters and native property accesses. + $this->optimizeLoopVars($ssaBuilder); + $this->optimizeObjectProps($ssaBuilder); + } $this->context->resetAnalysisTemporaries($oriLocalVars, $oriTmpVarIndex); } diff --git a/tests/aot/dynamic_call/call-namespaced-parent-override.phpt b/tests/aot/dynamic_call/call-namespaced-parent-override.phpt new file mode 100644 index 00000000..91318641 --- /dev/null +++ b/tests/aot/dynamic_call/call-namespaced-parent-override.phpt @@ -0,0 +1,43 @@ +--TEST-- +call overridden method through namespaced parent type +--FILE-- +run(); + } +} + +namespace { + use Demo\Impl; + + function main(): int + { + $r = Demo\run(new Impl()); + echo "result: $r\n"; + return $r === 'impl' ? 0 : 1; + } +} +?> +--EXPECT-- +result: impl diff --git a/tests/aot/dynamic_call/call-parent-return-override-no-native-types.phpt b/tests/aot/dynamic_call/call-parent-return-override-no-native-types.phpt new file mode 100644 index 00000000..423d0014 --- /dev/null +++ b/tests/aot/dynamic_call/call-parent-return-override-no-native-types.phpt @@ -0,0 +1,35 @@ +--TEST-- +call overridden method through parent parameter type without native_types +--FILE-- +run(); +} + +function main(): int +{ + $r = run(new Impl()); + echo "result: $r\n"; + return $r === 'impl' ? 0 : 1; +} +?> +--EXPECT-- +result: impl