diff --git a/phpunit/code/multi-return-tuple.php b/phpunit/code/multi-return-tuple.php index e38ba6f9..28bb3861 100644 --- a/phpunit/code/multi-return-tuple.php +++ b/phpunit/code/multi-return-tuple.php @@ -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]; diff --git a/phpunit/src/MultiReturnTest.php b/phpunit/src/MultiReturnTest.php index 27a0a09c..7daf1c95 100644 --- a/phpunit/src/MultiReturnTest.php +++ b/phpunit/src/MultiReturnTest.php @@ -24,6 +24,19 @@ final class MultiReturnTest extends TestCase 'std::tie(first, second) = typephp::detail::php_phpunit_multi_values()', $code, ); + $this->assertMatchesRegularExpression( + '/std::tuple (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 (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, diff --git a/src/CompilerBase.php b/src/CompilerBase.php index d10b6ffa..074a1517 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -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); diff --git a/tests/compiler/array/multi-return-move-production.phpt b/tests/compiler/array/multi-return-move-production.phpt new file mode 100644 index 00000000..5ccd6811 --- /dev/null +++ b/tests/compiler/array/multi-return-move-production.phpt @@ -0,0 +1,132 @@ +--TEST-- +Tuple multi-return production safely consumes final local uses +--FILE-- +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" + } +}