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
75 changes: 60 additions & 15 deletions lib/JIT/InstanceOfHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,30 @@
namespace PHPCompiler\JIT;

use PHPCompiler\JIT\Builtin\ErrorRaise;
use PHPCompiler\VM\InstanceOfClassName;
use PHPCompiler\VM\InstanceOfJitHelper;
use PHPCfg\Operand;
use PHPCfg\Operand\Literal;
use PHPLLVM\Builder;
use PHPLLVM\Value;

/**
* instanceof lowering for literal and dynamic class operands (#4339).
* instanceof lowering for literal and dynamic class operands (#4339, #10078).
*
* SSOT: {@see \PHPCompiler\VM\InstanceOfClassName}, {@see \PHPCompiler\VM\InstanceOfJitHelper}
*/
final class InstanceOfHelper
{
public const ERROR_MESSAGE = 'Class name must be a valid object or a string';
public const ERROR_MESSAGE = InstanceOfClassName::ERROR_MESSAGE;

private const HELPER_PATH = '/VM/InstanceOfJitHelper.php';

private const VALUE_BOX_RHS_KIND_HELPER = 'PHPCompiler\\VM\\InstanceOfJitHelper::valueBoxRhsKind';

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

public static function emit(Context $context, Variable $expr, Operand $classOp): Variable
{
Expand All @@ -28,14 +41,7 @@ public static function emit(Context $context, Variable $expr, Operand $classOp):

private static function emitDynamic(Context $context, Variable $expr, Variable $classVar): Variable
{
if (\in_array($classVar->type, [
Variable::TYPE_NATIVE_LONG,
Variable::TYPE_NATIVE_DOUBLE,
Variable::TYPE_NATIVE_BOOL,
Variable::TYPE_NULL,
Variable::TYPE_ARRAY,
Variable::TYPE_HASHTABLE,
], true)) {
if (InstanceOfJitHelper::jitRhsTypeIsInvalidClass($classVar->type)) {
self::emitInvalidClassRhsError($context);

$i1 = $context->getTypeFromString('int1');
Expand Down Expand Up @@ -86,7 +92,8 @@ private static function emitWithBoxedClassVar(Context $context, Variable $expr,
$typeByte = $context->builder->load(
$context->builder->structGep($valuePtr, $map['type'])
);
$i8 = $context->getTypeFromString('int8');
$rhsKind = self::callValueBoxRhsKind($context, $typeByte);
$i32 = $context->getTypeFromString('int32');

$stringBlock = BasicBlockHelper::append($context, 'instanceof_rhs_str');
$afterString = BasicBlockHelper::append($context, 'instanceof_rhs_after_str');
Expand All @@ -96,8 +103,8 @@ private static function emitWithBoxedClassVar(Context $context, Variable $expr,

$isString = $context->builder->icmp(
Builder::INT_EQ,
$typeByte,
$i8->constInt(Variable::TYPE_STRING, false)
$rhsKind,
$i32->constInt(InstanceOfJitHelper::RHS_KIND_STRING, false)
);
$context->builder->branchIf($isString, $stringBlock, $afterString);

Expand All @@ -114,8 +121,8 @@ private static function emitWithBoxedClassVar(Context $context, Variable $expr,
$context->builder->positionAtEnd($afterString);
$isObject = $context->builder->icmp(
Builder::INT_EQ,
$typeByte,
$i8->constInt(Variable::TYPE_OBJECT, false)
$rhsKind,
$i32->constInt(InstanceOfJitHelper::RHS_KIND_OBJECT, false)
);
$context->builder->branchIf($isObject, $objectBlock, $invalidBlock);

Expand Down Expand Up @@ -214,4 +221,42 @@ private static function ensureStrcasecmp(Context $context): void
$context->registerFunction('strcasecmp', $fn);
}
}

private static function ensureValueBoxBridgeLinked(Context $context): void
{
$abiName = '__instanceof__valueBoxRhsKind';
$probe = $context->module->getNamedFunction($abiName);
if (null !== $probe && $probe->countBasicBlocks() > 0) {
$context->registerFunction($abiName, $probe);

return;
}

$i8 = $context->getTypeFromString('int8');
$i32 = $context->getTypeFromString('int32');
JitVmHelperLink::ensureBridge(
$context,
$abiName,
'instanceof_value_box_rhs_kind_entry',
[$i8],
$i32,
self::VALUE_BOX_RHS_KIND_HELPER,
self::HELPER_PATH,
self::COMPILED_HELPERS,
'#10078'
);
$context->builder->clearInsertionPosition();
}

private static function callValueBoxRhsKind(Context $context, Value $typeByte): Value
{
self::ensureValueBoxBridgeLinked($context);
$fn = $context->lookupFunction('__instanceof__valueBoxRhsKind');
$i8 = $context->getTypeFromString('int8');

return $context->builder->call(
$fn,
$context->builder->trunc($typeByte, $i8)
);
}
}
49 changes: 49 additions & 0 deletions lib/VM/InstanceOfJitHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\VM;

use PHPCompiler\JIT\Variable as JitVariable;

/**
* Lowered into JIT/AOT modules for dynamic instanceof RHS guards (#10078, php-in-PHP).
*
* php-src: Zend/zend_execute.c — ZEND_INSTANCEOF class operand
* SSOT: {@see InstanceOfClassName}
*/
final class InstanceOfJitHelper
{
public const RHS_KIND_INVALID = 0;
public const RHS_KIND_STRING = 1;
public const RHS_KIND_OBJECT = 2;

/**
* JIT-native RHS type is invalid for instanceof class operand (#4339).
*/
public static function jitRhsTypeIsInvalidClass(int $jitType): bool
{
return \in_array($jitType, [
JitVariable::TYPE_NATIVE_LONG,
JitVariable::TYPE_NATIVE_DOUBLE,
JitVariable::TYPE_NATIVE_BOOL,
JitVariable::TYPE_NULL,
JitVariable::TYPE_HASHTABLE,
], true);
}

/**
* Value-box RHS dispatch: string class name, object class, or invalid (#4339).
*/
public static function valueBoxRhsKind(int $typeByte): int
{
if (Variable::TYPE_STRING === $typeByte) {
return self::RHS_KIND_STRING;
}
if (Variable::TYPE_OBJECT === $typeByte) {
return self::RHS_KIND_OBJECT;
}

return self::RHS_KIND_INVALID;
}
}
1 change: 1 addition & 0 deletions test/selfhost/compiler_lib_spine_smoke/main.php
Original file line number Diff line number Diff line change
Expand Up @@ -2699,6 +2699,7 @@
require_once __DIR__.'/../../../lib/VM/InOperatorJitHelper.php';
require_once __DIR__.'/../../../lib/VM/VmVarFetch.php';
require_once __DIR__.'/../../../lib/VM/VmVarFetchJitHelper.php';
require_once __DIR__.'/../../../lib/VM/InstanceOfJitHelper.php';
require_once __DIR__.'/../../../lib/VM/InstanceOfClassName.php';
require_once __DIR__.'/../../../lib/VM/InterfaceCheck.php';
require_once __DIR__.'/../../../lib/VM/InternalStrictArg.php';
Expand Down
1 change: 1 addition & 0 deletions test/unit/BootstrapSelfhostBundleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ final class BootstrapSelfhostBundleTest extends TestCase
'lib/JIT/CastArrayShared.php',
'lib/VM/CastJitHelper.php',
'lib/VM/InOperatorJitHelper.php',
'lib/VM/InstanceOfJitHelper.php',
'lib/VM/VmVarFetch.php',
'lib/VM/VmVarFetchJitHelper.php',
'lib/VM/VmFromCallable.php',
Expand Down
59 changes: 59 additions & 0 deletions test/unit/InstanceOfRuntimeShrinkTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\Test\Unit;

use PHPCompiler\JIT\Variable as JitVariable;
use PHPCompiler\VM\InstanceOfClassName;
use PHPCompiler\VM\InstanceOfJitHelper;
use PHPCompiler\VM\Variable as VmVariable;
use PHPUnit\Framework\TestCase;

/** Instanceof JIT routes dynamic RHS guards through InstanceOfJitHelper PHP (#10078). */
final class InstanceOfRuntimeShrinkTest extends TestCase
{
public function testInstanceOfHelperRoutesValueBoxThroughInstanceOfJitHelper(): void
{
$source = (string) file_get_contents(__DIR__.'/../../lib/JIT/InstanceOfHelper.php');
$this->assertStringContainsString('InstanceOfJitHelper', $source);
$this->assertStringContainsString('valueBoxRhsKind', $source);
$this->assertStringContainsString('JitVmHelperLink', $source);
$this->assertStringContainsString('jitRhsTypeIsInvalidClass', $source);
}

public function testInstanceOfHelperSharesErrorMessageWithVm(): void
{
$this->assertSame(InstanceOfClassName::ERROR_MESSAGE, \PHPCompiler\JIT\InstanceOfHelper::ERROR_MESSAGE);
}

public function testInstanceOfJitHelperJitRhsTypeIsInvalidClass(): void
{
$this->assertTrue(InstanceOfJitHelper::jitRhsTypeIsInvalidClass(JitVariable::TYPE_NULL));
$this->assertTrue(InstanceOfJitHelper::jitRhsTypeIsInvalidClass(JitVariable::TYPE_NATIVE_LONG));
$this->assertTrue(InstanceOfJitHelper::jitRhsTypeIsInvalidClass(JitVariable::TYPE_HASHTABLE));
$this->assertFalse(InstanceOfJitHelper::jitRhsTypeIsInvalidClass(JitVariable::TYPE_STRING));
$this->assertFalse(InstanceOfJitHelper::jitRhsTypeIsInvalidClass(JitVariable::TYPE_OBJECT));
$this->assertFalse(InstanceOfJitHelper::jitRhsTypeIsInvalidClass(JitVariable::TYPE_VALUE));
}

public function testInstanceOfJitHelperValueBoxRhsKind(): void
{
$this->assertSame(
InstanceOfJitHelper::RHS_KIND_STRING,
InstanceOfJitHelper::valueBoxRhsKind(VmVariable::TYPE_STRING)
);
$this->assertSame(
InstanceOfJitHelper::RHS_KIND_OBJECT,
InstanceOfJitHelper::valueBoxRhsKind(VmVariable::TYPE_OBJECT)
);
$this->assertSame(
InstanceOfJitHelper::RHS_KIND_INVALID,
InstanceOfJitHelper::valueBoxRhsKind(VmVariable::TYPE_NULL)
);
$this->assertSame(
InstanceOfJitHelper::RHS_KIND_INVALID,
InstanceOfJitHelper::valueBoxRhsKind(VmVariable::TYPE_ARRAY)
);
}
}