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
500 changes: 283 additions & 217 deletions docs/bootstrap-inventory.md

Large diffs are not rendered by default.

31 changes: 31 additions & 0 deletions ext/dom/DomParseSimpleXmlJitHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,37 @@ public static function rootTagArgv(string $xml): string
return 'root';
}

/**
* Root open-tag attributes for user-script AOT createFromString / loadXML (#27108).
*
* @return list<array{qname: string, value: string}>
*/
public static function rootAttributesArgv(string $xml): array
{
if (!preg_match('/<([a-zA-Z_][\w:.-]*)((?:\s[^>]*)?)\/?>/', $xml, $root)) {
return [];
}
$attrs = $root[2] ?? '';
if ('' === trim($attrs)) {
return [];
}
$out = [];
if (!preg_match_all('/([A-Za-z_][\w:.-]*)\s*=\s*"([^"]*)"/', $attrs, $pairs, PREG_SET_ORDER)
&& !preg_match_all("/([A-Za-z_][\w:.-]*)\s*=\s*'([^']*)'/", $attrs, $pairs, PREG_SET_ORDER)
) {
return [];
}
foreach ($pairs as $pair) {
$qname = $pair[1];
if (0 === stripos($qname, 'xmlns')) {
continue;
}
$out[] = ['qname' => $qname, 'value' => $pair[2]];
}

return $out;
}

/**
* Document-element textContent for user-script AOT (#25475).
*
Expand Down
142 changes: 115 additions & 27 deletions ext/dom/DomUserScriptAttributeCacheLlvm.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,58 +12,135 @@
*
* Each compile-time (namespace, localName) pair maps to a module global holding
* a DOMAttr*. Avoids DomRegistry bridges that segfault in standalone AOT.
*
* State is keyed by LLVM module id so NestedJIT module switches do not wipe
* the main-module present/value maps (#27108 rename dup check).
*/
final class DomUserScriptAttributeCacheLlvm
{
/** @var array<string, Value> */
private static array $slotByKey = [];

/** @var object|null */
private static $moduleIdentity = null;

private static ?string $lastCreateNamespace = null;

private static ?string $lastCreateLocalName = null;
/**
* @var array<int, array{
* slotByKey: array<string, Value>,
* presentByKey: array<string, true>,
* valueByKey: array<string, string>,
* lastCreateNamespace: ?string,
* lastCreateLocalName: ?string
* }>
*/
private static array $byModule = [];

public static function rememberCreate(string $namespace, string $qualifiedName): void
{
// rememberCreate is compile-time only; bind to whatever module is current later via store.
$pos = strpos($qualifiedName, ':');
$local = false === $pos ? $qualifiedName : substr($qualifiedName, $pos + 1);
self::$lastCreateNamespace = $namespace;
self::$lastCreateLocalName = $local;
self::$pendingCreate = [$namespace, $local];
}

/** @var null|array{0: string, 1: string} */
private static ?array $pendingCreate = null;

public static function lastCreateNamespace(): ?string
{
return self::$lastCreateNamespace;
return self::$pendingCreate[0] ?? null;
}

public static function lastCreateLocalName(): ?string
{
return self::$lastCreateLocalName;
return self::$pendingCreate[1] ?? null;
}

/** Compile-time cache presence check for getAttribute dispatch (#19281). */
/** Compile-time cache presence check for getAttribute / hasAttribute (#19281, #27108). */
public static function hasLiteralKey(string $namespace, string $localName): bool
{
foreach (self::$byModule as $state) {
$key = $namespace."\0".$localName;
if (isset($state['presentByKey'][$key]) || isset($state['slotByKey'][$key])) {
return true;
}
}

return false;
}

/** True when a non-null Attr was stored for this key this module (#27108). */
public static function hasPresentLiteral(string $namespace, string $localName): bool
{
foreach (self::$byModule as $state) {
if (isset($state['presentByKey'][$namespace."\0".$localName])) {
return true;
}
}

return false;
}

public static function literalValue(string $namespace, string $localName): ?string
{
$key = $namespace."\0".$localName;
foreach (self::$byModule as $state) {
if (isset($state['valueByKey'][$key])) {
return $state['valueByKey'][$key];
}
}

return isset(self::$slotByKey[$key]);
return null;
}

public static function storeLiteral(
Context $context,
string $namespace,
string $localName,
Value $attr
Value $attr,
?string $value = null
): Value {
$state = &self::state($context);
if (null !== self::$pendingCreate) {
$state['lastCreateNamespace'] = self::$pendingCreate[0];
$state['lastCreateLocalName'] = self::$pendingCreate[1];
}
$global = self::slotGlobal($context, $namespace, $localName);
$prev = $context->builder->load($global);
$context->builder->store($attr, $global);
$key = $namespace."\0".$localName;
$state['presentByKey'][$key] = true;
if (null !== $value) {
$state['valueByKey'][$key] = $value;
}

return $prev;
}

public static function rekeyLiteral(
Context $context,
string $oldNamespace,
string $oldLocalName,
string $newNamespace,
string $newLocalName,
Value $attr
): void {
$state = &self::state($context);
$objPtr = $context->getTypeFromString('__object__*');
$oldGlobal = self::slotGlobal($context, $oldNamespace, $oldLocalName);
$context->builder->store($objPtr->constNull(), $oldGlobal);
$oldKey = $oldNamespace."\0".$oldLocalName;
$value = $state['valueByKey'][$oldKey] ?? null;
unset($state['presentByKey'][$oldKey], $state['valueByKey'][$oldKey]);
self::storeLiteral($context, $newNamespace, $newLocalName, $attr, $value);
}

public static function clearLiteral(
Context $context,
string $namespace,
string $localName
): void {
$state = &self::state($context);
$objPtr = $context->getTypeFromString('__object__*');
$global = self::slotGlobal($context, $namespace, $localName);
$context->builder->store($objPtr->constNull(), $global);
unset($state['presentByKey'][$namespace."\0".$localName], $state['valueByKey'][$namespace."\0".$localName]);
}

public static function lookupLiteral(
Context $context,
string $namespace,
Expand All @@ -74,33 +151,44 @@ public static function lookupLiteral(
return $context->builder->load($global);
}

private static function slotGlobal(Context $context, string $namespace, string $localName): Value
/** @return array{slotByKey: array<string, Value>, presentByKey: array<string, true>, valueByKey: array<string, string>, lastCreateNamespace: ?string, lastCreateLocalName: ?string} */
private static function &state(Context $context): array
{
$module = $context->module;
if (self::$moduleIdentity !== $module) {
self::$moduleIdentity = $module;
self::$slotByKey = [];
self::$lastCreateNamespace = null;
self::$lastCreateLocalName = null;
$id = spl_object_id($context->module);
if (!isset(self::$byModule[$id])) {
self::$byModule[$id] = [
'slotByKey' => [],
'presentByKey' => [],
'valueByKey' => [],
'lastCreateNamespace' => null,
'lastCreateLocalName' => null,
];
}

return self::$byModule[$id];
}

private static function slotGlobal(Context $context, string $namespace, string $localName): Value
{
$state = &self::state($context);
$key = $namespace."\0".$localName;
if (isset(self::$slotByKey[$key])) {
return self::$slotByKey[$key];
if (isset($state['slotByKey'][$key])) {
return $state['slotByKey'][$key];
}

$module = $context->module;
$objPtr = $context->getTypeFromString('__object__*');
$globalName = '__phpc_dom_us_attr_'.substr(hash('crc32b', $key), 0, 8);
$existing = $module->getNamedGlobal($globalName);
if (null !== $existing) {
self::$slotByKey[$key] = $existing;
$state['slotByKey'][$key] = $existing;

return $existing;
}

$global = $module->addGlobal($objPtr, $globalName);
$global->setInitializer($objPtr->constNull());
self::$slotByKey[$key] = $global;
$state['slotByKey'][$key] = $global;

return $global;
}
Expand Down
38 changes: 38 additions & 0 deletions ext/dom/DomXmlDocumentCreateFromStringJitHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\ext\dom;

use PHPCompiler\VM\Context;
use PHPCompiler\VM\ObjectEntry;
use PHPCompiler\VM\Variable;

/**
* Dom\XMLDocument::createFromString() for compiled JIT/AOT modules (#27108 / #19581).
*
* php-src: ext/dom/xml_document.c — load_from_helper(DOM_LOAD_STRING)
*
* Note: thin-AOT get_class / documentElement still segfault — NestedJIT ObjectEntry* is
* not thin __object__ layout (class_id load crashes; rewriting class_id also crashes).
* Next: LLVM materialize Dom\XMLDocument (peer JitDomLoadXMLUserScript) + DomRegistry
* Attr slots for rename, or NestedJIT-only get_class/property with no thin class_id (#27108).
*/
final class DomXmlDocumentCreateFromStringJitHelper
{
public static function createFromStringArgv(
Context $ctx,
string $source,
int $options = 0
): ObjectEntry {
$var = VmDomLiving::createXmlFromString($ctx, $source, $options);
if (Variable::TYPE_OBJECT !== $var->type) {
throw new \DOMException(
'Invalid State Error',
DomExceptionConstants::INVALID_STATE_ERR
);
}

return $var->toObject();
}
}
Loading
Loading