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
21 changes: 17 additions & 4 deletions ext/standard/PendingHeadersJitHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,9 @@ public static function removeHeader(string $name): void
/** @return HashTable|null null when header_list() should return [] (no CGI gateway) */
public static function listHeadersTable(): ?HashTable
{
$gateway = getenv('GATEWAY_INTERFACE');
if (false === $gateway || '' === $gateway) {
// NestedJIT AOT: raw getenv() SEGVs — use phpc_getenv_kernel (#21888 / #20644).
$gateway = self::environGet('GATEWAY_INTERFACE');
if ('' === $gateway) {
return null;
}

Expand Down Expand Up @@ -177,12 +178,24 @@ private static function headerNameFromLine(string $line): ?string
private static function isWebResponseEnvPresent(): bool
{
foreach (['REQUEST_METHOD', 'GATEWAY_INTERFACE', 'QUERY_STRING'] as $key) {
$value = getenv($key);
if (false !== $value && '' !== $value) {
if ('' !== self::environGet($key)) {
return true;
}
}

return false;
}

/**
* Process environ lookup safe under NestedJIT AOT (#21888, #20644).
*
* Builtin environ lookup SEGVs when this helper is NestedJIT'd; use the
* Rename-shaped {@see phpc_getenv_kernel} libc leaf instead.
*/
private static function environGet(string $key): string
{
$value = \phpc_getenv_kernel($key);

return null === $value ? '' : (string) $value;
}
}
3 changes: 3 additions & 0 deletions lib/JIT/Builtin/PendingHeadersJitBridge.php
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,9 @@ private static function ensureJitHelperCompiled(Context $context): void
return;
}

// PendingHeadersJitHelper uses phpc_getenv_kernel (not raw getenv) under NestedJIT (#21888).
StringGetenv::ensureNativeHtInternalProxies($context);

$runtime = $context->runtime;
$path = \dirname(__DIR__, 3).self::HELPER_PATH;
NestedJitCompileScope::run($context, static function () use ($context, $runtime, $path): void {
Expand Down
201 changes: 9 additions & 192 deletions lib/JIT/Builtin/SuperglobalRefreshRuntime.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,58 +5,22 @@
namespace PHPCompiler\JIT\Builtin;

use PHPCompiler\ext\standard\JitSuperglobalRefreshKernel;
use PHPCompiler\JIT;
use PHPCompiler\JIT\Builtin;
use PHPCompiler\JIT\Context;
use PHPCompiler\JIT\JitNestedHelperCoerce;
use PHPCompiler\JIT\NestedJitCompileScope;
use PHPLLVM\Value;
use PHPLLVM\Value\Function_ as LlvmFunction;

/**
* JIT/AOT link for __superglobals__refresh via SuperglobalRefreshJitHelper PHP (#9907).
* JIT/AOT link for __superglobals__refresh (#9907, #21888).
*
* MCJIT embed: {@see \PHPCompiler\JIT\SuperglobalInit::implementRefresh} copies VM tables.
* Standalone AOT: compiled PHP bridge via {@see SuperglobalRefreshJitHelper}.
* SSOT: {@see \PHPCompiler\Web\Superglobals}
* Standalone AOT (user-script + self-host): {@see JitSuperglobalRefreshKernel} native LLVM.
*
* Former NestedJIT {@see \PHPCompiler\Web\SuperglobalRefreshJitHelper} bridge removed (#21888):
* `: HashTable` returns TypeError under self-host stubs (`HashTable, int returned` — peer #20652 /
* #13571). Zend/VM SSOT remains {@see \PHPCompiler\Web\Superglobals} + the JitHelper unit tests.
* php-src: main/php_variables.c
*/
final class SuperglobalRefreshRuntime
{
private const HELPER_PATH = '/lib/Web/SuperglobalRefreshJitHelper.php';

private const BUILD_GET = 'PHPCompiler\\Web\\SuperglobalRefreshJitHelper::buildGetTable';

private const BUILD_POST = 'PHPCompiler\\Web\\SuperglobalRefreshJitHelper::buildPostTable';

private const BUILD_FILES = 'PHPCompiler\\Web\\SuperglobalRefreshJitHelper::buildFilesTable';

private const BUILD_REQUEST = 'PHPCompiler\\Web\\SuperglobalRefreshJitHelper::buildRequestTable';

private const BUILD_SERVER = 'PHPCompiler\\Web\\SuperglobalRefreshJitHelper::buildServerTable';

private const BUILD_COOKIE = 'PHPCompiler\\Web\\SuperglobalRefreshJitHelper::buildCookieTable';

/** @var list<string> */
private const COMPILED_HELPERS = [
self::BUILD_GET,
self::BUILD_POST,
self::BUILD_FILES,
self::BUILD_REQUEST,
self::BUILD_SERVER,
self::BUILD_COOKIE,
];

/** @var list<array{0: string, 1: string}> */
private const REFRESH_STORES = [
['sg_GET', self::BUILD_GET],
['sg_POST', self::BUILD_POST],
['sg_FILES', self::BUILD_FILES],
['sg_REQUEST', self::BUILD_REQUEST],
['sg_SERVER', self::BUILD_SERVER],
['sg_COOKIE', self::BUILD_COOKIE],
];

public static function ensureLinked(Context $context): void
{
self::implement($context);
Expand Down Expand Up @@ -114,162 +78,15 @@ public static function implement(Context $context): void
return;
}

$restore = self::captureInsertBlock($context);
self::ensureGlobals($context);
self::ensureHeaderQueueExternal($context);
self::ensureJitHelperCompiled($context);

$fn = self::declareRefresh($context);
self::implementRefreshBridge($context, $fn);

self::restoreInsertBlock($context, $restore);
self::registerLinkedRuntime($context);
}

private static function declareRefresh(Context $context): LlvmFunction
{
$probe = $context->module->getNamedFunction('__superglobals__refresh');
if (null !== $probe) {
return $probe;
}

$fn = $context->module->addFunction(
'__superglobals__refresh',
$context->context->functionType($context->context->voidType(), false)
);
$context->registerFunction('__superglobals__refresh', $fn);

return $fn;
}

private static function implementRefreshBridge(Context $context, LlvmFunction $fn): void
{
if ($fn->countBasicBlocks() > 0) {
return;
}

$entry = $fn->appendBasicBlock('sg_refresh_bridge_entry');
$context->builder->positionAtEnd($entry);

$context->builder->call($context->lookupFunction('__phpc_header_queue_enable'));

foreach (self::REFRESH_STORES as [$globalName, $helperLogical]) {
$htRaw = JitNestedHelperCoerce::callHelper(
$context,
self::helperFunction($context, $helperLogical),
[]
);
$ht = JitNestedHelperCoerce::coerceToHashtablePtr($context, $htRaw);
$context->builder->store($ht, self::sgGlobalPtr($context, $globalName));
}

foreach (['sg_ENV', 'sg_SESSION'] as $globalName) {
$ht = $context->builder->call($context->lookupFunction('__hashtable__alloc'));
$context->builder->store($ht, self::sgGlobalPtr($context, $globalName));
}

$context->builder->returnVoid();
$context->builder->clearInsertionPosition();
}

private static function ensureGlobals(Context $context): void
{
$htPtr = $context->getTypeFromString('__hashtable__*');
foreach (['sg_GET', 'sg_POST', 'sg_REQUEST', 'sg_SERVER', 'sg_COOKIE', 'sg_ENV', 'sg_FILES', 'sg_SESSION'] as $name) {
if (null === $context->module->getNamedGlobal($name)) {
$g = $context->module->addGlobal($htPtr, $name);
$g->setInitializer($htPtr->constNull());
}
}
}

/**
* Type::register declares empty ABI shells; lookup alone is not enough (#20932 regression).
* Fill thin-AOT link stubs so ScriptExit / helper-runtime units resolve pending-header ABI.
*/
private static function ensureHeaderQueueExternal(Context $context): void
{
PendingHeadersRuntime::ensureThinAotLinkStubs($context);
}

private static function sgGlobalPtr(Context $context, string $name): Value
{
$global = $context->module->getNamedGlobal($name);
if (null === $global) {
throw new \LogicException('SuperglobalRefreshRuntime global missing: '.$name);
}
$htPtr = $context->getTypeFromString('__hashtable__*');

return $context->builder->pointerCast($global, $htPtr->pointerType(0));
}

private static function helperFunction(Context $context, string $logical): LlvmFunction
{
self::ensureJitHelperCompiled($context);
$lc = \strtolower($logical);
$fn = $context->functions[$lc] ?? null;
if (null === $fn) {
throw new \LogicException($logical.' missing after SuperglobalRefreshJitHelper compile (#9907)');
}

return $fn;
}

private static function ensureJitHelperCompiled(Context $context): void
{
$missing = false;
foreach (self::COMPILED_HELPERS as $logical) {
if (!isset($context->functions[\strtolower($logical)])) {
$missing = true;
break;
}
}
if (!$missing) {
return;
}

$runtime = $context->runtime;
$path = \dirname(__DIR__, 3).self::HELPER_PATH;
NestedJitCompileScope::run($context, static function () use ($context, $runtime, $path): void {
$block = $runtime->parseAndCompile((string) \file_get_contents($path), 'SuperglobalRefreshJitHelper.php');
if (null === $block) {
throw new \LogicException('SuperglobalRefreshJitHelper.php parseAndCompile failed (#9907)');
}
$jit = new JIT($context);
$jit->compile($block);
});
foreach (self::COMPILED_HELPERS as $logical) {
$lc = \strtolower($logical);
if (!isset($context->functions[$lc])) {
throw new \LogicException($lc.' was not compiled for JIT superglobal refresh (#9907)');
}
}
}

private static function captureInsertBlock(Context $context): ?\PHPLLVM\BasicBlock
{
try {
return $context->builder->getInsertBlock();
} catch (\Throwable) {
return null;
}
}

private static function restoreInsertBlock(Context $context, ?\PHPLLVM\BasicBlock $block): void
{
if (null !== $block) {
$context->builder->positionAtEnd($block);

return;
}
$context->builder->clearInsertionPosition();
// Always kernel for standalone — NestedJIT HashTable helper aborts on self-host (#21888).
JitSuperglobalRefreshKernel::implement($context);
}

private static function registerLinkedRuntime(Context $context): void
{
$fn = $context->module->getNamedFunction('__superglobals__refresh');
if (null === $fn || 0 === $fn->countBasicBlocks()) {
throw new \LogicException('__superglobals__refresh missing after SuperglobalRefreshRuntime bridge (#9907)');
throw new \LogicException('__superglobals__refresh missing after SuperglobalRefreshRuntime (#9907)');
}
$context->registerFunction('__superglobals__refresh', $fn);
}
Expand Down
57 changes: 57 additions & 0 deletions lib/JIT/ClassReturnCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@

/**
* Class/enum return type checks at JIT/AOT return sites (#17222, zend_type_hold.c).
*
* {@see \PHPCompiler\VM\HashTable} is special: NestedJIT lowers `: HashTable` to the
* `__hashtable__*` ABI (#21109), so return operands are native hashtables / i64 pointers —
* not `__object__*`. Treating them as ordinary class returns TypeErrors
* `HashTable, int returned` (#20652, #21888 / SuperglobalRefreshJitHelper).
*/
final class ClassReturnCheck
{
Expand All @@ -35,6 +40,11 @@ public static function enforce(
}
$expected = ltrim($block->returnDeclaredTypeLabel ?? $block->returnClassConstraint, '\\');
$callableName = self::callableName($block->func);
if (self::isVmHashTableClass($expected)
|| self::isVmHashTableClass($block->returnClassConstraint)
) {
return self::enforceVmHashTableReturn($context, $return, $callableName, $expected);
}
TypeErrorRaise::registerDeclarations($context);
TypeErrorRaise::ensureLinked($context);
$objectType = $context->type->object;
Expand All @@ -54,6 +64,53 @@ public static function enforce(
return self::branchOnBoolOrRaise($context, $ok, $callableName, $expected, $return, $objectType);
}

/** NestedJIT / param ABI name for {@see \PHPCompiler\VM\HashTable} (#21109). */
public static function isVmHashTableClass(string $name): bool
{
$lc = strtolower(ltrim($name, '\\'));

return 'phpcompiler\\vm\\hashtable' === $lc
|| str_ends_with($lc, '\\vm\\hashtable')
|| 'hashtable' === $lc;
}

/**
* Accept native hashtable ABI returns for `: HashTable` (#21888, #20652).
*
* @return bool false when a TypeError path was emitted
*/
private static function enforceVmHashTableReturn(
Context $context,
Variable $return,
?string $callableName,
string $expected
): bool {
TypeErrorRaise::registerDeclarations($context);
TypeErrorRaise::ensureLinked($context);
// __hashtable__* / NestedJIT i64 pointer / HashTable object / value-box hashtable
if (Variable::TYPE_HASHTABLE === $return->type
|| Variable::TYPE_NATIVE_LONG === $return->type
|| Variable::TYPE_OBJECT === $return->type
|| Variable::TYPE_VALUE === $return->type
|| 0 !== ($return->type & Variable::IS_NATIVE_ARRAY)
) {
return true;
}
if (Variable::TYPE_NULL === $return->type) {
self::raiseReturnTypeError($context, $callableName, $expected, 'null');

return false;
}
$scalarGiven = self::scalarGivenLabel($return);
if (null !== $scalarGiven) {
self::raiseReturnTypeError($context, $callableName, $expected, $scalarGiven);

return false;
}

return true;
}

private static function enforceValueBox(
Context $context,
ObjectType $objectType,
Expand Down
21 changes: 21 additions & 0 deletions test/unit/ClassReturnCheckHashTableTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\Test\Unit;

use PHPCompiler\JIT\ClassReturnCheck;
use PHPUnit\Framework\TestCase;

/** NestedJIT `: HashTable` uses __hashtable__* ABI — ClassReturnCheck must not TypeError (#21888, #20652). */
final class ClassReturnCheckHashTableTest extends TestCase
{
public function testVmHashTableClassNamesRecognized(): void
{
$this->assertTrue(ClassReturnCheck::isVmHashTableClass('PHPCompiler\\VM\\HashTable'));
$this->assertTrue(ClassReturnCheck::isVmHashTableClass('\\PHPCompiler\\VM\\HashTable'));
$this->assertTrue(ClassReturnCheck::isVmHashTableClass('HashTable'));
$this->assertFalse(ClassReturnCheck::isVmHashTableClass('stdClass'));
$this->assertFalse(ClassReturnCheck::isVmHashTableClass('PHPCompiler\\VM\\Variable'));
}
}
Loading