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 lib/JIT/Builtin/Type/Object_.php
Original file line number Diff line number Diff line change
Expand Up @@ -5002,7 +5002,7 @@ private function registerExternalClass(string $lcname, string $displayName): voi
$pubStatic = $pub | \PHPCfg\Func::FLAG_STATIC;
foreach ([
'__construct', 'count', 'getsize', 'setsize', 'toarray', 'getiterator',
'offsetget', 'offsetset', 'offsetexists', 'offsetunset',
'offsetget', 'offsetset', 'offsetexists', 'offsetunset', '__debuginfo',
'jsonserialize',
] as $method) {
$this->defineMethodVisibility($id, $method, $pub);
Expand Down
10 changes: 10 additions & 0 deletions lib/JIT/Call/SplFixedArrayMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,16 @@ public function call(Context $context, Variable ...$args): Value
$index
)
),
'__debuginfo' => $this->callExactArg(
$context,
$args,
'SplFixedArray::__debugInfo',
0,
static fn (Context $ctx, Variable $self): Value => SplFixedArrayJitHelper::compileDebugInfo(
$ctx,
$self
)
),
default => throw new \LogicException(
'SplFixedArray JIT lowering is not implemented for '.$this->method.'()'
),
Expand Down
2 changes: 1 addition & 1 deletion lib/JIT/Context.php
Original file line number Diff line number Diff line change
Expand Up @@ -1884,7 +1884,7 @@ private function defineBuiltins(int $loadType): void {
$this->type->object->lookup('SplFixedArray');
foreach ([
'__construct', 'fromArray', 'count', 'getSize', 'setSize', 'toArray',
'offsetGet', 'offsetSet', 'offsetExists', 'offsetUnset',
'offsetGet', 'offsetSet', 'offsetExists', 'offsetUnset', '__debugInfo',
] as $sfaMethod) {
$this->functionProxies['splfixedarray::'.strtolower($sfaMethod)] = new Call\SplFixedArrayMethod($sfaMethod);
}
Expand Down
91 changes: 91 additions & 0 deletions lib/JIT/VarDumpObjectDebugPropertiesLlvm.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\JIT;

use PHPCompiler\ext\standard\JitGetObjectVars;
use PHPCompiler\JIT\Variable as JitVariable;
use PHPCompiler\VM\SplFixedArrayJitHelper;
use PHPLLVM\Builder;
use PHPLLVM\Value;

/**
* var_dump() object property source — __debugInfo / spl backing ht before get_object_vars (#19783).
*
* php-src: ext/standard/var.c — zend_std_get_debug_info / spl_fixedarray_object_debug_info
*/
final class VarDumpObjectDebugPropertiesLlvm
{
private static int $seq = 0;

/**
* Hashtable of properties to dump for $objVar (may branch on runtime class).
*/
public static function resolveHashtable(Context $context, JitVariable $objVar): Value
{
$objPtr = self::objectPtr($context, $objVar);
$object = $context->type->object;
$sfaClassId = $object->lookup('SplFixedArray');
$objMap = $context->structFieldMap['__object__'];
$runtimeClassId = $context->builder->load(
$context->builder->structGep($objPtr, $objMap['class_id'])
);
$isSplFixedArray = $context->builder->icmp(
Builder::INT_EQ,
$runtimeClassId,
$context->constantFromInteger($sfaClassId)
);

$tag = (string) ++self::$seq;
$sfaBb = BasicBlockHelper::append($context, 'vd_dbg_sfa_'.$tag);
$govBb = BasicBlockHelper::append($context, 'vd_dbg_gov_'.$tag);
$doneBb = BasicBlockHelper::append($context, 'vd_dbg_done_'.$tag);
$htTy = $context->getTypeFromString('__hashtable__*');
$htSlot = BasicBlockHelper::entryAlloca($context, $htTy);
$context->builder->branchIf($isSplFixedArray, $sfaBb, $govBb);

$context->builder->positionAtEnd($sfaBb);
$receiver = new JitVariable(
$context,
JitVariable::TYPE_OBJECT,
JitVariable::KIND_VALUE,
$objPtr
);
$sfaHt = SplFixedArrayJitHelper::copyPackedHashtable($context, $receiver);
$context->builder->store($context->helper->loadValue($sfaHt), $htSlot);
$context->builder->branch($doneBb);

$context->builder->positionAtEnd($govBb);
$context->builder->store(self::hashtableFromGetObjectVars($context, $objVar), $htSlot);
$context->builder->branch($doneBb);

$context->builder->positionAtEnd($doneBb);

return $context->builder->load($htSlot);
}

private static function hashtableFromGetObjectVars(Context $context, JitVariable $objVar): Value
{
$varsBoxed = JitGetObjectVars::invoke($context, $objVar, false);

return $context->builder->call(
$context->lookupFunction('__value__readHashtable'),
JitValueBox::normalizeValuePtr($context, $varsBoxed)
);
}

private static function objectPtr(Context $context, JitVariable $objVar): Value
{
if (JitVariable::TYPE_OBJECT === $objVar->type) {
return JitVariable::KIND_VALUE === $objVar->kind
? $objVar->value
: $context->builder->load($objVar->value);
}

return $context->builder->call(
$context->lookupFunction('__value__readObject'),
JitValueBox::valuePtrFromVariable($context, $objVar)
);
}
}
7 changes: 1 addition & 6 deletions lib/JIT/VarDumpObjectLlvm.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace PHPCompiler\JIT;

use PHPCompiler\ext\standard\JitGetObjectVars;
use PHPCompiler\JIT\Call\HashTableExportKeyValuePairs;
use PHPCompiler\JIT\Variable as JitVariable;
use PHPLLVM\Builder;
Expand Down Expand Up @@ -41,11 +40,7 @@ public static function dump(Context $context, Value $valuePtr, Value $level): vo
$valuePtr
);
$className = ReflectionBuiltinHelper::getDebugTypeClassName($context, $objVar);
$varsBoxed = JitGetObjectVars::invoke($context, $objVar, false);
$ht = $context->builder->call(
$context->lookupFunction('__value__readHashtable'),
JitValueBox::normalizeValuePtr($context, $varsBoxed)
);
$ht = VarDumpObjectDebugPropertiesLlvm::resolveHashtable($context, $objVar);

$pairs = HashTableExportKeyValuePairs::exportPairsForSlice($context, $ht);
$num = $context->builder->call(
Expand Down
46 changes: 33 additions & 13 deletions lib/VM/SplFixedArrayJitHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@

namespace PHPCompiler\VM;

use PHPCompiler\CompilerVersion;
use PHPCompiler\ext\standard\JitIntdiv;
use PHPCompiler\JIT\BasicBlockHelper;
use PHPCompiler\JIT\Context;
use PHPCompiler\JIT\ExceptionBridge;
use PHPCompiler\JIT\HashTableHelper;
use PHPCompiler\JIT\JitValueBox;
use PHPCompiler\JIT\TryCatchHelper;
use PHPCompiler\JIT\Variable as JITVariable;
use PHPLLVM\Builder;
use PHPLLVM\Value;
Expand Down Expand Up @@ -206,6 +208,29 @@ public static function compileSetSize(Context $context, JITVariable $receiver, J
* Peer {@see ArrayObjectJitHelper::compileGetArrayCopy}.
*/
public static function compileToArray(Context $context, JITVariable $receiver): Value
{
$slot = JitValueBox::alloc($context);
$context->builder->call(
$context->lookupFunction('__value__writeHashtable'),
JitValueBox::pointer($context, $slot),
$context->helper->loadValue(self::copyPackedHashtable($context, $receiver))
);

return $slot;
}

/**
* SplFixedArray::__debugInfo() — indexed element map (php-src spl_fixedarray_object_debug_info; #19783).
*/
public static function compileDebugInfo(Context $context, JITVariable $receiver): Value
{
return self::compileToArray($context, $receiver);
}

/**
* Copy fixed `__spl_ht` slots for var_dump / __debugInfo (peer compileToArray).
*/
public static function copyPackedHashtable(Context $context, JITVariable $receiver): JITVariable
{
$ht = self::htPtr($context, self::loadObject($context, $receiver));
$copy = new JITVariable(
Expand All @@ -219,14 +244,8 @@ public static function compileToArray(Context $context, JITVariable $receiver):
$copy,
new JITVariable($context, JITVariable::TYPE_HASHTABLE, JITVariable::KIND_VALUE, $ht)
);
$slot = JitValueBox::alloc($context);
$context->builder->call(
$context->lookupFunction('__value__writeHashtable'),
JitValueBox::pointer($context, $slot),
$context->helper->loadValue($copy)
);

return $slot;
return $copy;
}

public static function compileOffsetGet(Context $context, JITVariable $receiver, JITVariable $index): Value
Expand Down Expand Up @@ -400,13 +419,14 @@ private static function assertIndexInRange(Context $context, Value $ht, Value $i
$okBb = BasicBlockHelper::append($context, 'sfa_oob_ok');
$context->builder->branchIf($ok, $okBb, $badBb);
$context->builder->positionAtEnd($badBb);
$msg = 'Index invalid or out of range';
$context->builder->call(
$context->lookupFunction('__compiler_jit_raise_logic_exception'),
$context->builder->pointerCast($context->constantFromString($msg), $context->getTypeFromString('int8*')),
$context->constantFromInteger(\strlen($msg), 'size_t')
$exceptionClass = CompilerVersion::supportsSplFixedArrayOutOfBoundsException()
? 'OutOfBoundsException'
: 'RuntimeException';
TryCatchHelper::emitCatchableClassError(
$context,
$exceptionClass,
'Index invalid or out of range'
);
$context->builder->branch($okBb);
$context->builder->positionAtEnd($okBb);
}

Expand Down
100 changes: 100 additions & 0 deletions test/unit/SplFixedArrayDebugInfo19783AotTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php

declare(strict_types=1);

namespace PHPCompiler;

use PHPUnit\Framework\TestCase;

/**
* AOT: SplFixedArray __debugInfo var_dump + OOB catchable exceptions (#19783, #21994).
*
* @see php-src ext/spl/spl_fixedarray.c spl_fixedarray_object_debug_info
*
* @group llvm
* @group aot
*/
final class SplFixedArrayDebugInfo19783AotTest extends TestCase
{
private const DEBUG_EXPECTED = <<<'OUT'
object(SplFixedArray)#1 (2) {
[0]=>
int(1)
[1]=>
NULL
}

OUT;

private const OOB_EXPECTED = "caught:RuntimeException\nafter\n";

public function testContextRegistersDebugInfoProxy(): void
{
$ctx = (string) file_get_contents(__DIR__.'/../../lib/JIT/Context.php');
$this->assertStringContainsString("'__debugInfo'", $ctx);
$this->assertStringContainsString(
"'offsetGet', 'offsetSet', 'offsetExists', 'offsetUnset', '__debugInfo'",
$ctx
);
}

public function testVmSplFixedArrayDebugInfoRepro(): void
{
$runtime = new Runtime();
$code = file_get_contents(dirname(__DIR__).'/repro/splfixedarray_debug_info.php');
$this->assertNotFalse($code);
$cwd = getcwd();
chdir(dirname(__DIR__, 2));
try {
ob_start();
$runtime->run($runtime->parseAndCompile($code, 'splfixedarray_debug_info.php'));
$out = (string) ob_get_clean();
} finally {
chdir($cwd);
}
$this->assertMatchesRegularExpression(
'/object\(SplFixedArray\)#\d+ \(2\) \{\n \[0\]=>\n int\(1\)\n \[1\]=>\n NULL\n\}\n/',
$out
);
}

public function testAotSplFixedArrayDebugInfoRepro(): void
{
if (!LlvmToolchain::hasLibrary(dirname(__DIR__, 2))) {
$this->markTestSkipped('LLVM 9 toolchain not available');
}
$this->assertSame(self::DEBUG_EXPECTED, $this->runAot(dirname(__DIR__).'/repro/splfixedarray_debug_info.php'));
}

public function testAotSplFixedArrayOobCatchableRepro(): void
{
if (!LlvmToolchain::hasLibrary(dirname(__DIR__, 2))) {
$this->markTestSkipped('LLVM 9 toolchain not available');
}
$this->assertSame(
self::OOB_EXPECTED,
$this->runAot(dirname(__DIR__).'/repro/splfixedarray_oob_catchable_21994.php')
);
}

private function runAot(string $src): string
{
$root = dirname(__DIR__, 2);
$bin = sys_get_temp_dir().'/phpc_sfa19783_'.getmypid().'.bin';
$compile = 'env PHP_COMPILER_HELPER_RUNTIME_O=0 '.escapeshellarg(PHP_BINARY).' '
.escapeshellarg($root.'/bin/compile.php')
.' -o '.escapeshellarg($bin).' '.escapeshellarg($src).' 2>&1';
exec($compile, $compileOut, $compileRc);
$this->assertSame(0, $compileRc, implode("\n", $compileOut));
$this->assertFileExists($bin);
try {
$runOut = [];
exec(escapeshellarg($bin).' 2>&1', $runOut, $runRc);
$this->assertSame(0, $runRc, implode("\n", $runOut));

return implode("\n", $runOut)."\n";
} finally {
@unlink($bin);
}
}
}
Loading