Skip to content
Closed
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
3,174 changes: 3,173 additions & 1 deletion ext/standard/JitPowIntegerEmitHigh.php

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions lib/JIT/DiscardedPureCallElisionNativeLongPowFolds.php
Original file line number Diff line number Diff line change
Expand Up @@ -186,20 +186,21 @@ trait DiscardedPureCallElisionNativeLongPowFolds
* - {@code $n ** 91} / {@code pow($n, 91)} → chained smul overflow→float (ninetieth × n)
* - {@code $n ** 92} / {@code pow($n, 92)} → chained smul overflow→float (ninetyfirst × n)
* - {@code $n ** 93} / {@code pow($n, 93)} → chained smul overflow→float (ninetysecond × n)
* - {@code $n ** 94} / {@code pow($n, 94)} → chained smul overflow→float (ninetythird × n)
*
* Omits {@code llvm.pow.f64} and the siToFp/fpToSi round-trip on the
* integer fast path ({@see \PHPCompiler\ext\standard\JitPow}). Peer
* compile-time {@code * 1} identity ({@see nativeLongArithIsCompileTimeIdentityOrZero})
* and {@code * 2^k} shl ({@see nativeLongMulCompileTimePowerOfTwoShift}).
*
* Float exponents ({@code 0.0}/{@code 1.0}/{@code 2.0}/{@code 3.0}/{@code 4.0}/{@code 5.0}/{@code 6.0}/{@code 7.0}/{@code 8.0}/{@code 9.0}/{@code 10.0}/{@code 11.0}/{@code 12.0}/{@code 13.0}/{@code 14.0}/{@code 15.0}/{@code 16.0}/{@code 17.0}/{@code 18.0}/{@code 19.0}/{@code 20.0}/{@code 21.0}/{@code 22.0}/{@code 23.0}/{@code 24.0}/{@code 25.0}/{@code 26.0}/{@code 27.0}/{@code 28.0}/{@code 29.0}/{@code 30.0}/{@code 31.0}/{@code 32.0}/{@code 33.0}/{@code 34.0}/{@code 35.0}/{@code 36.0}/{@code 37.0}/{@code 38.0}/{@code 39.0}/{@code 40.0}/{@code 41.0}/{@code 42.0}/{@code 43.0}/{@code 44.0}/{@code 45.0}/{@code 46.0}/{@code 47.0}/{@code 48.0}/{@code 49.0}/{@code 50.0}/{@code 51.0}/{@code 52.0}/{@code 53.0}/{@code 54.0}/{@code 55.0}/{@code 56.0}/{@code 57.0}/{@code 58.0}/{@code 59.0}/{@code 60.0}/{@code 61.0}/{@code 62.0}/{@code 63.0}/{@code 64.0}/{@code 65.0}/{@code 66.0}/{@code 67.0}/{@code 68.0}/{@code 69.0}/{@code 70.0}/{@code 71.0}/{@code 72.0}/{@code 73.0}/{@code 74.0}/{@code 75.0}/{@code 76.0}/{@code 77.0}/{@code 78.0}/{@code 79.0}/{@code 80.0}/{@code 81.0}/{@code 82.0}/{@code 83.0}/{@code 84.0}/{@code 85.0}/{@code 86.0}/{@code 87.0}/{@code 88.0}/{@code 89.0}/{@code 90.0}/{@code 91.0}/{@code 92.0}) stay
* Float exponents ({@code 0.0}/{@code 1.0}/{@code 2.0}/{@code 3.0}/{@code 4.0}/{@code 5.0}/{@code 6.0}/{@code 7.0}/{@code 8.0}/{@code 9.0}/{@code 10.0}/{@code 11.0}/{@code 12.0}/{@code 13.0}/{@code 14.0}/{@code 15.0}/{@code 16.0}/{@code 17.0}/{@code 18.0}/{@code 19.0}/{@code 20.0}/{@code 21.0}/{@code 22.0}/{@code 23.0}/{@code 24.0}/{@code 25.0}/{@code 26.0}/{@code 27.0}/{@code 28.0}/{@code 29.0}/{@code 30.0}/{@code 31.0}/{@code 32.0}/{@code 33.0}/{@code 34.0}/{@code 35.0}/{@code 36.0}/{@code 37.0}/{@code 38.0}/{@code 39.0}/{@code 40.0}/{@code 41.0}/{@code 42.0}/{@code 43.0}/{@code 44.0}/{@code 45.0}/{@code 46.0}/{@code 47.0}/{@code 48.0}/{@code 49.0}/{@code 50.0}/{@code 51.0}/{@code 52.0}/{@code 53.0}/{@code 54.0}/{@code 55.0}/{@code 56.0}/{@code 57.0}/{@code 58.0}/{@code 59.0}/{@code 60.0}/{@code 61.0}/{@code 62.0}/{@code 63.0}/{@code 64.0}/{@code 65.0}/{@code 66.0}/{@code 67.0}/{@code 68.0}/{@code 69.0}/{@code 70.0}/{@code 71.0}/{@code 72.0}/{@code 73.0}/{@code 74.0}/{@code 75.0}/{@code 76.0}/{@code 77.0}/{@code 78.0}/{@code 79.0}/{@code 80.0}/{@code 81.0}/{@code 82.0}/{@code 83.0}/{@code 84.0}/{@code 85.0}/{@code 86.0}/{@code 87.0}/{@code 88.0}/{@code 89.0}/{@code 90.0}/{@code 91.0}/{@code 92.0}/{@code 93.0}) stay
* on the float path — Zend returns {@code float} for those shapes.
*
* php-src: Zend/zend_operators.c {@code pow_function} /
* {@code zend_pow} / {@code mul_function}; ext/standard/math.c
* {@code PHP_FUNCTION(pow)}.
*
* @return 'one'|'identity'|'square'|'cube'|'fourth'|'fifth'|'sixth'|'seventh'|'eighth'|'ninth'|'tenth'|'eleventh'|'twelfth'|'thirteenth'|'fourteenth'|'fifteenth'|'sixteenth'|'seventeenth'|'eighteenth'|'nineteenth'|'twentieth'|'twentyfirst'|'twentysecond'|'twentythird'|'twentyfourth'|'twentyfifth'|'twentysixth'|'twentyseventh'|'twentyeighth'|'twentyninth'|'thirtieth'|'thirtyfirst'|'thirtysecond'|'thirtythird'|'thirtyfourth'|'thirtyfifth'|'thirtysixth'|'thirtyseventh'|'thirtyeighth'|'thirtyninth'|'fortieth'|'fortyfirst'|'fortysecond'|'fortythird'|'fortyfourth'|'fortyfifth'|'fortysixth'|'fortyseventh'|'fortyeighth'|'fortyninth'|'fiftieth'|'fiftyfirst'|'fiftysecond'|'fiftythird'|'fiftyfourth'|'fiftyfifth'|'fiftysixth'|'fiftyseventh'|'fiftyeighth'|'fiftyninth'|'sixtieth'|'sixtyfirst'|'sixtysecond'|'sixtythird'|'sixtyfourth'|'sixtyfifth'|'sixtysixth'|'sixtyseventh'|'sixtyeighth'|'sixtyninth'|'seventieth'|'seventyfirst'|'seventysecond'|'seventythird'|'seventyfourth'|'seventyfifth'|'seventysixth'|'seventyseventh'|'seventyeighth'|'seventyninth'|'eightieth'|'eightyfirst'|'eightysecond'|'eightythird'|'eightyfourth'|'eightyfifth'|'eightysixth'|'eightyseventh'|'eightyeighth'|'eightyninth'|'ninetieth'|'ninetyfirst'|'ninetysecond'|'ninetythird'|null fold to 1, keep base, mul square/cube/fourth/fifth/sixth/seventh/eighth/ninth/tenth/eleventh/twelfth/thirteenth/fourteenth/fifteenth/sixteenth/seventeenth/eighteenth/nineteenth/twentieth/twentyfirst/twentysecond/twentythird/twentyfourth/twentyfifth/twentysixth/twentyseventh/twentyeighth/twentyninth/thirtieth/thirtyfirst/thirtysecond/thirtythird/thirtyfourth/thirtyfifth/thirtysixth/thirtyseventh/thirtyeighth/thirtyninth/fortieth/fortyfirst/fortysecond/fortythird/fortyfourth/fortyfifth/fortysixth/fortyseventh/fortyeighth/fortyninth/fiftieth/fiftyfirst/fiftysecond/fiftythird/fiftyfourth/fiftyfifth/fiftysixth/fiftyseventh/fiftyeighth/fiftyninth/sixtieth/sixtyfirst/sixtysecond/sixtythird/sixtyfourth, sixtyfifth, sixtysixth, sixtyseventh, sixtyeighth, sixtyninth, seventieth, seventyfirst, seventysecond, seventythird, seventyfourth, seventyfifth, seventysixth, seventyseventh, seventyeighth, seventyninth, eightieth, eightyfirst, eightysecond, eightythird, eightyfourth, eightyfifth, eightysixth, eightyseventh, eightyeighth, eightyninth, ninetieth, ninetyfirst, ninetysecond, ninetythird, or null
* @return 'one'|'identity'|'square'|'cube'|'fourth'|'fifth'|'sixth'|'seventh'|'eighth'|'ninth'|'tenth'|'eleventh'|'twelfth'|'thirteenth'|'fourteenth'|'fifteenth'|'sixteenth'|'seventeenth'|'eighteenth'|'nineteenth'|'twentieth'|'twentyfirst'|'twentysecond'|'twentythird'|'twentyfourth'|'twentyfifth'|'twentysixth'|'twentyseventh'|'twentyeighth'|'twentyninth'|'thirtieth'|'thirtyfirst'|'thirtysecond'|'thirtythird'|'thirtyfourth'|'thirtyfifth'|'thirtysixth'|'thirtyseventh'|'thirtyeighth'|'thirtyninth'|'fortieth'|'fortyfirst'|'fortysecond'|'fortythird'|'fortyfourth'|'fortyfifth'|'fortysixth'|'fortyseventh'|'fortyeighth'|'fortyninth'|'fiftieth'|'fiftyfirst'|'fiftysecond'|'fiftythird'|'fiftyfourth'|'fiftyfifth'|'fiftysixth'|'fiftyseventh'|'fiftyeighth'|'fiftyninth'|'sixtieth'|'sixtyfirst'|'sixtysecond'|'sixtythird'|'sixtyfourth'|'sixtyfifth'|'sixtysixth'|'sixtyseventh'|'sixtyeighth'|'sixtyninth'|'seventieth'|'seventyfirst'|'seventysecond'|'seventythird'|'seventyfourth'|'seventyfifth'|'seventysixth'|'seventyseventh'|'seventyeighth'|'seventyninth'|'eightieth'|'eightyfirst'|'eightysecond'|'eightythird'|'eightyfourth'|'eightyfifth'|'eightysixth'|'eightyseventh'|'eightyeighth'|'eightyninth'|'ninetieth'|'ninetyfirst'|'ninetysecond'|'ninetythird'|'ninetyfourth'|null fold to 1, keep base, mul square/cube/fourth/fifth/sixth/seventh/eighth/ninth/tenth/eleventh/twelfth/thirteenth/fourteenth/fifteenth/sixteenth/seventeenth/eighteenth/nineteenth/twentieth/twentyfirst/twentysecond/twentythird/twentyfourth/twentyfifth/twentysixth/twentyseventh/twentyeighth/twentyninth/thirtieth/thirtyfirst/thirtysecond/thirtythird/thirtyfourth/thirtyfifth/thirtysixth/thirtyseventh/thirtyeighth/thirtyninth/fortieth/fortyfirst/fortysecond/fortythird/fortyfourth/fortyfifth/fortysixth/fortyseventh/fortyeighth/fortyninth/fiftieth/fiftyfirst/fiftysecond/fiftythird/fiftyfourth/fiftyfifth/fiftysixth/fiftyseventh/fiftyeighth/fiftyninth/sixtieth/sixtyfirst/sixtysecond/sixtythird/sixtyfourth, sixtyfifth, sixtysixth, sixtyseventh, sixtyeighth, sixtyninth, seventieth, seventyfirst, seventysecond, seventythird, seventyfourth, seventyfifth, seventysixth, seventyseventh, seventyeighth, seventyninth, eightieth, eightyfirst, eightysecond, eightythird, eightyfourth, eightyfifth, eightysixth, eightyseventh, eightyeighth, eightyninth, ninetieth, ninetyfirst, ninetysecond, ninetythird, ninetyfourth, or null
*/
public static function nativeLongPowCompileTimeExponentFold(
Variable $exponent
Expand Down Expand Up @@ -490,6 +491,9 @@ public static function nativeLongPowCompileTimeExponentFold(
if (93 === $e) {
return 'ninetythird';
}
if (94 === $e) {
return 'ninetyfourth';
}

return null;
}
Expand Down
10 changes: 5 additions & 5 deletions script/size-budgets.json
Original file line number Diff line number Diff line change
Expand Up @@ -644,9 +644,9 @@
"note": "nativeLongCompare* + nativeLongMul* + nativeLongArithIsCompileTimeIdentityOrZero extracted from NativeLongFolds (#36387); php-src Zend/zend_operators.c compare/mul/add/sub"
},
"lib/JIT/DiscardedPureCallElisionNativeLongPowFolds.php": {
"budget": 496,
"budget": 500,
"target": 600,
"note": "nativeLongPowCompileTimeExponentFold extracted from NativeLongFolds (#36387/#36386); includes ninetythird fold; php-src Zend/zend_operators.c pow_function / zend_pow / mul_function; ext/standard/math.c PHP_FUNCTION(pow)"
"note": "nativeLongPowCompileTimeExponentFold extracted from NativeLongFolds (#36387/#36386); includes ninetythird/ninetyfourth folds; php-src Zend/zend_operators.c pow_function / zend_pow / mul_function; ext/standard/math.c PHP_FUNCTION(pow)"
},
"lib/JIT/DiscardedPureCallElisionIntrospectOps.php": {
"budget": 407,
Expand Down Expand Up @@ -724,9 +724,9 @@
"note": "integer pow()/ ** hub: Low/Mid/Exponents50to69/70to79/80to91/High dispatch (#36387/#36386)"
},
"ext/standard/JitPowIntegerEmitHigh.php": {
"budget": 6151,
"target": 8000,
"note": "integer pow()/ ** chained-smul emit for ninetysecond..ninetythird; 80..91 in Exponents80to91 (#36387/#36386)"
"budget": 9323,
"target": 12000,
"note": "integer pow()/ ** chained-smul emit for ninetysecond..ninetyfourth; 80..91 in Exponents80to91 (#36387/#36386)"
},
"ext/standard/JitPowIntegerEmitLow.php": {
"budget": 3985,
Expand Down
3 changes: 2 additions & 1 deletion test/differential/OPCODE-CORPUS.md5
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,8 @@ test/differential/cases/pow_fourth_smul_36386.php ee5396091f6f9c186dd9a875550146
test/differential/cases/pow_nineteenth_smul_36386.php aafc67b69a6b7843ac706c090ea3e2ca
test/differential/cases/pow_ninetieth_smul_36386.php 450a0909426f9f18af085b169f193faa
test/differential/cases/pow_ninetyfirst_smul_36386.php 84be93828cace347396c3ace4b6660e7
test/differential/cases/pow_ninetysecond_smul_36386.php c8153b0dca26103220d33dcbc67cf004
test/differential/cases/pow_ninetyfourth_smul_36386.php af12381334065b04b0067038af5b30ad
test/differential/cases/pow_ninetysecond_smul_36386.php 30d528ddf53d7e3969484a1907509d48
test/differential/cases/pow_ninetythird_smul_36386.php 0031532bb9d830d07eda5e7e2c531fef
test/differential/cases/pow_ninth_smul_36386.php 48acf646cd7ccb6121a01dd0a8ad65fa
test/differential/cases/pow_seventeenth_smul_36386.php 1a32a1c8ef94785bf5b5cab539ed2d54
Expand Down
25 changes: 25 additions & 0 deletions test/differential/cases/pow_ninetyfourth_smul_36386.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
function work(int $n): int {
return $n ** 94;
}
function viaPow(int $n): int {
return pow($n, 94);
}
function overflow(int $n) {
return $n ** 94;
}
echo work(0), "
";
echo work(1), "
";
echo work(-1), "
";
echo viaPow(0), "
";
echo gettype(overflow(2)), "
";
echo gettype(overflow(-2)), "
";
echo gettype(overflow(PHP_INT_MAX)), "
";
173 changes: 173 additions & 0 deletions test/unit/NoThrowPowNinetyfourthMulAotTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\Test\Unit;

use PHPUnit\Framework\TestCase;

/**
* Typed integer {@code **} / {@code pow()} with compile-time exponent
* {@code 94} → {@code (ninetythird*n)} chained smul
* overflow→float — omit {@code llvm.pow.f64} (#36386).
*
* php-src: Zend/zend_operators.c pow_function / zend_pow / mul_function;
* ext/standard/math.c PHP_FUNCTION(pow).
* Peer: ** 2–** 93 (#37273…/#37656), ** 0 / ** 1 (#37269).
* Note: {@code (±2)**94} and larger magnitudes promote to float (signed i64).
* Even exponent: {@code (-1)**94 === 1}.
*
* @group aot-lint
*/
final class NoThrowPowNinetyfourthMulAotTest extends TestCase
{
public function testPowNinetyfourthOmitsFpowUsesSmul(): void
{
$src = <<<'PHP'
<?php
declare(strict_types=1);
function work(int $n): int {
return $n ** 94;
}
echo work(0), "\n";
echo work(1), "\n";
echo work(-1), "\n";
PHP;
$this->assertPowNinetyfourth($src, 'work', ['0', '1', '1']);
}

public function testBuiltinPowNinetyfourth(): void
{
$src = <<<'PHP'
<?php
declare(strict_types=1);
function work(int $n): int {
return pow($n, 94);
}
echo work(-1), "\n";
PHP;
$this->assertPowNinetyfourth($src, 'work', ['1']);
}

public function testPowNinetyfourthOverflowPromotesToFloat(): void
{
$src = <<<'PHP'
<?php
declare(strict_types=1);
function work(int $n) {
return $n ** 94;
}
echo gettype(work(2)), "\n";
echo gettype(work(-2)), "\n";
echo gettype(work(PHP_INT_MAX)), "\n";
PHP;
$path = sys_get_temp_dir().'/phpc_pow94_ov_'.getmypid().'.php';
$bin = sys_get_temp_dir().'/phpc_pow94_ov_'.getmypid().'.bin';
file_put_contents($path, $src);
try {
putenv('PHP_COMPILER_DUMP_IR=1');
putenv('PHP_COMPILER_CACHE=0');
$cmd = escapeshellarg(PHP_BINARY).' '
.escapeshellarg(__DIR__.'/../../bin/compile.php').' -o '
.escapeshellarg($bin).' '.escapeshellarg($path).' 2>&1';
exec($cmd, $out, $rc);
$this->assertSame(0, $rc, implode("\n", $out));
$body = $this->functionBody((string) file_get_contents('/tmp/phpc-last.ll'), 'work');
$this->assertStringNotContainsString('llvm.pow.f64', $body);
$this->assertStringContainsString('smul.with.overflow', $body);
exec(escapeshellarg($bin), $runOut, $runRc);
$this->assertSame(0, $runRc);
$this->assertSame(['double', 'double', 'double'], $runOut);
} finally {
putenv('PHP_COMPILER_DUMP_IR');
putenv('PHP_COMPILER_CACHE');
@unlink($path);
@unlink($bin);
}
}

public function testRuntimeExponentKeepsPow(): void
{
$src = <<<'PHP'
<?php
declare(strict_types=1);
function work(int $n, int $e): int {
return $n ** $e;
}
echo work(-1, 94), "\n";
PHP;
$path = sys_get_temp_dir().'/phpc_pow94_rt_'.getmypid().'.php';
$bin = sys_get_temp_dir().'/phpc_pow94_rt_'.getmypid().'.bin';
file_put_contents($path, $src);
try {
putenv('PHP_COMPILER_DUMP_IR=1');
putenv('PHP_COMPILER_CACHE=0');
$cmd = escapeshellarg(PHP_BINARY).' '
.escapeshellarg(__DIR__.'/../../bin/compile.php').' -o '
.escapeshellarg($bin).' '.escapeshellarg($path).' 2>&1';
exec($cmd, $out, $rc);
$this->assertSame(0, $rc, implode("\n", $out));
$body = $this->functionBody((string) file_get_contents('/tmp/phpc-last.ll'), 'work');
$this->assertTrue(
false !== strpos($body, 'llvm.pow.f64')
|| false !== strpos($body, '@llvm.pow')
|| false !== strpos($body, 'phpc_fpow')
|| false !== strpos($body, '__phpc_pow_int'),
"expected pow intrinsic/helper in IR:\n".$body
);
exec(escapeshellarg($bin), $runOut, $runRc);
$this->assertSame(0, $runRc);
$this->assertSame(['1'], $runOut);
} finally {
putenv('PHP_COMPILER_DUMP_IR');
putenv('PHP_COMPILER_CACHE');
@unlink($path);
@unlink($bin);
}
}

/**
* @param list<string> $expectedRun
*/
private function assertPowNinetyfourth(string $src, string $fn, array $expectedRun): void
{
$path = sys_get_temp_dir().'/phpc_pow94_'.getmypid().'.php';
$bin = sys_get_temp_dir().'/phpc_pow94_'.getmypid().'.bin';
file_put_contents($path, $src);
try {
putenv('PHP_COMPILER_DUMP_IR=1');
putenv('PHP_COMPILER_CACHE=0');
$cmd = escapeshellarg(PHP_BINARY).' '
.escapeshellarg(__DIR__.'/../../bin/compile.php').' -o '
.escapeshellarg($bin).' '.escapeshellarg($path).' 2>&1';
exec($cmd, $out, $rc);
$this->assertSame(0, $rc, implode("\n", $out));
$body = $this->functionBody((string) file_get_contents('/tmp/phpc-last.ll'), $fn);
$this->assertStringNotContainsString('llvm.pow.f64', $body);
$this->assertStringNotContainsString('@llvm.pow', $body);
$this->assertStringNotContainsString('phpc_fpow', $body);
$this->assertStringContainsString('smul.with.overflow', $body);
exec(escapeshellarg($bin), $runOut, $runRc);
$this->assertSame(0, $runRc);
$this->assertSame($expectedRun, $runOut);
} finally {
putenv('PHP_COMPILER_DUMP_IR');
putenv('PHP_COMPILER_CACHE');
@unlink($path);
@unlink($bin);
}
}

private function functionBody(string $ll, string $fn): string
{
if (!preg_match(
'/define[^\n]*@'.preg_quote($fn, '/').'[^\n]*\{(.*?)\n\}/s',
$ll,
$m
)) {
$this->fail("function @{$fn} not found in IR");
}

return $m[1];
}
}