Skip to content
Open
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
50 changes: 46 additions & 4 deletions ext/dom/VmDomInstanceInvoke.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,66 @@

namespace PHPCompiler\ext\dom;

use PHPCompiler\VM\ObjectEntry;
use PHPCompiler\VM\Variable;
use PHPCompiler\VM\VariableObject;

/**
* DOM instance method dispatch for JIT/AOT helpers (#17130).
*
* Keep this file to a single compiled entrypoint; bodies live in VmDomJitDispatch.
* Keep this file to per-arity entrypoints; bodies live in VmDomJitDispatch.
*/
final class VmDomInstanceInvoke
{
public static function invokeArgv(Variable $receiver, string $methodLc, Variable $argsTable): Variable
public static function invoke0Object(Variable $receiver, string $methodLc): Variable
{
return self::dispatch($receiver, $methodLc);
}

public static function invoke1Object(Variable $receiver, string $methodLc, Variable $a1): Variable
{
return self::dispatch($receiver, $methodLc, $a1);
}

public static function invoke2Object(
Variable $receiver,
string $methodLc,
Variable $a1,
Variable $a2
): Variable {
return self::dispatch($receiver, $methodLc, $a1, $a2);
}

public static function invoke3Object(
Variable $receiver,
string $methodLc,
Variable $a1,
Variable $a2,
Variable $a3
): Variable {
return self::dispatch($receiver, $methodLc, $a1, $a2, $a3);
}

public static function invoke4Object(
Variable $receiver,
string $methodLc,
Variable $a1,
Variable $a2,
Variable $a3,
Variable $a4
): Variable {
return self::dispatch($receiver, $methodLc, $a1, $a2, $a3, $a4);
}

private static function dispatch(Variable $receiver, string $methodLc, Variable ...$extra): Variable
{
$self = VariableObject::entry($receiver->resolveIndirect());
$ctx = VmDomJitFrame::vmContext();
$extra = VmDomJitDispatch::unpackArgs($argsTable);
$self = VariableObject::entry($receiver);
$methodLc = strtolower($methodLc);

return match ($methodLc) {
'loadhtml' => VmDomJitDispatch::loadHTML($ctx, $self, $extra),
'getelementbyid' => VmDomJitDispatch::getElementById($self, $extra),
'createelement' => VmDomJitDispatch::createElement($ctx, $self, $extra),
'appendchild' => VmDomJitDispatch::appendChild($ctx, $self, $extra),
'setattribute' => VmDomJitDispatch::setAttribute($ctx, $self, $extra),
Expand Down
38 changes: 38 additions & 0 deletions ext/dom/VmDomJitDispatch.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,44 @@
*/
final class VmDomJitDispatch
{
/**
* @param list<Variable> $extra
*/
public static function loadHTML(VmContext $ctx, ObjectEntry $document, array $extra): Variable
{
$html = self::stringArg($extra[0] ?? self::missingArg('loadHTML', 0), 'loadHTML', 0);
$options = 0;
if (isset($extra[1])) {
$optionsVar = $extra[1]->resolveIndirect();
if (Variable::TYPE_INTEGER !== $optionsVar->type) {
throw new \TypeError('DOMDocument::loadHTML(): Argument #2 ($options) must be of type int');
}
$options = $optionsVar->toInt();
}
$ok = VmDom::loadHTML($ctx, $document, $html, $options, VmDomJitFrame::executingFrame());
$var = new Variable();
$var->bool($ok);

return $var;
}

/**
* @param list<Variable> $extra
*/
public static function getElementById(ObjectEntry $document, array $extra): Variable
{
$id = self::stringArg($extra[0] ?? self::missingArg('getElementById', 0), 'getElementById', 0);
$found = VmDom::getElementById($document, $id);
$var = new Variable();
if (null === $found) {
$var->null();
} else {
$var->object($found);
}

return $var;
}

/**
* @param list<Variable> $extra
*/
Expand Down
26 changes: 9 additions & 17 deletions ext/dom/VmDomJitFrame.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,32 @@
use PHPCompiler\VM\Context as VmContext;
use PHPCompiler\Web\Superglobals;

/** Active frame lookup for DOM JIT helpers — not nested-compiled (#17130). */
/** Active VM context for DOM JIT/AOT helpers (#17130). */
final class VmDomJitFrame
{
public static function vmContext(): VmContext
{
$ctx = self::executingFrame()->vmContext;
$ctx = Superglobals::getActiveContext();
if (null === $ctx) {
throw new \LogicException('VmDomJitFrame requires VM context');
throw new \LogicException(
'VmDomJitFrame requires an active VM context in this compiler build'
);
}

return $ctx;
}

public static function executingFrame(): Frame
public static function executingFrame(): ?Frame
{
$ctx = Superglobals::getActiveContext();
if (null === $ctx) {
throw new \LogicException(
'VmDomJitFrame requires an active VM context in this compiler build'
);
return null;
}
$vm = $ctx->runtime->vm;
if (null === $vm) {
throw new \LogicException(
'VmDomJitFrame requires an active VM in this compiler build'
);
}
$frame = $vm->currentExecutingFrame();
if (null === $frame) {
throw new \LogicException(
'VmDomJitFrame requires an active executing frame in this compiler build'
);
return null;
}

return $frame;
return $vm->currentExecutingFrame();
}
}
114 changes: 90 additions & 24 deletions lib/JIT/Builtin/DomInstanceMethodRuntime.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,91 @@
namespace PHPCompiler\JIT\Builtin;

use PHPCompiler\JIT\Context;
use PHPCompiler\JIT\JitValueBox;
use PHPCompiler\JIT\JitVmHelperLink;
use PHPCompiler\JIT\Variable;
use PHPLLVM\Value;

/**
* JIT/AOT bridge for ext/dom instance methods via DomInstanceMethodJitHelper (#17130).
* JIT/AOT bridge for ext/dom instance methods via VmDomInstanceInvoke (#17130).
*
* php-src: ext/dom/php_dom.c — DOM*::method handlers
*/
final class DomInstanceMethodRuntime
{
public const ABI = '__phpc_jit_dom_instance_method';
public const MAX_EXTRA_ARGS = 4;

private const HELPER_PATH = '/ext/dom/VmDomInstanceInvoke.php';

private const INVOKE_HELPER = 'PHPCompiler\\ext\\dom\\VmDomInstanceInvoke::invokeArgv';
private const ABI_PREFIX = '__phpc_jit_dom_instance_method_';

/** @var array<int, string> */
private const INVOKE_BY_ARITY = [
0 => 'PHPCompiler\\ext\\dom\\VmDomInstanceInvoke::invoke0Object',
1 => 'PHPCompiler\\ext\\dom\\VmDomInstanceInvoke::invoke1Object',
2 => 'PHPCompiler\\ext\\dom\\VmDomInstanceInvoke::invoke2Object',
3 => 'PHPCompiler\\ext\\dom\\VmDomInstanceInvoke::invoke3Object',
4 => 'PHPCompiler\\ext\\dom\\VmDomInstanceInvoke::invoke4Object',
];

/** @var list<string> */
private const COMPILED_HELPERS = [
self::INVOKE_HELPER,
self::INVOKE_BY_ARITY[0],
self::INVOKE_BY_ARITY[1],
self::INVOKE_BY_ARITY[2],
self::INVOKE_BY_ARITY[3],
self::INVOKE_BY_ARITY[4],
];

public static function invoke(
Context $context,
int $extraArgCount,
string $methodLc,
Variable $receiver,
Variable ...$extraArgs
): Value {
if ($extraArgCount !== \count($extraArgs)) {
throw new \LogicException('DomInstanceMethodRuntime arity mismatch');
}
if ($extraArgCount > self::MAX_EXTRA_ARGS) {
throw new \LogicException('Too many arguments for DOM instance method JIT bridge');
}
self::ensureBridge($context, $extraArgCount);
$llvmArgs = [
self::receiverValuePtr($context, $receiver),
$context->builder->load($context->constantStringFromString($methodLc)),
];
foreach ($extraArgs as $arg) {
$llvmArgs[] = JitValueBox::valuePtrFromVariable($context, $arg);
}

return $context->builder->call(
$context->lookupFunction(self::ABI_PREFIX.$extraArgCount),
...$llvmArgs
);
}

public static function ensureLinked(Context $context): void
{
$probe = $context->module->getNamedFunction(self::ABI);
for ($i = 0; $i <= self::MAX_EXTRA_ARGS; ++$i) {
self::ensureBridge($context, $i);
}
}

public static function ensureStandaloneBodies(Context $context): void
{
self::ensureLinked($context);
}

public static function ensureBridge(Context $context, int $extraArgCount): void
{
if ($extraArgCount < 0 || $extraArgCount > self::MAX_EXTRA_ARGS) {
throw new \LogicException('Invalid DOM instance method JIT bridge arity');
}
$abi = self::ABI_PREFIX.$extraArgCount;
$probe = $context->module->getNamedFunction($abi);
if (null !== $probe && $probe->countBasicBlocks() > 0) {
$context->registerFunction(self::ABI, $probe);
$context->registerFunction($abi, $probe);

return;
}
Expand All @@ -43,13 +102,17 @@ public static function ensureLinked(Context $context): void

$valuePtr = $context->getTypeFromString('__value__*');
$strPtr = $context->getTypeFromString('__string__*');
$paramTypes = [$valuePtr, $strPtr];
for ($i = 0; $i < $extraArgCount; ++$i) {
$paramTypes[] = $valuePtr;
}
JitVmHelperLink::ensureBridge(
$context,
self::ABI,
'dom_instance_method_bridge_entry',
[$valuePtr, $strPtr, $valuePtr],
$abi,
'dom_instance_method_bridge_'.$extraArgCount,
$paramTypes,
$valuePtr,
self::INVOKE_HELPER,
self::INVOKE_BY_ARITY[$extraArgCount],
self::HELPER_PATH,
self::COMPILED_HELPERS,
'#17130'
Expand All @@ -62,20 +125,23 @@ public static function ensureLinked(Context $context): void
}
}

public static function invoke(
Context $context,
Value $receiverBox,
string $methodLc,
Value $argsBox
): Value {
self::ensureLinked($context);
$methodConst = $context->builder->load($context->constantStringFromString($methodLc));
private static function receiverValuePtr(Context $context, Variable $receiver): Value
{
if (Variable::TYPE_VALUE === $receiver->type) {
return JitValueBox::valuePtrFromVariable($context, $receiver);
}
$slot = JitValueBox::alloc($context);
$ptr = JitValueBox::pointer($context, $slot);
if (Variable::TYPE_OBJECT === $receiver->type) {
$context->builder->call(
$context->lookupFunction('__value__writeObject'),
$ptr,
$context->helper->loadValue($receiver)
);

return $context->builder->call(
$context->lookupFunction(self::ABI),
$receiverBox,
$methodConst,
$argsBox
);
return $ptr;
}

throw new \LogicException('DOM instance method receiver must be object or value box');
}
}
Loading