feat(compiler): 实现方法调用去虚化优化和BigFloat sqrt支持

- 添加classSubClasses属性用于存储反向类继承关系
- 扩展abs函数支持到BigFloat类型并添加sqrt函数实现
- 允许Big类型与原生类型之间的隐式转换
- 实现方法调用去虚化逻辑,支持final类、final方法、私有方法等情况
- 添加SSA稳定对象类型推断功能
- 修复SSABuilder中空参数值的处理
- 优化SSA类型分析中的非可缩小类型处理
- 添加多个
pull/1/head
韩天峰 3 months ago
parent a4a869b7cd
commit b640fef73b
  1. 4
      src/Php/Analysis/SsaBuilder.php
  2. 98
      src/Php/CompilerBase.php
  3. 3
      src/Php/Optimizer/FuncCallOptimizer.php
  4. 36
      src/Php/Optimizer/SsaTypeOptimizer.php
  5. 1
      src/Php/Preprocessor.php
  6. 1
      src/Php/UniversalMethodCall.php
  7. 31
      tests/aot/devirtualize/final-class.phpt
  8. 35
      tests/aot/devirtualize/final-method.phpt
  9. 35
      tests/aot/devirtualize/override-dynamic.phpt
  10. 33
      tests/aot/devirtualize/private-method.phpt

@ -1466,7 +1466,9 @@ class SsaBuilder
if ($node instanceof Expr\FuncCall || $node instanceof Expr\MethodCall
|| $node instanceof Expr\StaticCall || $node instanceof Expr\NullsafeMethodCall) {
foreach ($node->args as $arg) {
$this->collectVarUses($arg->value, $vars, false);
if (isset($arg->value)) {
$this->collectVarUses($arg->value, $vars, false);
}
}
if ($node instanceof Expr\MethodCall || $node instanceof Expr\NullsafeMethodCall) {
$this->collectVarUses($node->var, $vars, false);

@ -361,6 +361,12 @@ class CompilerBase extends \PhpAot\Core\Translator
*/
protected array $classExtends = [];
/**
* Reverse class hierarchy: parent class (lowercase) => list of child classes (lowercase)
* @var array<string, string[]>
*/
protected array $classSubClasses = [];
public function __construct(string $rootPath)
{
parent::__construct();
@ -2051,7 +2057,10 @@ class CompilerBase extends \PhpAot\Core\Translator
) {
return self::TYPE_DECIMAL;
}
if ($argType === self::TYPE_BIGFLOAT && $name === 'abs') {
if (
$argType === self::TYPE_BIGFLOAT
&& in_array($name, ['abs', 'sqrt'], true)
) {
return self::TYPE_BIGFLOAT;
}
}
@ -5016,6 +5025,11 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($this->isNativeType($toType) and $this->isNativeType($fromType)) {
return true;
}
// BigInt/BigFloat/Decimal 与原生类型之间可能发生隐式转换,允许重新赋值
$bigTypes = [self::TYPE_BIGINT, self::TYPE_DECIMAL, self::TYPE_BIGFLOAT];
if (in_array($toType, $bigTypes, true) or in_array($fromType, $bigTypes, true)) {
return true;
}
$varName = 'variable';
if ($this->isVarExpr($left)) {
$varName = '`$' . $this->parseIdentifier($left) . '`';
@ -5057,6 +5071,75 @@ class CompilerBase extends \PhpAot\Core\Translator
return isset($this->classMethodOverride[$fullMethodNameLower]) and $this->classMethodOverride[$fullMethodNameLower];
}
protected function hasSubClasses(string $classNameLower): bool
{
return !empty($this->classSubClasses[$classNameLower]);
}
protected function isCurrentClassFinal(): bool
{
return $this->classDef && ($this->classDef->flags & Modifiers::FINAL) !== 0;
}
protected function getMethodFlags(string $class, string $method): int
{
if (!$this->hasClass($class)) {
return 0;
}
$classDef = $this->getClass($class);
while (true) {
if ($classDef->hasMethod($method)) {
return $classDef->getMethod($method)->flags;
}
if (!$classDef->extends || !$this->hasClass($classDef->extends)) {
return 0;
}
$classDef = $this->getClass($classDef->extends);
}
}
/**
* Determine whether a method call can be devirtualized to a direct native call.
*
* Returns true when the exact class is known at compile time:
* 1. $this->m() in a final class (no subclass possible)
* 2. $this->m() where m is final (can't be overridden)
* 3. $this->m() where m is private (not virtual)
* 4. $obj->m() where obj's class has no known subclasses
* 5. $obj->m() where obj is SSA-stable (single def, no escape)
*/
protected function canDevirtualize(string $object, string $class, string $method): bool
{
// Case 1: Calling on 'this_' in a final class
if ($object === 'this_' && $this->isCurrentClassFinal()) {
return true;
}
// Case 2 & 3: Method is final or private
$flags = $this->getMethodFlags($class, $method);
if ($flags & (Modifiers::FINAL | Modifiers::PRIVATE)) {
return true;
}
// Case 4: Typed object whose class has no known subclasses
if ($object !== 'this_' && $this->hasClass($class)) {
$classLower = strtolower($class);
if (!$this->hasSubClasses($classLower) && !$this->isInterface($class)) {
return true;
}
}
// Case 5: SSA-stable object — compile-time proven exact type
if ($object !== 'this_' && isset($this->context->stableObjects[$object])) {
$stableClass = $this->context->stableObjects[$object];
if ($this->hasClass($stableClass) && !$this->isAbstractClass($stableClass)) {
return true;
}
}
return false;
}
protected function findNativeMethod(CallLike $expr, string $object, string $method): string|false
{
$classDef = null;
@ -5065,6 +5148,10 @@ class CompilerBase extends \PhpAot\Core\Translator
$classDef = $this->classDef;
} elseif (isset($this->context->objects[$object])) {
$class = $this->context->objects[$object];
// SSA-stable: use exact type from stableObjects (more specific than declared type)
if (isset($this->context->stableObjects[$object])) {
$class = $this->context->stableObjects[$object];
}
} else {
return false;
}
@ -5083,9 +5170,11 @@ class CompilerBase extends \PhpAot\Core\Translator
$fullMethodName = $object . '::' . $method;
}
// 存在子类同名方法,需要转为动态调用
// 存在子类同名方法,尝试去虚化
if ($this->isOverrideMethod($fullMethodName)) {
return false;
if (!$this->canDevirtualize($object, $class, $method)) {
return false;
}
}
if ($nativeFunc) {
$this->checkFunction($nativeFunc);
@ -5213,6 +5302,9 @@ class CompilerBase extends \PhpAot\Core\Translator
if (isset($this->context->arguments[$name])) {
continue;
}
if (isset($this->context->globalVars[$name])) {
continue;
}
$code .= $this->getIndent();
if ($type === self::TYPE_STD_ARRAY) {
$info = $this->context->stdArrays[$name];

@ -128,6 +128,9 @@ trait FuncCallOptimizer
if ($type === self::TYPE_DECIMAL) {
return 'php::Decimal::sqrt(' . $this->parseExpr($expr->args[0]->value) . ')';
}
if ($type === self::TYPE_BIGFLOAT) {
return 'php::BigFloat::sqrt(' . $this->parseExpr($expr->args[0]->value) . ')';
}
}
if ($name === 'floor') {
$type = $this->detectTypeOfExpr($expr->args[0]->value);

@ -58,6 +58,8 @@ trait SsaTypeOptimizer
$hasDanger = false;
$narrowedType = null;
$nonNarrowableType = null;
foreach ($varList as $ssaVar) {
if ($ssaVar->flags & SsaFlags::PHI) {
$hasPhi = true;
@ -69,10 +71,22 @@ trait SsaTypeOptimizer
}
$defType = $this->detectSsaDefType($ssaVar);
if ($defType === null || !isset($narrowableTypes[$defType])) {
if ($defType === null) {
$hasDanger = true;
break;
}
// Non-narrowable types (BigInt/BigFloat/Decimal/Stream/objects etc.)
// are not dangerous — they just can't be narrowed. Record the type
// so dependent SSA variables can resolve it later.
if (!isset($narrowableTypes[$defType])) {
if ($nonNarrowableType === null) {
$nonNarrowableType = $defType;
} elseif ($nonNarrowableType !== $defType) {
// Mixed non-narrowable types — can't determine a single type
$nonNarrowableType = self::TYPE_VAR;
}
continue;
}
if ($narrowedType === null) {
$narrowedType = $defType;
@ -82,7 +96,25 @@ trait SsaTypeOptimizer
}
}
if ($hasDanger || $narrowedType === null) {
if ($hasDanger) {
continue;
}
// Mixed narrowable and non-narrowable types (e.g. $x = [1,2] then $x = 42)
// — can't safely narrow.
if ($narrowedType !== null && $nonNarrowableType !== null && $nonNarrowableType !== self::TYPE_VAR) {
continue;
}
if ($narrowedType === null) {
// No narrowable type found, but register Big* types so dependent
// SSA variables can resolve them (these types have no extra metadata).
if (
$nonNarrowableType !== null
&& in_array($nonNarrowableType, [self::TYPE_BIGINT, self::TYPE_DECIMAL, self::TYPE_BIGFLOAT, self::TYPE_STREAM], true)
) {
$this->context->localVars[$varName] = $nonNarrowableType;
}
continue;
}

@ -430,6 +430,7 @@ class Preprocessor extends CompilerBase
$this->fatalError($class, "Class {$fullClassName} cannot extend itself");
}
$this->classExtends[$fullClassNameLower] = $parentClassLower;
$this->classSubClasses[$parentClassLower][] = $fullClassNameLower;
if (!$this->isInternalClass($parentClassLower)) {
$this->symbolCallInFile[$this->file][] = $parentClassLower;
}

@ -316,6 +316,7 @@ trait UniversalMethodCall
'neg' => ['handler' => 'cpp_fn', 'fn' => 'php::BigFloat::neg', 'return_type' => CompilerBase::TYPE_BIGFLOAT, 'min_args' => 0, 'max_args' => 0],
'cmp' => ['handler' => 'cpp_fn', 'fn' => 'php::BigFloat::cmp', 'return_type' => CompilerBase::TYPE_INT, 'min_args' => 1, 'max_args' => 1],
'abs' => ['handler' => 'cpp_fn', 'fn' => 'php::BigFloat::abs', 'return_type' => CompilerBase::TYPE_BIGFLOAT, 'min_args' => 0, 'max_args' => 0],
'sqrt' => ['handler' => 'cpp_fn', 'fn' => 'php::BigFloat::sqrt', 'return_type' => CompilerBase::TYPE_BIGFLOAT, 'min_args' => 0, 'max_args' => 0],
],
];

@ -0,0 +1,31 @@
--TEST--
Devirtualize: final class $this->method() uses native call
--FILE--
<?php
class Base {
public function name(): string {
return "base";
}
}
final class Sealed extends Base {
public function name(): string {
return "sealed";
}
public function getName(): string {
return $this->name();
}
}
function main() {
$sealed = new Sealed();
var_dump($sealed->getName());
var_dump($sealed->name());
}
?>
--EXPECT--
string(6) "sealed"
string(6) "sealed"

@ -0,0 +1,35 @@
--TEST--
Devirtualize: final method uses native call
--FILE--
<?php
class Animal {
final public function type(): string {
return "animal";
}
public function getType(): string {
return $this->type();
}
}
class Dog extends Animal {
public function name(): string {
return "dog";
}
}
function main() {
$animal = new Animal();
var_dump($animal->getType());
$dog = new Dog();
var_dump($dog->getType());
var_dump($dog->name());
}
?>
--EXPECT--
string(6) "animal"
string(6) "animal"
string(3) "dog"

@ -0,0 +1,35 @@
--TEST--
Devirtualize: overridden method stays dynamic (no false devirtualization)
--FILE--
<?php
class Parent_ {
public function greet(): string {
return "parent";
}
public function sayHello(): string {
return $this->greet();
}
}
class Child_ extends Parent_ {
public function greet(): string {
return "child";
}
}
function main() {
$parent = new Parent_();
var_dump($parent->sayHello());
$child = new Child_();
var_dump($child->sayHello());
var_dump($child->greet());
}
?>
--EXPECT--
string(6) "parent"
string(5) "child"
string(5) "child"

@ -0,0 +1,33 @@
--TEST--
Devirtualize: private method always uses native call (not virtual)
--FILE--
<?php
class Base {
private function value(): string {
return "base-private";
}
public function getValue(): string {
return $this->value();
}
private function withArg(int $n): string {
return "num:" . $n;
}
public function testArg(): string {
return $this->withArg(42);
}
}
function main() {
$base = new Base();
var_dump($base->getValue());
var_dump($base->testArg());
}
?>
--EXPECT--
string(12) "base-private"
string(6) "num:42"
Loading…
Cancel
Save