perf(compiler): optimize multi-return tuple assignment with move semantics

- Replace temporary Var construction with direct tuple element assignment
- Implement std::move for final local variable uses to avoid unnecessary copies
- Track remaining variable uses to determine when to apply move semantics
- Preserve PHP value-assignment semantics for references and indirect zvals
- Add comprehensive test coverage for tuple multi-return scenarios
- Include tests for repeated values, references, globals, and static variables
```
pull/20/head
韩天峰 1 month ago
parent fa7e2cf7c1
commit aba8d9194d
  1. 6
      phpunit/code/multi-return-tuple.php
  2. 13
      phpunit/src/MultiReturnTest.php
  3. 31
      src/CompilerBase.php
  4. 132
      tests/compiler/array/multi-return-move-production.phpt

@ -20,6 +20,12 @@ function phpunit_multi_three_values(): array
return [1, 2, 3];
}
function phpunit_multi_repeated_value(): array
{
$repeated = 'value';
return [$repeated, $repeated];
}
function phpunit_multi_side_effect(): array
{
return [time(), 2];

@ -24,6 +24,19 @@ final class MultiReturnTest extends TestCase
'std::tie(first, second) = typephp::detail::php_phpunit_multi_values()',
$code,
);
$this->assertMatchesRegularExpression(
'/std::tuple<php::Var, php::Var> (tmp_var_\\d+);\\s*'
. 'std::get<0>\\(\\1\\) = std::move\\(first\\);\\s*'
. 'std::get<1>\\(\\1\\) = std::move\\(second\\);\\s*'
. 'return \\1;/',
$code,
);
$this->assertMatchesRegularExpression(
'/std::tuple<php::Var, php::Var> (tmp_var_\\d+);\\s*'
. 'std::get<0>\\(\\1\\) = repeated;\\s*'
. 'std::get<1>\\(\\1\\) = std::move\\(repeated\\);/',
$code,
);
$this->assertStringContainsString(
'php::Array php_phpunit_multi_values()',
$code,

@ -1964,11 +1964,36 @@ class CompilerBase implements PropertyAccessContext
if (!$v->expr instanceof Expr\Array_) {
throw new \LogicException('Optimized multi-return function must return a fixed array literal');
}
$values = [];
// Assign tuple elements through Variant::operator= instead of constructing
// temporary Vars. The rvalue overload can transfer an owned zval without
// refcount churn while retaining PHP value-assignment semantics for
// references and indirect zvals.
$remainingVariableUses = [];
foreach ($v->expr->items as $item) {
$values[] = Type::VAR . '(' . $this->parseExprAsValue($item->value) . ')';
if ($this->isVarExpr($item->value) && is_string($item->value->name)) {
$name = $this->parseIdentifier($item->value);
$remainingVariableUses[$name] = ($remainingVariableUses[$name] ?? 0) + 1;
}
}
$tuple = $this->genTmpVarName();
$lines = [$this->functionDef->getMultiReturnCppType() . ' ' . $tuple . ';'];
foreach ($v->expr->items as $index => $item) {
$value = $this->parseExprAsValue($item->value);
if ($this->isVarExpr($item->value) && is_string($item->value->name)) {
$name = $this->parseIdentifier($item->value);
$remainingVariableUses[$name]--;
// Only consume a local on its final occurrence. Globals and
// statics outlive the function and must never be emptied.
if ($remainingVariableUses[$name] === 0 && $this->hasLocalVar($name)) {
$value = 'std::move(' . $value . ')';
}
}
$lines[] = 'std::get<' . $index . '>(' . $tuple . ') = ' . $value . ';';
}
return 'return ' . $this->functionDef->getMultiReturnCppType() . '{' . implode(', ', $values) . '};';
$lines[] = 'return ' . $tuple . ';';
return implode(PHP_EOL . $this->getIndent(), $lines);
}
// 实际函数的返回值
$type = $this->detectTypeOfExpr($v->expr);

@ -0,0 +1,132 @@
--TEST--
Tuple multi-return production safely consumes final local uses
--FILE--
<?php
function multi_return_owned_values(): array
{
$text = 'owned';
$array = [1, 2];
$object = new stdClass();
$object->value = 3;
return [$text, $array, $object];
}
function multi_return_repeated_value(): array
{
$value = ['repeated'];
return [$value, $value];
}
function multi_return_reference_value(&$value): array
{
$alias =& $value;
return [$alias, $alias];
}
function multi_return_global_value(): array
{
global $multiReturnGlobal;
return [$multiReturnGlobal, $multiReturnGlobal];
}
function multi_return_static_value(): array
{
static $value = ['static'];
return [$value, $value];
}
function main(): void
{
global $multiReturnGlobal;
$multiReturnGlobal = ['global'];
[$text, $array, $object] = multi_return_owned_values();
var_dump($text, $array, $object->value);
[$first, $second] = multi_return_repeated_value();
$first[] = 'changed';
var_dump($first, $second);
$source = 'reference';
[$referenceFirst, $referenceSecond] = multi_return_reference_value($source);
$referenceFirst = 'changed';
var_dump($source, $referenceFirst, $referenceSecond);
$compatibleArray = multi_return_reference_value($source);
$compatibleArray[0] = 'array changed';
var_dump($source, $compatibleArray);
[$globalFirst, $globalSecond] = multi_return_global_value();
$globalFirst[] = 'changed';
var_dump($multiReturnGlobal, $globalFirst, $globalSecond);
[$staticFirst, $staticSecond] = multi_return_static_value();
$staticFirst[] = 'changed';
var_dump($staticFirst, $staticSecond, multi_return_static_value());
}
?>
--EXPECT--
string(5) "owned"
array(2) {
[0]=>
int(1)
[1]=>
int(2)
}
int(3)
array(2) {
[0]=>
string(8) "repeated"
[1]=>
string(7) "changed"
}
array(1) {
[0]=>
string(8) "repeated"
}
string(9) "reference"
string(7) "changed"
string(9) "reference"
string(9) "reference"
array(2) {
[0]=>
string(13) "array changed"
[1]=>
string(9) "reference"
}
array(1) {
[0]=>
string(6) "global"
}
array(2) {
[0]=>
string(6) "global"
[1]=>
string(7) "changed"
}
array(1) {
[0]=>
string(6) "global"
}
array(2) {
[0]=>
string(6) "static"
[1]=>
string(7) "changed"
}
array(1) {
[0]=>
string(6) "static"
}
array(2) {
[0]=>
array(1) {
[0]=>
string(6) "static"
}
[1]=>
array(1) {
[0]=>
string(6) "static"
}
}
Loading…
Cancel
Save