From 043444b8b201091d053e746a8f8671620e0adcc1 Mon Sep 17 00:00:00 2001 From: PurHur Date: Wed, 20 May 2026 17:01:57 +0000 Subject: [PATCH] Stdlib: add array_product() with native LLVM lowering Implement array_product() for packed int/float arrays (VM, JIT, AOT), mirroring array_sum with multiply semantics and empty-array identity 1. Closes #385 Co-authored-by: Cursor --- docs/bootstrap-inventory.md | 85 +++-- docs/capabilities.md | 1 + ext/standard/Module.php | 1 + ext/standard/array_product.php | 86 +++++ lib/JIT/ArrayBuiltinHelper.php | 350 ++++++++++++++++++ .../cases/stdlib/array_product.phpt | 11 + .../cases/stdlib/array_product_float.phpt | 9 + .../cases/stdlib/array_product_jit.phpt | 13 + test/fixtures/aot/cases/array_product.phpt | 11 + 9 files changed, 528 insertions(+), 39 deletions(-) create mode 100644 ext/standard/array_product.php create mode 100644 test/compliance/cases/stdlib/array_product.phpt create mode 100644 test/compliance/cases/stdlib/array_product_float.phpt create mode 100644 test/compliance/cases/stdlib/array_product_jit.phpt create mode 100644 test/fixtures/aot/cases/array_product.phpt diff --git a/docs/bootstrap-inventory.md b/docs/bootstrap-inventory.md index 36348c2b..d126d526 100644 --- a/docs/bootstrap-inventory.md +++ b/docs/bootstrap-inventory.md @@ -8,9 +8,9 @@ Regenerate: `php script/bootstrap-inventory.php` | Metric | Count | |--------|------:| -| PHP files on vm.php path | 240 | +| PHP files on vm.php path | 241 | | Source constructs flagged (blockers) | 10 | -| Source constructs flagged (warnings) | 629 | +| Source constructs flagged (warnings) | 631 | ## Compiler CFG gaps (`lib/Compiler.php`) @@ -59,7 +59,7 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered: | `ext/standard/JitStripTags.php` | 0 | 1 | | `ext/standard/JitStrpos.php` | 0 | 1 | | `ext/standard/JitUrlencode.php` | 0 | 1 | -| `ext/standard/Module.php` | 0 | 110 | +| `ext/standard/Module.php` | 0 | 111 | | `ext/standard/VmDate.php` | 0 | 1 | | `ext/standard/VmExit.php` | 0 | 2 | | `ext/standard/VmFs.php` | 0 | 3 | @@ -75,6 +75,7 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered: | `ext/standard/array_keys.php` | 0 | 1 | | `ext/standard/array_merge.php` | 0 | 1 | | `ext/standard/array_pop.php` | 0 | 1 | +| `ext/standard/array_product.php` | 0 | 1 | | `ext/standard/array_push.php` | 0 | 2 | | `ext/standard/array_reverse.php` | 0 | 1 | | `ext/standard/array_search.php` | 0 | 1 | @@ -480,41 +481,42 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered: - new array_reverse (line 93) - new array_search (line 94) - new array_sum (line 95) -- new array_flip (line 96) -- new array_unique (line 97) -- new array_fill (line 98) -- new array_combine (line 99) -- new range (line 100) -- new bin2hex (line 101) -- new hex2bin (line 102) -- new random_bytes (line 103) -- new str_pad (line 104) -- new str_split (line 105) -- new htmlspecialchars (line 106) -- new strip_tags (line 107) -- new header_ (line 108) -- new header_remove (line 109) -- new header_list (line 110) -- new getallheaders_ (line 111) -- new http_response_code (line 112) -- new urlencode (line 113) -- new rawurlencode (line 114) -- new urldecode (line 115) -- new rawurldecode (line 116) -- new parse_url (line 117) -- new dirname (line 118) -- new basename (line 119) -- new realpath (line 120) -- new file_get_contents (line 121) -- new getenv_ (line 122) -- new putenv_ (line 123) -- new extract_ (line 124) -- new compact_ (line 125) -- new scandir (line 126) -- new glob_ (line 127) -- new time (line 128) -- new date (line 129) -- new gmdate (line 130) +- new array_product (line 96) +- new array_flip (line 97) +- new array_unique (line 98) +- new array_fill (line 99) +- new array_combine (line 100) +- new range (line 101) +- new bin2hex (line 102) +- new hex2bin (line 103) +- new random_bytes (line 104) +- new str_pad (line 105) +- new str_split (line 106) +- new htmlspecialchars (line 107) +- new strip_tags (line 108) +- new header_ (line 109) +- new header_remove (line 110) +- new header_list (line 111) +- new getallheaders_ (line 112) +- new http_response_code (line 113) +- new urlencode (line 114) +- new rawurlencode (line 115) +- new urldecode (line 116) +- new rawurldecode (line 117) +- new parse_url (line 118) +- new dirname (line 119) +- new basename (line 120) +- new realpath (line 121) +- new file_get_contents (line 122) +- new getenv_ (line 123) +- new putenv_ (line 124) +- new extract_ (line 125) +- new compact_ (line 126) +- new scandir (line 127) +- new glob_ (line 128) +- new time (line 129) +- new date (line 130) +- new gmdate (line 131) - 2 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler ### `ext/standard/VmDate.php` @@ -606,6 +608,11 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered: **Warnings** (review for bootstrap subset): - 2 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler +### `ext/standard/array_product.php` + +**Warnings** (review for bootstrap subset): +- 2 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler + ### `ext/standard/array_push.php` **Warnings** (review for bootstrap subset): @@ -1329,7 +1336,7 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered: - new Variable (line 655) - new Variable (line 1071) - new Variable (line 1079) -- 33 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler +- 37 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler ### `lib/JIT/BasicBlockHelper.php` diff --git a/docs/capabilities.md b/docs/capabilities.md index 782c0597..db5846af 100644 --- a/docs/capabilities.md +++ b/docs/capabilities.md @@ -12,6 +12,7 @@ Auto-generated by `script/capability-matrix.php`. Do not edit by hand. | `array_keys` | yes | yes | yes | standard | doc: VM only | | `array_merge` | yes | yes | yes | standard | doc: VM only | | `array_pop` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | +| `array_product` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `array_push` | yes | yes | yes | standard | doc: VM only; JIT PHPT | | `array_reverse` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `array_search` | yes | no | no | standard | doc: VM only; not implemented for JIT in this compiler build | diff --git a/ext/standard/Module.php b/ext/standard/Module.php index fbd47170..3870c5f0 100755 --- a/ext/standard/Module.php +++ b/ext/standard/Module.php @@ -93,6 +93,7 @@ public function getFunctions(): array new array_reverse(), new array_search(), new array_sum(), + new array_product(), new array_flip(), new array_unique(), new array_fill(), diff --git a/ext/standard/array_product.php b/ext/standard/array_product.php new file mode 100644 index 00000000..c1e60e5f --- /dev/null +++ b/ext/standard/array_product.php @@ -0,0 +1,86 @@ +calledArgs)) { + throw new \LogicException('array_product() requires exactly one argument'); + } + $array = $frame->calledArgs[0]->resolveIndirect(); + if (null === $frame->returnVar) { + return; + } + if (Variable::TYPE_ARRAY !== $array->type) { + throw new \LogicException('array_product() argument must be an array in this compiler build'); + } + $prodInt = 1; + $prodFloat = 1.0; + $useFloat = false; + foreach ($array->toArray()->iterate(true) as $value) { + $v = $value->resolveIndirect(); + if (Variable::TYPE_INTEGER === $v->type) { + if ($useFloat) { + $prodFloat *= (float) $v->toInt(); + } else { + $prodInt *= $v->toInt(); + } + continue; + } + if (Variable::TYPE_FLOAT === $v->type) { + if (!$useFloat) { + $useFloat = true; + $prodFloat = (float) $prodInt * $v->toFloat(); + } else { + $prodFloat *= $v->toFloat(); + } + continue; + } + throw new \LogicException('array_product() only supports integer and float elements in this compiler build'); + } + if ($useFloat) { + $frame->returnVar->float($prodFloat); + } else { + $frame->returnVar->int($prodInt); + } + } + + public Context $context; + + public function call(Context $context, JITVariable ...$args): Value + { + if (1 !== \count($args)) { + throw new \LogicException('array_product() requires exactly one argument'); + } + if ($args[0]->type & JITVariable::IS_NATIVE_ARRAY) { + return ArrayBuiltinHelper::arrayProduct($context, $args[0]); + } + if (JITVariable::TYPE_HASHTABLE === $args[0]->type) { + return ArrayBuiltinHelper::arrayProduct($context, $args[0]); + } + + throw new \LogicException('array_product() only supports arrays in this compiler build'); + } +} diff --git a/lib/JIT/ArrayBuiltinHelper.php b/lib/JIT/ArrayBuiltinHelper.php index 441fbf08..fd1c355d 100644 --- a/lib/JIT/ArrayBuiltinHelper.php +++ b/lib/JIT/ArrayBuiltinHelper.php @@ -1511,4 +1511,354 @@ private static function arraySumNativeValue(Context $context, Variable $array): $context->builder->siToFp($sumInt, $double) ); } + /** + * array_product() for packed lists (integers and floats; subset of PHP). + */ + public static function arrayProduct(Context $context, Variable $array): Value + { + if (self::isNativeArray($array->type)) { + return self::arrayProductNative($context, $array); + } + + return self::arrayProductHashTable($context, self::loadHashTable($context, $array)); + } + + private static function arrayProductNative(Context $context, Variable $array): Value + { + $elemType = $array->type & ~Variable::IS_NATIVE_ARRAY; + $sizeT = $context->getTypeFromString('size_t'); + $i64 = $context->getTypeFromString('int64'); + $double = $context->getTypeFromString('double'); + $zero = $sizeT->constInt(0, false); + $one = $sizeT->constInt(1, false); + $count = $context->constantFromInteger($array->nextFreeElement, 'size_t'); + + if (Variable::TYPE_NATIVE_DOUBLE === $elemType) { + $prodSlot = $context->builder->alloca($double, 1, 'array_product_native_f'); + $context->builder->store($double->constReal(1.0), $prodSlot); + if (0 === $array->nextFreeElement) { + return $context->builder->load($prodSlot); + } + $idxSlot = $context->builder->alloca($sizeT, 1, 'array_product_native_f_idx'); + $context->builder->store($zero, $idxSlot); + $head = BasicBlockHelper::append($context, 'array_product_native_f_head'); + $body = BasicBlockHelper::append($context, 'array_product_native_f_body'); + $done = BasicBlockHelper::append($context, 'array_product_native_f_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); + $slot = $context->builder->inBoundsGep($array->value, $zero, $idx); + $elem = $context->builder->load($slot); + $prod = $context->builder->load($prodSlot); + $context->builder->store($context->builder->fmul($prod, $elem), $prodSlot); + $context->builder->store($context->builder->addNoSignedWrap($idx, $one), $idxSlot); + $context->builder->branch($head); + + $context->builder->positionAtEnd($done); + + return $context->builder->load($prodSlot); + } + + if (Variable::TYPE_VALUE === $elemType) { + return self::arrayProductNativeValue($context, $array); + } + + if (Variable::TYPE_NATIVE_LONG !== $elemType) { + throw new \LogicException( + 'array_product() only supports integer and float elements in this compiler build' + ); + } + + $prodSlot = $context->builder->alloca($i64, 1, 'array_product_native_i'); + $context->builder->store($i64->constInt(1, false), $prodSlot); + if (0 === $array->nextFreeElement) { + return $context->builder->load($prodSlot); + } + + $idxSlot = $context->builder->alloca($sizeT, 1, 'array_product_native_i_idx'); + $context->builder->store($zero, $idxSlot); + $head = BasicBlockHelper::append($context, 'array_product_native_i_head'); + $body = BasicBlockHelper::append($context, 'array_product_native_i_body'); + $done = BasicBlockHelper::append($context, 'array_product_native_i_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); + $slot = $context->builder->inBoundsGep($array->value, $zero, $idx); + $elem = $context->builder->load($slot); + $prod = $context->builder->load($prodSlot); + $context->builder->store($context->builder->mulNoSignedWrap($prod, $elem), $prodSlot); + $context->builder->store($context->builder->addNoSignedWrap($idx, $one), $idxSlot); + $context->builder->branch($head); + + $context->builder->positionAtEnd($done); + + return $context->builder->load($prodSlot); + } + + private static function arrayProductHashTable(Context $context, Value $ht): Value + { + $sizeT = $context->getTypeFromString('size_t'); + $i64 = $context->getTypeFromString('int64'); + $double = $context->getTypeFromString('double'); + $i8 = $context->getTypeFromString('int8'); + $i1 = $context->getTypeFromString('int1'); + $zero = $sizeT->constInt(0, false); + $one = $sizeT->constInt(1, false); + + $prodIntSlot = $context->builder->alloca($i64, 1, 'array_product_int'); + $prodFloatSlot = $context->builder->alloca($double, 1, 'array_product_float'); + $useFloatSlot = $context->builder->alloca($i1, 1, 'array_product_use_float'); + $context->builder->store($i64->constInt(1, false), $prodIntSlot); + $context->builder->store($double->constReal(1.0), $prodFloatSlot); + $context->builder->store($i1->constInt(0, false), $useFloatSlot); + + $num = $context->builder->call( + $context->lookupFunction('__hashtable__getNumElements'), + $ht + ); + + $idxSlot = $context->builder->alloca($sizeT, 1, 'array_product_ht_idx'); + $context->builder->store($zero, $idxSlot); + $head = BasicBlockHelper::append($context, 'array_product_ht_head'); + $body = BasicBlockHelper::append($context, 'array_product_ht_body'); + $afterLong = BasicBlockHelper::append($context, 'array_product_ht_after_long'); + $longBlock = BasicBlockHelper::append($context, 'array_product_ht_long'); + $doubleBlock = BasicBlockHelper::append($context, 'array_product_ht_double'); + $continueBlock = BasicBlockHelper::append($context, 'array_product_ht_continue'); + $doneBlock = BasicBlockHelper::append($context, 'array_product_ht_done'); + + $isEmpty = $context->builder->icmp(Builder::INT_EQ, $num, $zero); + $context->builder->branchIf($isEmpty, $doneBlock, $head); + + $context->builder->positionAtEnd($head); + $idx = $context->builder->load($idxSlot); + $atEnd = $context->builder->icmp(Builder::INT_SGE, $idx, $num); + $context->builder->branchIf($atEnd, $doneBlock, $body); + + $context->builder->positionAtEnd($body); + $entry = self::listEntryAt($context, $ht, $idx); + $valueMap = $context->structFieldMap['__value__']; + $typeByte = $context->builder->load( + $context->builder->structGep($entry, $valueMap['type']) + ); + $isLong = $context->builder->icmp( + Builder::INT_EQ, + $typeByte, + $i8->constInt(Variable::TYPE_NATIVE_LONG, false) + ); + $isDouble = $context->builder->icmp( + Builder::INT_EQ, + $typeByte, + $i8->constInt(Variable::TYPE_NATIVE_DOUBLE, false) + ); + $context->builder->branchIf($isLong, $longBlock, $afterLong); + + $context->builder->positionAtEnd($afterLong); + $context->builder->branchIf($isDouble, $doubleBlock, $continueBlock); + + $context->builder->positionAtEnd($longBlock); + $longVal = $context->builder->call($context->lookupFunction('__value__readLong'), $entry); + $useFloat = $context->builder->load($useFloatSlot); + $floatPath = BasicBlockHelper::append($context, 'array_product_ht_long_as_float'); + $intPath = BasicBlockHelper::append($context, 'array_product_ht_long_as_int'); + $longDone = BasicBlockHelper::append($context, 'array_product_ht_long_done'); + $context->builder->branchIf($useFloat, $floatPath, $intPath); + + $context->builder->positionAtEnd($intPath); + $prodInt = $context->builder->load($prodIntSlot); + $context->builder->store( + $context->builder->mulNoSignedWrap($prodInt, $longVal), + $prodIntSlot + ); + $context->builder->branch($longDone); + + $context->builder->positionAtEnd($floatPath); + $prodFloat = $context->builder->load($prodFloatSlot); + $context->builder->store( + $context->builder->fmul($prodFloat, $context->builder->siToFp($longVal, $double)), + $prodFloatSlot + ); + $context->builder->branch($longDone); + + $context->builder->positionAtEnd($longDone); + $context->builder->branch($continueBlock); + + $context->builder->positionAtEnd($doubleBlock); + $doubleVal = $context->builder->call($context->lookupFunction('__value__readDouble'), $entry); + $useFloatNow = $context->builder->load($useFloatSlot); + $promoteBlock = BasicBlockHelper::append($context, 'array_product_ht_promote'); + $addFloatBlock = BasicBlockHelper::append($context, 'array_product_ht_add_float'); + $doubleDone = BasicBlockHelper::append($context, 'array_product_ht_double_done'); + $context->builder->branchIf($useFloatNow, $addFloatBlock, $promoteBlock); + + $context->builder->positionAtEnd($promoteBlock); + $prodInt = $context->builder->load($prodIntSlot); + $context->builder->store( + $context->builder->fmul($context->builder->siToFp($prodInt, $double), $doubleVal), + $prodFloatSlot + ); + $context->builder->store($i1->constInt(1, false), $useFloatSlot); + $context->builder->branch($doubleDone); + + $context->builder->positionAtEnd($addFloatBlock); + $prodFloat = $context->builder->load($prodFloatSlot); + $context->builder->store($context->builder->fmul($prodFloat, $doubleVal), $prodFloatSlot); + $context->builder->branch($doubleDone); + + $context->builder->positionAtEnd($doubleDone); + $context->builder->branch($continueBlock); + + $context->builder->positionAtEnd($continueBlock); + $context->builder->store($context->builder->addNoSignedWrap($idx, $one), $idxSlot); + $context->builder->branch($head); + + $context->builder->positionAtEnd($doneBlock); + $useFloat = $context->builder->load($useFloatSlot); + $prodInt = $context->builder->load($prodIntSlot); + $prodFloat = $context->builder->load($prodFloatSlot); + + return $context->builder->select( + $useFloat, + $prodFloat, + $context->builder->siToFp($prodInt, $double) + ); + } + + /** Native array of boxed __value__ (e.g. array(1, 2.5)). */ + private static function arrayProductNativeValue(Context $context, Variable $array): Value + { + $sizeT = $context->getTypeFromString('size_t'); + $i64 = $context->getTypeFromString('int64'); + $double = $context->getTypeFromString('double'); + $i8 = $context->getTypeFromString('int8'); + $i1 = $context->getTypeFromString('int1'); + $zero = $sizeT->constInt(0, false); + $one = $sizeT->constInt(1, false); + $count = $context->constantFromInteger($array->nextFreeElement, 'size_t'); + $valueMap = $context->structFieldMap['__value__']; + + $prodIntSlot = $context->builder->alloca($i64, 1, 'array_product_nv_int'); + $prodFloatSlot = $context->builder->alloca($double, 1, 'array_product_nv_float'); + $useFloatSlot = $context->builder->alloca($i1, 1, 'array_product_nv_use_float'); + $context->builder->store($i64->constInt(1, false), $prodIntSlot); + $context->builder->store($double->constReal(1.0), $prodFloatSlot); + $context->builder->store($i1->constInt(0, false), $useFloatSlot); + + if (0 === $array->nextFreeElement) { + return $context->builder->load($prodIntSlot); + } + + $idxSlot = $context->builder->alloca($sizeT, 1, 'array_product_nv_idx'); + $context->builder->store($zero, $idxSlot); + $head = BasicBlockHelper::append($context, 'array_product_nv_head'); + $body = BasicBlockHelper::append($context, 'array_product_nv_body'); + $afterLong = BasicBlockHelper::append($context, 'array_product_nv_after_long'); + $longBlock = BasicBlockHelper::append($context, 'array_product_nv_long'); + $doubleBlock = BasicBlockHelper::append($context, 'array_product_nv_double'); + $continueBlock = BasicBlockHelper::append($context, 'array_product_nv_continue'); + $doneBlock = BasicBlockHelper::append($context, 'array_product_nv_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, $doneBlock, $body); + + $context->builder->positionAtEnd($body); + $entry = $context->builder->inBoundsGep($array->value, $zero, $idx); + $typeByte = $context->builder->load( + $context->builder->structGep($entry, $valueMap['type']) + ); + $isLong = $context->builder->icmp( + Builder::INT_EQ, + $typeByte, + $i8->constInt(Variable::TYPE_NATIVE_LONG, false) + ); + $isDouble = $context->builder->icmp( + Builder::INT_EQ, + $typeByte, + $i8->constInt(Variable::TYPE_NATIVE_DOUBLE, false) + ); + $context->builder->branchIf($isLong, $longBlock, $afterLong); + + $context->builder->positionAtEnd($afterLong); + $context->builder->branchIf($isDouble, $doubleBlock, $continueBlock); + + $context->builder->positionAtEnd($longBlock); + $longVal = $context->builder->call($context->lookupFunction('__value__readLong'), $entry); + $useFloat = $context->builder->load($useFloatSlot); + $floatPath = BasicBlockHelper::append($context, 'array_product_nv_long_f'); + $intPath = BasicBlockHelper::append($context, 'array_product_nv_long_i'); + $longDone = BasicBlockHelper::append($context, 'array_product_nv_long_done'); + $context->builder->branchIf($useFloat, $floatPath, $intPath); + + $context->builder->positionAtEnd($intPath); + $prodInt = $context->builder->load($prodIntSlot); + $context->builder->store( + $context->builder->mulNoSignedWrap($prodInt, $longVal), + $prodIntSlot + ); + $context->builder->branch($longDone); + + $context->builder->positionAtEnd($floatPath); + $prodFloat = $context->builder->load($prodFloatSlot); + $context->builder->store( + $context->builder->fmul($prodFloat, $context->builder->siToFp($longVal, $double)), + $prodFloatSlot + ); + $context->builder->branch($longDone); + + $context->builder->positionAtEnd($longDone); + $context->builder->branch($continueBlock); + + $context->builder->positionAtEnd($doubleBlock); + $doubleVal = $context->builder->call($context->lookupFunction('__value__readDouble'), $entry); + $useFloatNow = $context->builder->load($useFloatSlot); + $promoteBlock = BasicBlockHelper::append($context, 'array_product_nv_promote'); + $addFloatBlock = BasicBlockHelper::append($context, 'array_product_nv_add_float'); + $doubleDone = BasicBlockHelper::append($context, 'array_product_nv_double_done'); + $context->builder->branchIf($useFloatNow, $addFloatBlock, $promoteBlock); + + $context->builder->positionAtEnd($promoteBlock); + $prodInt = $context->builder->load($prodIntSlot); + $context->builder->store( + $context->builder->fmul($context->builder->siToFp($prodInt, $double), $doubleVal), + $prodFloatSlot + ); + $context->builder->store($i1->constInt(1, false), $useFloatSlot); + $context->builder->branch($doubleDone); + + $context->builder->positionAtEnd($addFloatBlock); + $prodFloat = $context->builder->load($prodFloatSlot); + $context->builder->store($context->builder->fmul($prodFloat, $doubleVal), $prodFloatSlot); + $context->builder->branch($doubleDone); + + $context->builder->positionAtEnd($doubleDone); + $context->builder->branch($continueBlock); + + $context->builder->positionAtEnd($continueBlock); + $context->builder->store($context->builder->addNoSignedWrap($idx, $one), $idxSlot); + $context->builder->branch($head); + + $context->builder->positionAtEnd($doneBlock); + $useFloat = $context->builder->load($useFloatSlot); + $prodInt = $context->builder->load($prodIntSlot); + $prodFloat = $context->builder->load($prodFloatSlot); + + return $context->builder->select( + $useFloat, + $prodFloat, + $context->builder->siToFp($prodInt, $double) + ); + } } diff --git a/test/compliance/cases/stdlib/array_product.phpt b/test/compliance/cases/stdlib/array_product.phpt new file mode 100644 index 00000000..bb2897ab --- /dev/null +++ b/test/compliance/cases/stdlib/array_product.phpt @@ -0,0 +1,11 @@ +--TEST-- +stdlib array_product() for integers +--FILE-- +