diff --git a/lib/JIT/Builtin/Type/Object_.php b/lib/JIT/Builtin/Type/Object_.php index 426cb847226..ad15d109d80 100755 --- a/lib/JIT/Builtin/Type/Object_.php +++ b/lib/JIT/Builtin/Type/Object_.php @@ -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); diff --git a/lib/JIT/Call/SplFixedArrayMethod.php b/lib/JIT/Call/SplFixedArrayMethod.php index b96b522e4a9..10013b2b7e4 100644 --- a/lib/JIT/Call/SplFixedArrayMethod.php +++ b/lib/JIT/Call/SplFixedArrayMethod.php @@ -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.'()' ), diff --git a/lib/JIT/Context.php b/lib/JIT/Context.php index c954f4cd186..ff40e1bc9bc 100644 --- a/lib/JIT/Context.php +++ b/lib/JIT/Context.php @@ -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); } diff --git a/lib/JIT/VarDumpObjectDebugPropertiesLlvm.php b/lib/JIT/VarDumpObjectDebugPropertiesLlvm.php new file mode 100644 index 00000000000..0b527ff9d1c --- /dev/null +++ b/lib/JIT/VarDumpObjectDebugPropertiesLlvm.php @@ -0,0 +1,91 @@ +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) + ); + } +} diff --git a/lib/JIT/VarDumpObjectLlvm.php b/lib/JIT/VarDumpObjectLlvm.php index 2f958a3490a..ef2aebd218a 100644 --- a/lib/JIT/VarDumpObjectLlvm.php +++ b/lib/JIT/VarDumpObjectLlvm.php @@ -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; @@ -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( diff --git a/lib/VM/SplFixedArrayJitHelper.php b/lib/VM/SplFixedArrayJitHelper.php index a53e6d4e672..c69dd5945ec 100644 --- a/lib/VM/SplFixedArrayJitHelper.php +++ b/lib/VM/SplFixedArrayJitHelper.php @@ -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; @@ -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( @@ -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 @@ -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); } diff --git a/test/unit/SplFixedArrayDebugInfo19783AotTest.php b/test/unit/SplFixedArrayDebugInfo19783AotTest.php new file mode 100644 index 00000000000..68fe0b8438f --- /dev/null +++ b/test/unit/SplFixedArrayDebugInfo19783AotTest.php @@ -0,0 +1,100 @@ + + 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); + } + } +}