diff --git a/lib/Compiler.php b/lib/Compiler.php index 84883cc3ad7..d0fa349a62a 100755 --- a/lib/Compiler.php +++ b/lib/Compiler.php @@ -8584,11 +8584,12 @@ protected function findArrayExprForResult(Operand $result, Block $block): ?Op\Ex } /** - * php-cfg may lower `f(['a' => 1])` as Expr_Array (result Var#n) then FuncCall (arg Var#m) (#8561). + * php-cfg may lower inline Expr_Array / Expr_New results and call/ctor args to distinct + * temporaries (`f(['a' => 1])`, `new C(['x'])`, `g(new C('x'))`) (#8561). * - * @return ?OpCode TYPE_ASSIGN bridging producer slot → call-arg slot + * @return ?string producer slot to pass to TYPE_ARG_SEND instead of the empty arg slot */ - private function tryBridgeInlineArrayLiteralCallArg(Operand $arg, Block $block): ?OpCode + private function findInlineExprCallArgProducerSlot(Operand $arg, Block $block): ?string { if (!$arg instanceof Operand\Temporary || null === $block->orig) { return null; @@ -8597,28 +8598,14 @@ private function tryBridgeInlineArrayLiteralCallArg(Operand $arg, Block $block): $argRoot = Block::cfgVarRoot($arg); for ($i = 0, $n = count($children); $i < $n; ++$i) { $child = $children[$i]; - if (!($child instanceof Op\Expr\FuncCall - || $child instanceof Op\Expr\NsFuncCall - || $child instanceof Op\Expr\MethodCall - || $child instanceof Op\Expr\StaticCall)) { + if (!$this->isInlineExprCallArgConsumer($child)) { continue; } - $usesArg = false; - foreach ($child->args as $callArg) { - if ($callArg === $arg) { - $usesArg = true; - break; - } - if (null !== $argRoot && Block::cfgVarRoot($callArg) === $argRoot) { - $usesArg = true; - break; - } - } - if (!$usesArg || 0 === $i) { + if (!$this->inlineExprCallArgUsesOperand($child, $arg, $argRoot) || 0 === $i) { continue; } $prev = $children[$i - 1]; - if (!$prev instanceof Op\Expr\Array_) { + if (!$this->isInlineExprCallArgProducer($prev)) { continue; } $producerSlot = $block->slotForOperand($prev->result); @@ -8630,12 +8617,47 @@ private function tryBridgeInlineArrayLiteralCallArg(Operand $arg, Block $block): return null; } - return new OpCode(OpCode::TYPE_ASSIGN, $argSlot, $argSlot, $producerSlot); + return $producerSlot; } return null; } + private function isInlineExprCallArgConsumer(Op $op): bool + { + return $op instanceof Op\Expr\FuncCall + || $op instanceof Op\Expr\NsFuncCall + || $op instanceof Op\Expr\MethodCall + || $op instanceof Op\Expr\StaticCall + || $op instanceof Op\Expr\New_; + } + + private function isInlineExprCallArgProducer(Op $op): bool + { + return $op instanceof Op\Expr\Array_ + || $op instanceof Op\Expr\New_; + } + + /** + * @param ?Operand $argRoot from Block::cfgVarRoot($arg) + */ + private function inlineExprCallArgUsesOperand(Op $consumer, Operand $arg, ?Operand $argRoot): bool + { + if (!property_exists($consumer, 'args') || !is_array($consumer->args)) { + return false; + } + foreach ($consumer->args as $callArg) { + if ($callArg === $arg) { + return true; + } + if (null !== $argRoot && Block::cfgVarRoot($callArg) === $argRoot) { + return true; + } + } + + return false; + } + protected function findPropertyFetchForResult(Operand $result, Block $block): ?Op\Expr\PropertyFetch { foreach ($block->orig->children as $child) { @@ -9720,11 +9742,10 @@ protected function compileCallArgSends(array $args, Block $block): array $sends = []; foreach ($args as $arg) { - $bridge = $this->tryBridgeInlineArrayLiteralCallArg($arg, $block); - if (null !== $bridge) { - $sends[] = $bridge; + $valueSlot = $this->findInlineExprCallArgProducerSlot($arg, $block); + if (null === $valueSlot) { + $valueSlot = $this->compileOperand($arg, $block, true); } - $valueSlot = $this->compileOperand($arg, $block, true); if (null === $valueSlot && $arg instanceof Operand\NullOperand) { $valueSlot = $this->registerNullConstantSlot($block, $arg); } diff --git a/test/aot/BootstrapPhaseCTest.php b/test/aot/BootstrapPhaseCTest.php index 8f2b50aaf37..8189533b493 100644 --- a/test/aot/BootstrapPhaseCTest.php +++ b/test/aot/BootstrapPhaseCTest.php @@ -76,6 +76,23 @@ public function testInlineArrayLiteralCallArgAotLinkAndExecute(): void ); } + /** Inline new/array call args: php-cfg uses distinct temporaries for Expr_New vs FuncCall/New (#8561). */ + public function testInlineNewExprCallArgAotLinkAndExecute(): void + { + $this->assertBootstrapFixtureLinkAndExecute( + 'const_string_folder_smoke.php', + '11' + ); + } + + public function testInlineNewArrayCtorArgAotLinkAndExecute(): void + { + $this->assertBootstrapFixtureLinkAndExecute( + 'const_string_folder_func_call_args.php', + '1' + ); + } + public function testMinimalClassAotLinkAndExecute(): void { if (!self::isLlvmReady()) { diff --git a/test/repro/inline_new_array_ctor_arg.php b/test/repro/inline_new_array_ctor_arg.php new file mode 100644 index 00000000000..1b4362364c0 --- /dev/null +++ b/test/repro/inline_new_array_ctor_arg.php @@ -0,0 +1,22 @@ + */ + public array $args; + + public function __construct(array $args) + { + $this->args = $args; + } +} + +function hasCallArg(DeployCallStub $call, int $index): int +{ + return isset($call->args[$index]) ? 1 : 0; +} + +$call = new DeployCallStub(['templates', 'fallback']); +echo (string) hasCallArg($call, 0), "\n"; diff --git a/test/repro/inline_new_call_arg.php b/test/repro/inline_new_call_arg.php new file mode 100644 index 00000000000..76df5ddc13b --- /dev/null +++ b/test/repro/inline_new_call_arg.php @@ -0,0 +1,20 @@ +value = $value; + } +} + +function foldLiteral(LiteralStub $operand): string +{ + return $operand->value; +} + +echo foldLiteral(new LiteralStub('templates')) === 'templates' ? '1' : '0';