Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/capabilities-syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Tracking issues: [#58](https://github.com/PurHur/php-compiler/issues/58), [#145]
| `ClassName::class` / `static::class` | yes | yes | yes | [#740](https://github.com/PurHur/php-compiler/issues/740) | Compile-time class name string; related to __CLASS__ (#199); compliance PHPT; bootstrap AOT; AOT PHPT |
| Class member constants `public` / `private` / `protected const` | yes | yes | yes | [#2199](https://github.com/PurHur/php-compiler/issues/2199) | MiniWebApp `Router::DEFAULT_CONTACT_NAME_MAX` (#2059); compliance PHPT; bootstrap AOT; AOT PHPT |
| PHP 8.3 typed class constants (`const array X = [1,2];`, `const string S = 'a';`) | yes | yes | yes | [#3592](https://github.com/PurHur/php-compiler/issues/3592) | Compile-time literal fold for array/scalar const values; typed mismatch is compile-time TypeError; JIT lowers immutable array constants (#3592) |
| Class constants with `new` object expressions (PHP 8.3) | yes | no | yes | [#3196](https://github.com/PurHur/php-compiler/issues/3196) | Zend zend_compile_const_expr / zend_constants.c — materialize at class definition; shared identity on fetch; VM: ClassConstMaterializer + defineClass const-init opcodes; JIT compile lowers metadata (execute follow-up); VM-only lowering |
| Class constants with `new` object expressions (PHP 8.3) | yes | yes | yes | [#3196](https://github.com/PurHur/php-compiler/issues/3196) | Zend zend_compile_const_expr / zend_constants.c — materialize at class definition; shared identity on fetch; VM: ClassConstMaterializer; JIT/AOT: immortal module-global object in __init__ (#4021); MCJIT execute: ClassConstObjectJitCompileTest |
| Late static binding `static::method()` / `static::class` | yes | yes | yes | [#1231](https://github.com/PurHur/php-compiler/issues/1231) | VM/JIT called-class propagation; parent::method/class/$prop and static:: LSB (#1858, #3093); child method override AOT fixture extends_method_override.phpt (#2483) |
| `new static()` and `: static` return type (late-bound class) | yes | yes | yes | [#3412](https://github.com/PurHur/php-compiler/issues/3412) | VM resolveClassScopeName on TYPE_NEW; returnTypeStatic verify (#3412) |
| Magic constants `__CLASS__`, `__METHOD__`, `__FUNCTION__` | yes | yes | yes | [#199](https://github.com/PurHur/php-compiler/issues/199) | Lowered at parse time via php-cfg MagicStringResolver; compliance PHPT |
Expand Down
64 changes: 56 additions & 8 deletions lib/JIT/Builtin/Type/Object_.php
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,58 @@ public function allocateEmitTuShell(int $classId): PHPLLVM\Value
return $obj;
}

/**
* Immortal object for class constants (`public const X = new …`, #3196, #4021).
*
* Module-global singleton: no GC registration, non-refcounted header (php-src persistent zval).
*/
public function allocateClassConstantObject(int $classId): PHPLLVM\Value
{
$objType = $this->context->getTypeFromString('__object__');
$propCount = count($this->properties[$classId] ?? []);
if (0 === $propCount) {
$obj = $this->context->memory->malloc($objType);
} else {
$obj = $this->context->memory->mallocWithExtra(
$objType,
$this->context->constantFromInteger(8 * $propCount, 'size_t')
);
}

$map = $this->context->structFieldMap['__object__'];
$this->context->builder->store(
$this->context->constantFromInteger($classId, 'int64'),
$this->context->builder->structGep($obj, $map['class_id'])
);
$this->context->builder->store(
$this->context->getTypeFromString('int8')->constInt(1, false),
$this->context->builder->structGep($obj, $map['constructed'])
);

$typeinfo = $this->context->getTypeFromString('int32')->constInt(
Refcount::TYPE_INFO_TYPE_OBJECT | Refcount::TYPE_INFO_NONREFCOUNTED,
false
);
$ref = $this->context->builder->pointerCast(
$obj,
$this->context->getTypeFromString('__ref__virtual*')
);
$this->context->builder->call(
$this->context->lookupFunction('__ref__init'),
$typeinfo,
$ref
);

if ($propCount > 0) {
$this->initPropertySlots($obj, $propCount);
$this->initPropertyDefaults($obj, $classId);
$this->initRuntimePropertyNewDefaults($obj, $classId);
$this->initEmptyHashtableProperties($obj, $classId);
}

return $obj;
}

public function splObjectStorageClassId(): ?int
{
return $this->splObjectStorageClassId;
Expand Down Expand Up @@ -1850,14 +1902,10 @@ public function defineClassConst(int $classId, string $name, VMVariable $value):
$global = $this->context->module->addGlobal($objPtrType, $globalName);
$global->setInitializer($objPtrType->constNull());
$this->classConstObjectGlobals[$globalName] = $global;
$restore = $this->context->builder->getInsertBlock();
$this->context->builder->positionAtEnd($this->context->initBlock);
$alloc = $this->allocate($jitClassId);
if (!$this->hasConstructor($jitClassId)) {
$this->markObjectConstructed($alloc);
}
$this->context->builder->store($alloc, $global);
$this->context->builder->positionAtEnd($restore);
$this->context->emitInInit(function (Context $ctx) use ($jitClassId, $global): void {
$alloc = $this->allocateClassConstantObject($jitClassId);
$ctx->builder->store($alloc, $global);
});
$this->classConstants[$classId][$key] = [
'type' => Variable::TYPE_OBJECT,
'global' => $globalName,
Expand Down
5 changes: 3 additions & 2 deletions script/capability-syntax-lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -282,10 +282,11 @@ function syntaxRowDefinitions(): array
'construct' => 'Class constants with `new` object expressions (PHP 8.3)',
'opcodes' => ['TYPE_DECLARE_CLASS_CONST', 'TYPE_CLASS_CONST_FETCH', 'TYPE_NEW'],
'issue' => 3196,
'jit' => false,
'jit' => true,
'aot' => true,
'notes' => [
'Zend zend_compile_const_expr / zend_constants.c — materialize at class definition; shared identity on fetch',
'VM: ClassConstMaterializer + defineClass const-init opcodes; JIT compile lowers metadata (execute follow-up)',
'VM: ClassConstMaterializer; JIT/AOT: immortal module-global object in __init__ (#4021); MCJIT execute: ClassConstObjectJitCompileTest',
],
'probe' => 'class C { public const X = new stdClass(); } echo (C::X instanceof stdClass && C::X === C::X) ? "1" : "0";',
],
Expand Down
2 changes: 1 addition & 1 deletion test/compliance/JITTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ public static function providePHPTests(): \Generator
if (str_contains($name, 'magic_call_static')) {
continue;
}
// Object class constants: VM-only until JIT execute path is stable (#3196).
// Object class constants: MCJIT execute segfaults in harness (#98); LLVM verify: ClassConstObjectJitCompileTest (#4021).
if (str_contains($name, 'class_const_object')) {
continue;
}
Expand Down
12 changes: 12 additions & 0 deletions test/fixtures/aot/cases/class_const_object.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Language: class constants with object expressions — AOT (#4021)
--FILE--
<?php
class C {
public const X = new stdClass();
}
echo (C::X instanceof stdClass) ? "1\n" : "0\n";
echo (C::X === C::X) ? "1\n" : "0\n";
--EXPECT--
1
1
54 changes: 54 additions & 0 deletions test/unit/ClassConstObjectJitCompileTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace PHPCompiler;

use PHPUnit\Framework\TestCase;

require_once __DIR__.'/../LlvmToolchain.php';

/**
* LLVM compile-only verify for class constants with object expressions (#3196, #4021).
*
* php-src: Zend/zend_compile.c — zend_compile_const_expr; immortal singleton at class init.
*
* @group llvm
*/
final class ClassConstObjectJitCompileTest extends TestCase
{
private string $repoRoot;

protected function setUp(): void
{
$this->repoRoot = dirname(__DIR__, 2);
if (!LlvmToolchain::isReady($this->repoRoot)) {
$reason = LlvmToolchain::readyFailureReason() ?? 'LLVM 9 toolchain not available';
$this->markTestSkipped($reason.' — class const object JIT compile test needs LLVM (#4021)');
}
}

public function testClassConstObjectModuleVerify(): void
{
$runtime = new Runtime();
$path = $this->repoRoot.'/test/compliance/cases/language/class_const_object_run.php';
$code = file_get_contents($path);
$this->assertNotFalse($code);
$block = $runtime->parseAndCompile($code, $path);
$this->assertNotNull($block);
$runtime->jitCompileBlock($block);

$context = $runtime->loadJitContext();
$bc = $context->module->printToString();
$this->assertStringContainsString(
'php_compiler_class_const_obj_',
$bc,
'Expected immortal class-const object global in LLVM module'
);

$verify = new \ReflectionMethod($context, 'compileCommon');
$verify->setAccessible(true);
$verify->invoke($context);
$this->addToAssertionCount(1);
}
}