feat(compiler): 优化函数调用编译器实现支持可变参数类型转换

- 添加 array_merge 混合类型参数测试用例
- 在 CompilerBase 中为表达式添加括号包装确保正确解析
- 将 FuncCallOptimizer 中的所有私有方法和常量改为受保护访问级别
- 实现可变参数类型的类型感知转换功能
- 添加 variadicType 配置选项用于指定可变参数类型
- 扩展参数反射信息获取逻辑包含可变参数类型信息
- 更新生成可变参数调用方法支持类型转换
- 添加基础容器数组表达式转换辅助方法
- 修改返回赋值属性测试用例验证功能完整性
pull/1/head
韩天峰 2 months ago
parent 26bdf5eb59
commit 246689db4c
  1. 2
      src/Php/CompilerBase.php
  2. 138
      src/Php/Optimizer/FuncCallOptimizer.php
  3. 37
      tests/aot/basic/return-assign-set-prop.phpt
  4. 24
      tests/aot/stdlib/array_merge.phpt

@ -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 {

@ -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<string,string|array>|null */
private ?array $_funcCallConfig = null;
protected ?array $_funcCallConfig = null;
/** @var array<string,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 = [];

@ -0,0 +1,37 @@
--TEST--
return assign array dim
--SKIPIF--
--FILE--
<?php
class TestReturnAssignSetProp
{
protected ?array $array = null;
protected function makeArray(): array {
return [1, 3, 4];
}
public function test()
{
if ($this->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)
}

@ -0,0 +1,24 @@
--TEST--
array_merge with mixed typed arguments
--FILE--
<?php
class TestArrayMerge {
public array $arr1 = ['foo'];
public array $arr2 = ['baz'];
}
function main() {
$o = new TestArrayMerge;
$v = any(['bar']);
$array = array_merge($o->arr1, $v, $o->arr2);
var_dump($array);
}
?>
--EXPECT--
array(3) {
[0]=>
string(3) "foo"
[1]=>
string(3) "bar"
[2]=>
string(3) "baz"
}
Loading…
Cancel
Save