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
24 changes: 24 additions & 0 deletions ext/standard/VersionCompareJitHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\ext\standard;

/**
* Lowered into JIT/AOT modules for __compiler_version_compare (#9813, php-in-PHP).
*
* SSOT: {@see VmInfo::version_compare()} without operator (php-src ext/standard/versioning.c).
*/
final class VersionCompareJitHelper
{
/** @return int -1, 0, or 1 */
public static function compare(string $ver1, string $ver2): int
{
$result = VmInfo::version_compare($ver1, $ver2);
if (!\is_int($result)) {
throw new \LogicException('version_compare() without operator must return int (#9813)');
}

return $result;
}
}
114 changes: 111 additions & 3 deletions lib/JIT/Builtin/StringVersionCompare.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,128 @@

namespace PHPCompiler\JIT\Builtin;

use PHPCompiler\JIT;
use PHPCompiler\JIT\Context;
use PHPLLVM\Value\Function_ as LlvmFunction;

/**
* JIT LLVM body for __compiler_version_compare (php-src ext/standard/versioning.c; #6277).
* JIT/AOT link for __compiler_version_compare via VersionCompareJitHelper PHP (#9813).
*
* Replaces former ~800-line LLVM tokenization with thin bridge into {@see VmInfo} SSOT.
* php-src: ext/standard/versioning.c — php_version_compare
*/
final class StringVersionCompare
{
private const HELPER_PATH = '/ext/standard/VersionCompareJitHelper.php';

private const COMPARE_HELPER = 'PHPCompiler\\ext\\standard\\VersionCompareJitHelper::compare';

/** @var list<string> */
private const COMPILED_HELPERS = [
self::COMPARE_HELPER,
];

/** @var list<string> */
private const ABI_FUNCTIONS = [
'__compiler_version_compare',
];

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

public static function implement(Context $context): void
{
self::ensureLinked($context);
$probe = $context->module->getNamedFunction('__compiler_version_compare');
if (null !== $probe && $probe->countBasicBlocks() > 0) {
self::registerLinkedRuntime($context);

return;
}

self::ensureJitHelperCompiled($context);
self::implementCompareBridge($context);
self::registerLinkedRuntime($context);
$context->builder->clearInsertionPosition();
}

private static function implementCompareBridge(Context $context): void
{
$abiName = '__compiler_version_compare';
$probe = $context->module->getNamedFunction($abiName);
if (null !== $probe && $probe->countBasicBlocks() > 0) {
$context->registerFunction($abiName, $probe);

return;
}

$strPtr = $context->getTypeFromString('__string__*');
$i64 = $context->getTypeFromString('int64');
$ft = $context->context->functionType($i64, false, $strPtr, $strPtr);
$fn = null !== $probe
? $probe
: $context->module->addFunction($abiName, $ft);

$entry = $fn->appendBasicBlock('version_compare_bridge_entry');
$context->builder->positionAtEnd($entry);
$result = $context->builder->call(
self::helperFunction($context, self::COMPARE_HELPER),
$fn->getParam(0),
$fn->getParam(1)
);
$context->builder->returnValue($result);
$context->registerFunction($abiName, $fn);
}

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 VersionCompareJitHelper compile (#9813)');
}

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;
$block = $runtime->parseAndCompile((string) \file_get_contents($path), 'VersionCompareJitHelper.php');
if (null === $block) {
throw new \LogicException('VersionCompareJitHelper.php parseAndCompile failed (#9813)');
}
$jit = new JIT($context);
$jit->compile($block);
foreach (self::COMPILED_HELPERS as $logical) {
if (!isset($context->functions[\strtolower($logical)])) {
throw new \LogicException($logical.' was not compiled for JIT (#9813)');
}
}
}

private static function registerLinkedRuntime(Context $context): void
{
foreach (self::ABI_FUNCTIONS as $name) {
$fn = $context->module->getNamedFunction($name);
if (null === $fn) {
throw new \LogicException($name.' missing after StringVersionCompare bridge (#9813)');
}
$context->registerFunction($name, $fn);
}
}
}
Loading