diff --git a/composer.json b/composer.json index 377cdbef..e5ede0a3 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,8 @@ "require": { "nikic/php-parser": "5.6.1", "league/climate": "^3.10", - "marcj/topsort": "^2.0" + "marcj/topsort": "^2.0", + "symfony/var-dumper": "^8.0" }, "require-dev": { "phpunit/phpunit": "^10.4", diff --git a/composer.lock b/composer.lock index f3291ecc..8ffa02a5 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d4e35915568beccdcd533360bf30a206", + "content-hash": "4982ed71f89f83b090b223c2a581f719", "packages": [ { "name": "league/climate", @@ -296,6 +296,178 @@ "source": "https://github.com/Seldaek/cli-prompt/tree/1.0.4" }, "time": "2020-12-15T21:32:01+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v8.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "326e0406fc315eca57ef5740fa4a280b7a068c82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/326e0406fc315eca57ef5740fa4a280b7a068c82", + "reference": "326e0406fc315eca57ef5740fa4a280b7a068c82", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "symfony/console": "<7.4", + "symfony/error-handler": "<7.4" + }, + "require-dev": { + "symfony/console": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", + "twig/twig": "^3.12" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v8.0.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-01T23:07:29+00:00" } ], "packages-dev": [ @@ -3764,91 +3936,6 @@ ], "time": "2024-09-09T11:45:10+00:00" }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.33.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", - "shasum": "" - }, - "require": { - "ext-iconv": "*", - "php": ">=7.2" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-12-23T08:48:59+00:00" - }, { "name": "symfony/polyfill-php80", "version": "v1.33.0", diff --git a/examples/to_array.php b/examples/to_array.php new file mode 100644 index 00000000..39ca4ad2 --- /dev/null +++ b/examples/to_array.php @@ -0,0 +1,5 @@ +getType(); $this->writeLog('Line ' . $this->getLine($expr) . ': ' . $type); if ($expr->getLine() === $this->debugLine) { - var_dump($expr); + dump($expr); } switch ($type) { case 'Expr_Isset': @@ -571,14 +577,10 @@ class CompilerBase extends \PhpAot\Core\Translator case 'Expr_Empty': return $this->parseEmpty($expr); case 'Expr_Assign': - $this->inAssignExpr = true; $result = $this->parseAssign($expr); - $this->inAssignExpr = false; return $result; case 'Expr_AssignRef': - $this->inAssignExpr = true; $result = $this->parseAssignRef($expr); - $this->inAssignExpr = false; return $result; case 'Expr_Print': return $this->parsePrint($expr); @@ -653,7 +655,7 @@ class CompilerBase extends \PhpAot\Core\Translator case self::EXPR_ARRAY_DIM_FETCH: return $this->parseArrayDimFetch($expr); case 'Expr_PropertyFetch': - return $this->parsePropertyFetch($expr); + return $this->parsePropertyFetch($expr, $this->inAssignExpr); case 'Expr_BinaryOp_ShiftLeft': return $this->parseBinaryOpShiftLeft($expr); case 'Expr_BinaryOp_ShiftRight': @@ -760,7 +762,10 @@ class CompilerBase extends \PhpAot\Core\Translator if ($this->isPropertyFetch($left)) { return $this->parseAssignPropertyArrayDim($left, $right); } + $oriInAssignExpr = $this->inAssignExpr; + $this->inAssignExpr = true; $array = $this->parseIdentifier($left->var); + $this->inAssignExpr = $oriInAssignExpr; $code = ''; // 这是 PHP 的初始化+赋值写法,需要先创建数组 if (!$this->hasVar($array) and $this->isVarExpr($left->var)) { @@ -846,7 +851,9 @@ class CompilerBase extends \PhpAot\Core\Translator $this->indentLevel--; return $code . '}'; } + $this->inAssignExpr = true; $var = $this->parseIdentifier($left); + $this->inAssignExpr = false; if ($var === 'this_') { $this->fatalError($left, 'Cannot re-assign $this'); } @@ -871,6 +878,8 @@ class CompilerBase extends \PhpAot\Core\Translator $this->addLocalVar($var, $type); } return $var . ' = ' . $this->parseIdentifier($right->args[0]->value); + } else { + $type = $type === self::TYPE_VOID ? self::TYPE_VAR : $type; } } @@ -985,7 +994,19 @@ class CompilerBase extends \PhpAot\Core\Translator if ($this->getReturnType() === 'void') { $this->resetReturnType($type); } - return 'return ' . $this->convertExprType($expr, $this->getReturnType(), $type); + $exprCode = $this->convertExprType($expr, $this->getReturnType(), $type); + // return 如果使用了 Indirect 语句,可能会导致变量提前析构,出现悬空指针 + // 将 Indirect 赋值给临时变量后,使用 Ctor::Copy 解除了 Indirect,保证内存安全 + if (!$this->isVarExpr($v->expr)) { + $tmpVar = $this->genTmpVarName(); + // 必须提前声明变量,否则在末尾声明并 return 可能会被 gcc 优化掉 + $this->addLocalVar($tmpVar, $type); + $code = $tmpVar . ' = ' . $exprCode . ';' . PHP_EOL; + $code .= $this->getIndent() . 'return ' . $tmpVar; + } else { + $code = 'return ' . $exprCode; + } + return $code; } protected function parseBinaryOpMul(mixed $expr): string @@ -1037,9 +1058,6 @@ class CompilerBase extends \PhpAot\Core\Translator protected function detectExprType($expr): string { $exprType = $expr->getType(); - if ($this->debugLine === $expr->getLine()) { - var_dump($exprType); - } switch ($exprType) { case 'Expr_Cast_Int': case 'Scalar_Int': @@ -2461,24 +2479,26 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseAssignRef(Node\Expr\AssignRef $expr): string { if ($this->isVarExpr($expr->var)) { - $var = $this->parseIdentifier($expr->var); - if (!$this->hasVar($var)) { - $this->addLocalVar($var, self::TYPE_REF); + $this->inAssignExpr = true; + $left = $this->parseIdentifier($expr->var); + $this->inAssignExpr = false; + if (!$this->hasVar($left)) { + $this->addLocalVar($left, self::TYPE_REF); } else { - $type = $this->getVarType($var); + $type = $this->getVarType($left); if ($type !== self::TYPE_REF) { $this->fatalError($expr, 'Cannot assign reference to variable of type ' . $type); } } if ($this->isVarExpr($expr->expr)) { - return $var . ' = ' . $this->parseIdentifier($expr->expr) . '.toReference()'; + return $left . ' = ' . $this->parseIdentifier($expr->expr) . '.toReference()'; } elseif ($expr->expr->getType() === self::EXPR_ARRAY_DIM_FETCH) { - return $var . ' = ' . $this->parseIdentifier($expr->expr); + return $left . ' = ' . $this->parseIdentifier($expr->expr); } elseif ($this->isPropertyFetch($expr->expr)) { - $var = $this->parseIdentifier($expr->var); + $left = $this->parseIdentifier($expr->var); $object = $this->convertToObject($expr->expr->var); $prop = $this->identifierToStr($expr->expr->name); - return $var . ' = ' . $object . '.getPropertyReference(' . $prop . ')'; + return $left . ' = ' . $object . '.getPropertyReference(' . $prop . ')'; } } abort($expr); diff --git a/src/Php/Translator.php b/src/Php/Translator.php index fbcff471..1f6911d7 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -29,58 +29,13 @@ class Translator extends Preprocessor public function __construct(string $rootPath) { parent::__construct($rootPath); - $this->climate->arguments->add([ - 'optimize' => [ - 'prefix' => 'O', - 'longPrefix' => 'optimize', - 'description' => 'Set the optimization level of the gcc compiler to 0 by default', - 'required' => false, - 'castTo' => 'int', - 'defaultValue' => 0, - ], - 'output' => [ - 'prefix' => 'o', - 'longPrefix' => 'output', - 'description' => 'Output file', - ], - 'help' => [ - 'prefix' => 'h', - 'longPrefix' => 'help', - 'description' => 'Show help', - 'noValue' => true, - ], - 'profile' => [ - 'longPrefix' => 'profile', - 'description' => 'Enable performance profiling', - 'required' => false, - 'noValue' => true, - ], - 'noLiteralStrings' => [ - 'longPrefix' => 'no-literal-strings', - 'description' => 'Disable literal strings optimization', - 'required' => false, - 'noValue' => true, - ], - 'force' => [ - 'prefix' => 'f', - 'longPrefix' => 'force', - 'description' => 'Force compile even if cache exists', - 'required' => false, - 'noValue' => true, - ], - 'mode' => [ - 'longPrefix' => 'mode', - 'prefix' => 'm', - 'description' => 'Build mode, -m bin(binary) or -m ext(extension), default: bin', - 'required' => false, - 'defaultValue' => 'bin', - ], - ]); - + $this->climate->arguments->add(require __DIR__ . '/../config/compiler_options.php'); $this->preprocessArgvAdvanced(); $this->climate->arguments->parse(); + $this->optimizeLevel = $this->climate->arguments->get('optimize'); $this->buildMode = $this->climate->arguments->get('mode'); + $this->debugLine = intval($this->climate->arguments->get('debug-line')); // $this->noLiteralStrings = $this->climate->arguments->get('noLiteralStrings'); $this->noLiteralStrings = true; $this->enableProfiler = $this->climate->arguments->defined('profile'); diff --git a/src/config/compiler_options.php b/src/config/compiler_options.php new file mode 100644 index 00000000..267da990 --- /dev/null +++ b/src/config/compiler_options.php @@ -0,0 +1,54 @@ + [ + 'prefix' => 'O', + 'longPrefix' => 'optimize', + 'description' => 'Set the optimization level of the gcc compiler to 0 by default', + 'required' => false, + 'castTo' => 'int', + 'defaultValue' => 0, + ], + 'output' => [ + 'prefix' => 'o', + 'longPrefix' => 'output', + 'description' => 'Output file', + ], + 'help' => [ + 'prefix' => 'h', + 'longPrefix' => 'help', + 'description' => 'Show help', + 'noValue' => true, + ], + 'profile' => [ + 'longPrefix' => 'profile', + 'description' => 'Enable performance profiling', + 'required' => false, + 'noValue' => true, + ], + 'noLiteralStrings' => [ + 'longPrefix' => 'no-literal-strings', + 'description' => 'Disable literal strings optimization', + 'required' => false, + 'noValue' => true, + ], + 'force' => [ + 'prefix' => 'f', + 'longPrefix' => 'force', + 'description' => 'Force compile even if cache exists', + 'required' => false, + 'noValue' => true, + ], + 'mode' => [ + 'longPrefix' => 'mode', + 'prefix' => 'm', + 'description' => 'Build mode, -m bin(binary) or -m ext(extension), default: bin', + 'required' => false, + 'defaultValue' => 'bin', + ], + 'debug-line' => [ + 'longPrefix' => 'debug-line', + 'description' => 'Enable debug line', + 'required' => false, + 'defaultValue' => 0, + ], +]; \ No newline at end of file