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

declare(strict_types=1);

namespace PHPCompiler\ext\standard;

use PHPCompiler\VM\Variable;
use PHPCompiler\Web\Superglobals;

/**
* class_implements() for compiled JIT/AOT modules (#16960, php-in-PHP).
*
* SSOT: {@see VmReflection::resolveClassForClassImplements()}, {@see VmReflection::classImplementsArray()}
* php-src: ext/standard/basic_functions.c — PHP_FUNCTION(class_implements)
*/
final class ClassImplementsJitHelper
{
public static function implementsArgv(Variable $objectOrClass, bool $autoload): Variable
{
$ctx = Superglobals::getActiveContext();
if (null === $ctx) {
throw new \LogicException(
'ClassImplementsJitHelper::implementsArgv() requires an active VM context in this compiler build'
);
}

$entry = VmReflection::resolveClassForClassImplements($ctx, $objectOrClass, $autoload);
if (null === $entry) {
$result = new Variable();
$result->bool(false);

return $result;
}

return VmReflection::classImplementsArray($entry, $ctx);
}
}
111 changes: 77 additions & 34 deletions ext/standard/JitClassImplements.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
namespace PHPCompiler\ext\standard;

use PHPCompiler\JIT\BasicBlockHelper;
use PHPCompiler\JIT\Builtin\StringClassImplements;
use PHPCompiler\JIT\Builtin\Type\Object_ as ObjectBuiltin;
use PHPCompiler\JIT\Context;
use PHPCompiler\JIT\HashTableHelper;
use PHPCompiler\JIT\JitStringArg;
Expand All @@ -13,61 +15,98 @@
use PHPLLVM\Builder;
use PHPLLVM\Value;

/** LLVM lowering for class_implements() (issue #3099). */
/**
* JIT/AOT helper for class_implements() via ClassImplementsJitHelper PHP (#3099, #16960).
*
* Compile-time literal class names keep registry fast path; boxed/value operands route through PHP.
* php-src: ext/standard/basic_functions.c — PHP_FUNCTION(class_implements)
*/
final class JitClassImplements
{
private static int $seq = 0;

public static function invoke(Context $context, JITVariable $whatArg, bool $autoload): Value
{
$compileTimeEnum = $whatArg->compileTimeEnumCase ?? null;
if (\is_array($compileTimeEnum) && isset($compileTimeEnum['classId'])) {
$object = $context->type->object;
if ($object instanceof ObjectBuiltin) {
return self::invokeForClassName(
$context,
$object->classNameForId((int) $compileTimeEnum['classId']),
$autoload
);
}
}

$literal = JitStringArg::compileTimeLiteral($whatArg);
if (null !== $literal) {
return self::invokeForClassName($context, $literal, $autoload);
}

if (JITVariable::TYPE_OBJECT === $whatArg->type) {
return self::invokeForObject($context, $whatArg, $autoload);
}

$literal = JitStringArg::compileTimeLiteral($whatArg);
if (null === $literal) {
if (JITVariable::TYPE_VALUE === $whatArg->type) {
$valuePtr = JitValueBox::valuePtrFromVariable($context, $whatArg);
$obj = $context->builder->call(
$context->lookupFunction('__value__readObject'),
$valuePtr
);
$objType = $context->getTypeFromString('__object__*');
$isObject = $context->builder->icmp(
Builder::INT_NE,
$obj,
$objType->constNull()
);
if (!$isObject) {
throw new \LogicException(
'class_implements() argument must be an object or class name string in this compiler build'
);
}
$objVar = new JITVariable(
$context,
JITVariable::TYPE_OBJECT,
JITVariable::KIND_VALUE,
$obj
);
if (JITVariable::TYPE_VALUE === $whatArg->type) {
$valuePtr = JitValueBox::valuePtrFromVariable($context, $whatArg);
$obj = $context->builder->call(
$context->lookupFunction('__value__readObject'),
$valuePtr
);
$objVar = new JITVariable(
$context,
JITVariable::TYPE_OBJECT,
JITVariable::KIND_VALUE,
$obj
);

return self::invokeForObject($context, $objVar, $autoload);
}
throw new \LogicException(
'class_implements() class name must be a string literal in this compiler build'
return self::invokeForObject($context, $objVar, $autoload);
}

return self::routeThroughPhpHelper($context, $whatArg, $autoload);
}

private static function routeThroughPhpHelper(
Context $context,
JITVariable $whatArg,
bool $autoload
): Value {
$operandPtr = self::operandToValueBox($context, $whatArg);
$i1 = $context->getTypeFromString('int1');
$autoloadVal = $autoload ? $i1->constInt(1, false) : $i1->constInt(0, false);

return StringClassImplements::invoke($context, $operandPtr, $autoloadVal);
}

private static function operandToValueBox(Context $context, JITVariable $whatArg): Value
{
if (JITVariable::TYPE_VALUE === $whatArg->type) {
return JitValueBox::valuePtrFromVariable($context, $whatArg);
}
if (JITVariable::TYPE_STRING === $whatArg->type) {
$slot = JitValueBox::alloc($context);
$ptr = JitValueBox::pointer($context, $slot);
$context->builder->call(
$context->lookupFunction('__value__writeString'),
$ptr,
$context->helper->loadValue($whatArg)
);

return $ptr;
}

return self::invokeForClassName($context, $literal, $autoload);
throw new \LogicException(
'class_implements() operand must be a compile-time literal, object, or value box in this compiler build'
);
}

private static function invokeForObject(
Context $context,
JITVariable $objectArg,
bool $autoload
): Value {
$obj = JITVariable::TYPE_OBJECT === $objectArg->type
? $context->helper->loadValue($objectArg)
: $objectArg->value;
$obj = $context->helper->loadValue($objectArg);
$objMap = $context->structFieldMap['__object__'];
$classId = $context->builder->load(
$context->builder->structGep($obj, $objMap['class_id'])
Expand Down Expand Up @@ -146,6 +185,10 @@ private static function invokeForClassName(Context $context, string $className,
);
}

if (!$autoload) {
return self::returnFalse($context);
}

return self::returnFalse($context);
}

Expand Down
87 changes: 87 additions & 0 deletions lib/JIT/Builtin/StringClassImplements.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\JIT\Builtin;

use PHPCompiler\JIT\Context;
use PHPCompiler\JIT\JitVmHelperLink;
use PHPLLVM\Value;

/**
* JIT/AOT link for class_implements() via ClassImplementsJitHelper PHP (#16960).
*
* Replaces inline class-id scan LLVM in ext/standard/JitClassImplements.php.
* SSOT: {@see \PHPCompiler\ext\standard\ClassImplementsJitHelper}.
* php-src: ext/standard/basic_functions.c — PHP_FUNCTION(class_implements)
*/
final class StringClassImplements
{
private const ABI = '__phpc_jit_class_implements';

private const HELPER_PATH = '/ext/standard/ClassImplementsJitHelper.php';

private const INVOKE_HELPER = 'PHPCompiler\\ext\\standard\\ClassImplementsJitHelper::implementsArgv';

/** @var list<string> */
private const COMPILED_HELPERS = [
self::INVOKE_HELPER,
];

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

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

public static function invoke(Context $context, Value $objectOrClass, Value $autoload): Value
{
self::ensureLinked($context);

return $context->builder->call(
$context->lookupFunction(self::ABI),
$objectOrClass,
$autoload
);
}

private static function implement(Context $context): void
{
$probe = $context->module->getNamedFunction(self::ABI);
if (null !== $probe && $probe->countBasicBlocks() > 0) {
$context->registerFunction(self::ABI, $probe);

return;
}

$savedBlock = null;
try {
$savedBlock = $context->builder->getInsertBlock();
} catch (\Throwable) {
}

$valuePtr = $context->getTypeFromString('__value__*');
$i1 = $context->getTypeFromString('int1');
JitVmHelperLink::ensureBridge(
$context,
self::ABI,
'class_implements_bridge_entry',
[$valuePtr, $i1],
$valuePtr,
self::INVOKE_HELPER,
self::HELPER_PATH,
self::COMPILED_HELPERS,
'#16960'
);

if (null !== $savedBlock) {
$context->builder->positionAtEnd($savedBlock);
} else {
$context->builder->clearInsertionPosition();
}
}
}
2 changes: 2 additions & 0 deletions test/selfhost/compiler_lib_spine_smoke/main.php
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,7 @@
require_once __DIR__.'/../../../ext/standard/ChownJitHelper.php';
require_once __DIR__.'/../../../ext/standard/ChunkSplitJitHelper.php';
require_once __DIR__.'/../../../ext/standard/ClassExistsJitHelper.php';
require_once __DIR__.'/../../../ext/standard/ClassImplementsJitHelper.php';
require_once __DIR__.'/../../../ext/standard/ClassParentsJitHelper.php';
require_once __DIR__.'/../../../ext/standard/ClassUsesJitHelper.php';
require_once __DIR__.'/../../../ext/standard/CliArgvJitHelper.php';
Expand Down Expand Up @@ -3169,6 +3170,7 @@
require_once __DIR__.'/../../../lib/JIT/Builtin/StringChmod.php';
require_once __DIR__.'/../../../lib/JIT/Builtin/StringChunkSplit.php';
require_once __DIR__.'/../../../lib/JIT/Builtin/StringClassExists.php';
require_once __DIR__.'/../../../lib/JIT/Builtin/StringClassImplements.php';
require_once __DIR__.'/../../../lib/JIT/Builtin/StringClassParents.php';
require_once __DIR__.'/../../../lib/JIT/Builtin/StringClassUses.php';
require_once __DIR__.'/../../../lib/JIT/Builtin/StringClassUsesRecursive.php';
Expand Down
41 changes: 41 additions & 0 deletions test/unit/ClassImplementsRuntimeShrinkTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\Test\Unit;

use PHPUnit\Framework\TestCase;

/** class_implements() JIT routes through ClassImplementsJitHelper PHP not inline LLVM (#16960). */
final class ClassImplementsRuntimeShrinkTest extends TestCase
{
public function testJitClassImplementsDelegatesToStringClassImplementsBridge(): void
{
$source = (string) file_get_contents(__DIR__.'/../../ext/standard/JitClassImplements.php');
$this->assertStringContainsString('StringClassImplements::invoke', $source);
$this->assertStringContainsString('routeThroughPhpHelper', $source);
$this->assertStringContainsString('ClassImplementsJitHelper', (string) file_get_contents(__DIR__.'/../../lib/JIT/Builtin/StringClassImplements.php'));
}

public function testStringClassImplementsUsesJitHelperNotInlineLlvm(): void
{
$source = (string) file_get_contents(__DIR__.'/../../lib/JIT/Builtin/StringClassImplements.php');
$this->assertStringContainsString('ClassImplementsJitHelper', $source);
$this->assertStringContainsString('JitVmHelperLink', $source);
}

public function testClassImplementsJitHelperDelegatesToVmReflection(): void
{
$source = (string) file_get_contents(__DIR__.'/../../ext/standard/ClassImplementsJitHelper.php');
$this->assertStringContainsString('VmReflection::resolveClassForClassImplements', $source);
$this->assertStringContainsString('VmReflection::classImplementsArray', $source);
$this->assertStringContainsString('Superglobals::getActiveContext', $source);
}

public function testSpineBundleIncludesClassImplementsJitHelper(): void
{
$spine = (string) file_get_contents(__DIR__.'/../../test/selfhost/compiler_lib_spine_smoke/main.php');
$this->assertStringContainsString('ClassImplementsJitHelper.php', $spine);
$this->assertStringContainsString('StringClassImplements.php', $spine);
}
}
Loading