From 1fbd8a7b4cc217c13ac9eac2a159ed0c3858c3b0 Mon Sep 17 00:00:00 2001 From: PurHur Date: Fri, 28 Aug 2026 19:06:33 +0000 Subject: [PATCH] php-in-php: lazy undef-array-key from HashTable::implement (Re #32122, #35392) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move StringTriggerError declare/ensure from HashTable::implement entry to implementReadStringKeyValue only — peer #35626 strcoll lazy pattern. Scripts that use arrays but never read missing string keys skip NestedJIT trigger_error during init, reducing cold-build LLVM surface (#31894 / #32122 .1 mint class). Co-authored-by: Cursor --- lib/JIT/Builtin/StringTriggerError.php | 7 +- lib/JIT/Builtin/Type.php | 20 ++--- lib/JIT/Builtin/Type/HashTable.php | 22 ++++- ...LazyUndefinedArrayKeyRuntimeShrinkTest.php | 85 +++++++++++++++++++ ...peDeadTriggerErrorAbiRuntimeShrinkTest.php | 23 +++-- ...TimeEnvTriggerPendingRuntimeShrinkTest.php | 4 +- ...dUndefinedArrayKeyAbiRuntimeShrinkTest.php | 4 +- ...azyStringTriggerErrorRuntimeShrinkTest.php | 35 ++++---- 8 files changed, 154 insertions(+), 46 deletions(-) create mode 100644 test/unit/HashTableLazyUndefinedArrayKeyRuntimeShrinkTest.php diff --git a/lib/JIT/Builtin/StringTriggerError.php b/lib/JIT/Builtin/StringTriggerError.php index 65518ce9f6a..fe2e7f0308b 100644 --- a/lib/JIT/Builtin/StringTriggerError.php +++ b/lib/JIT/Builtin/StringTriggerError.php @@ -14,9 +14,10 @@ * Do not re-add empty always-on shells in {@see Type} — leftover decls mint * trigger_error.1 / undefined_array_key_warning_*.1 (#31894 / #32122 / #33234 / #33249). * Context ensureMinimal no longer eagerly links this (#34641); ensureFull likewise - * (#35073). Type::register always-on declare+ensureLinked removed (#35392) — - * {@see Type\HashTable::implement} declares/ensures at entry; call sites / - * AssertFail::ensureLinked ensure before lookup. + * (#35073). Type::register always-on declare+ensureLinked removed (#35392). + * HashTable::implement entry ensure removed (#35648) — only readStringKeyValue + + * HashTableReadLlvm / call sites ensure before lookup; AssertFail::ensureLinked + * still covers assert paths. * {@see JitTriggerErrorKernel::declareTriggerErrorAbi} before SilenceRuntime NestedJIT (#33253). */ final class StringTriggerError diff --git a/lib/JIT/Builtin/Type.php b/lib/JIT/Builtin/Type.php index fd56dc35bce..626816fa57b 100755 --- a/lib/JIT/Builtin/Type.php +++ b/lib/JIT/Builtin/Type.php @@ -600,16 +600,16 @@ public function register(): void { // __compiler_undefined_array_key_warning_cstr / _long always-on shells removed // (#33249): StringTriggerError / JitTriggerErrorKernel owns the ABIs // (getNamedFunction first via declareUndefinedArrayKeyAbis / implementUndefKey*Bridge). - // Type::register always-on declare+ensureLinked removed (#35392): HashTable::implement - // declares/ensures at entry; HashTableReadLlvm / call sites ensure before lookup - // (peer initialize #34513 / Context #34641 / #35073). SessionStartOptions NestedJIT - // moved to call-site (#33945). HELPER_RUNTIME_O=0 stays green via HashTable::implement - // + call-site ensure (#33248). Leftover Type NestedJIT vs Runtime ABI drift mint - // undefined_array_key_warning_*.1 (#31894 / #32122). + // Type::register always-on declare+ensureLinked removed (#35392): HashTable + // readStringKeyValue + HashTableReadLlvm / call sites ensure before lookup + // (#35648 lazy entry drop; peer initialize #34513 / Context #34641 / #35073). + // SessionStartOptions NestedJIT moved to call-site (#33945). HELPER_RUNTIME_O=0 + // stays green via call-site ensure (#33248). Leftover Type NestedJIT vs Runtime + // ABI drift mint undefined_array_key_warning_*.1 (#31894 / #32122). // __compiler_trigger_error always-on shell removed (#33234): StringTriggerError // / JitTriggerErrorKernel owns the ABI (getNamedFunction first via // implementTriggerErrorBridge). Type::register always-on ensureLinked removed - // (#35392 / peer #34513 initialize): HashTable::implement + AssertFail / + // (#35392 / peer #34513 initialize): HashTable::readStringKeyValue + AssertFail / // trigger_error_ / JitBuiltinWarning / JitIncDec ensureLinked before lookup. // Leftover Type NestedJIT vs Runtime ABI drift mint trigger_error.1 // (#31894 / #32122). User-script trigger_error()/user_error() stay @@ -963,11 +963,11 @@ public function initialize(): void { // StringTime / EnvLocal / StringTriggerError(initialize) / PendingHeaders // always-on ensureLinked removed (#34513): StringTime::invoke / // TouchLibcRuntime / Context::ensureStandaloneBodies (EnvLocal) / - // HashTable::implement + trigger_error_ / JitBuiltinWarning / header_ / + // HashTable::readStringKeyValue + trigger_error_ / JitBuiltinWarning / header_ / // PendingHeaders already ensureLinked before lookup (peer #34474). // Type::register always-on StringTriggerError also dropped (#35392) — - // HELPER_RUNTIME_O=0 NestedJIT covered by HashTable::implement + call sites - // (#33248). Eager NestedJIT on every full load vs Runtime ABI drift mints + // HELPER_RUNTIME_O=0 NestedJIT covered by call sites + readStringKeyValue + // (#33248 / #35648). Eager NestedJIT on every full load vs Runtime ABI drift mints // time.1 / env_local_lookup.1 / trigger_error.1 / pending_header_*.1 // (#31894 / #32122). // StringCslashes::ensureStandaloneBodies always-on removed (#34534): diff --git a/lib/JIT/Builtin/Type/HashTable.php b/lib/JIT/Builtin/Type/HashTable.php index a6c8a9b5196..bb2cd0a46c2 100644 --- a/lib/JIT/Builtin/Type/HashTable.php +++ b/lib/JIT/Builtin/Type/HashTable.php @@ -169,10 +169,11 @@ private function registerFn(string $name, string $returnType, array $paramTypes) public function implement(): void { - // Undef-key + trigger_error ABIs before any implement* lookup (#35392 / #33249). - // Type::register no longer eagerly NestedJIT StringTriggerError (#32122 .1 mint). - StringTriggerError::declareUndefinedArrayKeyAbis($this->context); - StringTriggerError::ensureLinked($this->context); + // StringTriggerError undef-key always-on ensureLinked removed (#35648 / peer #35392): + // only implementReadStringKeyValue emits __compiler_undefined_array_key_warning_*; + // HashTableReadLlvm / call sites already ensure before lookup. Thin hello-world + // must not NestedJIT trigger_error during HashTable init — leftover NestedJIT vs + // Runtime ABI drift mints undefined_array_key_warning_*.1 (#31894 / #32122). // StringNaturalCompare / StringStrcoll always-on ensureLinked removed (#35626): // locale/natural sort implement* call ensureStrcollAbis / ensureNaturalCompareAbis // before lookupFunction (peer #35614 Type::String_::implement lazy batch). Thin @@ -261,6 +262,18 @@ private function ensureNaturalCompareAbis(): void StringNaturalCompare::ensureStandaloneBodies($this->context); } + /** + * Undefined string-key read warning ABI — only for readStringKeyValue (#35648). + * + * peekStringKeyValue / isset paths must not NestedJIT trigger_error. Do not + * re-add at implement() entry — leftover decls mint undefined_array_key_warning_*.1. + */ + private function ensureUndefinedArrayKeyAbis(): void + { + StringTriggerError::declareUndefinedArrayKeyAbis($this->context); + StringTriggerError::ensureLinked($this->context); + } + private function implementAlloc(): void { $fn = $this->context->lookupFunction('__hashtable__alloc'); @@ -1615,6 +1628,7 @@ private function implementPeekStringKeyValue(): void private function implementReadStringKeyValue(): void { + $this->ensureUndefinedArrayKeyAbis(); $fn = $this->context->lookupFunction('__hashtable__readStringKeyValue'); $block = $fn->appendBasicBlock('main'); $this->context->builder->positionAtEnd($block); diff --git a/test/unit/HashTableLazyUndefinedArrayKeyRuntimeShrinkTest.php b/test/unit/HashTableLazyUndefinedArrayKeyRuntimeShrinkTest.php new file mode 100644 index 00000000000..40b168daa87 --- /dev/null +++ b/test/unit/HashTableLazyUndefinedArrayKeyRuntimeShrinkTest.php @@ -0,0 +1,85 @@ +assertStringContainsString('#35648', $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 ([ + 'StringTriggerError::declareUndefinedArrayKeyAbis', + 'StringTriggerError::ensureLinked', + 'ensureUndefinedArrayKeyAbis', + ] as $forbidden) { + $this->assertStringNotContainsString( + $forbidden, + $body, + 'HashTable::implement must not eagerly '.$forbidden.' (#35648)' + ); + } + } + + public function testReadStringKeyValueEnsuresUndefinedArrayKeyBeforeLookup(): void + { + $source = (string) file_get_contents(__DIR__.'/../../lib/JIT/Builtin/Type/HashTable.php'); + $fnPos = strpos($source, 'private function implementReadStringKeyValue('); + $this->assertNotFalse($fnPos); + $nextFn = strpos($source, 'private function implementReadStringKeyHashtable(', $fnPos + 1); + $this->assertNotFalse($nextFn); + $chunk = substr($source, $fnPos, $nextFn - $fnPos); + $this->assertStringContainsString( + 'ensureUndefinedArrayKeyAbis', + $chunk, + 'implementReadStringKeyValue must ensureUndefinedArrayKeyAbis before lookup (#35648)' + ); + $ensurePos = strpos($chunk, 'ensureUndefinedArrayKeyAbis'); + $lookupPos = strpos($chunk, '__compiler_undefined_array_key_warning_cstr'); + $this->assertNotFalse($ensurePos); + $this->assertNotFalse($lookupPos); + $this->assertLessThan($lookupPos, $ensurePos); + } + + public function testHashTableReadLlvmStillEnsuresBeforeEmit(): void + { + $source = (string) file_get_contents(__DIR__.'/../../lib/JIT/HashTableReadLlvm.php'); + $this->assertStringContainsString('StringTriggerErrorJit::implement', $source); + } + + public function testNoNewRuntimeCForLazyUndefinedArrayKeyAbis(): void + { + $runtimeDir = dirname(__DIR__, 2).'/lib/AOT/runtime'; + foreach ([ + 'undefined_array_key.c', + 'compiler_undefined_array_key.c', + 'trigger_error.c', + ] as $name) { + $this->assertFileDoesNotExist( + $runtimeDir.'/'.$name, + 'must not add '.$name.' for #35648 — PHP JIT bridges only' + ); + } + $linker = (string) file_get_contents(__DIR__.'/../../lib/AOT/Linker.php'); + $this->assertStringContainsString('RUNTIME_C_SOURCES = [', $linker); + $this->assertStringNotContainsString('undefined_array_key.c', $linker); + } +} diff --git a/test/unit/TypeDeadTriggerErrorAbiRuntimeShrinkTest.php b/test/unit/TypeDeadTriggerErrorAbiRuntimeShrinkTest.php index 1e26b7e09e5..b8a39315bf3 100644 --- a/test/unit/TypeDeadTriggerErrorAbiRuntimeShrinkTest.php +++ b/test/unit/TypeDeadTriggerErrorAbiRuntimeShrinkTest.php @@ -30,10 +30,10 @@ public function testTypeBuiltinDropsLeftoverAlwaysOnTriggerErrorAbi(): void 'Builtin\\Type must not always-register __compiler_trigger_error (#33234)' ); // No further Type always-on leftover after #33267 exit/abort drop; - // StringTriggerError register ensure moved to HashTable::implement (#35392). + // StringTriggerError register ensure moved to readStringKeyValue (#35648). $this->assertStringContainsString('LibcExtern::ensureExitAbort', $type); $ht = (string) file_get_contents(__DIR__.'/../../lib/JIT/Builtin/Type/HashTable.php'); - $this->assertStringContainsString('StringTriggerError::ensureLinked', $ht); + $this->assertStringContainsString('ensureUndefinedArrayKeyAbis', $ht); } public function testRuntimeOwnerDeclaresTriggerErrorAbiModuleLocally(): void @@ -111,14 +111,25 @@ public function testTypeRegisterNoLongerEagerLinksSessionStartOptionsNestedJit() ); $ht = (string) file_get_contents(__DIR__.'/../../lib/JIT/Builtin/Type/HashTable.php'); $this->assertStringContainsString( - 'StringTriggerError::declareUndefinedArrayKeyAbis($this->context)', + 'ensureUndefinedArrayKeyAbis', $ht, - 'HashTable::implement declares undef-key ABIs (#35392 / #33249)' + 'HashTable readStringKeyValue ensures undef-key ABIs (#35648 / #33249)' + ); + $fnPos = strpos($ht, 'private function implementReadStringKeyValue('); + $this->assertNotFalse($fnPos); + $chunk = substr($ht, $fnPos, 400); + $this->assertStringContainsString( + 'ensureUndefinedArrayKeyAbis', + $chunk, + 'readStringKeyValue must ensure undef-key ABIs (#35648 / #33248)' ); + $ensureMethodPos = strpos($ht, 'private function ensureUndefinedArrayKeyAbis'); + $this->assertNotFalse($ensureMethodPos); + $ensureChunk = substr($ht, $ensureMethodPos, 400); $this->assertStringContainsString( 'StringTriggerError::ensureLinked($this->context)', - $ht, - 'HashTable::implement ensureLinked StringTriggerError (#35392 / #33248)' + $ensureChunk, + 'ensureUndefinedArrayKeyAbis must ensureLinked StringTriggerError (#35648 / #33248)' ); } diff --git a/test/unit/TypeDeadTypeInitializeLazyTimeEnvTriggerPendingRuntimeShrinkTest.php b/test/unit/TypeDeadTypeInitializeLazyTimeEnvTriggerPendingRuntimeShrinkTest.php index 777b6e01af8..f2959978b32 100644 --- a/test/unit/TypeDeadTypeInitializeLazyTimeEnvTriggerPendingRuntimeShrinkTest.php +++ b/test/unit/TypeDeadTypeInitializeLazyTimeEnvTriggerPendingRuntimeShrinkTest.php @@ -49,9 +49,9 @@ public function testTypeInitializeDropsEagerTimeEnvTriggerPendingEnsureLinked(): ); $ht = (string) file_get_contents(__DIR__.'/../../lib/JIT/Builtin/Type/HashTable.php'); $this->assertStringContainsString( - 'StringTriggerError::ensureLinked($this->context)', + 'ensureUndefinedArrayKeyAbis', $ht, - 'HashTable::implement ensureLinked StringTriggerError for HELPER_RUNTIME_O=0 (#35392 / #33248)' + 'HashTable readStringKeyValue ensures StringTriggerError for HELPER_RUNTIME_O=0 (#35648 / #33248)' ); // SessionStorageGlobals::ensureGlobals also lazy as of #34566 (peer). } diff --git a/test/unit/TypeDeadUndefinedArrayKeyAbiRuntimeShrinkTest.php b/test/unit/TypeDeadUndefinedArrayKeyAbiRuntimeShrinkTest.php index 1dd2f418184..dfc026783be 100644 --- a/test/unit/TypeDeadUndefinedArrayKeyAbiRuntimeShrinkTest.php +++ b/test/unit/TypeDeadUndefinedArrayKeyAbiRuntimeShrinkTest.php @@ -41,10 +41,10 @@ public function testTypeBuiltinDropsLeftoverAlwaysOnUndefinedArrayKeyAbi(): void 'Builtin\\Type must not always-register undef-key long ABI (#33249)' ); // No further Type always-on leftover after #33267 exit/abort drop; - // StringTriggerError register ensure moved to HashTable::implement (#35392). + // StringTriggerError register ensure moved to readStringKeyValue (#35648). $this->assertStringContainsString('LibcExtern::ensureExitAbort', $type); $ht = (string) file_get_contents(__DIR__.'/../../lib/JIT/Builtin/Type/HashTable.php'); - $this->assertStringContainsString('StringTriggerError::ensureLinked', $ht); + $this->assertStringContainsString('ensureUndefinedArrayKeyAbis', $ht); $this->assertStringContainsString('StringTriggerError::declareUndefinedArrayKeyAbis', $ht); } diff --git a/test/unit/TypeRegisterLazyStringTriggerErrorRuntimeShrinkTest.php b/test/unit/TypeRegisterLazyStringTriggerErrorRuntimeShrinkTest.php index 3d2d6800e8e..c69348e82c5 100644 --- a/test/unit/TypeRegisterLazyStringTriggerErrorRuntimeShrinkTest.php +++ b/test/unit/TypeRegisterLazyStringTriggerErrorRuntimeShrinkTest.php @@ -9,8 +9,8 @@ /** * Drop Type::register always-on StringTriggerError (#35392 / peer #34513 initialize). * - * HashTable::implement declares undef-key ABIs + ensureLinked at entry; call sites - * already ensure before lookup. Thin AOT must not NestedJIT trigger_error during + * HashTable::readStringKeyValue ensures undef-key ABIs; call sites already ensure + * before lookup. Thin AOT must not NestedJIT trigger_error during Type::register * Type::register (#31894 / #32122 .1 mint class). */ final class TypeRegisterLazyStringTriggerErrorRuntimeShrinkTest extends TestCase @@ -43,31 +43,28 @@ public function testTypeRegisterDropsEagerStringTriggerError(): void ); } - public function testHashTableImplementEnsuresStringTriggerErrorBeforeLookups(): void + public function testHashTableReadStringKeyValueEnsuresStringTriggerErrorBeforeLookup(): void { $ht = (string) file_get_contents(__DIR__.'/../../lib/JIT/Builtin/Type/HashTable.php'); - $this->assertStringContainsString('#35392', $ht); + $this->assertStringContainsString('#35648', $ht); + $fnPos = strpos($ht, 'private function implementReadStringKeyValue('); + $this->assertNotFalse($fnPos); + $nextFn = strpos($ht, 'private function implementReadStringKeyHashtable(', $fnPos + 1); + $this->assertNotFalse($nextFn); + $chunk = substr($ht, $fnPos, $nextFn - $fnPos); + $this->assertStringContainsString( + 'ensureUndefinedArrayKeyAbis', + $chunk, + 'implementReadStringKeyValue must ensure undef-key ABIs before lookup (#35648)' + ); $implPos = strpos($ht, 'public function implement(): void'); $this->assertNotFalse($implPos); $implBody = substr($ht, $implPos, 600); - $this->assertStringContainsString( - 'StringTriggerError::declareUndefinedArrayKeyAbis($this->context)', - $implBody, - 'HashTable::implement must declare undef-key ABIs before lookups (#35392)' - ); - $this->assertStringContainsString( + $this->assertStringNotContainsString( 'StringTriggerError::ensureLinked($this->context)', $implBody, - 'HashTable::implement must ensureLinked before undef-key lookup (#35392)' + 'HashTable::implement must not eagerly ensureLinked (#35648)' ); - $declPos = strpos($implBody, 'StringTriggerError::declareUndefinedArrayKeyAbis'); - $ensurePos = strpos($implBody, 'StringTriggerError::ensureLinked'); - $allocPos = strpos($implBody, 'implementAlloc'); - $this->assertNotFalse($declPos); - $this->assertNotFalse($ensurePos); - $this->assertNotFalse($allocPos); - $this->assertLessThan($allocPos, $declPos); - $this->assertLessThan($allocPos, $ensurePos); } public function testJitScalarEnumCoerceEnsuresBeforeLookup(): void