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
36 changes: 36 additions & 0 deletions ext/standard/GetClassJitHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\ext\standard;

/**
* get_class() class-id lookup for compiled JIT/AOT modules (#10222, php-in-PHP).
*
* Per-TU name table is embedded when {@see \PHPCompiler\JIT\Builtin\GetClassRuntime} compiles this helper.
* php-src: ext/standard/basic_functions.c — PHP_FUNCTION(get_class)
*/
final class GetClassJitHelper
{
/** @var array<int, string> */
private static array $namesById = [];

/**
* @param array<int, string> $namesById compile-unit class table
*/
public static function seedNamesById(array $namesById): void
{
self::$namesById = $namesById;
}

public static function classNameFromClassId(int $classId): string
{
return self::$namesById[$classId] ?? '';
}

/** @internal test reset */
public static function resetForTest(): void
{
self::$namesById = [];
}
}
119 changes: 119 additions & 0 deletions lib/JIT/Builtin/GetClassRuntime.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\JIT\Builtin;

use PHPCompiler\JIT;
use PHPCompiler\JIT\Context;
use PHPCompiler\JIT\JitVmHelperLink;
use PHPCompiler\JIT\NestedJitCompileScope;
use PHPLLVM\Value\Function_ as LlvmFunction;

/**
* JIT/AOT link for __phpc_class_name_from_id via GetClassJitHelper PHP (#10222).
*
* Replaces LLVM select-walk in {@see \PHPCompiler\JIT\ReflectionBuiltinHelper::classNameFromId}.
* php-src: ext/standard/basic_functions.c — get_class / zend_get_object_classname
*/
final class GetClassRuntime
{
private const CLASS_NAME_HELPER = 'PHPCompiler\\ext\\standard\\GetClassJitHelper::classNameFromClassId';

private const ABI_NAME = '__phpc_class_name_from_id';

public static function ensureLinked(Context $context): void
{
self::implement($context);
}

public static function implement(Context $context): void
{
if (null !== self::probeLinked($context)) {
return;
}

self::ensureJitHelperCompiled($context);

$i64 = $context->getTypeFromString('int64');
$strPtr = $context->getTypeFromString('__string__*');
JitVmHelperLink::ensureBridge(
$context,
self::ABI_NAME,
'get_class_name_bridge_entry',
[$i64],
$strPtr,
self::CLASS_NAME_HELPER,
self::helperRelativePath(),
[self::CLASS_NAME_HELPER],
'#10222'
);
$context->builder->clearInsertionPosition();
}

public static function helperSourceForMap(array $namesById): string
{
$exported = var_export($namesById, true);

return <<<PHP
<?php

declare(strict_types=1);

namespace PHPCompiler\\ext\\standard;

/** @generated per compile unit — {@see GetClassRuntime} */
final class GetClassJitHelper
{
/** @var array<int, string> */
private static array \$namesById = {$exported};

public static function classNameFromClassId(int \$classId): string
{
return self::\$namesById[\$classId] ?? '';
}
}

PHP;
}

private static function helperRelativePath(): string
{
return '/ext/standard/GetClassJitHelper.php';
}

private static function ensureJitHelperCompiled(Context $context): void
{
$lc = strtolower(self::CLASS_NAME_HELPER);
if (isset($context->functions[$lc])) {
return;
}

$map = $context->type->object->allClassNamesById();
$source = self::helperSourceForMap($map);
$runtime = $context->runtime;
NestedJitCompileScope::run($context, static function () use ($context, $runtime, $source): void {
$block = $runtime->parseAndCompile($source, 'GetClassJitHelper.php');
if (null === $block) {
throw new \LogicException('GetClassJitHelper.php parseAndCompile failed (#10222)');
}
$jit = new JIT($context);
$jit->compile($block);
});
if (!isset($context->functions[$lc])) {
throw new \LogicException(self::CLASS_NAME_HELPER.' was not compiled for JIT (#10222)');
}
}

private static function probeLinked(Context $context): ?LlvmFunction
{
$probe = $context->module->getNamedFunction(self::ABI_NAME);
if (null !== $probe && $probe->countBasicBlocks() > 0) {
$context->registerFunction(self::ABI_NAME, $probe);

return $probe;
}

return null;
}
}
16 changes: 6 additions & 10 deletions lib/JIT/ReflectionBuiltinHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace PHPCompiler\JIT;

use PHPCompiler\JIT\Builtin\GetClassRuntime;
use PHPCompiler\JIT\Builtin\Type\Object_;
use PHPCompiler\JIT\JitValueBox;
use PHPCompiler\VM\LazyGhostTraitSupport;
Expand Down Expand Up @@ -321,16 +322,11 @@ public static function classNameStringFromClassId(Context $context, Value $class

private static function classNameFromId(Context $context, Value $classId): Value
{
$names = self::objectBuiltin($context)->allClassNamesById();
$strPtr = $context->getTypeFromString('__string__*');
$result = $context->builder->load($context->constantStringFromString(''));
foreach ($names as $id => $name) {
$expected = $context->constantFromInteger($id, 'int64');
$isId = $context->builder->icmp(Builder::INT_EQ, $classId, $expected);
$candidate = $context->builder->load($context->constantStringFromString($name));
$result = $context->builder->select($isId, $candidate, $result);
}
GetClassRuntime::ensureLinked($context);

return $result;
return $context->builder->call(
$context->lookupFunction('__phpc_class_name_from_id'),
$classId
);
}
}
26 changes: 26 additions & 0 deletions test/unit/JIT/GetClassRuntimeStandaloneTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\JIT;

use PHPUnit\Framework\TestCase;

/**
* Issue #10222: get_class() class-id lookup routes through GetClassJitHelper PHP.
*
* @group aot-lint
*/
final class GetClassRuntimeStandaloneTest extends TestCase
{
public function testGetClassRuntimeBridgeContract(): void
{
$runtime = (string) file_get_contents(__DIR__.'/../../../lib/JIT/Builtin/GetClassRuntime.php');
$this->assertStringContainsString('__phpc_class_name_from_id', $runtime);
$this->assertStringContainsString('GetClassJitHelper', $runtime);
$this->assertStringContainsString('helperSourceForMap', $runtime);
$this->assertStringContainsString('JitVmHelperLink::ensureBridge', $runtime);
$helper = (string) file_get_contents(__DIR__.'/../../../ext/standard/GetClassJitHelper.php');
$this->assertStringContainsString('classNameFromClassId', $helper);
}
}
37 changes: 37 additions & 0 deletions test/unit/ReflectionBuiltinHelperRuntimeShrinkTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\Test\Unit;

use PHPCompiler\ext\standard\GetClassJitHelper;
use PHPUnit\Framework\TestCase;

/** ReflectionBuiltinHelper get_class LLVM routes through GetClassJitHelper PHP (#10222). */
final class ReflectionBuiltinHelperRuntimeShrinkTest extends TestCase
{
public function testReflectionBuiltinHelperUsesGetClassRuntimeBridge(): void
{
$source = (string) file_get_contents(__DIR__.'/../../lib/JIT/ReflectionBuiltinHelper.php');
$this->assertStringContainsString('GetClassRuntime::ensureLinked', $source);
$this->assertStringContainsString('__phpc_class_name_from_id', $source);
$this->assertStringNotContainsString('allClassNamesById()', $source);
$this->assertStringNotContainsString('->select($isId', $source);
}

public function testGetClassRuntimeUsesGeneratedJitHelper(): void
{
$source = (string) file_get_contents(__DIR__.'/../../lib/JIT/Builtin/GetClassRuntime.php');
$this->assertStringContainsString('GetClassJitHelper', $source);
$this->assertStringContainsString('helperSourceForMap', $source);
$this->assertStringContainsString('JitVmHelperLink::ensureBridge', $source);
}

public function testGetClassJitHelperLookup(): void
{
GetClassJitHelper::resetForTest();
GetClassJitHelper::seedNamesById([3 => 'Foo\\Bar']);
$this->assertSame('Foo\\Bar', GetClassJitHelper::classNameFromClassId(3));
$this->assertSame('', GetClassJitHelper::classNameFromClassId(99));
}
}