From 2b088d71c5f0dd84f9bac809760c51fcccd09083 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Wed, 6 May 2026 19:43:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(php):=20=E6=B7=BB=E5=8A=A0=20std::array=20?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E6=94=AF=E6=8C=81=E5=B9=B6=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=BC=96=E8=AF=91=E5=99=A8=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 StdArrayParser trait 处理 std::array 类型解析 - 添加 std::array 类型检测和类型转换功能 - 实现 std::array 的维度检查和安全索引访问 - 添加 polyfills.php 文件提供类型定义和数组填充功能 - 更新 C++ 标准从 c++14 到 c++17 - 修复表达式解析中的类型检测逻辑 - 优化编译器的并发编译功能 - 添加数组循环性能测试示例 - 实现 std::array 的多维数组支持 --- cli.php | 1 + examples/array-loop/jit.php | 17 ++++ examples/array-loop/loop.cc | 32 ++++++ examples/array-loop/main.php | 23 +++++ examples/array-loop/std-array.php | 14 +++ project.yml | 2 +- src/Php/AstNodeType.php | 5 + src/Php/CompilerBase.php | 65 +++++++++---- src/Php/Constants.php | 2 +- src/Php/Context/FunctionContext.php | 4 + src/Php/Parser/StdArrayParser.php | 146 ++++++++++++++++++++++++++++ src/Php/Symbol.php | 5 + src/Php/Translator.php | 15 ++- src/polyfills.php | 13 +++ 14 files changed, 316 insertions(+), 28 deletions(-) create mode 100644 examples/array-loop/jit.php create mode 100644 examples/array-loop/loop.cc create mode 100644 examples/array-loop/main.php create mode 100644 examples/array-loop/std-array.php create mode 100644 src/Php/Parser/StdArrayParser.php diff --git a/cli.php b/cli.php index bf5b6081..a8460894 100755 --- a/cli.php +++ b/cli.php @@ -1,4 +1,5 @@ #!/usr/bin/env php +#include +#include +#include +#include + +int main(int argc, char* argv[]) { + std::srand(static_cast(std::time(nullptr))); + + long u = std::stoi(argv[1]); + std::cout << "u: " << u << "\n"; + + long r = std::rand() % 10001; + std::vector a(10000, 0); + + auto begin = std::chrono::high_resolution_clock::now(); + + for (int i = 0; i < 10000; i++) { + for (int j = 0; j < 100000; j++) { + a[i] += j % u; + } + a[i] += r; + } + + std::cout << a[r] << "\n"; + + auto end = std::chrono::high_resolution_clock::now(); + std::chrono::duration diff = end - begin; + std::cout << "sec: " << diff.count() << "\n"; + + return 0; +} diff --git a/examples/array-loop/main.php b/examples/array-loop/main.php new file mode 100644 index 00000000..5427effc --- /dev/null +++ b/examples/array-loop/main.php @@ -0,0 +1,23 @@ +isClangAvailable()) { // 优先使用 Clang(如果可用) $this->cppCompiler = 'clang++'; - $this->cxxStd = 'c++17'; $this->climate->info('Using Clang compiler (clang++)'); } else { // 默认使用 MSVC $this->cppCompiler = 'cl'; - $this->cxxStd = 'c++17'; $this->climate->info('Using MSVC compiler (cl)'); } } else { // Unix/Linux/macOS 使用 g++ $this->cppCompiler = 'g++'; - $this->cxxStd = 'c++14'; } } @@ -1511,7 +1511,6 @@ class CompilerBase extends \PhpAot\Core\Translator $this->fatalError($left, 'Cannot re-assign $this'); } - $expr = $this->parseExpr($right); $type = $this->detectTypeOfExpr($right); if ($this->isVarExpr($left)) { @@ -1550,14 +1549,22 @@ class CompilerBase extends \PhpAot\Core\Translator } else { $type = $type === self::TYPE_VOID ? self::TYPE_VAR : $type; } - } elseif ($this->isStaticCall($right) and $this->isIdExpr($right->name)) { + } elseif ($this->isStaticCall($right) and $this->isNameExpr($right->class) and $this->isIdExpr($right->name)) { $class = $this->parseIdentifier($right->class); if ($class === 'std') { - $valueExpr = $this->parseStdCall($right); - if (!$this->hasVar($var)) { - $this->addLocalVar($var, $right->getAttribute('nativeType')); + if ($right->name->toString() === 'array') { + if ($this->hasVar($var)) { + $this->fatalError($left, "Cannot re-assign `\${$var}` to std::array"); + } + $this->addLocalVar($var, self::TYPE_STD_ARRAY); + return $this->parseStdArray($var, $right); + } else { + $valueExpr = $this->parseStdCall($right); + if (!$this->hasVar($var)) { + $this->addLocalVar($var, $right->getAttribute('nativeType')); + } + return $var . ' = ' . $valueExpr; } - return $var . ' = ' . $valueExpr; } } elseif ($this->isVarExpr($right)) { $rightVar = $this->parseIdentifier($right); @@ -1584,7 +1591,7 @@ class CompilerBase extends \PhpAot\Core\Translator return $this->parseAssignArrayDim($left, $right); } - return $var . ' = ' . $this->convertExprType($expr, $this->detectTypeOfExpr($left), $this->detectTypeOfExpr($right)); + return $var . ' = ' . $this->convertExprType($this->parseExpr($right), $this->detectTypeOfExpr($left), $this->detectTypeOfExpr($right)); } protected function parseEcho(mixed $v): string @@ -2058,7 +2065,9 @@ class CompilerBase extends \PhpAot\Core\Translator protected function detectVarType($var): string { $name = $this->parseIdentifier($var); - + if ($this->isStdArray($name)) { + return self::TYPE_ARRAY; + } return $this->getVarType($name); } @@ -2141,6 +2150,11 @@ class CompilerBase extends \PhpAot\Core\Translator } } break; + case 'Expr_ArrayDimFetch': + if ($this->isStdArrayExpr($expr)) { + return $this->getStdArrayInfo($expr)['type']; + } + break; case 'Expr_New': return self::TYPE_OBJECT; case 'Expr_Assign': @@ -2902,6 +2916,9 @@ class CompilerBase extends \PhpAot\Core\Translator } if ($this->isArrayDimFetch($node->var)) { + if ($this->isStdArrayExpr($node->var)) { + return $this->parseStdArrayAssignOp($node, $op); + } /** * $count[$r] -= 1; * 需要转为下面语句: @@ -3044,6 +3061,10 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseArrayDimFetch(Expr\ArrayDimFetch $node, bool $write): string { + if ($this->isStdArrayExpr($node)) { + return $this->parseStdArrayDimFetch($node); + } + $var = $this->parseIdentifier($node->var); if ($this->isVarExpr($node->var)) { if ($var === 'GLOBALS') { @@ -3433,7 +3454,11 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseArg(Node\Arg $arg): string { - return $this->parseIdentifier($arg->value); + $expr = $this->parseIdentifier($arg->value); + if ($this->isVarExpr($arg->value) and $this->isStdArray($arg->value->name)) { + return $this->convertArrayExpr($expr); + } + return $expr; } protected function parseArrayArg(Node\Arg $expr): string @@ -5611,9 +5636,15 @@ class CompilerBase extends \PhpAot\Core\Translator if (isset($this->context->arguments[$name])) { continue; } - $code .= $this->getIndent() . $type . ' ' . $name; - if ($type === self::TYPE_INT or $type === self::TYPE_FLOAT or $type === self::TYPE_BOOL) { - $code .= ' = 0'; + $code .= $this->getIndent(); + if ($type === self::TYPE_STD_ARRAY) { + $info = $this->context->stdArrays[$name]; + $code .= $info['decl'] . ' ' . $name . '{}'; + } else { + $code .= $type . ' ' . $name; + if ($type === self::TYPE_INT or $type === self::TYPE_FLOAT or $type === self::TYPE_BOOL) { + $code .= ' = 0'; + } } $code .= ';' . PHP_EOL; } diff --git a/src/Php/Constants.php b/src/Php/Constants.php index 6837db0e..816f3bdf 100644 --- a/src/Php/Constants.php +++ b/src/Php/Constants.php @@ -151,7 +151,7 @@ class Constants ], 'cxx-std' => [ 'longPrefix' => 'cxx-std', - 'description' => 'C++ standard version (c++14, c++17, c++20, etc.)', + 'description' => 'C++ standard version (c++17, c++20, etc.)', 'required' => false, 'defaultValue' => 'c++17', ], diff --git a/src/Php/Context/FunctionContext.php b/src/Php/Context/FunctionContext.php index b1ec68d6..d89227c6 100644 --- a/src/Php/Context/FunctionContext.php +++ b/src/Php/Context/FunctionContext.php @@ -14,6 +14,10 @@ class FunctionContext * @var array */ public array $objects = []; + /** + * @var array + */ + public array $stdArrays = []; public array $localVars = []; public array $staticVars = []; public array $globalVars = []; diff --git a/src/Php/Parser/StdArrayParser.php b/src/Php/Parser/StdArrayParser.php new file mode 100644 index 00000000..fcd7c725 --- /dev/null +++ b/src/Php/Parser/StdArrayParser.php @@ -0,0 +1,146 @@ +hasLocalVar($var) and $this->getVarType($var) === self::TYPE_STD_ARRAY; + } + + protected function isStdArrayExpr(Expr\ArrayDimFetch $expr): bool + { + $info = $this->getStdArrayInfo($expr); + return $info !== null; + } + + protected function fillStdArray(Expr\StaticCall $expr): string + { + if (!$this->isVarExpr($expr->args[0]->value) or !$this->isStdArray($this->parseIdentifier($expr->args[0]->value))) { + $this->fatalError($expr, 'fill() only support std::array'); + } + $array = $this->parseIdentifier($expr->args[0]->value); + $valueExpr = $this->parseExpr($expr->args[1]->value); + $type = $this->context->stdArrays[$array]['type']; + $value = $this->convertExprFromType($type, $valueExpr); + return "{$array}.fill({$value})"; + } + + protected function getStdArrayInfo(Expr\ArrayDimFetch $expr): ?array + { + $tmp = $expr->var; + while (true) { + if ($this->isArrayDimFetch($tmp)) { + $tmp = $tmp->var; + } elseif ($this->isVarExpr($tmp) and $this->isStdArray($this->parseVariable($tmp))) { + return $this->context->stdArrays[$this->parseVariable($tmp)]; + } else { + return null; + } + } + } + + protected function parseStdArrayAssignOp(Expr\AssignOp $expr, string $op): string + { + $binaryOp = $this->removeAssignOp($op); + if ($binaryOp === '.') { + $this->fatalError($expr, 'Cannot concat string to std::array'); + } + + $info = $this->getStdArrayInfo($expr->var); + $arrayDimFetch = $this->parseStdArrayDimFetch($expr->var); + return $arrayDimFetch . ' ' . $binaryOp . '= ' . $this->convertExprFromType($info['type'], $this->parseExpr($expr->expr)); + } + + protected function parseStdArrayDimFetch(Expr\ArrayDimFetch $expr): string + { + $tmp = $expr; + $nesting = []; + $level = 0; + $info = $this->getStdArrayInfo($expr); + + while (true) { + if ($this->isArrayDimFetch($tmp)) { + if ($tmp->dim === null) { + $this->fatalError($tmp, 'std::array() expects an index'); + } + $size = $info['sizes'][$level]; + if ($this->isScalarInt($tmp->dim)) { + if ($tmp->dim->value < 0 || $tmp->dim->value >= $size) { + $this->fatalError($tmp, "Array index out of bounds: index {$tmp->dim->value}, size {$size}"); + } + } + $index = $this->parseExpr($tmp->dim); + $nesting[] = '[' . Symbol::safeIndex($index, $info['sizes'][$level]) . ']'; + $tmp = $tmp->var; + $level++; + } else { + $nesting[] = $this->parseVariable($tmp); + break; + } + } + + return implode('', array_reverse($nesting)); + } + + protected function parseStdArray(string $var, Expr\StaticCall $expr): string + { + $tmp = $expr; + $nesting = []; + + while(true) { + if (count($tmp->args) !== 2) { + $this->fatalError($tmp, 'std::array() expects two arguments'); + } + if (!$this->isScalarInt($tmp->args[1]->value)) { + $this->fatalError($tmp, 'std::array() expects second argument to be an integer'); + } + $size = $this->parseScalar($tmp->args[1]->value); + $nesting[] = $size; + $typeExpr = $tmp->args[0]->value; + if ($this->isClassConstFetch($typeExpr)) { + if (!$this->isNameExpr($typeExpr->class) || !$this->isIdExpr($typeExpr->name) || $typeExpr->class->toString() !== 'native_types') { + $this->fatalError($tmp, 'An incorrect `std::array` definition'); + } + switch ($typeExpr->name->name) { + case 'type_int': + $type = self::TYPE_INT; + break; + case 'type_float': + $type = self::TYPE_FLOAT; + break; + case 'type_bool': + $type = self::TYPE_BOOL; + break; + default: + $this->fatalError($tmp, 'An incorrect `std::array` definition'); + break; + } + break; + } elseif ($this->isStaticCall($typeExpr)) { + $tmp = $typeExpr; + if (!$this->isNameExpr($tmp->class) || !$this->isIdExpr($tmp->name) || $tmp->class->toString() !== 'std' || $tmp->name->toString() !== 'array') { + $this->fatalError($tmp, 'An incorrect `std::array` definition'); + } + } else { + $this->fatalError($tmp, 'std::array() expects first argument to be a class constant'); + } + } + + $decl = str_repeat('std::array<', count($nesting)); + $decl .= $type; + for ($i = count($nesting) - 1; $i >= 0; $i--) { + $decl .= ', ' . $nesting[$i] . '>'; + } + $this->context->stdArrays[$var] = [ + 'decl' => $decl, + 'type' => $type, + 'sizes' => array_reverse($nesting), + ]; + return ''; + } +} \ No newline at end of file diff --git a/src/Php/Symbol.php b/src/Php/Symbol.php index f033520d..5617e6f6 100644 --- a/src/Php/Symbol.php +++ b/src/Php/Symbol.php @@ -54,4 +54,9 @@ class Symbol { return 'php::ArgList'; } + + public static function safeIndex(string $index, string $size): string + { + return "php::safeIndex($index, $size)"; + } } diff --git a/src/Php/Translator.php b/src/Php/Translator.php index 3ffb7ab2..904f93f3 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -91,7 +91,7 @@ class Translator extends Preprocessor $climate->tab()->out('-O Optimization level (0-3, default: 0)'); $climate->tab()->out('-p, --profile Enable performance profiling'); $climate->tab()->out('-d, --debug-info Enable debug info (auto-disable optimizations, add -g/-Zi)'); - $climate->tab()->out('--cxx-std C++ standard version (c++14, c++17, c++20, etc.)'); + $climate->tab()->out('--cxx-std C++ standard version (c++17, c++20, etc.)'); $climate->tab()->out('-o, --output Output binary name (default: input basename)'); $climate->tab()->out('-v, --version Show version'); $climate->tab()->out('-h, --help Show this help message'); @@ -812,24 +812,21 @@ CODE; } // Windows 不支持 pcntl_fork,使用串行编译或 proc_open - if ($this->isWindows()) { - return $this->compileOnWindows($sourceFiles); + if ($this->isWindows() or $job <= 1) { + return $this->compileSourceFile($sourceFiles); } // Unix/Linux/macOS 使用 pcntl 并行编译 return $this->compileWithPcntl($sourceFiles, $job); } - /** - * Windows 平台编译(不使用 pcntl) - */ - protected function compileOnWindows(array $sourceFiles): array + protected function compileSourceFile(array $sourceFiles): array { $objectFiles = []; $totalFiles = count($sourceFiles); $failedFiles = []; - $this->climate->lightBlue("Starting compilation for {$totalFiles} files (Windows mode)"); + $this->climate->lightBlue("Starting compilation for {$totalFiles} files"); foreach ($sourceFiles as $cppFile) { $objectFile = $this->getObjectFile($cppFile); @@ -864,7 +861,7 @@ CODE; // 检查 pcntl 扩展是否可用 if (!function_exists('pcntl_fork')) { $this->climate->warning('pcntl extension not available, using sequential compilation'); - return $this->compileOnWindows($sourceFiles); + return $this->compileSourceFile($sourceFiles); } $objectFiles = []; diff --git a/src/polyfills.php b/src/polyfills.php index 5b72e83b..45ccbcd2 100644 --- a/src/polyfills.php +++ b/src/polyfills.php @@ -11,6 +11,7 @@ class native_types public const type_int = 'int'; public const type_float = 'float'; public const type_bool = 'bool'; + public const type_any = 'php::Var'; } class std @@ -29,6 +30,18 @@ class std { return boolval($value); } + + public static function array(mixed $type, int $size): array + { + return []; + } + + public static function fill(array $array, mixed $value): void + { + for ($i = 0; $i < count($array); $i++) { + $array[$i] = $value; + } + } } function objval(mixed $obj, string $class): object