feat(compiler): 实现对静态属性和常量的原生支持

- 添加 hasConstant、getMethod 和 getConstant 方法到 ClassDef 类
- 在 CompilerBase 中实现静态属性和常量的原生解析功能
- 添加 parseNativeStaticPropertyFetch 和 findNativeStaticProperty 方法
- 修改 getFuncPtr 方法以支持宏参数
- 实现对类常量和静态属性的直接访问优化
- 添加对后置操作符中属性获取的支持
- 更新 examples/micro_bench.php 测试用例以验证功能
- 在 PropertyDef 中添加 isStatic 方法判断静态属性
- 重构预处理器对常量定义的处理逻辑
pull/1/head
韩天峰 7 months ago
parent 08635302b6
commit 57e1790639
  1. 92
      examples/micro_bench.php
  2. 16
      src/Php/ClassDef.php
  3. 147
      src/Php/CompilerBase.php
  4. 4
      src/Php/Preprocessor.php
  5. 5
      src/Php/PropertyDef.php
  6. 28
      tests/aot/prop-dec-inc.phpt

@ -25,7 +25,7 @@ function simpleicall($n)
class Foo {
static $a = 0;
public $b = 0;
const TEST = 0;
const TEST = 23;
static function read_static($n) {
for ($i = 0; $i < $n; ++$i) {
@ -169,7 +169,8 @@ function create_object($n) {
function read_const($n) {
for ($i = 0; $i < $n; ++$i) {
$x = TEST;
// $x = TEST;
$x = TEST_CONST_2;
}
}
@ -283,6 +284,7 @@ const TEST = null;
function main()
{
global $total, $last_time, $g_var;
define('TEST_CONST_2', 1999);
$g_var = 0;
$t0 = $t = start_test();
empty_loop(N);
@ -296,51 +298,51 @@ function main()
$t = end_test($t, 'int_func()', $overhead);
Foo::read_static(N);
$t = end_test($t, '$x = self::$x', $overhead);
// Foo::write_static(N);
// $t = end_test($t, 'self::$x = 0', $overhead);
// Foo::isset_static(N);
// $t = end_test($t, 'isset(self::$x)', $overhead);
// Foo::empty_static(N);
// $t = end_test($t, 'empty(self::$x)', $overhead);
// read_static(N);
// $t = end_test($t, '$x = Foo::$x', $overhead);
// write_static(N);
// $t = end_test($t, 'Foo::$x = 0', $overhead);
// isset_static(N);
// $t = end_test($t, 'isset(Foo::$x)', $overhead);
// empty_static(N);
// $t = end_test($t, 'empty(Foo::$x)', $overhead);
// Foo::call_static(N);
// $t = end_test($t, 'self::f()', $overhead);
// call_static(N);
// $t = end_test($t, 'Foo::f()', $overhead);
// $x = new Foo();
// $x->read_prop(N);
// $t = end_test($t, '$x = $this->x', $overhead);
// $x->write_prop(N);
// $t = end_test($t, '$this->x = 0', $overhead);
// $x->assign_add_prop(N);
// $t = end_test($t, '$this->x += 2', $overhead);
// $x->pre_inc_prop(N);
// $t = end_test($t, '++$this->x', $overhead);
// $x->pre_dec_prop(N);
// $t = end_test($t, '--$this->x', $overhead);
// $x->post_inc_prop(N);
// $t = end_test($t, '$this->x++', $overhead);
// $x->post_dec_prop(N);
// $t = end_test($t, '$this->x--', $overhead);
// $x->isset_prop(N);
// $t = end_test($t, 'isset($this->x)', $overhead);
// $x->empty_prop(N);
// $t = end_test($t, 'empty($this->x)', $overhead);
// $x->call(N);
// $t = end_test($t, '$this->f()', $overhead);
// $x->read_const(N);
// $t = end_test($t, '$x = Foo::TEST', $overhead);
Foo::write_static(N);
$t = end_test($t, 'self::$x = 0', $overhead);
Foo::isset_static(N);
$t = end_test($t, 'isset(self::$x)', $overhead);
Foo::empty_static(N);
$t = end_test($t, 'empty(self::$x)', $overhead);
read_static(N);
$t = end_test($t, '$x = Foo::$x', $overhead);
write_static(N);
$t = end_test($t, 'Foo::$x = 0', $overhead);
isset_static(N);
$t = end_test($t, 'isset(Foo::$x)', $overhead);
empty_static(N);
$t = end_test($t, 'empty(Foo::$x)', $overhead);
Foo::call_static(N);
$t = end_test($t, 'self::f()', $overhead);
call_static(N);
$t = end_test($t, 'Foo::f()', $overhead);
$x = new Foo();
$x->read_prop(N);
$t = end_test($t, '$x = $this->x', $overhead);
$x->write_prop(N);
$t = end_test($t, '$this->x = 0', $overhead);
$x->assign_add_prop(N);
$t = end_test($t, '$this->x += 2', $overhead);
$x->pre_inc_prop(N);
$t = end_test($t, '++$this->x', $overhead);
$x->pre_dec_prop(N);
$t = end_test($t, '--$this->x', $overhead);
$x->post_inc_prop(N);
$t = end_test($t, '$this->x++', $overhead);
$x->post_dec_prop(N);
$t = end_test($t, '$this->x--', $overhead);
$x->isset_prop(N);
$t = end_test($t, 'isset($this->x)', $overhead);
$x->empty_prop(N);
$t = end_test($t, 'empty($this->x)', $overhead);
$x->call(N);
$t = end_test($t, '$this->f()', $overhead);
$x->read_const(N);
$t = end_test($t, '$x = Foo::TEST', $overhead);
// create_object(N);
// $t = end_test($t, 'new Foo()', $overhead);
// read_const(N);
// $t = end_test($t, '$x = TEST', $overhead);
read_const(N);
$t = end_test($t, '$x = TEST', $overhead);
// read_auto_global(N);
// $t = end_test($t, '$x = $_GET', $overhead);
// read_global_var(N);

@ -45,8 +45,24 @@ class ClassDef extends ClassLikeDef
return isset($this->properties[$property]);
}
public function hasConstant(string $name): bool
{
return isset($this->constants[$name]);
}
public function getProperty($property): PropertyDef
{
return $this->properties[$property];
}
public function getMethod($method): MethodDef
{
return $this->methods[$method];
}
public function getConstant($name): ConstantDef
{
return $this->constants[$name];
}
}

@ -121,6 +121,7 @@ class CompilerBase extends \PhpAot\Core\Translator
protected string $interface = '';
/**
* key 类名,包含命名空间
* @var array<string, ClassDef>
*/
protected array $classes = [];
@ -614,7 +615,7 @@ class CompilerBase extends \PhpAot\Core\Translator
return 'php_get_class(' . $id . ', ' . $this->getLiteralString($className) . ')';
}
protected function getFuncPtr(string $funcName): string
protected function getFuncPtr(string $funcName, bool $macro = true): string
{
if (isset($this->funcMap[$funcName])) {
$id = $this->funcMap[$funcName];
@ -622,7 +623,11 @@ class CompilerBase extends \PhpAot\Core\Translator
$id = $this->funcIndex++;
$this->funcMap[$funcName] = $id;
}
if ($macro) {
return $id . ', ' . $this->getLiteralString($funcName);
} else {
return 'php_get_func(' . $id . ', ' . $this->getLiteralString($funcName) . ')';
}
}
protected function parseFunctionDeclaration(Node\Stmt\Function_|Node\Stmt\ClassMethod $v): FunctionDef
@ -999,10 +1004,13 @@ class CompilerBase extends \PhpAot\Core\Translator
return $this->parseAssignArrayDim($left, $right);
}
if ($left->getType() === 'Expr_StaticPropertyFetch') {
$value = $this->trimBrackets($this->parseExpr($right));
$native = $this->parseNativeStaticPropertyFetch($left);
if ($native) {
return $native . ' = ' . $value;
}
$class = $this->identifierToStr($left->class);
$propName = $this->identifierToStr($left->name);
$value = $this->trimBrackets($this->parseExpr($right));
return "php::setStaticProperty({$class}, {$propName}, {$value})";
}
@ -1239,6 +1247,18 @@ class CompilerBase extends \PhpAot\Core\Translator
return isset($this->localVars[$name]);
}
protected function hasNativeClass(string $name): bool
{
$name = trim($name, '\\');
return array_key_exists($name, $this->classes);
}
protected function getClassDef(string $name): ClassDef
{
$name = trim($name, '\\');
return $this->classes[$name];
}
protected function resetReturnType(string $type): void
{
$this->functionDef->returnType = $type;
@ -1751,6 +1771,7 @@ class CompilerBase extends \PhpAot\Core\Translator
return $code;
}
$fn = $this->getFuncPtr($name);
$this->beforeStmtLines[] = "// Func Call: " . $name;
$call = $silent ? 'CALL_SILENT' : 'CALL';
} else {
$tmpVar = $this->genTmpVarName();
@ -1827,20 +1848,15 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parsePostOp($expr, string $op): string
{
if ($this->isVarExpr($expr->var)) {
if ($this->isVarExpr($expr->var) or $this->isPropertyFetch($expr->var)) {
return $this->parseIdentifier($expr->var) . str_repeat($op, 2);
}
if ($this->isPropertyFetch($expr->var)) {
$obj = $this->parseIdentifier($expr->var->var);
$prop = $this->identifierToStr($expr->var->name);
$tmpVar = $this->genTmpVarName();
$this->addLocalVar($tmpVar, self::TYPE_VAR);
$this->beforeStmtLines[] = $tmpVar . ' = ' . $obj . '.getProperty(' . $prop . ');';
$this->afterStmtLines[] = $obj . '.setProperty(' . $prop . ', ' . $tmpVar . ' ' . $op . ' 1);';
return $tmpVar;
}
if ($this->isStaticPropertyFetch($expr->var)) {
$native = $this->parseNativeStaticPropertyFetch($expr->var);
if ($native) {
return $native . str_repeat($op, 2);
}
$class = $this->identifierToStr($expr->var->class);
$prop = $this->identifierToStr($expr->var->name);
$tmpVar = $this->genTmpVarName();
@ -2171,7 +2187,7 @@ class CompilerBase extends \PhpAot\Core\Translator
abort($expr);
}
$name = $this->parseIdentifier($expr->name);
if ($this->hasConstant($name)) {
if ($this->isNameExpr($expr->name) and $this->hasConstant($name)) {
return $this->getConstant($name);
}
if ($name === 'null') {
@ -2186,7 +2202,15 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($name === 'PHP_EOL') {
return '"' . $this->escapeString(PHP_EOL) . '"';
}
if ($this->isNameExpr($expr->name)) {
if (str_contains($name, '::')) {
$ns = explode('::', $name)[0];
$ce = $this->getClassEntryPtr($ns[0]);
return 'php::constant(' . $ce . ', ' . $this->getLiteralString($ns[1]) . ')';
} else {
return 'php::constant(nullptr, ' . $this->getLiteralString($name) . ')';
}
}
return 'php::constant("' . $this->escapeString($name) . '")';
}
@ -2654,13 +2678,19 @@ class CompilerBase extends \PhpAot\Core\Translator
return $this->parseIdentifier($var->var) . '.offsetExists(' . $this->parseIdentifier($var->dim) . ')';
}
if ($var instanceof Node\Expr\StaticPropertyFetch) {
$nativeProp = $this->findNativeStaticProperty($var, $class, $namespace);
if ($nativeProp) {
return 'true';
}
return 'php::hasStaticProperty(' . $this->identifierToStr($var->class) . ', ' . $this->identifierToStr($var->name) . ')';
}
if ($var instanceof Node\Expr\PropertyFetch) {
$object = $var->var;
$prop = $var->name;
return $this->parseIdentifier($object) . '.propertyExists(' . $this->identifierToStr($prop) . ')';
$object = $this->parseIdentifier($var->var);
if ($object === 'this_' and $this->isIdExpr($prop)) {
return $this->escapeBool($this->classDef->hasProperty($this->parseIdentifier($prop)));
}
return $object . '.propertyExists(' . $this->identifierToStr($prop) . ')';
}
abort($var);
}
@ -2819,30 +2849,87 @@ class CompilerBase extends \PhpAot\Core\Translator
return $this->parseParentMethodCall($expr);
}
$method = $this->parseIdentifier($expr->name);
$fn = '"' . $class . '::' . $method . '"';
$ce = $this->getClassEntryPtr($class);
$fn = $ce . ', ' . $this->getFuncPtr($class . '::' . $method, false);
}
$call = 'php::call';
if (empty($expr->args)) {
return 'php::call(' . $fn . ')';
return $call . '(' . $fn . ')';
}
return $call . '(' . $fn . ', {' . $this->parseCallArgs($expr->args) . '})';
}
protected function findNativeStaticProperty(Node\Expr\StaticPropertyFetch $expr, ?string &$class, ?string &$namespace): ?PropertyDef
{
if ($this->isNameExpr($expr->class) and $this->isIdExpr($expr->name)) {
$class = $this->parseIdentifier($expr->class);
$prop = $this->parseIdentifier($expr->name);
if ($class === 'self') {
$classDef = $this->classDef;
$class = $this->class;
$namespace = $this->namespace;
} else {
$classDef = $this->classes[$class];
$namespace = $classDef->namespace;
}
if ($classDef->hasProperty($prop)) {
$propDef = $classDef->getProperty($prop);
if ($propDef->isStatic()) {
return $propDef;
}
}
}
return 'php::call(' . $fn . ', {' . $this->parseCallArgs($expr->args) . '})';
return null;
}
protected function parseStaticPropertyFetch(Node $expr): string
protected function parseNativeStaticPropertyFetch(Node\Expr\StaticPropertyFetch $expr): string|bool
{
$nativeProp = $this->findNativeStaticProperty($expr, $class, $namespace);
if ($nativeProp) {
$classPtr = $this->getClassEntryPtr($class);
$propOffset = self::PREFIX . $this->getPropertyOffset($nativeProp->name, $class, $namespace);
return 'php::getStaticProperty(' . $classPtr . ', ' . $propOffset . ')';
}
return false;
}
protected function parseStaticPropertyFetch(Node\Expr\StaticPropertyFetch $expr): string
{
$native = $this->parseNativeStaticPropertyFetch($expr);
if ($native) {
return $native;
}
return 'php::getStaticProperty(' . $this->identifierToStr($expr->class) . ', ' . $this->identifierToStr($expr->name) . ')';
}
protected function parseClassConstFetch(Node\Expr\ClassConstFetch $expr): string
{
$class = $this->parseIdentifier($expr->class);
$class = ($class === 'self' or $class === 'this_') ? $this->class : $class;
$self = false;
if ($class === 'self' or $class === 'this_') {
$self = true;
$class = $this->class;
}
$const = $this->escapeString($this->parseIdentifier($expr->name));
$class = $this->escapeString($this->getNamespacedClassName($class));
$class = $this->getNamespacedClassName($class);
if ($const === 'class') {
return '"' . $class . '"';
return '"' . $this->escapeString($class) . '"';
}
if (($self or $this->isNameExpr($expr->class)) and $this->isIdExpr($expr->name)) {
if ($this->hasNativeClass($class)) {
$classDef = $this->getClassDef($class);
if ($classDef->hasConstant($const)) {
return $classDef->getConstant($const)->value;
}
}
$ce = $this->getClassEntryPtr($class);
return 'php::constant(' . $ce . ', ' . $this->getLiteralString($const) . ')';
} else {
$name = $class . '::' . $const;
$name = $this->getLiteralString($name);
return 'php::constant(' . $name . ')';
}
return 'php::constant("' . $class . '::' . $const . '")';
}
protected function parseThrow(mixed $expr): string
@ -3109,11 +3196,11 @@ class CompilerBase extends \PhpAot\Core\Translator
$nativeFunc = $this->getNativeName($method, $this->namespace, $this->class);
} elseif (isset($this->objects[$object])) {
$class = $this->objects[$object];
if (!isset($this->classes[$class])) {
if (!$this->hasNativeClass($class)) {
return false;
}
$classDef = $this->classes[$class];
if (!isset($classDef->methods[$method])) {
if (!$classDef->hasMethod($method)) {
return false;
}
$methodDef = $classDef->methods[$method];

@ -98,10 +98,12 @@ class Preprocessor extends CompilerBase
break;
case 'Stmt_Declare':
case 'Stmt_Use':
case 'Stmt_Const':
case 'Stmt_Interface':
case 'Stmt_Nop':
break;
case 'Stmt_Const':
$this->parseConstDef($v);
break;
default:
$this->fatalError($v, 'Unsupported statement: ' . $type);
break;

@ -39,4 +39,9 @@ class PropertyDef
{
return !$this->isPrivate() && !$this->isProtected();
}
public function isStatic(): bool
{
return $this->flags & Modifiers::STATIC;
}
}

@ -0,0 +1,28 @@
--TEST--
object property inc/dec
--FILE--
<?php
class TestObj {
public int $a = 999;
static public int $a1 = 0;
static public int $b = 100;
}
function main()
{
$o = new TestObj();
$o->a++;
var_dump($o->a);
$o->a--;
var_dump($o->a);
TestObj::$b++;
var_dump(TestObj::$b);
TestObj::$b--;
var_dump(TestObj::$b);
}
?>
--EXPECT--
int(1000)
int(999)
int(101)
int(100)
Loading…
Cancel
Save