diff --git a/docs/capabilities.md b/docs/capabilities.md index 704acb203..a2abb130a 100644 --- a/docs/capabilities.md +++ b/docs/capabilities.md @@ -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 | | @@ -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 | @@ -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 | @@ -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 | diff --git a/ext/standard/array_values.php b/ext/standard/array_values.php index 2483c01bf..ecd71ea86 100644 --- a/ext/standard/array_values.php +++ b/ext/standard/array_values.php @@ -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 { @@ -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]); } } diff --git a/lib/JIT/ArrayBuiltinHelper.php b/lib/JIT/ArrayBuiltinHelper.php index 9bbb16e51..5fc1b0354 100644 --- a/lib/JIT/ArrayBuiltinHelper.php +++ b/lib/JIT/ArrayBuiltinHelper.php @@ -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( @@ -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( @@ -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 diff --git a/test/compliance/cases/stdlib/array_values_jit.phpt b/test/compliance/cases/stdlib/array_values_jit.phpt new file mode 100644 index 000000000..4aa6a2f67 --- /dev/null +++ b/test/compliance/cases/stdlib/array_values_jit.phpt @@ -0,0 +1,19 @@ +--TEST-- +stdlib array_values() JIT +--FILE-- +