From 04c3376d70eae89fabd03e1eb321099953d097f0 Mon Sep 17 00:00:00 2001 From: PurHur Date: Thu, 21 May 2026 17:08:19 +0000 Subject: [PATCH] Fix AOT ?? compile path for superglobal array keys (partial #148) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Skip eager ArrayDimFetch before coalesce when php-cfg inserts ConstFetch between fetch and Coalesce; defer leftBlock scope inherit until after ISSET; avoid freeing the merge target before branch lowering; refresh endBlock scope after wiring branches. Pass dim operand into dimFetch for literal keys. Do not free coalesce result before branch JIT (issue #99). Remaining: coalesce_get_present.phpt still prints empty at AOT runtime (LLVM merge of branch assignments into coalesce result — see #148). Co-authored-by: Cursor --- lib/Compiler.php | 33 +++++++++++++++++++++++++++++---- lib/JIT.php | 4 ++-- lib/JIT/IssetHelper.php | 2 +- lib/JIT/Variable.php | 5 ++++- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/lib/Compiler.php b/lib/Compiler.php index 6ea4147e..31e595bf 100755 --- a/lib/Compiler.php +++ b/lib/Compiler.php @@ -93,8 +93,7 @@ protected function compileOps(array $ops, Block $block): void { $block = $this->compileNullsafePropertyFetch($child, $block); } elseif ( $child instanceof Op\Expr\ArrayDimFetch - && $i + 1 < $opCount - && $this->isArrayDimFetchOnlyCoalesceLeft($child, $ops[$i + 1]) + && $this->findsCoalesceUsingFetchAsLeft($ops, $i, $child) ) { // Lowered by compileCoalesce via isset(container, dim) — no eager fetch (#99, #273). break; @@ -129,6 +128,29 @@ private function isArrayDimFetchOnlyCoalesceLeft( return $left === $fetch->result; } + /** + * php-cfg may emit ConstFetch (e.g. ?? right literal) between fetch and Coalesce; still skip eager fetch. + * + * @param Op[] $ops + */ + private function findsCoalesceUsingFetchAsLeft(array $ops, int $fetchIndex, Op\Expr\ArrayDimFetch $fetch): bool + { + $opCount = count($ops); + for ($j = $fetchIndex + 1; $j < $opCount; ++$j) { + $candidate = $ops[$j]; + if ($candidate instanceof Op\Expr\BinaryOp\Coalesce) { + return $this->isArrayDimFetchOnlyCoalesceLeft($fetch, $candidate); + } + if ($candidate instanceof Op\Expr\ConstFetch) { + continue; + } + + return false; + } + + return false; + } + protected function compileClassLike(Op\Stmt\ClassLike $class, Block $block): OpCode { $type = 0; if ($class instanceof Op\Stmt\Class_) { @@ -637,11 +659,13 @@ protected function compileIsset(Op\Expr\Isset_ $expr, Block $block): array protected function compileCoalesce(Op\Expr\BinaryOp\Coalesce $expr, Block $block): Block { + if ($expr->result instanceof Temporary) { + $expr->result->usages[] = $expr->result; + } $resultSlot = $this->compileOperand($expr->result, $block, false); $endBlock = new Block($block->orig); $endBlock->inheritUndefinedLocals = true; - $endBlock->inheritScopeFrom($block); $rightBlock = new Block($block->orig); $rightBlock->inheritUndefinedLocals = true; @@ -656,7 +680,6 @@ protected function compileCoalesce(Op\Expr\BinaryOp\Coalesce $expr, Block $block $leftBlock = new Block($block->orig); $leftBlock->inheritUndefinedLocals = true; - $leftBlock->inheritScopeFrom($block); $checkSlot = $this->compileBoolTemporary($block); $dimFetch = $this->findCoalesceArrayDimFetch($expr->left, $block); @@ -672,6 +695,7 @@ protected function compileCoalesce(Op\Expr\BinaryOp\Coalesce $expr, Block $block $dimSlot )); if (null !== $dimFetch) { + $leftBlock->inheritScopeFrom($block); $this->compileArrayDimFetchRead($dimFetch, $leftBlock); $leftSlot = $this->compileOperand($dimFetch->result, $leftBlock, true); $leftBlock->addOpCode(new OpCode( @@ -723,6 +747,7 @@ protected function compileCoalesce(Op\Expr\BinaryOp\Coalesce $expr, Block $block $coalesceOp->block2 = $rightBlock; $coalesceOp->block3 = $endBlock; $block->addOpCode($coalesceOp); + $endBlock->inheritScopeFrom($block); return $endBlock; } diff --git a/lib/JIT.php b/lib/JIT.php index 46474530..ba1fa887 100644 --- a/lib/JIT.php +++ b/lib/JIT.php @@ -234,7 +234,7 @@ private function compileBlockInternal( break; } if ($value->type === Variable::TYPE_HASHTABLE) { - $fetched = $value->dimFetch($dim, $resultOp->type, $forWrite); + $fetched = $value->dimFetch($dim, $resultOp->type, $forWrite, $dimOp); if ($forWrite) { $this->context->setVariableOp($resultOp, $fetched); } else { @@ -561,7 +561,7 @@ private function compileBlockInternal( $leftBb = JIT\CoalesceHelper::compileBranch($this, $func, $op->block1); $rightBb = JIT\CoalesceHelper::compileBranch($this, $func, $op->block2); $builder->positionAtEnd($branchBlock); - $this->context->freeDeadVariables($func, $branchBlock, $block); + // Do not free here: $op->arg1 is assigned in branch blocks (#99, #148). $builder->branchIf($condition, $leftBb, $rightBb); if (null !== $op->block3) { $mergeBb = JIT\BasicBlockHelper::append($this->context, 'coalesce_merge'); diff --git a/lib/JIT/IssetHelper.php b/lib/JIT/IssetHelper.php index d65ca5e6..510c039a 100644 --- a/lib/JIT/IssetHelper.php +++ b/lib/JIT/IssetHelper.php @@ -45,7 +45,7 @@ private static function superglobalName(Variable $container, ?Operand $container return null; } - private static function literalStringKey(?Operand $dimOp): ?string + public static function literalStringKey(?Operand $dimOp): ?string { if (null === $dimOp) { return null; diff --git a/lib/JIT/Variable.php b/lib/JIT/Variable.php index 614fb32d..0685e9c5 100755 --- a/lib/JIT/Variable.php +++ b/lib/JIT/Variable.php @@ -372,7 +372,7 @@ public function toString(\gcc_jit_block_ptr $block): Variable { } } - public function dimFetch(self $dim, ?Type $expectedType = null, bool $forWrite = false): Variable { + public function dimFetch(self $dim, ?Type $expectedType = null, bool $forWrite = false, ?Operand $dimOp = null): Variable { switch ($this->type) { case self::TYPE_STRING: $ptr = StringOffsetHelper::dimFetch( @@ -395,6 +395,9 @@ public function dimFetch(self $dim, ?Type $expectedType = null, bool $forWrite = && (null === $expectedType || Type::TYPE_ARRAY !== $expectedType->type) ) { $key = $dim->compileTimeString; + if (null === $key && isset($dimOp)) { + $key = IssetHelper::literalStringKey($dimOp); + } if (null !== $key) { $baked = SuperglobalInit::compileTimeReadString( $this->context,