From 72b7ebdfae966939e7fc66fe20c1ba6aefdb3eeb Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Wed, 31 Dec 2025 17:54:54 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8D=95=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- run-tests.php | 24 ++-- src/Php/Reflection.php | 31 +++++ src/Php/Translator.php | 119 ++++++++++++++----- tests/aot/arithmetic_operators.phpt | 147 +++++++++++++++++++++++ tests/aot/assignment_operators.phpt | 158 +++++++++++++++++++++++++ tests/aot/comparison_operators.phpt | 177 ++++++++++++++++++++++++++++ tests/aot/control_structures.phpt | 82 +++++++++++++ tests/aot/logical_operators.phpt | 136 +++++++++++++++++++++ 8 files changed, 838 insertions(+), 36 deletions(-) create mode 100644 src/Php/Reflection.php create mode 100644 tests/aot/arithmetic_operators.phpt create mode 100644 tests/aot/assignment_operators.phpt create mode 100644 tests/aot/comparison_operators.phpt create mode 100644 tests/aot/control_structures.phpt create mode 100644 tests/aot/logical_operators.phpt diff --git a/run-tests.php b/run-tests.php index 7592c7a9..712d9739 100755 --- a/run-tests.php +++ b/run-tests.php @@ -149,7 +149,7 @@ function main(): void $end_time, $environment, $exts_skipped, $exts_tested, $exts_to_test, $failed_tests_file, $ignored_by_ext, $ini_overwrites, $colorize, - $log_format, $no_clean, $no_file_cache, + $log_format, $no_clean, $no_aot, $no_file_cache, $pass_options, $php, $php_cgi, $preload, $result_tests_file, $slow_min_ms, $start_time, $temp_source, $temp_target, $test_cnt, @@ -491,6 +491,9 @@ function main(): void case '--no-clean': $no_clean = true; break; + case '--no-aot': + $no_aot = true; + break; case '--color': $colorize = true; break; @@ -2426,8 +2429,11 @@ TEST $file $orig_cmd = $cmd; - $bin_file = compile_php_file($test_file); - $cmd = './' . $bin_file . ' ' . $args . $cmdRedirect; + global $no_aot; + if (!$no_aot) { + $bin_file = compile_php_file($test_file); + $cmd = './' . $bin_file . ' ' . $args . $cmdRedirect; + } if ($valgrind) { $env['USE_ZEND_ALLOC'] = '0'; @@ -2622,11 +2628,13 @@ COMMAND $cmd if (!$cfg['keep']['php'] && !$leaked) { @unlink($test_file); @unlink($preload_filename); - // 刪除 AOT 编译器生成的 .cc 和 .o 文件 - $file = substr($test_file, 0, strlen($test_file) - 4); - @unlink($file . '.cc'); - @unlink($file . '.cc.o'); - @unlink('./' . $bin_file); + if (!$no_aot) { + // 刪除 AOT 编译器生成的 .cc 和 .o 文件 + $file = substr($test_file, 0, strlen($test_file) - 4); + @unlink($file . '.cc'); + @unlink($file . '.cc.o'); + @unlink('./' . $bin_file); + } } @unlink($tmp_post); diff --git a/src/Php/Reflection.php b/src/Php/Reflection.php new file mode 100644 index 00000000..4be80fd6 --- /dev/null +++ b/src/Php/Reflection.php @@ -0,0 +1,31 @@ +getReturnType(); + if (!$returnType) { + return null; + } + if ($returnType instanceof \ReflectionUnionType) { + return null; + } + return $returnType->getName(); + } +} \ No newline at end of file diff --git a/src/Php/Translator.php b/src/Php/Translator.php index e6165bb1..0bba7e20 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -265,9 +265,20 @@ class Translator extends \PhpAot\Core\Translator return $node->getType(); } - public function getZendType(string $type): string + private function getVarType(string $name): string { - return $this->zendTypeMap[$type] ?? 'zval *'; + if ($this->hasLocalVar($name)) { + return $this->localVars[$name]; + } + if ($this->hasLocalVar($name)) { + return $this->globalVars[$name]; + } + return self::TYPE_VAR; + } + + public function getTypeFromZendType(string $type): string + { + return $this->zendTypeMap[$type] ?? self::TYPE_VAR; } public function isInternalFunction(string $name): bool @@ -300,7 +311,7 @@ class Translator extends \PhpAot\Core\Translator $this->functionDef = new FunctionDef(); $this->functionDef->name = $name; if ($v->returnType) { - $this->functionDef->returnType = $this->getZendType($this->parseIdentifier($v->returnType)); + $this->functionDef->returnType = $this->getTypeFromZendType($this->parseIdentifier($v->returnType)); } else { $this->functionDef->returnType = 'void'; } @@ -770,13 +781,7 @@ class Translator extends \PhpAot\Core\Translator private function detectVarType($var): string { $name = $this->parseIdentifier($var); - if ($this->hasLocalVar($name)) { - return $this->localVars[$name]; - } - if ($this->hasLocalVar($name)) { - return $this->globalVars[$name]; - } - return self::TYPE_VAR; + return $this->getVarType($name); } private function detectExprType($expr): string @@ -823,7 +828,7 @@ class Translator extends \PhpAot\Core\Translator if ($this->isInternalFunction($name)) { return $this->internalFunctions[$name]->returnType; } - break; + return $this->detectFuncCallReturnType($expr); case 'Expr_New': return self::TYPE_OBJECT; case 'Expr_Assign': @@ -1029,7 +1034,7 @@ class Translator extends \PhpAot\Core\Translator private function parseBinaryOpSmaller(mixed $expr): string { - return $this->parseBinaryOp($expr->left, $expr->right, '<'); + return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '<')); } private function parsePreInc(mixed $expr): string @@ -1080,7 +1085,7 @@ class Translator extends \PhpAot\Core\Translator private function parseAssignOpMul(mixed $expr): string { $var = $this->parseIdentifier($expr->var); - return $var . ' *= ' . $this->parseIdentifier($expr->expr); + return $var . ' *= ' . $this->convertVarType($var, $this->parseExpr($expr->expr)); } private function parseAssignOpDiv(mixed $expr): string @@ -1108,6 +1113,18 @@ class Translator extends \PhpAot\Core\Translator if ($name === 'strlen' and $expr->args[0]->value->getType() === 'Expr_Variable') { return '((php::Int)' . $this->parseIdentifier($expr->args[0]->value) . '.length())'; } + if (count($expr->args) == 1) { + switch ($name) { + case 'intval': + return $this->convertIntExpr($this->parseExpr($expr->args[0]->value)); + case 'floatval': + return $this->convertFloatExpr($this->parseExpr($expr->args[0]->value)); + case 'boolval': + return $this->convertBoolExpr($this->parseExpr($expr->args[0]->value)); + default: + break; + } + } if (empty($expr->args)) { return 'php::call(' . $fn . ')'; } else { @@ -1163,13 +1180,13 @@ class Translator extends \PhpAot\Core\Translator private function parseBinaryOpGreater(mixed $expr): string { - return $this->parseBinaryOp($expr->left, $expr->right, '>'); + return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '>')); } private function parseAssignOpMod(mixed $expr): string { $var = $this->parseIdentifier($expr->var); - return $var . ' % ' . $this->parseIdentifier($expr->expr); + return $var . ' %= ' . $this->parseIdentifier($expr->expr); } private function parseBinaryOpPow(mixed $expr): string @@ -1177,7 +1194,7 @@ class Translator extends \PhpAot\Core\Translator $left = $this->parseIdentifier($expr->left); $right = $this->parseIdentifier($expr->right); - return 'pow(' . $left . ', ' . $right . ')'; + return 'php::pow(' . $left . ', ' . $right . ')'; } private function parsePreDec(mixed $expr): string @@ -1240,27 +1257,30 @@ class Translator extends \PhpAot\Core\Translator private function parseBinaryOpEqual(mixed $expr): string { - return $this->parseBinaryOp($expr->left, $expr->right, '=='); + return 'php::equals(' . $this->parseExpr($expr->left) . ', ' . $this->parseExpr($expr->right) . ')'; } private function parseBinaryOpNotEqual(mixed $expr): string { - return $this->parseBinaryOp($expr->left, $expr->right, '!='); + return '!php::equals(' . $this->parseExpr($expr->left) . ', ' . $this->parseExpr($expr->right) . ')'; } + /** + * 逻辑比较的运算,必须返回 bool 类型 + */ private function parseBinaryOpLogicalAnd(Node $expr): string { - return $this->parseBinaryOp($expr->left, $expr->right, '&&'); + return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '&&')); } private function parseBinaryOpLogicalOr(Node $expr): string { - return $this->parseBinaryOp($expr->left, $expr->right, '||'); + return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '||')); } private function parseBinaryOpLogicalXor(Node $expr): string { - return $this->parseBinaryOp($expr->left, $expr->right, '^'); + return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '^')); } private function parseBooleanNot(Node $expr): string @@ -1285,37 +1305,52 @@ class Translator extends \PhpAot\Core\Translator private function convertIntExpr(string $expr): string { - return 'php::to_int(' . $expr . ')'; + if (!str_starts_with($expr, 'php::to_int(')) { + return 'php::to_int(' . $expr . ')'; + } + return $expr; } private function convertFloatExpr(string $expr): string { - return 'php::to_float(' . $expr . ')'; + if (!str_starts_with($expr, 'php::to_float(')) { + return 'php::to_float(' . $expr . ')'; + } + return $expr; } private function convertStringExpr(string $expr): string { - return 'php::to_string(' . $expr . ')'; + if (!str_starts_with($expr, 'php::to_string(')) { + return 'php::to_string(' . $expr . ')'; + } + return $expr; } private function convertArrayExpr(string $expr): string { - return 'php::to_array(' . $expr . ')'; + if (!str_starts_with($expr, 'php::to_array(')) { + return 'php::to_array(' . $expr . ')'; + } + return $expr; } private function convertBoolExpr(string $expr): string { - return 'php::to_bool(' . $expr . ')'; + if (!str_starts_with($expr, 'php::to_bool(')) { + return 'php::to_bool(' . $expr . ')'; + } + return $expr; } private function parseBinaryOpSmallerOrEqual(Node $expr): string { - return $this->parseBinaryOp($expr->left, $expr->right, '<='); + return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '<=')); } private function parseBinaryOpGreaterOrEqual(Node $expr): string { - return $this->parseBinaryOp($expr->left, $expr->right, '>='); + return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '>=')); } private function parsePrint(Node $expr): string @@ -1794,4 +1829,32 @@ class Translator extends \PhpAot\Core\Translator { return 'php::to_float(' . $this->parseIdentifier($expr->expr) . ')'; } + + private function detectFuncCallReturnType($expr): string + { + $name = $expr->name; + $returnType = Reflection::getFunctionReturnType($name); + if ($returnType) { + return $this->getTypeFromZendType($returnType); + } else { + return self::TYPE_VAR; + } + } + + private function convertVarType($var, $expr): string + { + if ($this->hasVar($var)) { + $type = $this->getVarType($var); + if ($type === self::TYPE_FLOAT) { + return $this->convertFloatExpr($expr); + } + if ($type === self::TYPE_INT) { + return $this->convertIntExpr($expr); + } + if ($type === self::TYPE_BOOL) { + return $this->convertBoolExpr($expr); + } + } + return $expr; + } } diff --git a/tests/aot/arithmetic_operators.phpt b/tests/aot/arithmetic_operators.phpt new file mode 100644 index 00000000..85300c7b --- /dev/null +++ b/tests/aot/arithmetic_operators.phpt @@ -0,0 +1,147 @@ +--TEST-- +Arithmetic Operators +--FILE-- + +--EXPECT-- +int(13) +int(7) +int(30) +float(3.3333333333333335) +int(1) +int(1000) +float(13.7) +float_div: +========================== +float(3.28125) +int(5) +int(6) +int(7) +int(6) +int(5) +int(25) +self plus: +========================== +int(22) +int(44) +assign div: +========================== +int(11) +int(1) +int(14) +int(20) +int(9) +int(512) +int(-1) +neg float div: +========================== +float(-3.3333333333333335) +int(5) +int(0) \ No newline at end of file diff --git a/tests/aot/assignment_operators.phpt b/tests/aot/assignment_operators.phpt new file mode 100644 index 00000000..5ad8f756 --- /dev/null +++ b/tests/aot/assignment_operators.phpt @@ -0,0 +1,158 @@ +--TEST-- +Assignment Operators +--FILE-- + +--EXPECT-- +int(10) +int(15) +int(12) +int(24) +int(6) +int(2) +string(5) "Hello" +string(11) "Hello World" +int(100) +int(25) +float(37.5) +int(11) +int(15) +int(48) +int(15) +int(10) +int(10) +int(10) +int(15) +int(20) +int(7) +array(3) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) +} +array(4) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) + [3]=> + int(4) +} +string(12) "Hello, John!" +int(10) +int(10) +int(11) +int(11) +int(12) +int(11) diff --git a/tests/aot/comparison_operators.phpt b/tests/aot/comparison_operators.phpt new file mode 100644 index 00000000..9ec4c489 --- /dev/null +++ b/tests/aot/comparison_operators.phpt @@ -0,0 +1,177 @@ +--TEST-- +Comparison Operators +--FILE-- +) +var_dump($a != $b); // true +var_dump($a <> $c); // false + +// Not identical (!==) +var_dump($a !== "5"); // true +var_dump($a !== $c); // false + +// Greater than +var_dump($b > $a); // true +var_dump($a > $c); // false + +// Less than +var_dump($a < $b); // true +var_dump($a < $c); // false + +// Greater than or equal +var_dump($a >= $c); // true +var_dump($b >= $a); // true +var_dump($a >= $b); // false + +// Less than or equal +var_dump($a <= $c); // true +var_dump($a <= $b); // true +var_dump($b <= $a); // false + +// Test with different data types +$str_num = "10"; +$int_num = 10; +var_dump($str_num == $int_num); // true (loose comparison) +var_dump($str_num === $int_num); // false (strict comparison) + +// Test spaceship operator (PHP 7+) +$spaceship1 = 5 <=> 10; // -1 +var_dump($spaceship1); + +$spaceship2 = 10 <=> 10; // 0 +var_dump($spaceship2); + +$spaceship3 = 15 <=> 10; // 1 +var_dump($spaceship3); + +// Test comparisons with strings +$str1 = "apple"; +$str2 = "banana"; +$str3 = "apple"; + +var_dump($str1 < $str2); // true (lexicographic comparison) +var_dump($str1 == $str3); // true +var_dump($str1 > $str3); // false + +// Test comparisons with floats +$float1 = 1.5; +$float2 = 1.7; +$float3 = 1.5; + +var_dump($float1 < $float2); // true +var_dump($float1 == $float3); // true +var_dump($float1 > $float2); // false + +// Test boolean comparisons +$bool1 = true; +$bool2 = false; +$int1 = 1; +$int0 = 0; + +var_dump($bool1 == $int1); // true +var_dump($bool2 == $int0); // true +var_dump($bool1 === $int1); // false (different types) + +// Test in conditional contexts +if (5 > 3) { + $result1 = "5 is greater than 3"; +} +var_dump($result1); + +if (2 < 1) { + $result2 = "This won't execute"; +} else { + $result2 = "2 is not less than 1"; +} +var_dump($result2); + +// Test complex comparisons +$x = 15; +$y = 20; +$z = 15; + +$complex1 = ($x < $y) && ($x == $z); +var_dump($complex1); + +$complex2 = ($x > $y) || ($x == $z); +var_dump($complex2); + +$complex3 = !($x > $y); +var_dump($complex3); + +// Test null comparisons +$null_val = null; +$zero_val = 0; +$empty_str = ""; + +var_dump($null_val == $zero_val); // true +var_dump($null_val === $zero_val); // false +echo '$zero_val == $empty_str:' . PHP_EOL; +var_dump($zero_val == $empty_str); // true +var_dump($zero_val === $empty_str); // false + +$num_a = 999; +$num_b = 999; +var_dump('int==int', $num_a == $num_b); +var_dump('int!=int', $num_a != $num_b); +?> +--EXPECT-- +bool(true) +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(true) +bool(true) +bool(false) +bool(true) +bool(true) +bool(false) +bool(true) +bool(false) +int(-1) +int(0) +int(1) +bool(true) +bool(true) +bool(false) +bool(true) +bool(true) +bool(false) +bool(true) +bool(true) +bool(false) +string(19) "5 is greater than 3" +string(20) "2 is not less than 1" +bool(true) +bool(true) +bool(true) +bool(true) +bool(false) +$zero_val == $empty_str: +bool(false) +bool(false) +string(8) "int==int" +bool(true) +string(8) "int!=int" +bool(false) \ No newline at end of file diff --git a/tests/aot/control_structures.phpt b/tests/aot/control_structures.phpt new file mode 100644 index 00000000..46d41eee --- /dev/null +++ b/tests/aot/control_structures.phpt @@ -0,0 +1,82 @@ +--TEST-- +Control Structures - if/else, for/while, switch +--FILE-- + $b) { + $result = "a is greater"; +} elseif ($a < $b) { + $result = "b is greater"; +} else { + $result = "a and b are equal"; +} + +var_dump($result); + +// Test for loop +$sum = 0; +for ($i = 1; $i <= 5; $i++) { + $sum += $i; +} +var_dump($sum); + +// Test while loop +$counter = 0; +$while_sum = 0; +while ($counter < 5) { + $counter++; + $while_sum += $counter; +} +var_dump($while_sum); + +// Test do-while loop +$do_counter = 0; +$do_sum = 0; +do { + $do_counter++; + $do_sum += $do_counter; +} while ($do_counter < 5); +var_dump($do_sum); + +// Test switch statement +$number = 2; +switch ($number) { + case 1: + $switch_result = "one"; + break; + case 2: + $switch_result = "two"; + break; + case 3: + $switch_result = "three"; + break; + default: + $switch_result = "other"; + break; +} +var_dump($switch_result); + +// Test nested conditions +$x = 5; +$y = 10; +if ($x > 0) { + if ($y > $x) { + $nested_result = "y is greater than x"; + } else { + $nested_result = "x is greater than or equal to y"; + } +} else { + $nested_result = "x is not positive"; +} +var_dump($nested_result); +?> +--EXPECT-- +string(12) "b is greater" +int(15) +int(15) +int(15) +string(3) "two" +string(19) "y is greater than x" \ No newline at end of file diff --git a/tests/aot/logical_operators.phpt b/tests/aot/logical_operators.phpt new file mode 100644 index 00000000..c10ad0ca --- /dev/null +++ b/tests/aot/logical_operators.phpt @@ -0,0 +1,136 @@ +--TEST-- +Logical Operators +--FILE-- + 0) && ($y > 0)); // true +var_dump(($x > 0) && ($y < 0)); // false + +// OR operator (||) +var_dump($a || $b); // true +var_dump($b || $b); // false +var_dump(($x > 10) || ($y > 5)); // true +var_dump(($x > 10) || ($y < 5)); // false + +// NOT operator (!) +var_dump(!$a); // false +var_dump(!$b); // true +var_dump(!($x > 0)); // false +var_dump(!($x > 10)); // true + +// XOR operator (XOR) +var_dump('$a xor $b', $a xor $b); // true +var_dump($a xor $a); // false +var_dump($b xor $b); // false +var_dump(($x > 0) xor ($y < 0)); // true + +// Alternative operators +var_dump($a and $b); // false (lower precedence) +var_dump($a or $b); // true (lower precedence) +var_dump(!$b); // true (lower precedence) + +// Complex logical expressions +$age = 25; +$has_license = true; +$has_car = false; + +$can_drive = ($age >= 18) && $has_license; +var_dump($can_drive); + +$can_travel = $can_drive || $has_car; +var_dump($can_travel); + +$should_buy_car = !$has_car && $can_drive; +var_dump($should_buy_car); + +// Test with numeric values (0 is false, non-zero is true) +$num1 = 0; +$num2 = 5; +$num3 = -3; + +var_dump($num1 && $num2); // false +var_dump($num2 && $num3); // true +var_dump($num1 || $num2); // true +var_dump(!$num1); // true +var_dump(!$num2); // false + +// Test with strings (empty string is false, non-empty is true) +$str1 = ""; +$str2 = "hello"; + +var_dump($str1 || $str2); // true +var_dump(!$str1); // true + +// Test precedence: && has higher precedence than || +$result3 = true || false && false; +var_dump($result3); // true (true || (false && false)) + +$result4 = (true || false) && false; +var_dump($result4); // false + +// Test with null and other falsy values +$null_val = null; +$falsy1 = 0; +$falsy2 = ""; + +var_dump(!$null_val); // true +var_dump($falsy1 || $falsy2); // false + +$message = ""; +if ($x > 0 && $y > 0) { + $message = "Both x and y are positive"; +} +var_dump($message); + +if ($x < 0 || $y < 0) { + $message2 = "Either x or y is negative"; +} else { + $message2 = "Neither x nor y is negative"; +} +var_dump($message2); +?> +--EXPECT-- +bool(true) +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(false) +bool(true) +bool(false) +bool(true) +string(9) "$a xor $b" +bool(true) +bool(false) +bool(false) +bool(true) +bool(false) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(false) +bool(true) +bool(true) +bool(true) +bool(false) +bool(true) +bool(true) +bool(true) +bool(false) +bool(true) +bool(false) +string(25) "Both x and y are positive" +string(27) "Neither x nor y is negative"