TypePHP 编译器
https://swoole.com/aot/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
122 lines
4.5 KiB
122 lines
4.5 KiB
<?php
|
|
|
|
use TypePhp\CompilerTest;
|
|
|
|
/**
|
|
* PHP evaluates call arguments and concat operands left to right. When a
|
|
* later operand hoists captured statements (an assignment), earlier
|
|
* plain-variable reads must be snapshotted at their own position, or the
|
|
* hoisted side effect executes first: pair($j, $j = 5) must return "1,5"
|
|
* and $m . ',' . ($m = 9) must be "1,9". Plain arithmetic is exempt:
|
|
* Zend's ADD opcode reads the CV at op time, so $k + ($k = 5) is 10 in
|
|
* both worlds and must keep its existing codegen.
|
|
*/
|
|
final class EvalOrderSideEffectsCodegenTest extends \BaseTest
|
|
{
|
|
public function testCallArgumentReadIsSnapshottedBeforeLaterAssignment(): void
|
|
{
|
|
$code = $this->compileFixture();
|
|
$body = $this->extractFunctionBody($code, 'php_callargorder()');
|
|
|
|
self::assertMatchesRegularExpression(
|
|
'/(tmp_var_\d+) = j;\s*\n\s*(tmp_var_\d+) = j = 5L{1,2};/',
|
|
$body,
|
|
'the old value of $j must be captured before $j = 5 executes',
|
|
);
|
|
self::assertDoesNotMatchRegularExpression(
|
|
'/php_pair\(php::toIntArgExact\(j,/',
|
|
$body,
|
|
'$j must not be read directly after the hoisted assignment',
|
|
);
|
|
}
|
|
|
|
public function testConcatOperandReadIsSnapshottedBeforeLaterAssignment(): void
|
|
{
|
|
$code = $this->compileFixture();
|
|
$body = $this->extractFunctionBody($code, 'php_concatorder()');
|
|
|
|
self::assertMatchesRegularExpression(
|
|
'/(tmp_var_\d+) = m;\s*\n\s*(tmp_var_\d+) = m = 9L{1,2};/',
|
|
$body,
|
|
'the old value of $m must be captured before $m = 9 executes',
|
|
);
|
|
self::assertDoesNotMatchRegularExpression(
|
|
'/php::concat\(\{php::toString\(m\)/',
|
|
$body,
|
|
'$m must not be read directly after the hoisted assignment',
|
|
);
|
|
}
|
|
|
|
public function testCastWrappedAssignmentStillSnapshotsEarlierArgument(): void
|
|
{
|
|
$code = $this->compileFixture();
|
|
$body = $this->extractFunctionBody($code, 'php_castwrappedcallargorder()');
|
|
|
|
self::assertMatchesRegularExpression(
|
|
'/(tmp_var_\d+) = i;\s*\n\s*(tmp_var_\d+) = php::toInt\(i = 5L{1,2}\);/',
|
|
$body,
|
|
'the old value of $i must be captured before the cast-wrapped $i = 5 executes',
|
|
);
|
|
self::assertDoesNotMatchRegularExpression(
|
|
'/php_pair\(php::toIntArgExact\(i,/',
|
|
$body,
|
|
'$i must not be read directly alongside the wrapped assignment',
|
|
);
|
|
}
|
|
|
|
public function testBooleanNotWrappedAssignmentStillSnapshotsEarlierArgument(): void
|
|
{
|
|
$code = $this->compileFixture();
|
|
$body = $this->extractFunctionBody($code, 'php_notwrappedcallargorder()');
|
|
|
|
self::assertMatchesRegularExpression(
|
|
'/(tmp_var_\d+) = k;\s*\n\s*(tmp_var_\d+) = !\(php::toBool\(k = 0L{1,2}\)\);/',
|
|
$body,
|
|
'the old value of $k must be captured before the negated $k = 0 executes',
|
|
);
|
|
self::assertDoesNotMatchRegularExpression(
|
|
'/php_pairvalue\(k,/',
|
|
$body,
|
|
'$k must not be read directly alongside the wrapped assignment',
|
|
);
|
|
}
|
|
|
|
public function testPlainArithmeticKeepsZendCvReadSemantics(): void
|
|
{
|
|
$code = $this->compileFixture();
|
|
$body = $this->extractFunctionBody($code, 'php_plainarithmeticunchanged()');
|
|
|
|
// Zend reads the CV when the ADD executes, i.e. after the nested
|
|
// assignment; the direct read of k matches that and must stay.
|
|
self::assertMatchesRegularExpression(
|
|
'/(tmp_var_\d+) = k = 5L{1,2};\s*\n[^\n]*\(\(k\) \+ \(\1\)\)/',
|
|
$body,
|
|
);
|
|
self::assertStringNotContainsString('= k;', $body);
|
|
}
|
|
|
|
private function extractFunctionBody(string $code, string $marker): string
|
|
{
|
|
$start = strpos($code, $marker);
|
|
self::assertIsInt($start, "missing function: {$marker}");
|
|
$end = strpos($code, "\n}", $start);
|
|
self::assertIsInt($end);
|
|
return substr($code, $start, $end - $start);
|
|
}
|
|
|
|
private function compileFixture(): string
|
|
{
|
|
global $translator;
|
|
|
|
$compiler = CompilerTest::create(TYPEPHP_ROOT_PATH);
|
|
$translator = $compiler;
|
|
$source = TYPEPHP_ROOT_PATH . '/phpunit/code/eval-order-side-effects.php';
|
|
$compiler->addFiles([$source]);
|
|
$compiler->prepareFile($source);
|
|
$generated = $compiler->convertFile($source);
|
|
$code = file_get_contents($generated);
|
|
|
|
self::assertIsString($code);
|
|
return $code;
|
|
}
|
|
}
|
|
|