From 8b1c8e7d2670b4cb39af0fee3fff0556cac52633 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 28 Aug 2026 16:41:56 +0800 Subject: [PATCH] perf: streamline dynamic property writes --- src/Parser/AssignOpTrait.php | 20 ++++++- src/Parser/ForeachTrait.php | 2 +- src/Parser/PropertyAccessTrait.php | 2 +- .../dynamic-property-write-fast-path.phpt | 60 +++++++++++++++++++ 4 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 tests/compiler/object_property/dynamic-property-write-fast-path.phpt diff --git a/src/Parser/AssignOpTrait.php b/src/Parser/AssignOpTrait.php index 63dd112e..d0aca8a6 100644 --- a/src/Parser/AssignOpTrait.php +++ b/src/Parser/AssignOpTrait.php @@ -102,7 +102,12 @@ trait AssignOpTrait return $code . '((' . $tmp . ' = ' . $value . ', ' . "{$array}.offsetSet({$dim}, {$tmp})" . '), ' . $tmp . ')'; } - protected function parseAssignPropertyFetch(NodeAbstract $left, NodeAbstract $right, ?PropertyWriteTarget $target = null): string + protected function parseAssignPropertyFetch( + NodeAbstract $left, + NodeAbstract $right, + ?PropertyWriteTarget $target = null, + bool $resultUnused = false, + ): string { if ($target !== null) { $this->assertCanAssignPropertyWrite($target, $right); @@ -115,6 +120,15 @@ trait AssignOpTrait $rightExpr = $this->wrapObjectPropertyAssignTypeCheck($left, $right, $rightExpr); } + if ($resultUnused + && $left instanceof Expr\PropertyFetch + && !$this->shouldMaterializeOrderedOperand($left->name) + && $this->canEmitDirectArrayWriteOperand($right) + && $this->canEmitDynamicPropertyTarget($target) + ) { + return $this->emitDynamicPropertyFetchWrite($left, $rightExpr, $target); + } + $tmp = $this->genTmpVarName(); $this->addLocalVar($tmp, Type::VAR); // Comma expression: store RHS → execute side effect → evaluate to stored value @@ -494,7 +508,7 @@ trait AssignOpTrait } if ($propertyWriteTarget !== null && $this->shouldUseDynamicNativePropertyWrite($left, $type)) { - return $this->parseAssignPropertyFetch($left, $right, $propertyWriteTarget); + return $this->parseAssignPropertyFetch($left, $right, $propertyWriteTarget, $resultUnused); } if ($this->isVarExpr($left)) { @@ -645,7 +659,7 @@ trait AssignOpTrait } } } elseif ($this->isPropertyFetch($left) and !$this->isNativePropertyAccess($left)) { - return $this->parseAssignPropertyFetch($left, $right, $propertyWriteTarget); + return $this->parseAssignPropertyFetch($left, $right, $propertyWriteTarget, $resultUnused); } elseif ($this->isArrayDimFetch($left) and $this->isVarExpr($left->var)) { $tmp = $this->parseIdentifier($left->var); if ($this->getVarType($tmp) === Type::STR and $left->dim === null) { diff --git a/src/Parser/ForeachTrait.php b/src/Parser/ForeachTrait.php index 54f5478d..edd16e3c 100644 --- a/src/Parser/ForeachTrait.php +++ b/src/Parser/ForeachTrait.php @@ -124,7 +124,7 @@ trait ForeachTrait { $iterator = $this->genTmpVarName(); $byRef = $node->byRef ? 'true' : 'false'; - $scope = $this->class ? $this->getClassEntryPtr($this->getFullClassName()) : 'nullptr'; + $scope = $this->class ? $this->getLocalClassEntryPtr($this->getFullClassName()) : 'nullptr'; $code = '{' . PHP_EOL; $this->indentLevel++; $code .= $this->getIndent() . "php::ForeachIterator $iterator{{$iterableVar}, $byRef, $scope};" . PHP_EOL; diff --git a/src/Parser/PropertyAccessTrait.php b/src/Parser/PropertyAccessTrait.php index a73a615c..f821b092 100644 --- a/src/Parser/PropertyAccessTrait.php +++ b/src/Parser/PropertyAccessTrait.php @@ -41,7 +41,7 @@ trait PropertyAccessTrait { $scope = $this->usesTraitPropertyScope($object) ? 'php::FakeScopeGuard::current()' - : ($this->class ? $this->getClassEntryPtr($this->getFullClassName()) : 'nullptr'); + : ($this->class ? $this->getLocalClassEntryPtr($this->getFullClassName()) : 'nullptr'); return 'typephp_write_property_scoped(' . $object . ', ' . $property . ', ' . $value . ', ' . $scope . ')'; } diff --git a/tests/compiler/object_property/dynamic-property-write-fast-path.phpt b/tests/compiler/object_property/dynamic-property-write-fast-path.phpt new file mode 100644 index 00000000..ad08be30 --- /dev/null +++ b/tests/compiler/object_property/dynamic-property-write-fast-path.phpt @@ -0,0 +1,60 @@ +--TEST-- +Dynamic property statement writes preserve scope, evaluation and reference value semantics +--FILE-- +$name = $value; + } + + public function writeFromReference(string $name, mixed &$value): void + { + $this->$name = $value; + } + + public function writeComputed(string $name, int &$calls): void + { + $this->$name = nextDynamicValue($calls); + } + + public function value(): int + { + return $this->hidden; + } +} + +function nextDynamicValue(int &$calls): int +{ + $calls++; + return 41; +} + +function main(): void +{ + $writer = new DynamicWriter(); + $name = 'hidden'; + $writer->write($name, 17); + var_dump($writer->value()); + + $calls = 0; + $writer->writeComputed($name, $calls); + var_dump($writer->value(), $calls); + + $source = 42; + $writer->writeFromReference($name, $source); + $source = 43; + var_dump($writer->value(), $source); +} +?> +--EXPECT-- +int(17) +int(41) +int(1) +int(42) +int(43)