feat(php): 添加静态属性赋值检查和本地槽位优化支持

- 在 AssignOpTrait 中添加对静态属性赋值的支持
- 扩展 CompilerBase 以实现静态属性类型检测和赋值验证
- 添加 assertCanAssignStaticProp 方法进行静态属性赋值检查
- 重构静态属性本地槽位生成逻辑,支持不同数据类型
- 为对象属性提升优化添加安全检查机制
- 新增测试用例验证静态属性原生类型默认值功能
- 更新函数上下文文档注释以反映静态属性槽位映射变化
pull/1/head
韩天峰 3 months ago
parent 4ff12b58d1
commit feb5041d2e
  1. 98
      src/Php/CompilerBase.php
  2. 2
      src/Php/Context/FunctionContext.php
  3. 11
      src/Php/Optimizer/SsaPropOptimizer.php
  4. 2
      src/Php/Parser/AssignOpTrait.php
  5. 31
      tests/aot/optimizations/objprop-hoist-this-object-arg-escape.phpt
  6. 53
      tests/aot/static/static-prop-native-defaults.phpt

@ -2067,6 +2067,19 @@ class CompilerBase extends \PhpAot\Core\Translator
}
}
break;
case 'Expr_StaticPropertyFetch':
if ($this->isIdExpr($expr->name)) {
if (!$expr->hasAttribute('nativePropertyDef')) {
$class = null;
$this->findNativeStaticProperty($expr, $class);
}
if ($expr->hasAttribute('nativePropertyDef')) {
/** @var PropertyDef $def */
$def = $expr->getAttribute('nativePropertyDef');
return $def->type;
}
}
break;
case 'Expr_ArrayDimFetch':
if ($this->isStdArrayExpr($expr)) {
if (!$expr->hasAttribute('stdArrayDimFetch')) {
@ -3604,6 +3617,57 @@ class CompilerBase extends \PhpAot\Core\Translator
}
}
protected function assertCanAssignStaticProp(Expr\StaticPropertyFetch $left, Expr $right): void
{
if (!$left->hasAttribute('nativePropertyDef')) {
return;
}
/** @var PropertyDef $def */
$def = $left->getAttribute('nativePropertyDef');
$propName = $this->parseIdentifier($left->name);
if ($this->isNull($right)) {
if ($this->isFixedObjectProp($def)) {
$this->fatalError(
$left,
"Cannot assign null to static property `{$propName}` of fixed type `{$def->type}`"
);
}
return;
}
if ($def->type !== self::TYPE_OBJECT) {
return;
}
$rightType = $this->detectTypeOfExpr($right);
if ($rightType !== self::TYPE_VAR && $rightType !== self::TYPE_OBJECT) {
$this->fatalError(
$left,
"Cannot assign value of type `{$rightType}` to static property `{$propName}` of type `{$def->type}`"
);
}
if ($def->class === '') {
return;
}
$rightClass = $this->detectClassOfExpr($right);
if ($rightClass === '') {
$this->fatalError(
$left,
"Cannot assign object of unknown class to static property `{$propName}` of class `{$def->class}`"
);
}
if ($rightClass !== $def->class) {
$this->fatalError(
$left,
"Cannot assign object of class `{$rightClass}` to static property `{$propName}` of class `{$def->class}`"
);
}
}
protected function parseUnset(Node\Stmt\Unset_ $node): string
{
$vars = $node->vars;
@ -4591,24 +4655,22 @@ class CompilerBase extends \PhpAot\Core\Translator
$class = null;
$nativeProp = $this->findNativeStaticProperty($expr, $class);
if ($nativeProp) {
// Hoist typed int/float static properties to C++ native references,
// analogous to the $this->intProp reference optimization.
if ($this->nativeTypes && $expr->hasAttribute('nativePropertyDef')) {
/** @var PropertyDef $def */
$def = $expr->getAttribute('nativePropertyDef');
if ($def->type === self::TYPE_INT || $def->type === self::TYPE_FLOAT) {
$propName = $this->parseIdentifier($expr->name);
$refVar = '_static_' . str_replace('\\', '_', $class) . '_' . $propName;
if (!isset($this->context->staticPropRefs[$refVar])) {
$classPtr = $this->getClassEntryPtr($class);
$this->context->staticPropRefs[$refVar] = [
'type' => $def->type,
'classPtr' => $classPtr,
'offsetExpr' => $nativeProp,
];
}
return $refVar;
$propName = $this->parseIdentifier($expr->name);
$refVar = '_static_' . str_replace('\\', '_', $class) . '_' . $propName;
if (!isset($this->context->staticPropRefs[$refVar])) {
$info = $this->getHoistedObjectPropInfo($def->type);
$classPtr = $this->getClassEntryPtr($class);
$this->context->staticPropRefs[$refVar] = [
'type' => $info['type'],
'classPtr' => $classPtr,
'offsetExpr' => $nativeProp,
'kind' => $info['kind'],
];
}
return $refVar;
}
if ($expr->hasAttribute('nativeProperty')) {
@ -5317,8 +5379,12 @@ class CompilerBase extends \PhpAot\Core\Translator
}
foreach ($this->context->staticPropRefs as $name => $info) {
$getter = Symbol::getStaticProperty() . '(' . $info['classPtr'] . ', ' . $info['offsetExpr'] . ')';
$zvalMacro = ($info['type'] === self::TYPE_FLOAT) ? 'Z_DVAL_P' : 'Z_LVAL_P';
$code .= $this->getIndent() . $info['type'] . ' &' . $name . ' = ' . $zvalMacro . '(' . $getter . '.unwrap_ptr());' . PHP_EOL;
if (($info['kind'] ?? 'zval') === 'var') {
$code .= $this->getIndent() . self::TYPE_VAR . ' ' . $name . ' = ' . $getter . ';' . PHP_EOL;
} else {
$zvalMacro = ($info['type'] === self::TYPE_FLOAT) ? 'Z_DVAL_P' : 'Z_LVAL_P';
$code .= $this->getIndent() . $info['type'] . ' &' . $name . ' = ' . $zvalMacro . '(' . $getter . '.unwrap_ptr());' . PHP_EOL;
}
}
return $code;
}

@ -58,7 +58,7 @@ class FunctionContext
public array $beforeStmtLines = [];
public array $afterStmtLines = [];
public array $objectProps;
/** Map of ref var name => ['type' => ..., 'class' => ..., 'prop' => ..., 'ceExpr' => ..., 'offsetExpr' => ...] */
/** Map of static property local slots. int/float use zval refs; other types use Var slots. */
public array $staticPropRefs = [];
public int $scopeLevel = 0;
/**

@ -45,6 +45,17 @@ trait SsaPropOptimizer
return;
}
if ($this->classDef && !$this->classDef->trait) {
if ($this->isClassSafeForPropHoisting($this->getFullClassName())) {
$unsafeProps = $this->collectDangerousPropOps('this_', $ssa->getStmts());
if ($unsafeProps) {
$this->context->unsafeObjectProps['this_'] = $unsafeProps;
}
} else {
$this->context->unsafeObjectProps['this_'] = ['*' => true];
}
}
if (empty($ssa->ssaVars)) {
return;
}

@ -263,6 +263,8 @@ trait AssignOpTrait
if ($this->isPropertyFetch($left)) {
$this->assertCanAssignObjectProp($left, $right);
} elseif ($this->isStaticPropertyFetch($left)) {
$this->assertCanAssignStaticProp($left, $right);
}
$rightExpr = $this->parseAssignRightExpr($right);

@ -0,0 +1,31 @@
--TEST--
SSA object prop: this object argument escape prevents property hoisting
--FILE--
<?php
use native_types;
class Foo {
public int $a;
public function run(): void {
$this->a = 1;
$fn = 'make_ref';
$fn($this);
$this->a += 1;
var_dump($this->a);
}
}
function make_ref(Foo $o): void {
$ref =& $o->a;
$ref = 99;
}
function main(): void {
(new Foo())->run();
}
?>
--EXPECT--
int(100)

@ -0,0 +1,53 @@
--TEST--
Static properties with native_types keep defaults and local slots
--FILE--
<?php
use native_types;
class StaticNativeDefaults {
public static int $i = 42;
public static float $f = 3.5;
public static bool $b = true;
public static string $s = "seed";
public static array $a = [1, 2];
}
function main(): void {
var_dump(StaticNativeDefaults::$i);
var_dump(StaticNativeDefaults::$f);
var_dump(StaticNativeDefaults::$b);
var_dump(StaticNativeDefaults::$s);
var_dump(StaticNativeDefaults::$a);
StaticNativeDefaults::$i = 100;
StaticNativeDefaults::$f = 2.25;
StaticNativeDefaults::$b = false;
StaticNativeDefaults::$s = "changed";
StaticNativeDefaults::$a = [9];
var_dump(StaticNativeDefaults::$i);
var_dump(StaticNativeDefaults::$f);
var_dump(StaticNativeDefaults::$b);
var_dump(StaticNativeDefaults::$s);
var_dump(StaticNativeDefaults::$a);
}
?>
--EXPECT--
int(42)
float(3.5)
bool(true)
string(4) "seed"
array(2) {
[0]=>
int(1)
[1]=>
int(2)
}
int(100)
float(2.25)
bool(false)
string(7) "changed"
array(1) {
[0]=>
int(9)
}
Loading…
Cancel
Save