diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 38e798bc..a20bd059 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -1553,7 +1553,7 @@ class CompilerBase extends \PhpAot\Core\Translator $tmpVar = $this->genTmpVarName(); // 必须提前声明变量,否则在末尾声明并 return 可能会被 gcc 优化掉 $this->addLocalVar($tmpVar, $returnType); - $code = $tmpVar . ' = ' . $exprCode . ';' . PHP_EOL; + $code = $tmpVar . ' = (' . $exprCode . ');' . PHP_EOL; // 解析表达式后可能会插入语句,因此需要在末尾添加 return 语句,而不是直接返回 $this->context->afterStmtLines[] = $this->getIndent() . 'return ' . $tmpVar . ';'; } else { diff --git a/src/Php/Optimizer/FuncCallOptimizer.php b/src/Php/Optimizer/FuncCallOptimizer.php index 5c941762..998a725b 100644 --- a/src/Php/Optimizer/FuncCallOptimizer.php +++ b/src/Php/Optimizer/FuncCallOptimizer.php @@ -18,35 +18,35 @@ use PhpParser\Node; */ trait FuncCallOptimizer { - private const string ARG_TYPE_VAR = 'v'; - private const string ARG_TYPE_STR = 's'; - private const string ARG_TYPE_INT = 'i'; - private const string ARG_TYPE_FLOAT = 'f'; - private const string ARG_TYPE_BOOL = 'b'; - private const string ARG_TYPE_REF = 'R'; - private const string ARG_TYPE_ARRAY = 'A'; - private const string ARG_OPTIONAL = '?'; - - private const int FOLD_STRING_LEN = 1; - private const int FOLD_STRING_CASE = 2; - private const int FOLD_CMP2 = 3; - private const int FOLD_CMP3 = 4; - private const int FOLD_COUNT_LITERAL = 5; - private const int FOLD_KNOWN_CLASS = 6; - private const int FOLD_KNOWN_CONSTANT = 7; - private const int FOLD_SSA_TYPE = 8; + protected const string ARG_TYPE_VAR = 'v'; + protected const string ARG_TYPE_STR = 's'; + protected const string ARG_TYPE_INT = 'i'; + protected const string ARG_TYPE_FLOAT = 'f'; + protected const string ARG_TYPE_BOOL = 'b'; + protected const string ARG_TYPE_REF = 'R'; + protected const string ARG_TYPE_ARRAY = 'A'; + protected const string ARG_OPTIONAL = '?'; + + protected const int FOLD_STRING_LEN = 1; + protected const int FOLD_STRING_CASE = 2; + protected const int FOLD_CMP2 = 3; + protected const int FOLD_CMP3 = 4; + protected const int FOLD_COUNT_LITERAL = 5; + protected const int FOLD_KNOWN_CLASS = 6; + protected const int FOLD_KNOWN_CONSTANT = 7; + protected const int FOLD_SSA_TYPE = 8; /** @var array|null */ - private ?array $_funcCallConfig = null; + protected ?array $_funcCallConfig = null; /** @var array Cache for auto-detected arg reflection info */ - private array $_autoArgTypes = []; + protected array $_autoArgTypes = []; // ========================================================================= // Config // ========================================================================= - private function getFuncCallConfig(): array + protected function getFuncCallConfig(): array { if ($this->_funcCallConfig !== null) { return $this->_funcCallConfig; @@ -54,7 +54,7 @@ trait FuncCallOptimizer return $this->_funcCallConfig = $this->buildFuncCallConfig(); } - private function buildFuncCallConfig(): array + protected function buildFuncCallConfig(): array { $simple = [ 'method_exists', 'property_exists', @@ -224,7 +224,7 @@ trait FuncCallOptimizer // Generic dispatcher // ========================================================================= - private function dispatchFuncCall(string $name, Node\Expr\FuncCall $expr, array $config): string|false + protected function dispatchFuncCall(string $name, Node\Expr\FuncCall $expr, array $config): string|false { $target = $config['target'] ?? null; if ($target === null) { @@ -238,7 +238,8 @@ trait FuncCallOptimizer $defaults = $config['defaults'] ?? []; if (!empty($config['variadic']) || ($refInfo['variadic'] ?? false)) { - return $this->genVariadicCall($target, $expr); + $variadicType = $config['variadicType'] ?? $refInfo['variadicType'] ?? ''; + return $this->genVariadicCall($target, $expr, $variadicType); } if (isset($config['constFold'])) { @@ -256,7 +257,7 @@ trait FuncCallOptimizer // Auto-detect argument types from PHP reflection // ========================================================================= - private function getArgReflectionInfo(string $funcName): array + protected function getArgReflectionInfo(string $funcName): array { if (isset($this->_autoArgTypes[$funcName])) { return $this->_autoArgTypes[$funcName]; @@ -269,9 +270,11 @@ trait FuncCallOptimizer $types = []; $variadic = false; + $variadicType = ''; foreach ($ref->getParameters() as $param) { if ($param->isVariadic()) { $variadic = true; + $variadicType = $this->phpParamToArgChar($param); continue; } $char = $this->phpParamToArgChar($param); @@ -281,10 +284,10 @@ trait FuncCallOptimizer $types[] = $char; } - return $this->_autoArgTypes[$funcName] = ['args' => implode('_', $types), 'variadic' => $variadic]; + return $this->_autoArgTypes[$funcName] = ['args' => implode('_', $types), 'variadic' => $variadic, 'variadicType' => $variadicType]; } - private function phpParamToArgChar(\ReflectionParameter $param): string + protected function phpParamToArgChar(\ReflectionParameter $param): string { if ($param->isPassedByReference()) { return self::ARG_TYPE_REF; @@ -307,12 +310,12 @@ trait FuncCallOptimizer // Arg helpers // ========================================================================= - private function getArg(Node\Expr\FuncCall $expr, int $i): string + protected function getArg(Node\Expr\FuncCall $expr, int $i): string { return $this->parseIdentifier($expr->args[$i]->value); } - private function getRefArg(Node\Expr\FuncCall $expr, int $i): string + protected function getRefArg(Node\Expr\FuncCall $expr, int $i): string { $arg = $expr->args[$i]->value; if ($this->isArrayDimFetch($arg) and $this->isVarExpr($arg->var)) { @@ -332,7 +335,7 @@ trait FuncCallOptimizer return $this->getArg($expr, $i); } - private function resolveArg(Node\Expr\FuncCall $expr, int $index, string $type): string + protected function resolveArg(Node\Expr\FuncCall $expr, int $index, string $type): string { $base = ($type[0] ?? '') === self::ARG_OPTIONAL ? substr($type, 1) : $type; @@ -348,7 +351,7 @@ trait FuncCallOptimizer }; } - private function convertStdContainerArrayExpr(Node\Expr\FuncCall $expr, int $index, string $raw): string + protected function convertStdContainerArrayExpr(Node\Expr\FuncCall $expr, int $index, string $raw): string { $arg = $expr->args[$index]->value; if ($this->isVarExpr($arg) and $this->isStdContainer($arg->name)) { @@ -357,7 +360,7 @@ trait FuncCallOptimizer return $this->convertArrayExpr($raw); } - private function buildArgList(Node\Expr\FuncCall $expr, string $argTypeStr, array $defaults = []): array + protected function buildArgList(Node\Expr\FuncCall $expr, string $argTypeStr, array $defaults = []): array { if ($argTypeStr === '') { return []; @@ -385,16 +388,25 @@ trait FuncCallOptimizer // Variadic, conversion, Big* dispatch // ========================================================================= - private function genVariadicCall(string $target, Node\Expr\FuncCall $expr): string + protected function genVariadicCall(string $target, Node\Expr\FuncCall $expr, string $variadicType = ''): string { + $base = ($variadicType !== '' && ($variadicType[0] ?? '') === self::ARG_OPTIONAL) ? substr($variadicType, 1) : $variadicType; $args = []; - foreach ($expr->args as $arg) { - $args[] = $this->parseExpr($arg->value); + foreach ($expr->args as $index => $arg) { + $raw = $this->parseExpr($arg->value); + $args[] = match ($base) { + self::ARG_TYPE_STR => $this->convertStringExpr($raw), + self::ARG_TYPE_INT => $this->convertIntExpr($raw), + self::ARG_TYPE_FLOAT => $this->convertFloatExpr($raw), + self::ARG_TYPE_BOOL => $this->convertBoolExpr($raw), + self::ARG_TYPE_ARRAY => $this->convertStdContainerArrayExpr($expr, $index, $raw), + default => $raw, + }; } return $target . '(' . implode(', ', $args) . ')'; } - private function dispatchConversion(Node\Expr\FuncCall $expr, string $convType): string + protected function dispatchConversion(Node\Expr\FuncCall $expr, string $convType): string { $arg = $expr->args[0]->value; $type = $this->detectTypeOfExpr($arg); @@ -417,7 +429,7 @@ trait FuncCallOptimizer }; } - private function dispatchBigType(Node\Expr\FuncCall $expr, array $dispatch): string|false + protected function dispatchBigType(Node\Expr\FuncCall $expr, array $dispatch): string|false { $type = $this->detectTypeOfExpr($expr->args[0]->value); $target = $dispatch[$type] ?? $dispatch['fallback'] ?? null; @@ -437,7 +449,7 @@ trait FuncCallOptimizer // Constant folding // ========================================================================= - private function tryConstFold(int $rule, mixed $extra, Node\Expr\FuncCall $expr): string|false + protected function tryConstFold(int $rule, mixed $extra, Node\Expr\FuncCall $expr): string|false { return match ($rule) { self::FOLD_STRING_LEN => $this->doFoldStringLen($expr), @@ -452,7 +464,7 @@ trait FuncCallOptimizer }; } - private function doFoldStringLen(Node\Expr\FuncCall $expr): string|false + protected function doFoldStringLen(Node\Expr\FuncCall $expr): string|false { $arg = $expr->args[0]->value; return ($arg instanceof Node\Scalar\String_) @@ -460,7 +472,7 @@ trait FuncCallOptimizer : false; } - private function doFoldStringCase(Node\Expr\FuncCall $expr): string|false + protected function doFoldStringCase(Node\Expr\FuncCall $expr): string|false { $arg = $expr->args[0]->value; if (!$this->isScalarString($arg)) { @@ -471,7 +483,7 @@ trait FuncCallOptimizer return $this->getLiteralString($val); } - private function doFoldCmp2(Node\Expr\FuncCall $expr): string|false + protected function doFoldCmp2(Node\Expr\FuncCall $expr): string|false { $a0 = $expr->args[0]->value; $a1 = $expr->args[1]->value; @@ -485,7 +497,7 @@ trait FuncCallOptimizer return $result . $this->getPlatform()->getIntegerLiteralSuffix(); } - private function doFoldCmp3(Node\Expr\FuncCall $expr): string|false + protected function doFoldCmp3(Node\Expr\FuncCall $expr): string|false { $a0 = $expr->args[0]->value; $a1 = $expr->args[1]->value; @@ -500,7 +512,7 @@ trait FuncCallOptimizer return $result . $this->getPlatform()->getIntegerLiteralSuffix(); } - private function doFoldCountLiteral(Node\Expr\FuncCall $expr): string|false + protected function doFoldCountLiteral(Node\Expr\FuncCall $expr): string|false { if (count($expr->args) !== 1 || !($expr->args[0] instanceof Node\Arg)) { return false; @@ -512,19 +524,19 @@ trait FuncCallOptimizer return $this->genStdContainerCount($arg); } - private function doFoldKnownClass(Node\Expr\FuncCall $expr): string|false + protected function doFoldKnownClass(Node\Expr\FuncCall $expr): string|false { $cn = $expr->args[0]->value; return ($this->isScalarString($cn) && $this->hasClass($cn->value)) ? 'true' : false; } - private function doFoldKnownConstant(Node\Expr\FuncCall $expr): string|false + protected function doFoldKnownConstant(Node\Expr\FuncCall $expr): string|false { $cn = $expr->args[0]->value; return ($this->isScalarString($cn) && $this->hasConstant($cn->value)) ? 'true' : false; } - private function doFoldSsaType(Node\Expr\FuncCall $expr, mixed $expectType): string|false + protected function doFoldSsaType(Node\Expr\FuncCall $expr, mixed $expectType): string|false { if (count($expr->args) !== 1 || !($expr->args[0] instanceof Node\Arg)) { return false; @@ -536,12 +548,12 @@ trait FuncCallOptimizer // Custom handlers // ========================================================================= - private function genIsNull(string $n, Node\Expr\FuncCall $e, array $c): string + protected function genIsNull(string $n, Node\Expr\FuncCall $e, array $c): string { return $this->parseIdentifier($e->args[0]->value) . '.isNull()'; } - private function genIsCallable(string $n, Node\Expr\FuncCall $e, array $c): string|false + protected function genIsCallable(string $n, Node\Expr\FuncCall $e, array $c): string|false { if (count($e->args) >= 3) { return false; @@ -549,7 +561,7 @@ trait FuncCallOptimizer return $this->dispatchFuncCall('is_callable', $e, ['target' => 'php::std::is_callable']); } - private function genGetClassOptimized(string $n, Node\Expr\FuncCall $e, array $c): string + protected function genGetClassOptimized(string $n, Node\Expr\FuncCall $e, array $c): string { $obj = $e->args[0]->value; if ($this->isVarExpr($obj) && $this->isTypedObject($obj->name)) { @@ -558,7 +570,7 @@ trait FuncCallOptimizer return 'php::std::get_class(' . $this->parseIdentifier($obj) . ')'; } - private function genGetParentClass(string $n, Node\Expr\FuncCall $e, array $c): string + protected function genGetParentClass(string $n, Node\Expr\FuncCall $e, array $c): string { if (count($e->args) === 0) { if ($this->classDef && $this->classDef->extends) { @@ -575,32 +587,32 @@ trait FuncCallOptimizer return 'php::std::get_parent_class(' . $this->parseIdentifier($arg) . ')'; } - private function genFunctionExistsOptimized(string $n, Node\Expr\FuncCall $e, array $c): string + protected function genFunctionExistsOptimized(string $n, Node\Expr\FuncCall $e, array $c): string { return $this->genFunctionExists($n, $e); } - private function genFuncGetArgOptimized(string $n, Node\Expr\FuncCall $e, array $c): string + protected function genFuncGetArgOptimized(string $n, Node\Expr\FuncCall $e, array $c): string { return $this->genFuncGetArg($n, $e); } - private function genFuncGetArgsOptimized(string $n, Node\Expr\FuncCall $e, array $c): string + protected function genFuncGetArgsOptimized(string $n, Node\Expr\FuncCall $e, array $c): string { return $this->genFuncGetArgs($n, $e); } - private function genFuncNumArgsOptimized(string $n, Node\Expr\FuncCall $e, array $c): string + protected function genFuncNumArgsOptimized(string $n, Node\Expr\FuncCall $e, array $c): string { return $this->genFuncNumArgs($n, $e); } - private function genCompactOptimized(string $n, Node\Expr\FuncCall $e, array $c): string + protected function genCompactOptimized(string $n, Node\Expr\FuncCall $e, array $c): string { return $this->genCompactOrig($e); } - private function genArrayKeys(string $n, Node\Expr\FuncCall $e, array $c): string + protected function genArrayKeys(string $n, Node\Expr\FuncCall $e, array $c): string { $cnt = count($e->args); if ($cnt >= 3) { @@ -612,12 +624,12 @@ trait FuncCallOptimizer return 'php::std::array_keys(' . $this->getArg($e, 0) . ')'; } - private function genArrayKeyExists(string $n, Node\Expr\FuncCall $e, array $c): string + protected function genArrayKeyExists(string $n, Node\Expr\FuncCall $e, array $c): string { return $this->getArg($e, 1) . '.offsetExists(' . $this->getArg($e, 0) . ')'; } - private function genRound(string $n, Node\Expr\FuncCall $e, array $c): string + protected function genRound(string $n, Node\Expr\FuncCall $e, array $c): string { $type = $this->detectTypeOfExpr($e->args[0]->value); if ($type === self::TYPE_DECIMAL) { @@ -637,7 +649,7 @@ trait FuncCallOptimizer return 'php::std::round(' . $this->getArg($e, 0) . ')'; } - private function genCount(string $n, Node\Expr\FuncCall $e, array $c): string + protected function genCount(string $n, Node\Expr\FuncCall $e, array $c): string { $folded = $this->doFoldCountLiteral($e); if ($folded !== false) return $folded; @@ -647,7 +659,7 @@ trait FuncCallOptimizer return 'php::std::count(' . $this->getArg($e, 0) . ')'; } - private function genDefine(string $n, Node\Expr\FuncCall $e, array $c): string|false + protected function genDefine(string $n, Node\Expr\FuncCall $e, array $c): string|false { $arg = $e->args[0]->value; if ($this->isScalarString($arg) && !$this->isValidDefineName($arg->value)) { @@ -664,7 +676,7 @@ trait FuncCallOptimizer // Legacy helpers // ========================================================================= - private function genFuncGetArgs(string $name, Node\Expr\FuncCall $expr): string + protected function genFuncGetArgs(string $name, Node\Expr\FuncCall $expr): string { $this->warningUndefinedBehavior($expr); $funcDef = $this->functionDef; @@ -702,7 +714,7 @@ trait FuncCallOptimizer } } - private function genFuncNumArgs(string $name, Node\Expr\FuncCall $expr): string + protected function genFuncNumArgs(string $name, Node\Expr\FuncCall $expr): string { $this->warningUndefinedBehavior($expr); $funcDef = $this->functionDef; @@ -728,7 +740,7 @@ trait FuncCallOptimizer return 'php::std::function_exists(' . $this->parseIdentifier($funcName) . ')'; } - private function genGetClass(Node\Expr\FuncCall $expr): string + protected function genGetClass(Node\Expr\FuncCall $expr): string { $object = $expr->args[0]->value; if ($this->isVarExpr($object) and $this->isTypedObject($object->name)) { @@ -737,7 +749,7 @@ trait FuncCallOptimizer return 'php::std::get_class(' . $this->parseIdentifier($object) . ')'; } - private function genCompactOrig(Node\Expr\FuncCall $expr): string + protected function genCompactOrig(Node\Expr\FuncCall $expr): string { $list = []; diff --git a/tests/aot/basic/return-assign-set-prop.phpt b/tests/aot/basic/return-assign-set-prop.phpt new file mode 100644 index 00000000..5341ec82 --- /dev/null +++ b/tests/aot/basic/return-assign-set-prop.phpt @@ -0,0 +1,37 @@ +--TEST-- +return assign array dim +--SKIPIF-- +--FILE-- +array !== null) { + return $this->array; + } + return $this->array = $this->makeArray(); + } +} + +function main() +{ + $obj = new TestReturnAssignSetProp; + var_dump($obj->test()); +} +?> +--EXPECT-- +array(3) { + [0]=> + int(1) + [1]=> + int(3) + [2]=> + int(4) +} \ No newline at end of file diff --git a/tests/aot/stdlib/array_merge.phpt b/tests/aot/stdlib/array_merge.phpt new file mode 100644 index 00000000..d238805e --- /dev/null +++ b/tests/aot/stdlib/array_merge.phpt @@ -0,0 +1,24 @@ +--TEST-- +array_merge with mixed typed arguments +--FILE-- +arr1, $v, $o->arr2); + var_dump($array); +} +?> +--EXPECT-- +array(3) { + [0]=> + string(3) "foo" + [1]=> + string(3) "bar" + [2]=> + string(3) "baz" +}