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
124 changes: 23 additions & 101 deletions lib/JIT/Builtin/StringChunkSplit.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,22 @@

namespace PHPCompiler\JIT\Builtin;

use PHPCompiler\JIT;
use PHPCompiler\JIT\BasicBlockHelper;
use PHPCompiler\JIT\Context;
use PHPCompiler\JIT\JitVmHelperLink;
use PHPCompiler\JIT\NestedJitCompileScope;
use PHPLLVM\Value\Function_ as LlvmFunction;

/**
* JIT/AOT link for __compiler_chunk_split via ChunkSplitJitHelper PHP (#14626).
* JIT/AOT link for __compiler_chunk_split via ChunkSplitJitHelper PHP (#14626, #21399).
*
* Replaces inline ~110-line LLVM loop in JitChunkSplit::split().
* Nested helper compile: {@see JitVmHelperLink::ensureBridge} (HelperRuntimeCache + user-script
* env clear — no hand-rolled NestedJit compile loop). Peer: StringSoundex #21362 / StringMetaphone #21342.
* SSOT: {@see \PHPCompiler\ext\standard\VmString}.
* php-src: ext/standard/string.c — PHP_FUNCTION(chunk_split)
*/
final class StringChunkSplit
{
private const ABI = '__compiler_chunk_split';

private const HELPER_PATH = '/ext/standard/ChunkSplitJitHelper.php';

private const CHUNK_SPLIT_HELPER = 'PHPCompiler\\ext\\standard\\ChunkSplitJitHelper::chunkSplitArgv';
Expand All @@ -28,10 +29,7 @@ final class StringChunkSplit
self::CHUNK_SPLIT_HELPER,
];

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

public static function ensureLinked(Context $context): void
{
Expand All @@ -40,110 +38,34 @@ public static function ensureLinked(Context $context): void

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

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

if (NestedJitCompileScope::isActive()) {
return;
}

// Restore caller insert block after bridge emit (#19998 / #21190) — clearInsertionPosition
// left the user-script builder detached ("Current basic block has no parent function").
$savedInsert = BasicBlockHelper::tryGetInsertBlock($context);
self::ensureJitHelperCompiled($context);
self::implementBridge($context);
self::registerLinkedRuntime($context);
if (null !== $savedInsert) {
BasicBlockHelper::restoreInsertBlock($context, $savedInsert);
} else {
$context->builder->clearInsertionPosition();
}
}

private static function implementBridge(Context $context): void
{
$abiName = '__compiler_chunk_split';
$probe = $context->module->getNamedFunction($abiName);
if (null !== $probe && $probe->countBasicBlocks() > 0) {
$context->registerFunction($abiName, $probe);
$probe = $context->module->getNamedFunction(self::ABI);
if (JitVmHelperLink::hasNamedBridgeEntry($probe, self::BRIDGE_ENTRY)) {
$context->registerFunction(self::ABI, $probe);

return;
}

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

$entry = $fn->appendBasicBlock('chunk_split_bridge_entry');
$context->builder->positionAtEnd($entry);
$result = $context->builder->call(
self::helperFunction($context, self::CHUNK_SPLIT_HELPER),
$fn->getParam(0),
$fn->getParam(1),
$fn->getParam(2)
JitVmHelperLink::ensureBridge(
$context,
self::ABI,
self::BRIDGE_ENTRY,
[$strPtr, $i64, $strPtr],
$strPtr,
self::CHUNK_SPLIT_HELPER,
self::HELPER_PATH,
self::COMPILED_HELPERS,
'#21399'
);
$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 ChunkSplitJitHelper compile (#14626)');
}

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), 'ChunkSplitJitHelper.php');
if (null === $block) {
throw new \LogicException('ChunkSplitJitHelper.php parseAndCompile failed (#14626)');
}
$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 (#14626)');
}
}
}

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 StringChunkSplit bridge (#14626)');
}
$context->registerFunction($name, $fn);
}
}
}
6 changes: 5 additions & 1 deletion test/unit/ChunkSplitRuntimeShrinkTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@
use PHPCompiler\ext\standard\VmString;
use PHPUnit\Framework\TestCase;

/** chunk_split() JIT routes through ChunkSplitJitHelper PHP not inline LLVM (#14626). */
/** chunk_split() JIT routes through ChunkSplitJitHelper PHP not inline LLVM (#14626, #21399). */
final class ChunkSplitRuntimeShrinkTest extends TestCase
{
public function testStringChunkSplitUsesJitHelperNotInlineLlvm(): void
{
$source = (string) file_get_contents(__DIR__.'/../../lib/JIT/Builtin/StringChunkSplit.php');
$this->assertStringContainsString('ChunkSplitJitHelper', $source);
$this->assertStringContainsString('JitVmHelperLink::ensureBridge', $source);
$this->assertStringNotContainsString('parseAndCompile', $source);
$this->assertStringNotContainsString('new JIT(', $source);
$this->assertStringNotContainsString('use PHPCompiler\\JIT;', $source);

$jitChunk = (string) file_get_contents(__DIR__.'/../../ext/standard/JitChunkSplit.php');
$this->assertStringNotContainsString('chunksplit_head', $jitChunk);
Expand Down