From 64ff1c43d5b45ee89c8229b6bba9bd04a1ad7189 Mon Sep 17 00:00:00 2001 From: PurHur Date: Mon, 3 Aug 2026 15:20:53 +0000 Subject: [PATCH] AOT: array_push/unshift without NestedJIT iterate (#27226) Thin AOT NestedJIT of ArrayPush/UnshiftJitHelper foreach($ht->iterate()) hit ObjectPropertyForeach with a Variable-typed temp and a null objectPropertySlot. Append/prepend at the call site (appendElement / HashTableUnshiftPrepend), accept TYPE_HASHTABLE in the value-box guard, and keep already-boxed property fetches copy-safe. Co-authored-by: Cursor --- ext/standard/JitArrayPush.php | 23 ++- lib/JIT.php | 22 +++ lib/JIT/Builtin/ArrayPushRuntime.php | 123 +++++-------- lib/JIT/Builtin/ArrayUnshiftRuntime.php | 173 ++++++++++-------- .../Type/ObjectInstancePropertyLlvm.php | 16 ++ test/repro/aot_array_push_unshift_27226.php | 6 + test/unit/ArrayPushRuntimeShrinkTest.php | 14 +- test/unit/ArrayUnshiftRuntimeShrinkTest.php | 14 +- 8 files changed, 219 insertions(+), 172 deletions(-) create mode 100644 test/repro/aot_array_push_unshift_27226.php diff --git a/ext/standard/JitArrayPush.php b/ext/standard/JitArrayPush.php index 5969f49cf70..b59ad78128a 100644 --- a/ext/standard/JitArrayPush.php +++ b/ext/standard/JitArrayPush.php @@ -55,22 +55,31 @@ public static function pushWithValueBoxGuard( $context->builder->structGep($loaded, $typeField) ); $i8 = $context->getTypeFromString('int8'); - $isArray = $context->builder->icmp( + // Mask IS_REFCOUNTED — HT/array boxes store TYPE_ARRAY|0x80 (#27226, peer HashTableReadLlvm). + // __value__writeHashtable tags TYPE_HASHTABLE (JIT), not VM TYPE_ARRAY (#27226). + $kind = $context->builder->and($typeByte, $i8->constInt(0x7f, false)); + $isVmArray = $context->builder->icmp( Builder::INT_EQ, - $typeByte, - $i8->constInt(Variable::TYPE_ARRAY, false) + $kind, + $i8->constInt(Variable::TYPE_ARRAY & 0x7f, false) ); + $isJitHashtable = $context->builder->icmp( + Builder::INT_EQ, + $kind, + $i8->constInt(JITVariable::TYPE_HASHTABLE & 0x7f, false) + ); + $isArray = $context->builder->or($isVmArray, $isJitHashtable); // Uninitialized boxed property slots (TYPE_UNDEFINED) and null may materialize arrays // like HashTableHelper::ensureHashtablePointer (#1086, bootstrap array_value_box). $isUndefined = $context->builder->icmp( Builder::INT_EQ, - $typeByte, - $i8->constInt(Variable::TYPE_UNDEFINED, false) + $kind, + $i8->constInt(Variable::TYPE_UNDEFINED & 0x7f, false) ); $isNull = $context->builder->icmp( Builder::INT_EQ, - $typeByte, - $i8->constInt(Variable::TYPE_NULL, false) + $kind, + $i8->constInt(Variable::TYPE_NULL & 0x7f, false) ); $canPush = $context->builder->or( $isArray, diff --git a/lib/JIT.php b/lib/JIT.php index ffdd1bfa850..737bd5c10b2 100644 --- a/lib/JIT.php +++ b/lib/JIT.php @@ -13776,6 +13776,28 @@ private function assignCallResultOperand(Operand $result, PHPLLVM\Value $llvmRes return; } + // HashTable::iterate() returns the receiver HT for IteratorHelper foreach. + // CFG often types the Traversable temp as PHPCompiler\VM\Variable (element + // type leak); keep TYPE_HASHTABLE so ObjectPropertyForeach does not win (#27226). + if ( + $this->context->scope->toCall instanceof JIT\Call\HashTableIterate + && '__hashtable__*' === $llvmTy + ) { + if ($this->context->hasVariableOp($result)) { + $this->context->getVariableFromOp($result)->free(); + } + $this->context->setVariableOp( + $result, + new Variable( + $this->context, + Variable::TYPE_HASHTABLE, + Variable::KIND_VALUE, + $llvmResult + ) + ); + + return; + } // WeakReference::get() returns an owning __value__ box. Promote to an entry // alloca KIND_VARIABLE so freeDeadVariables at ternary/branch edges can // valueDelref (KIND_VALUE free is a no-op and would keep the referent) (#27118). diff --git a/lib/JIT/Builtin/ArrayPushRuntime.php b/lib/JIT/Builtin/ArrayPushRuntime.php index 13d438dfda4..0ba5bb682f9 100644 --- a/lib/JIT/Builtin/ArrayPushRuntime.php +++ b/lib/JIT/Builtin/ArrayPushRuntime.php @@ -5,55 +5,40 @@ namespace PHPCompiler\JIT\Builtin; use PHPCompiler\JIT\ArrayBuiltinHelper; +use PHPCompiler\JIT\BasicBlockHelper; use PHPCompiler\JIT\Context; use PHPCompiler\JIT\HashTableHelper; -use PHPCompiler\JIT\JitNestedHelperCoerce; -use PHPCompiler\JIT\JitVmHelperLink; use PHPCompiler\JIT\Variable as JITVariable; +use PHPLLVM\Builder; use PHPLLVM\Value; use PHPLLVM\Value\Function_ as LlvmFunction; /** - * JIT/AOT link for array_push() via ArrayPushJitHelper PHP (#12719, #22801). + * JIT/AOT link for array_push() (#12719, #22801, #27226). * - * Helper compile: {@see JitVmHelperLink::ensureCompiled} (peer MathModf #22519). - * Standalone AOT compiles {@see ArrayPushJitHelper} via nested JIT bridges (#14303, #17580). - * SSOT: {@see \PHPCompiler\ext\standard\array_push} + * Thin AOT NestedJIT of ArrayPushJitHelper::pushFromList died on + * foreach ($ht->iterate()) (CFG types Traversable as Variable -> + * ObjectPropertyForeach null slot, #27226). Append at the call site with + * ArrayBuiltinHelper::appendElement instead. VM still uses ArrayPushJitHelper. + * + * SSOT (VM): {@see \PHPCompiler\ext\standard\array_push} * php-src: ext/standard/array.c — PHP_FUNCTION(array_push) */ final class ArrayPushRuntime { - private const HELPER_PATH = '/ext/standard/ArrayPushJitHelper.php'; - - private const COUNT_HELPER = 'PHPCompiler\\ext\\standard\\ArrayPushJitHelper::countElements'; - - private const APPEND_HELPER = 'PHPCompiler\\ext\\standard\\ArrayPushJitHelper::pushFromList'; - - /** @var list */ - private const COMPILED_HELPERS = [ - self::COUNT_HELPER, - self::APPEND_HELPER, - ]; - public static function push(Context $context, JITVariable $array, JITVariable ...$values): Value { self::ensureLinked($context); $ht = ArrayBuiltinHelper::loadHashTable($context, $array); $native = ArrayBuiltinHelper::isNativeArray($array->type); - if (0 === \count($values)) { - $count = self::callCount($context, $ht); - } else { - $valuesHt = HashTableHelper::alloc($context); - foreach ($values as $value) { - ArrayBuiltinHelper::appendElement($context, $valuesHt, $value); - } - $count = self::callAppend($context, $ht, $valuesHt); + foreach ($values as $value) { + ArrayBuiltinHelper::appendElement($context, $ht, $value); } if ($native) { HashTableHelper::storeHashtableInArrayVariable($context, $array, $ht); } - return $count; + return ArrayBuiltinHelper::getNumElements($context, $ht); } public static function ensureLinked(Context $context): void @@ -81,7 +66,6 @@ public static function implement(Context $context): void } catch (\Throwable) { } - self::ensureJitHelperCompiled($context); self::implementIfMissing($context, '__array_push__count', self::implementCountBridge(...)); self::implementIfMissing($context, '__array_push__append', self::implementAppendBridge(...)); self::registerLinkedRuntime($context); @@ -93,9 +77,7 @@ public static function implement(Context $context): void } } - /** - * @param callable(Context, LlvmFunction): void $emit - */ + /** @param callable(Context, LlvmFunction): void $emit */ private static function implementIfMissing(Context $context, string $name, callable $emit): void { $probe = $context->module->getNamedFunction($name); @@ -116,7 +98,6 @@ private static function declareFunction(Context $context, string $name): LlvmFun try { return $context->lookupFunction($name); } catch (\Throwable) { - // fall through } $htPtr = $context->getTypeFromString('__hashtable__*'); @@ -140,62 +121,44 @@ private static function implementCountBridge(Context $context, LlvmFunction $fn) { $entry = $fn->appendBasicBlock('array_push_count_bridge_entry'); $context->builder->positionAtEnd($entry); - $countRaw = JitNestedHelperCoerce::callHelper( - $context, - self::helperFunction($context, self::COUNT_HELPER), - [$fn->getParam(0)] + $context->builder->returnValue( + ArrayBuiltinHelper::getNumElements($context, $fn->getParam(0)) ); - $context->builder->returnValue(JitNestedHelperCoerce::coerceBridgeResult($context, $countRaw, $context->getTypeFromString('int64'))); } private static function implementAppendBridge(Context $context, LlvmFunction $fn): void { $entry = $fn->appendBasicBlock('array_push_append_bridge_entry'); $context->builder->positionAtEnd($entry); - $countRaw = JitNestedHelperCoerce::callHelper( - $context, - self::helperFunction($context, self::APPEND_HELPER), - [$fn->getParam(0), $fn->getParam(1)] - ); - $context->builder->returnValue(JitNestedHelperCoerce::coerceBridgeResult($context, $countRaw, $context->getTypeFromString('int64'))); - } - - private static function callCount(Context $context, Value $ht): Value - { - self::ensureLinked($context); - - return $context->builder->call( - $context->lookupFunction('__array_push__count'), - $ht - ); - } - - private static function callAppend(Context $context, Value $ht, Value $valuesHt): Value - { - self::ensureLinked($context); - - return $context->builder->call( - $context->lookupFunction('__array_push__append'), - $ht, - $valuesHt - ); - } - - private static function helperFunction(Context $context, string $logical): LlvmFunction - { - self::ensureJitHelperCompiled($context); - - return JitVmHelperLink::lookupCompiled($context, $logical, '#22801'); - } - - private static function ensureJitHelperCompiled(Context $context): void - { - JitVmHelperLink::ensureCompiled( - $context, - self::HELPER_PATH, - self::COMPILED_HELPERS, - '#22801' + $dest = $fn->getParam(0); + $valuesHt = $fn->getParam(1); + $sizeT = $context->getTypeFromString('size_t'); + $zero = $sizeT->constInt(0, false); + $one = $sizeT->constInt(1, false); + $count = $context->builder->truncOrBitCast( + ArrayBuiltinHelper::getNumElements($context, $valuesHt), + $sizeT ); + $idxSlot = BasicBlockHelper::entryAlloca($context, $sizeT); + $context->builder->store($zero, $idxSlot); + $head = BasicBlockHelper::append($context, 'array_push_append_head'); + $body = BasicBlockHelper::append($context, 'array_push_append_body'); + $done = BasicBlockHelper::append($context, 'array_push_append_done'); + $context->builder->branch($head); + + $context->builder->positionAtEnd($head); + $idx = $context->builder->load($idxSlot); + $atEnd = $context->builder->icmp(Builder::INT_SGE, $idx, $count); + $context->builder->branchIf($atEnd, $done, $body); + + $context->builder->positionAtEnd($body); + $value = HashTableHelper::readIndexedToValueBox($context, $valuesHt, $idx); + ArrayBuiltinHelper::appendElement($context, $dest, $value); + $context->builder->store($context->builder->addNoSignedWrap($idx, $one), $idxSlot); + $context->builder->branch($head); + + $context->builder->positionAtEnd($done); + $context->builder->returnValue(ArrayBuiltinHelper::getNumElements($context, $dest)); } private static function registerLinkedRuntime(Context $context): void diff --git a/lib/JIT/Builtin/ArrayUnshiftRuntime.php b/lib/JIT/Builtin/ArrayUnshiftRuntime.php index 5c763790a71..9e5225b1587 100644 --- a/lib/JIT/Builtin/ArrayUnshiftRuntime.php +++ b/lib/JIT/Builtin/ArrayUnshiftRuntime.php @@ -5,49 +5,48 @@ namespace PHPCompiler\JIT\Builtin; use PHPCompiler\JIT\ArrayBuiltinHelper; +use PHPCompiler\JIT\BasicBlockHelper; +use PHPCompiler\JIT\Call\HashTableUnshiftPrepend; use PHPCompiler\JIT\Context; use PHPCompiler\JIT\HashTableHelper; use PHPCompiler\JIT\JitNestedHelperCoerce; -use PHPCompiler\JIT\JitVmHelperLink; use PHPCompiler\JIT\Variable as JITVariable; +use PHPLLVM\Builder; use PHPLLVM\Value; use PHPLLVM\Value\Function_ as LlvmFunction; /** - * JIT/AOT link for array_unshift() via ArrayUnshiftJitHelper PHP (#12717, #22818). + * JIT/AOT link for array_unshift() (#12717, #22818, #27226). * - * Helper compile: {@see JitVmHelperLink::ensureCompiled} (peer ArrayPush #22801). - * Standalone AOT compiles {@see ArrayUnshiftJitHelper} via nested JIT bridges (#14316, #17580). - * SSOT: {@see \PHPCompiler\ext\standard\array_unshift} + * Avoid NestedJIT of ArrayUnshiftJitHelper::unshiftFromList + * (`foreach ($ht->iterate())` → ObjectPropertyForeach null slot, #27226). + * Call-site prepend via HashTableUnshiftPrepend. VM still uses the PHP helper. + * + * SSOT (VM): {@see \PHPCompiler\ext\standard\array_unshift} * php-src: ext/standard/array.c — PHP_FUNCTION(array_unshift) */ final class ArrayUnshiftRuntime { - private const HELPER_PATH = '/ext/standard/ArrayUnshiftJitHelper.php'; - - private const COUNT_HELPER = 'PHPCompiler\\ext\\standard\\ArrayUnshiftJitHelper::countElements'; - - private const PREPEND_HELPER = 'PHPCompiler\\ext\\standard\\ArrayUnshiftJitHelper::unshiftFromList'; - - /** @var list */ - private const COMPILED_HELPERS = [ - self::COUNT_HELPER, - self::PREPEND_HELPER, - ]; - public static function unshift(Context $context, JITVariable $array, JITVariable ...$values): Value { self::ensureLinked($context); $ht = ArrayBuiltinHelper::loadHashTable($context, $array); $native = ArrayBuiltinHelper::isNativeArray($array->type); + $htVar = new JITVariable( + $context, + JITVariable::TYPE_HASHTABLE, + JITVariable::KIND_VALUE, + $ht + ); if (0 === \count($values)) { - $count = self::callCount($context, $ht); + $count = ArrayBuiltinHelper::getNumElements($context, $ht); } else { - $valuesHt = HashTableHelper::alloc($context); - foreach ($values as $value) { - ArrayBuiltinHelper::appendElement($context, $valuesHt, $value); - } - $count = self::callPrepend($context, $ht, $valuesHt); + $countRaw = (new HashTableUnshiftPrepend())->call($context, $htVar, ...$values); + $count = JitNestedHelperCoerce::coerceBridgeResult( + $context, + $countRaw, + $context->getTypeFromString('int64') + ); } if ($native) { HashTableHelper::storeHashtableInArrayVariable($context, $array, $ht); @@ -81,7 +80,6 @@ public static function implement(Context $context): void } catch (\Throwable) { } - self::ensureJitHelperCompiled($context); self::implementIfMissing($context, '__array_unshift__count', self::implementCountBridge(...)); self::implementIfMissing($context, '__array_unshift__prepend', self::implementPrependBridge(...)); self::registerLinkedRuntime($context); @@ -93,9 +91,7 @@ public static function implement(Context $context): void } } - /** - * @param callable(Context, LlvmFunction): void $emit - */ + /** @param callable(Context, LlvmFunction): void $emit */ private static function implementIfMissing(Context $context, string $name, callable $emit): void { $probe = $context->module->getNamedFunction($name); @@ -116,7 +112,6 @@ private static function declareFunction(Context $context, string $name): LlvmFun try { return $context->lookupFunction($name); } catch (\Throwable) { - // fall through } $htPtr = $context->getTypeFromString('__hashtable__*'); @@ -140,61 +135,93 @@ private static function implementCountBridge(Context $context, LlvmFunction $fn) { $entry = $fn->appendBasicBlock('array_unshift_count_bridge_entry'); $context->builder->positionAtEnd($entry); - $countRaw = JitNestedHelperCoerce::callHelper( - $context, - self::helperFunction($context, self::COUNT_HELPER), - [$fn->getParam(0)] + $context->builder->returnValue( + ArrayBuiltinHelper::getNumElements($context, $fn->getParam(0)) ); - $context->builder->returnValue(JitNestedHelperCoerce::coerceBridgeResult($context, $countRaw, $context->getTypeFromString('int64'))); } private static function implementPrependBridge(Context $context, LlvmFunction $fn): void { $entry = $fn->appendBasicBlock('array_unshift_prepend_bridge_entry'); $context->builder->positionAtEnd($entry); - $countRaw = JitNestedHelperCoerce::callHelper( - $context, - self::helperFunction($context, self::PREPEND_HELPER), - [$fn->getParam(0), $fn->getParam(1)] + $dest = $fn->getParam(0); + $valuesHt = $fn->getParam(1); + $sizeT = $context->getTypeFromString('size_t'); + $zero = $sizeT->constInt(0, false); + $one = $sizeT->constInt(1, false); + $k = $context->builder->truncOrBitCast( + ArrayBuiltinHelper::getNumElements($context, $valuesHt), + $sizeT ); - $context->builder->returnValue(JitNestedHelperCoerce::coerceBridgeResult($context, $countRaw, $context->getTypeFromString('int64'))); - } - - private static function callCount(Context $context, Value $ht): Value - { - self::ensureLinked($context); - - return $context->builder->call( - $context->lookupFunction('__array_unshift__count'), - $ht - ); - } - - private static function callPrepend(Context $context, Value $ht, Value $valuesHt): Value - { - self::ensureLinked($context); - - return $context->builder->call( - $context->lookupFunction('__array_unshift__prepend'), - $ht, - $valuesHt + $n = $context->builder->call($context->lookupFunction('__hashtable__getNumElements'), $dest); + + $isEmptyValues = $context->builder->icmp(Builder::INT_EQ, $k, $zero); + $retBb = BasicBlockHelper::append($context, 'array_unshift_prepend_ret'); + $workBb = BasicBlockHelper::append($context, 'array_unshift_prepend_work'); + $context->builder->branchIf($isEmptyValues, $retBb, $workBb); + + $context->builder->positionAtEnd($workBb); + $need = $context->builder->addNoSignedWrap($n, $k); + $context->builder->call($context->lookupFunction('__hashtable__grow'), $dest, $need); + + $indexSlot = BasicBlockHelper::entryAlloca($context, $sizeT); + $context->builder->store($context->builder->subNoSignedWrap($n, $one), $indexSlot); + $sHead = BasicBlockHelper::append($context, 'array_unshift_prepend_shift_head'); + $sBody = BasicBlockHelper::append($context, 'array_unshift_prepend_shift_body'); + $sDone = BasicBlockHelper::append($context, 'array_unshift_prepend_shift_done'); + $skipShift = $context->builder->icmp(Builder::INT_EQ, $n, $zero); + $context->builder->branchIf($skipShift, $sDone, $sHead); + + $context->builder->positionAtEnd($sHead); + $idx = $context->builder->load($indexSlot); + $past = $context->builder->icmp(Builder::INT_SLT, $idx, $zero); + $context->builder->branchIf($past, $sDone, $sBody); + $context->builder->positionAtEnd($sBody); + $srcVal = HashTableHelper::readIndexedToValueBox($context, $dest, $idx); + HashTableHelper::setAtIndex( + $context, + $dest, + $context->builder->addNoSignedWrap($idx, $k), + $srcVal ); - } - - private static function helperFunction(Context $context, string $logical): LlvmFunction - { - self::ensureJitHelperCompiled($context); - - return JitVmHelperLink::lookupCompiled($context, $logical, '#22818'); - } - - private static function ensureJitHelperCompiled(Context $context): void - { - JitVmHelperLink::ensureCompiled( + $context->builder->store($context->builder->subNoSignedWrap($idx, $one), $indexSlot); + $context->builder->branch($sHead); + + $context->builder->positionAtEnd($sDone); + $wIdx = BasicBlockHelper::entryAlloca($context, $sizeT); + $context->builder->store($zero, $wIdx); + $wHead = BasicBlockHelper::append($context, 'array_unshift_prepend_write_head'); + $wBody = BasicBlockHelper::append($context, 'array_unshift_prepend_write_body'); + $context->builder->branch($wHead); + $context->builder->positionAtEnd($wHead); + $wi = $context->builder->load($wIdx); + $wDone = $context->builder->icmp(Builder::INT_SGE, $wi, $k); + $context->builder->branchIf($wDone, $retBb, $wBody); + $context->builder->positionAtEnd($wBody); + HashTableHelper::setAtIndex( $context, - self::HELPER_PATH, - self::COMPILED_HELPERS, - '#22818' + $dest, + $wi, + HashTableHelper::readIndexedToValueBox($context, $valuesHt, $wi) + ); + $context->builder->store($context->builder->addNoSignedWrap($wi, $one), $wIdx); + $context->builder->branch($wHead); + + $context->builder->positionAtEnd($retBb); + $map = $context->structFieldMap['__hashtable__']; + $finalN = $context->builder->addNoSignedWrap($n, $k); + $context->builder->store($finalN, $context->builder->structGep($dest, $map['nextFreeElement'])); + $context->builder->store($finalN, $context->builder->structGep($dest, $map['numElements'])); + $context->builder->returnValue( + JitNestedHelperCoerce::i64ToScalar( + $context, + JitNestedHelperCoerce::scalarToI64( + $context, + $finalN, + $context->getTypeFromString('int64') + ), + $context->getTypeFromString('int64') + ) ); } diff --git a/lib/JIT/Builtin/Type/ObjectInstancePropertyLlvm.php b/lib/JIT/Builtin/Type/ObjectInstancePropertyLlvm.php index b5298a82e8c..d758a2f5437 100644 --- a/lib/JIT/Builtin/Type/ObjectInstancePropertyLlvm.php +++ b/lib/JIT/Builtin/Type/ObjectInstancePropertyLlvm.php @@ -293,6 +293,22 @@ public static function boxFetchedPropertyIntoValue( $context = $object->jitContext(); $destPtr = JitValueBox::pointer($context, $destSlot); if (Variable::TYPE_VALUE === $propertyType) { + // Already-boxed fetch (runtime class dispatch / empty-prop foreach dummy) + // has no objectPropertySlot — copy the __value__ rather than load_value_slot (#27226). + if (null === $fetched->objectPropertySlot) { + if (Variable::TYPE_VALUE === $fetched->type && null !== $fetched->value) { + JitValueBox::copyFromPointer( + $context, + $destSlot, + JitValueBox::valuePtrFromVariable($context, $fetched) + ); + + return; + } + throw new \LogicException( + 'boxFetchedPropertyIntoValue TYPE_VALUE without objectPropertySlot (#27226)' + ); + } $context->builder->call( $context->lookupFunction('__object__load_value_slot'), $fetched->objectPropertySlot, diff --git a/test/repro/aot_array_push_unshift_27226.php b/test/repro/aot_array_push_unshift_27226.php new file mode 100644 index 00000000000..680d0c28e21 --- /dev/null +++ b/test/repro/aot_array_push_unshift_27226.php @@ -0,0 +1,6 @@ +assertStringContainsString('ArrayPushJitHelper', $runtime); + $this->assertStringContainsString('ArrayBuiltinHelper::appendElement', $runtime); $this->assertStringContainsString('storeHashtableInArrayVariable', $runtime); - $this->assertStringContainsString('JitVmHelperLink::ensureCompiled', $runtime); - $this->assertStringContainsString('JitVmHelperLink::lookupCompiled', $runtime); + $this->assertStringContainsString('#27226', $runtime); + $this->assertStringNotContainsString('JitVmHelperLink::ensureCompiled', $runtime); $this->assertStringNotContainsString('NestedJitCompileScope::run', $runtime); $this->assertStringNotContainsString('parseAndCompile', $runtime); $this->assertStringNotContainsString('new JIT(', $runtime); - $this->assertStringNotContainsString('use PHPCompiler\\JIT;', $runtime); $this->assertStringNotContainsString('UserScriptAotDeferNestedJit', $runtime); $this->assertStringNotContainsString('ArrayBuiltinHelper::push', $runtime); $this->assertStringNotContainsString('LOAD_TYPE_STANDALONE', $runtime); @@ -35,6 +34,9 @@ public function testArrayPushRuntimeUsesJitHelperNotDirectLlvmMonolith(): void $builtin = (string) file_get_contents(__DIR__.'/../../ext/standard/array_push.php'); $this->assertStringContainsString('ArrayPushRuntime::push', $builtin); $this->assertStringNotContainsString('ArrayBuiltinHelper::push(', $builtin); + + $jitHelper = (string) file_get_contents(__DIR__.'/../../ext/standard/ArrayPushJitHelper.php'); + $this->assertStringContainsString('function pushFromList', $jitHelper); } public function testArrayPushJitHelperAppendsValues(): void diff --git a/test/unit/ArrayUnshiftRuntimeShrinkTest.php b/test/unit/ArrayUnshiftRuntimeShrinkTest.php index 6e031deb0cf..b0f564daa9e 100644 --- a/test/unit/ArrayUnshiftRuntimeShrinkTest.php +++ b/test/unit/ArrayUnshiftRuntimeShrinkTest.php @@ -10,21 +10,20 @@ use PHPUnit\Framework\TestCase; /** - * array_unshift() NestedJIT via JitVmHelperLink::ensureCompiled (#22818 / peer #22801). + * array_unshift() call-site HashTableUnshiftPrepend (#27226) — NestedJIT unshiftFromList avoided. */ final class ArrayUnshiftRuntimeShrinkTest extends TestCase { - public function testArrayUnshiftRuntimeUsesJitHelperNotDirectLlvmMonolith(): void + public function testArrayUnshiftRuntimeUsesCallSitePrependNotNestedIterate(): void { $runtime = (string) file_get_contents(__DIR__.'/../../lib/JIT/Builtin/ArrayUnshiftRuntime.php'); - $this->assertStringContainsString('ArrayUnshiftJitHelper', $runtime); + $this->assertStringContainsString('HashTableUnshiftPrepend', $runtime); $this->assertStringContainsString('storeHashtableInArrayVariable', $runtime); - $this->assertStringContainsString('JitVmHelperLink::ensureCompiled', $runtime); - $this->assertStringContainsString('JitVmHelperLink::lookupCompiled', $runtime); + $this->assertStringContainsString('#27226', $runtime); + $this->assertStringNotContainsString('JitVmHelperLink::ensureCompiled', $runtime); $this->assertStringNotContainsString('NestedJitCompileScope::run', $runtime); $this->assertStringNotContainsString('parseAndCompile', $runtime); $this->assertStringNotContainsString('new JIT(', $runtime); - $this->assertStringNotContainsString('use PHPCompiler\\JIT;', $runtime); $this->assertStringNotContainsString('UserScriptAotDeferNestedJit', $runtime); $this->assertStringNotContainsString('ArrayBuiltinHelper::unshift', $runtime); $this->assertStringNotContainsString('LOAD_TYPE_STANDALONE', $runtime); @@ -35,6 +34,9 @@ public function testArrayUnshiftRuntimeUsesJitHelperNotDirectLlvmMonolith(): voi $builtin = (string) file_get_contents(__DIR__.'/../../ext/standard/array_unshift.php'); $this->assertStringContainsString('ArrayUnshiftRuntime::unshift', $builtin); $this->assertStringNotContainsString('ArrayBuiltinHelper::unshift', $builtin); + + $jitHelper = (string) file_get_contents(__DIR__.'/../../ext/standard/ArrayUnshiftJitHelper.php'); + $this->assertStringContainsString('function unshiftFromList', $jitHelper); } public function testArrayUnshiftJitHelperPrependsValues(): void