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
8 changes: 4 additions & 4 deletions docs/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Auto-generated by `script/capability-matrix.php`. Do not edit by hand.
| `array_slice` | yes | no | no | standard | doc: VM only; not implemented for JIT in this compiler build |
| `array_sum` | yes | no | no | standard | doc: VM only; not implemented for JIT in this compiler build |
| `array_unique` | yes | no | no | standard | doc: VM only; not implemented for JIT in this compiler build |
| `array_values` | yes | no | no | standard | doc: VM only; not implemented for JIT in this compiler build |
| `array_values` | yes | yes | yes | standard | JIT PHPT; AOT PHPT |
| `basename` | yes | yes | yes | standard | AOT PHPT |
| `bin2hex` | yes | yes | yes | standard | |
| `bindec` | yes | yes | yes | standard | |
Expand All @@ -44,7 +44,7 @@ Auto-generated by `script/capability-matrix.php`. Do not edit by hand.
| `header` | yes | yes | yes | standard | AOT PHPT |
| `hexdec` | yes | yes | yes | standard | |
| `htmlspecialchars` | yes | yes | yes | standard | AOT PHPT |
| `implode` | yes | yes | yes | standard | doc: VM only; JIT PHPT; AOT PHPT |
| `implode` | yes | yes | yes | standard | doc: VM only; AOT PHPT |
| `in_array` | yes | yes | yes | standard | doc: VM only; JIT PHPT |
| `intdiv` | yes | yes | yes | standard | |
| `intval` | yes | yes | yes | standard | JIT PHPT; AOT PHPT |
Expand All @@ -70,7 +70,7 @@ Auto-generated by `script/capability-matrix.php`. Do not edit by hand.
| `mb_strlen` | yes | yes | yes | types | JIT PHPT |
| `min` | yes | yes | yes | standard | |
| `nl2br` | yes | no | no | standard | doc: VM only; not implemented for JIT in this compiler build |
| `number_format` | yes | yes | yes | standard | AOT PHPT |
| `number_format` | yes | yes | yes | standard | JIT PHPT; AOT PHPT |
| `octdec` | yes | yes | yes | standard | |
| `ord` | yes | yes | yes | standard | |
| `parse_url` | yes | no | no | standard | doc: VM only; not implemented for JIT in this compiler build |
Expand All @@ -86,7 +86,7 @@ Auto-generated by `script/capability-matrix.php`. Do not edit by hand.
| `scandir` | yes | yes | yes | standard | |
| `sin` | yes | yes | yes | standard | |
| `sizeof` | yes | yes | yes | standard | |
| `sort` | yes | yes | yes | standard | JIT PHPT; AOT PHPT |
| `sort` | yes | yes | yes | standard | |
| `sqrt` | yes | yes | yes | standard | |
| `str_contains` | yes | yes | yes | standard | |
| `str_ends_with` | yes | yes | yes | standard | AOT PHPT |
Expand Down
9 changes: 7 additions & 2 deletions ext/standard/array_values.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@

use PHPCompiler\Frame;
use PHPCompiler\Func\Internal;
use PHPCompiler\JIT\ArrayBuiltinHelper;
use PHPCompiler\JIT\Context;
use PHPCompiler\JIT\Variable as JITVariable;
use PHPCompiler\VM\Variable;
use PHPLLVM\Value;

/**
* array_values() re-indexing list values (subset of PHP; VM only).
* array_values() re-indexing list values (subset of PHP).
*/
final class array_values extends Internal
{
Expand All @@ -42,6 +43,10 @@ public function execute(Frame $frame): void

public function call(Context $context, JITVariable ...$args): Value
{
throw new \LogicException('array_values() is not implemented for JIT in this compiler build');
if (1 !== \count($args)) {
throw new \LogicException('array_values() requires exactly one argument');
}

return ArrayBuiltinHelper::buildValuesArray($context, $args[0]);
}
}
260 changes: 252 additions & 8 deletions lib/JIT/ArrayBuiltinHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,257 @@ public static function shiftFirst(Context $context, Variable $array): Value
return $resultPtr;
}

/**
* Copy defined list elements into a new packed array (array_values subset).
*/
public static function buildValuesArray(Context $context, Variable $array): Value
{
if (self::isNativeArray($array->type)) {
return self::buildValuesFromNativeArray($context, $array);
}

return self::buildValuesFromHashTable($context, self::loadHashTable($context, $array));
}

private static function buildValuesFromNativeArray(Context $context, Variable $array): Value
{
$elemType = $array->type & ~Variable::IS_NATIVE_ARRAY;
$sizeT = $context->getTypeFromString('size_t');
$zero = $sizeT->constInt(0, false);
$one = $sizeT->constInt(1, false);
$count = $context->constantFromInteger($array->nextFreeElement, 'size_t');
$isEmpty = $context->builder->icmp(Builder::INT_EQ, $count, $zero);
$emptyBlock = BasicBlockHelper::append($context, 'array_values_native_empty');
$workBlock = BasicBlockHelper::append($context, 'array_values_native_work');
$doneBlock = BasicBlockHelper::append($context, 'array_values_native_done');
$context->builder->branchIf($isEmpty, $emptyBlock, $workBlock);

$context->builder->positionAtEnd($emptyBlock);
$emptyHt = HashTableHelper::alloc($context);
$context->builder->branch($doneBlock);

$context->builder->positionAtEnd($workBlock);
$dest = HashTableHelper::alloc($context);
$idxSlot = $context->builder->alloca($sizeT, 1, 'array_values_native_idx');
$destIdxSlot = $context->builder->alloca($sizeT, 1, 'array_values_native_dest');
$context->builder->store($zero, $idxSlot);
$context->builder->store($zero, $destIdxSlot);

$head = BasicBlockHelper::append($context, 'array_values_native_head');
$body = BasicBlockHelper::append($context, 'array_values_native_body');
$advance = BasicBlockHelper::append($context, 'array_values_native_advance');
$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, $doneBlock, $body);

$context->builder->positionAtEnd($body);
$slot = $context->builder->inBoundsGep($array->value, $zero, $idx);
if (Variable::TYPE_STRING === $elemType) {
$elem = new Variable($context, $elemType, Variable::KIND_VARIABLE, $slot);
} else {
$elem = new Variable(
$context,
$elemType,
Variable::KIND_VALUE,
$context->builder->load($slot)
);
}
$destIdx = $context->builder->load($destIdxSlot);
HashTableHelper::setAtIndex($context, $dest, $destIdx, $elem);
$context->builder->store(
$context->builder->addNoSignedWrap($destIdx, $one),
$destIdxSlot
);
$context->builder->branch($advance);

$context->builder->positionAtEnd($advance);
$context->builder->store(
$context->builder->addNoSignedWrap($idx, $one),
$idxSlot
);
$context->builder->branch($head);

$context->builder->positionAtEnd($doneBlock);
$phi = $context->builder->phi($emptyHt->typeOf());
$phi->addIncoming($emptyHt, $emptyBlock);
$phi->addIncoming($dest, $head);

return $phi;
}

private static function buildValuesFromHashTable(Context $context, Value $src): Value
{
$map = $context->structFieldMap['__hashtable__'];
$sizeT = $context->getTypeFromString('size_t');
$nextFree = $context->builder->load(
$context->builder->structGep($src, $map['nextFreeElement'])
);
$zero = $sizeT->constInt(0, false);
$isEmpty = $context->builder->icmp(Builder::INT_EQ, $nextFree, $zero);
$emptyBlock = BasicBlockHelper::append($context, 'array_values_empty');
$workBlock = BasicBlockHelper::append($context, 'array_values_work');
$doneBlock = BasicBlockHelper::append($context, 'array_values_done');
$context->builder->branchIf($isEmpty, $emptyBlock, $workBlock);

$context->builder->positionAtEnd($emptyBlock);
$emptyHt = HashTableHelper::alloc($context);
$context->builder->branch($doneBlock);

$context->builder->positionAtEnd($workBlock);
$dest = HashTableHelper::alloc($context);
$srcIdxSlot = $context->builder->alloca($sizeT, 1, 'array_values_src');
$destIdxSlot = $context->builder->alloca($sizeT, 1, 'array_values_dest');
$context->builder->store($zero, $srcIdxSlot);
$context->builder->store($zero, $destIdxSlot);
$one = $sizeT->constInt(1, false);

$head = BasicBlockHelper::append($context, 'array_values_head');
$check = BasicBlockHelper::append($context, 'array_values_check');
$copyBlock = BasicBlockHelper::append($context, 'array_values_copy');
$skip = BasicBlockHelper::append($context, 'array_values_skip');
$advance = BasicBlockHelper::append($context, 'array_values_advance');
$context->builder->branch($head);

$context->builder->positionAtEnd($head);
$srcIdx = $context->builder->load($srcIdxSlot);
$atEnd = $context->builder->icmp(Builder::INT_SGE, $srcIdx, $nextFree);
$context->builder->branchIf($atEnd, $doneBlock, $check);

$context->builder->positionAtEnd($check);
$isSet = $context->builder->call(
$context->lookupFunction('__hashtable__offsetIsSet'),
$src,
$srcIdx
);
$context->builder->branchIf($isSet, $copyBlock, $skip);

$context->builder->positionAtEnd($copyBlock);
$destIdx = $context->builder->load($destIdxSlot);
self::copyListEntry($context, $src, $srcIdx, $dest, $destIdx);
$context->builder->store(
$context->builder->addNoSignedWrap($destIdx, $one),
$destIdxSlot
);
$context->builder->branch($advance);

$context->builder->positionAtEnd($skip);
$context->builder->branch($advance);

$context->builder->positionAtEnd($advance);
$context->builder->store(
$context->builder->addNoSignedWrap($srcIdx, $one),
$srcIdxSlot
);
$context->builder->branch($head);

$context->builder->positionAtEnd($doneBlock);
$phi = $context->builder->phi($emptyHt->typeOf());
$phi->addIncoming($emptyHt, $emptyBlock);
$phi->addIncoming($dest, $head);

return $phi;
}

private static function copyListEntry(
Context $context,
Value $src,
Value $srcIndex,
Value $dest,
Value $destIndex
): void {
$srcEntry = self::listEntryAt($context, $src, $srcIndex);
$valueMap = $context->structFieldMap['__value__'];
$typeByte = $context->builder->load(
$context->builder->structGep($srcEntry, $valueMap['type'])
);
$i8 = $context->getTypeFromString('int8');

$longBlock = BasicBlockHelper::append($context, 'array_values_copy_long');
$stringBlock = BasicBlockHelper::append($context, 'array_values_copy_string');
$doubleBlock = BasicBlockHelper::append($context, 'array_values_copy_double');
$boolBlock = BasicBlockHelper::append($context, 'array_values_copy_bool');
$done = BasicBlockHelper::append($context, 'array_values_copy_done');

$isString = $context->builder->icmp(
Builder::INT_EQ,
$typeByte,
$i8->constInt(Variable::TYPE_STRING, false)
);
$isLong = $context->builder->icmp(
Builder::INT_EQ,
$typeByte,
$i8->constInt(Variable::TYPE_NATIVE_LONG, false)
);
$isBool = $context->builder->icmp(
Builder::INT_EQ,
$typeByte,
$i8->constInt(Variable::TYPE_NATIVE_BOOL, false)
);
$isDouble = $context->builder->icmp(
Builder::INT_EQ,
$typeByte,
$i8->constInt(Variable::TYPE_NATIVE_DOUBLE, false)
);

$afterString = BasicBlockHelper::append($context, 'array_values_after_string');
$context->builder->branchIf($isString, $stringBlock, $afterString);

$context->builder->positionAtEnd($stringBlock);
$context->builder->call(
$context->lookupFunction('__hashtable__setStringAt'),
$dest,
$destIndex,
$context->builder->call($context->lookupFunction('__value__readString'), $srcEntry)
);
$context->builder->branch($done);

$context->builder->positionAtEnd($afterString);
$afterLong = BasicBlockHelper::append($context, 'array_values_after_long');
$context->builder->branchIf($isLong, $longBlock, $afterLong);

$context->builder->positionAtEnd($longBlock);
$context->builder->call(
$context->lookupFunction('__hashtable__setLongAt'),
$dest,
$destIndex,
$context->builder->call($context->lookupFunction('__value__readLong'), $srcEntry)
);
$context->builder->branch($done);

$context->builder->positionAtEnd($afterLong);
$afterBool = BasicBlockHelper::append($context, 'array_values_after_bool');
$context->builder->branchIf($isBool, $boolBlock, $afterBool);

$context->builder->positionAtEnd($boolBlock);
$context->builder->call(
$context->lookupFunction('__hashtable__setBoolAt'),
$dest,
$destIndex,
$context->builder->truncOrBitCast(
$context->builder->call($context->lookupFunction('__value__readLong'), $srcEntry),
$context->getTypeFromString('int1')
)
);
$context->builder->branch($done);

$context->builder->positionAtEnd($afterBool);
$context->builder->branchIf($isDouble, $doubleBlock, $done);

$context->builder->positionAtEnd($doubleBlock);
$context->builder->call(
$context->lookupFunction('__hashtable__setDoubleAt'),
$dest,
$destIndex,
$context->builder->call($context->lookupFunction('__value__readDouble'), $srcEntry)
);
$context->builder->branch($done);

$context->builder->positionAtEnd($done);
}

public static function buildKeysArray(Context $context, Value $ht): Value
{
$num = $context->builder->call(
Expand Down Expand Up @@ -306,10 +557,7 @@ public static function copyInto(Context $context, Value $dest, Value $src): void
);
$context->builder->branch($head);

$merge = BasicBlockHelper::append($context, 'merge_copy_exit');
$context->builder->positionAtEnd($done);
$context->builder->branch($merge);
$context->builder->positionAtEnd($merge);
}

public static function inArray(
Expand Down Expand Up @@ -364,13 +612,9 @@ public static function inArray(
$context->builder->store($context->getTypeFromString('int1')->constInt(1, false), $foundSlot);
$context->builder->branch($done);

$merge = BasicBlockHelper::append($context, 'in_array_merge');
$context->builder->positionAtEnd($done);
$result = $context->builder->load($foundSlot);
$context->builder->branch($merge);
$context->builder->positionAtEnd($merge);

return $result;
return $context->builder->load($foundSlot);
}

private static function listEntryAt(Context $context, Value $ht, Value $index): Value
Expand Down
19 changes: 19 additions & 0 deletions test/compliance/cases/stdlib/array_values_jit.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
stdlib array_values() JIT
--FILE--
<?php
$a = array(1, 2, 3);
$b = array_values($a);
echo count($b), "\n";
echo $b[0], "\n";
echo $b[2], "\n";
$c = array(10, 20);
$d = array_values($c);
echo count($d), "\n";
echo $d[1], "\n";
--EXPECT--
3
1
3
2
20
16 changes: 16 additions & 0 deletions test/fixtures/aot/cases/array_values.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
AOT: array_values() on packed list arrays
--FILE--
<?php
$a = array(1, 2, 3);
$b = array_values($a);
echo count($b), "\n";
echo $b[0], "\n";
echo $b[2], "\n";
$n = array(10, 20);
echo count(array_values($n)), "\n";
--EXPECT--
3
1
3
2
Loading