diff --git a/examples/micro_bench.php b/examples/micro_bench.php index bd72558b..378be03d 100644 --- a/examples/micro_bench.php +++ b/examples/micro_bench.php @@ -32,29 +32,29 @@ function simpleicall(int $n) } class Foo { - static $a = 12; + static int $a = 12; public int $b = 0; const TEST = 23; - static function read_static($n) { + static function read_static(int $n) { for ($i = 0; $i < $n; ++$i) { $x = self::$a; } } - static function write_static($n) { + static function write_static(int $n) { for ($i = 0; $i < $n; ++$i) { self::$a = 0; } } - static function isset_static($n) { + static function isset_static(int $n) { for ($i = 0; $i < $n; ++$i) { $x = isset(self::$a); } } - static function empty_static($n) { + static function empty_static(int $n) { for ($i = 0; $i < $n; ++$i) { $x = empty(self::$a); } @@ -63,7 +63,7 @@ class Foo { static function f() { } - static function call_static($n) { + static function call_static(int $n) { for ($i = 0; $i < $n; ++$i) { self::f(); } @@ -81,43 +81,43 @@ class Foo { } } - function assign_add_prop($n) { + function assign_add_prop(int $n) { for ($i = 0; $i < $n; ++$i) { $this->b += 2; } } - function pre_inc_prop($n) { + function pre_inc_prop(int $n) { for ($i = 0; $i < $n; ++$i) { ++$this->b; } } - function pre_dec_prop($n) { + function pre_dec_prop(int $n) { for ($i = 0; $i < $n; ++$i) { --$this->b; } } - function post_inc_prop($n) { + function post_inc_prop(int $n) { for ($i = 0; $i < $n; ++$i) { $this->b++; } } - function post_dec_prop($n) { + function post_dec_prop(int $n) { for ($i = 0; $i < $n; ++$i) { $this->b--; } } - function isset_prop($n) { + function isset_prop(int $n) { for ($i = 0; $i < $n; ++$i) { $x = isset($this->b); } } - function empty_prop($n) { + function empty_prop(int $n) { for ($i = 0; $i < $n; ++$i) { $x = empty($this->b); } @@ -126,13 +126,13 @@ class Foo { function g() { } - function call($n) { + function call(int $n) { for ($i = 0; $i < $n; ++$i) { $this->g(); } } - function read_const($n) { + function read_const(int $n) { for ($i = 0; $i < $n; ++$i) { $x = $this::TEST; } @@ -140,49 +140,49 @@ class Foo { } -function read_static($n) { +function read_static(int $n) { for ($i = 0; $i < $n; ++$i) { $x = Foo::$a; } } -function write_static($n) { +function write_static(int $n) { for ($i = 0; $i < $n; ++$i) { Foo::$a = 0; } } -function isset_static($n) { +function isset_static(int $n) { for ($i = 0; $i < $n; ++$i) { $x = isset(Foo::$a); } } -function empty_static($n) { +function empty_static(int $n) { for ($i = 0; $i < $n; ++$i) { $x = empty(Foo::$a); } } -function call_static($n) { +function call_static(int $n) { for ($i = 0; $i < $n; ++$i) { Foo::f(); } } -function create_object($n) { +function create_object(int $n) { for ($i = 0; $i < $n; ++$i) { $x = new Foo(); } } -function read_const($n) { +function read_const(int $n) { for ($i = 0; $i < $n; ++$i) { $x = TEST_CONST_2; } } -function read_auto_global($n) { +function read_auto_global(int $n) { for ($i = 0; $i < $n; ++$i) { $x = $_GET; } @@ -208,21 +208,22 @@ function read_str_offset(int $n) { } } -function issetor(int $n) { - $val = array(0,1,2,3,4,5,6,7,8,9); +function issetor(int $n) +{ + $val = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); for ($i = 0; $i < $n; ++$i) { $x = $val ?: null; } } -function issetor2($n) { +function issetor2(int $n) { $f = false; $j = 0; for ($i = 0; $i < $n; ++$i) { $x = $f ?: $j + 1; } } -function ternary($n) { +function ternary(int $n) { $val = array(0,1,2,3,4,5,6,7,8,9); $f = false; for ($i = 0; $i < $n; ++$i) { @@ -230,7 +231,7 @@ function ternary($n) { } } -function ternary2($n) { +function ternary2(int $n) { $f = false; $j = 0; for ($i = 0; $i < $n; ++$i) { $x = $f ? $f : $j + 1; @@ -239,7 +240,7 @@ function ternary2($n) { /*****/ -function empty_loop($n) { +function empty_loop(int $n) { for ($i = 0; $i < $n; ++$i) { } } @@ -305,68 +306,68 @@ function main() $t = end_test($t, 'undef_func()', $overhead); simpleicall(N); $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); -// create_object(N); -// $t = end_test($t, 'new Foo()', $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); -// $t = end_test($t, '$x = $GLOBALS[\'v\']', $overhead); -// read_hash(N); -// $t = end_test($t, '$x = $hash[\'v\']', $overhead); -// read_str_offset(N); -// $t = end_test($t, '$x = $str[0]', $overhead); -// issetor(N); -// $t = end_test($t, '$x = $a ?: null', $overhead); -// issetor2(N); -// $t = end_test($t, '$x = $f ?: tmp', $overhead); -// ternary(N); -// $t = end_test($t, '$x = $f ? $f : $a', $overhead); -// ternary2(N); -// $t = end_test($t, '$x = $f ? $f : tmp', $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); + create_object(N); + $t = end_test($t, 'new Foo()', $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); + $t = end_test($t, '$x = $GLOBALS[\'v\']', $overhead); + read_hash(N); + $t = end_test($t, '$x = $hash[\'v\']', $overhead); + read_str_offset(N); + $t = end_test($t, '$x = $str[0]', $overhead); + issetor(N); + $t = end_test($t, '$x = $a ?: null', $overhead); + issetor2(N); + $t = end_test($t, '$x = $f ?: tmp', $overhead); + ternary(N); + $t = end_test($t, '$x = $f ? $f : $a', $overhead); + ternary2(N); + $t = end_test($t, '$x = $f ? $f : tmp', $overhead); total(); } diff --git a/perf/class-static-prop.php b/perf/class-static-prop.php new file mode 100644 index 00000000..7e066911 --- /dev/null +++ b/perf/class-static-prop.php @@ -0,0 +1,25 @@ +exec('Cannot use [] for array unset', 'unset-array-dim-fetch-empty.php'); } + + public function testUnsetStaticProp(): void + { + $this->exec('Attempt to unset static property', 'unset-static-prop.php'); + } } \ No newline at end of file diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index e1862ddf..9f3b43a6 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -3736,6 +3736,8 @@ class CompilerBase extends \PhpAot\Core\Translator $lines[] = $this->getObjectPropVarName($object, $propName) . ' = 0;'; } } + } elseif ($this->isStaticPropertyFetch($var)) { + $this->fatalError($var, 'Attempt to unset static property ' . $this->parseIdentifier($var->class) . '::$' . $this->parseIdentifier($var->name)); } elseif ($this->isVarExpr($var)) { $name = $this->parseIdentifier($var); if (!$this->hasVar($name)) { @@ -4650,8 +4652,29 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseNativeStaticPropertyFetch(Expr\StaticPropertyFetch $expr): string|bool { + $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; + } + } + if ($expr->hasAttribute('nativeProperty')) { $classPtr = $this->getClassEntryPtr($class); return Symbol::getStaticProperty() . '(' . $classPtr . ', ' . $nativeProp . ')'; @@ -5373,6 +5396,11 @@ class CompilerBase extends \PhpAot\Core\Translator foreach ($this->context->objectProps as $name => $info) { $code .= $this->getIndent() . $info['type'] . ' &' . $name . ' = Z_LVAL_P(' . $info['getter'] . '.unwrap_ptr());' . PHP_EOL; } + 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; + } return $code; } diff --git a/src/Php/Context/FunctionContext.php b/src/Php/Context/FunctionContext.php index c40f8e25..2091d977 100644 --- a/src/Php/Context/FunctionContext.php +++ b/src/Php/Context/FunctionContext.php @@ -55,6 +55,8 @@ class FunctionContext public array $beforeStmtLines = []; public array $afterStmtLines = []; public array $objectProps; + /** Map of ref var name => ['type' => ..., 'class' => ..., 'prop' => ..., 'ceExpr' => ..., 'offsetExpr' => ...] */ + public array $staticPropRefs = []; public int $scopeLevel = 0; /** * @var array @@ -72,6 +74,7 @@ class FunctionContext $this->objectProps = []; $this->stableObjects = []; $this->hoistedProps = []; + $this->staticPropRefs = []; $this->ceWrappers = []; $this->tmpVarIndex = 0; $this->scopeLayouts = [];