Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions ext/standard/JitArrayPush.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
22 changes: 22 additions & 0 deletions lib/JIT.php
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
123 changes: 43 additions & 80 deletions lib/JIT/Builtin/ArrayPushRuntime.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> */
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
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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__*');
Expand All @@ -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
Expand Down
Loading
Loading