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
31 changes: 24 additions & 7 deletions lib/JIT/Builtin/Type/HashTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,11 @@ public function implement(): void
// Type::register no longer eagerly NestedJIT StringTriggerError (#32122 .1 mint).
StringTriggerError::declareUndefinedArrayKeyAbis($this->context);
StringTriggerError::ensureLinked($this->context);
$this->ensureStringCompareAbis();
// StringNaturalCompare / StringStrcoll always-on ensureLinked removed (#35626):
// locale/natural sort implement* call ensureStrcollAbis / ensureNaturalCompareAbis
// before lookupFunction (peer #35614 Type::String_::implement lazy batch). Thin
// hello-world must not NestedJIT strcoll/strnatcmp during init — leftover HashTable
// NestedJIT vs Runtime ABI drift mints strcoll.1 / strnatcmp.1 (#31894 / #32122).
$this->ensureLibcStrtol();
$this->implementAlloc();
$this->implementGrow();
Expand Down Expand Up @@ -236,17 +240,25 @@ private function ensureLibcStrtol(): void
}

/**
* Length-aware string compare + PHP natural-order ABIs (#29019).
* Locale string compare ABI — only for ksort/asort locale sort implement* (#35626).
*
* Do not declare empty `strcoll` externs — PHP algorithm owned by {@see StringStrcoll},
* not libc. ksort string keys without locale use {@see JitStringCompare::strcmp} (memcmp).
*/
private function ensureStrcollAbis(): void
{
StringStrcoll::ensureLinked($this->context);
}

/**
* PHP natural-order compare ABI — only for natsort implement* (#29019 / #35626).
*
* Do not declare empty `strnatcmp`/`strnatcasecmp` externs — those are PHP algorithms
* owned by {@see StringNaturalCompare}, not libc. ksort string keys use
* {@see JitStringCompare::strcmp} (memcmp); locale sorts use {@see StringStrcoll}.
* owned by {@see StringNaturalCompare}, not libc.
*/
private function ensureStringCompareAbis(): void
private function ensureNaturalCompareAbis(): void
{
LibcExtern::register($this->context);
StringNaturalCompare::ensureStandaloneBodies($this->context);
StringStrcoll::ensureLinked($this->context);
}

private function implementAlloc(): void
Expand Down Expand Up @@ -2329,6 +2341,7 @@ private function implementSortStringKeys(bool $caseInsensitive = false): void

private function implementSortStringKeysLocale(): void
{
$this->ensureStrcollAbis();
$fn = $this->context->lookupFunction('__hashtable__sortStringKeysLocale');
$main = $fn->appendBasicBlock('main');
$this->context->builder->positionAtEnd($main);
Expand Down Expand Up @@ -2714,6 +2727,7 @@ private function implementSortStringKeyValues(): void

private function implementSortStringKeyValuesLocale(): void
{
$this->ensureStrcollAbis();
$fn = $this->context->lookupFunction('__hashtable__sortStringKeyValuesLocale');
$main = $fn->appendBasicBlock('main');
$this->context->builder->positionAtEnd($main);
Expand Down Expand Up @@ -2869,6 +2883,7 @@ private function implementSortStringKeyValuesLocale(): void

private function implementSortStringKeyValuesNatural(): void
{
$this->ensureNaturalCompareAbis();
$fn = $this->context->lookupFunction('__hashtable__sortStringKeyValuesNatural');
$main = $fn->appendBasicBlock('main');
$this->context->builder->positionAtEnd($main);
Expand Down Expand Up @@ -2994,6 +3009,7 @@ private function implementSortStringKeyValuesNatural(): void

private function implementSortStringKeyValuesNaturalCase(): void
{
$this->ensureNaturalCompareAbis();
$fn = $this->context->lookupFunction('__hashtable__sortStringKeyValuesNaturalCase');
$main = $fn->appendBasicBlock('main');
$this->context->builder->positionAtEnd($main);
Expand Down Expand Up @@ -3408,6 +3424,7 @@ private function implementSortPacked(bool $reverse, bool $caseInsensitive = fals
*/
private function implementSortPackedNatural(bool $caseInsensitive): void
{
$this->ensureNaturalCompareAbis();
$abi = $caseInsensitive
? '__hashtable__sortPackedNaturalCase'
: '__hashtable__sortPackedNatural';
Expand Down
4 changes: 3 additions & 1 deletion test/unit/HashTableCompareRuntimeShrinkTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ final class HashTableCompareRuntimeShrinkTest extends TestCase
public function testHashTableDoesNotDeclareEmptyStrnatLibcExterns(): void
{
$source = (string) file_get_contents(__DIR__.'/../../lib/JIT/Builtin/Type/HashTable.php');
$this->assertStringContainsString('ensureStringCompareAbis', $source);
$this->assertStringContainsString('ensureStrcollAbis', $source);
$this->assertStringContainsString('ensureNaturalCompareAbis', $source);
$this->assertStringContainsString('StringNaturalCompare::ensureStandaloneBodies', $source);
$this->assertStringContainsString('StringStrcoll::ensureLinked', $source);
$this->assertStringContainsString('#35626', $source);
$this->assertStringNotContainsString('ensureLibcStringCompare', $source);
$this->assertStringNotContainsString("addFunction('strnatcmp'", $source);
$this->assertStringNotContainsString("addFunction('strnatcasecmp'", $source);
Expand Down
104 changes: 104 additions & 0 deletions test/unit/HashTableLazyStringCompareRuntimeShrinkTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\Test\Unit;

use PHPUnit\Framework\TestCase;

/**
* Drop HashTable::implement always-on NestedJIT for locale/natural string compare ABIs
* (#35626 / peer #35614 Type::String_::implement lazy batch).
*
* Thin hello-world AOT must not NestedJIT strcoll/strnatcmp during init — leftover
* HashTable NestedJIT vs Runtime ABI drift mints *.1 (#31894 / #32122).
*/
final class HashTableLazyStringCompareRuntimeShrinkTest extends TestCase
{
public function testHashTableImplementDropsEagerStringCompareEnsureLinked(): void
{
$source = (string) file_get_contents(__DIR__.'/../../lib/JIT/Builtin/Type/HashTable.php');
$this->assertStringContainsString('#35626', $source);
$pos = strpos($source, 'public function implement(): void');
$this->assertNotFalse($pos);
$next = strpos($source, 'private function ensureLibcStrtol', $pos);
$this->assertNotFalse($next);
$body = substr($source, $pos, $next - $pos);

foreach ([
'$this->ensureStringCompareAbis',
'$this->ensureStrcollAbis',
'$this->ensureNaturalCompareAbis',
'StringNaturalCompare::ensureStandaloneBodies',
'StringStrcoll::ensureLinked',
] as $forbidden) {
$this->assertStringNotContainsString(
$forbidden,
$body,
'HashTable::implement must not eagerly '.$forbidden.' (#35626)'
);
}
}

public function testLocaleSortImplementMethodsEnsureStrcollBeforeLookup(): void
{
$source = (string) file_get_contents(__DIR__.'/../../lib/JIT/Builtin/Type/HashTable.php');
foreach ([
'implementSortStringKeysLocale',
'implementSortStringKeyValuesLocale',
] as $method) {
$fnPos = strpos($source, "private function {$method}(");
$this->assertNotFalse($fnPos, $method.' must exist');
$nextFn = strpos($source, 'private function ', $fnPos + 1);
$chunk = false === $nextFn ? substr($source, $fnPos) : substr($source, $fnPos, $nextFn - $fnPos);
$this->assertStringContainsString(
'ensureStrcollAbis',
$chunk,
$method.' must ensureStrcollAbis before lookup (#35626)'
);
$this->assertStringContainsString(
'StringStrcoll::ABI_STRCOLL',
$chunk,
$method.' must lookup strcoll ABI (#35626)'
);
}
}

public function testNaturalSortImplementMethodsEnsureNaturalCompareBeforeLookup(): void
{
$source = (string) file_get_contents(__DIR__.'/../../lib/JIT/Builtin/Type/HashTable.php');
foreach ([
'implementSortStringKeyValuesNatural',
'implementSortStringKeyValuesNaturalCase',
'implementSortPackedNatural',
] as $method) {
$fnPos = strpos($source, "private function {$method}(");
$this->assertNotFalse($fnPos, $method.' must exist');
$nextFn = strpos($source, 'private function ', $fnPos + 1);
$chunk = false === $nextFn ? substr($source, $fnPos) : substr($source, $fnPos, $nextFn - $fnPos);
$this->assertStringContainsString(
'ensureNaturalCompareAbis',
$chunk,
$method.' must ensureNaturalCompareAbis before lookup (#35626)'
);
}
}

public function testNoNewRuntimeCForLazyStringCompareAbis(): void
{
$runtimeDir = dirname(__DIR__, 2).'/lib/AOT/runtime';
foreach ([
'strcoll.c',
'strnatcmp.c',
'strnatcasecmp.c',
] as $name) {
$this->assertFileDoesNotExist(
$runtimeDir.'/'.$name,
'must not add '.$name.' for #35626 — PHP JIT bridges only'
);
}
$linker = (string) file_get_contents(__DIR__.'/../../lib/AOT/Linker.php');
$this->assertStringContainsString('RUNTIME_C_SOURCES = [', $linker);
$this->assertStringNotContainsString('strcoll.c', $linker);
}
}
Loading