diff --git a/ext/standard/JitGcCollectCyclesStandaloneKernel.php b/ext/standard/JitGcCollectCyclesStandaloneKernel.php index 083989c3a09..65813e90b84 100644 --- a/ext/standard/JitGcCollectCyclesStandaloneKernel.php +++ b/ext/standard/JitGcCollectCyclesStandaloneKernel.php @@ -40,11 +40,14 @@ public static function ensureCycleScanInternals(Context $context): void 'phpc_gc_clear_slots_pointing_to' => [$voidTy, false, [$i8p]], ]; foreach ($internals as $name => [$ret, $vararg, $params]) { - if (null !== $context->module->getNamedFunction($name)) { - continue; + $fn = $context->module->getNamedFunction($name); + if (null === $fn) { + $fn = $context->module->addFunction( + $name, + $context->context->functionType($ret, $vararg, ...$params) + ); } - $ft = $context->context->functionType($ret, $vararg, ...$params); - $context->registerFunction($name, $context->module->addFunction($name, $ft)); + $context->registerFunction($name, $fn); } self::implementSlotReadObject($context); diff --git a/lib/JIT/Builtin/BackedEnumFromRuntime.php b/lib/JIT/Builtin/BackedEnumFromRuntime.php index c8bb4a83638..a21f96a9ce8 100644 --- a/lib/JIT/Builtin/BackedEnumFromRuntime.php +++ b/lib/JIT/Builtin/BackedEnumFromRuntime.php @@ -477,21 +477,29 @@ private static function raisePendingFromString(Context $context, Value $messageS private static function ensureExternals(Context $context): void { - $i32 = $context->getTypeFromString('int32'); $i8p = $context->getTypeFromString('int8*'); - $sizeT = $context->getTypeFromString('size_t'); $longTy = $context->getTypeFromString('int64'); $i8pp = $context->getTypeFromString('int8**'); - $charPtr = $context->getTypeFromString('char*'); $int32 = $context->getTypeFromString('int32'); + // Canonical vararg snprintf via LibcExtern — a non-vararg addFunction('snprintf') + // silently becomes snprintf.1 and Module.php:180 aborts (#32122 / #31894). + LibcExtern::ensureSnprintf($context); foreach ([ - ['snprintf', $i32, [$charPtr, $sizeT, $charPtr]], ['strtol', $longTy, [$i8p, $i8pp, $int32]], ] as [$name, $ret, $params]) { - if (null === $context->module->getNamedFunction($name)) { - $ft = $context->context->functionType($ret, false, ...$params); - $context->registerFunction($name, $context->module->addFunction($name, $ft)); + try { + $context->lookupFunction($name); + continue; + } catch (\Throwable) { + } + $fn = $context->module->getNamedFunction($name); + if (null === $fn) { + $fn = $context->module->addFunction( + $name, + $context->context->functionType($ret, false, ...$params) + ); } + $context->registerFunction($name, $fn); } } diff --git a/lib/JIT/Builtin/GcCollectCyclesRuntime.php b/lib/JIT/Builtin/GcCollectCyclesRuntime.php index 0a5ad751cab..317f79e9cac 100644 --- a/lib/JIT/Builtin/GcCollectCyclesRuntime.php +++ b/lib/JIT/Builtin/GcCollectCyclesRuntime.php @@ -169,11 +169,11 @@ private static function declareFunction( \PHPLLVM\Type $returnType, array $paramTypes ): void { - if (null !== $context->module->getNamedFunction($name)) { - return; + $fn = $context->module->getNamedFunction($name); + if (null === $fn) { + $fnType = $context->context->functionType($returnType, false, ...$paramTypes); + $fn = $context->module->addFunction($name, $fnType); } - $fnType = $context->context->functionType($returnType, false, ...$paramTypes); - $fn = $context->module->addFunction($name, $fnType); $context->registerFunction($name, $fn); } @@ -571,11 +571,14 @@ private static function ensureInternalDeclarations(Context $context): void 'phpc_object_release_storage' => [$voidTy, false, [$i8p]], ]; foreach ($internals as $name => [$ret, $vararg, $params]) { - if (null !== $context->module->getNamedFunction($name)) { - continue; + $fn = $context->module->getNamedFunction($name); + if (null === $fn) { + $fn = $context->module->addFunction( + $name, + $context->context->functionType($ret, $vararg, ...$params) + ); } - $ft = $context->context->functionType($ret, $vararg, ...$params); - $context->registerFunction($name, $context->module->addFunction($name, $ft)); + $context->registerFunction($name, $fn); } self::implementIndexOf($context); diff --git a/lib/JIT/Builtin/NumberFormatRuntime.php b/lib/JIT/Builtin/NumberFormatRuntime.php index 3598086b2de..6e738ff8b9b 100644 --- a/lib/JIT/Builtin/NumberFormatRuntime.php +++ b/lib/JIT/Builtin/NumberFormatRuntime.php @@ -250,17 +250,15 @@ private static function copyBytes( private static function ensureDecls(Context $context): void { - $i32 = $context->getTypeFromString('int32'); $i64 = $context->getTypeFromString('int64'); - $charPtr = $context->getTypeFromString('char*'); $sizeT = $context->getTypeFromString('size_t'); $i8p = $context->getTypeFromString('int8*'); $strPtr = $context->getTypeFromString('__string__*'); $voidTy = $context->getTypeFromString('void'); + LibcExtern::ensureSnprintf($context); foreach ( [ - 'snprintf' => [$i32, true, [$charPtr, $sizeT, $charPtr]], '__mm__malloc' => [$i8p, false, [$sizeT]], '__mm__free' => [$voidTy, false, [$i8p]], '__string__init' => [$strPtr, false, [$i64, $i8p]], @@ -268,10 +266,18 @@ private static function ensureDecls(Context $context): void ) { try { $context->lookupFunction($name); + continue; } catch (\Throwable) { - $ft = $context->context->functionType($ret, $vararg, ...$params); - $context->registerFunction($name, $context->module->addFunction($name, $ft)); } + // Reuse the module symbol when lookupFunction misses the registry (#32122 / #31894). + $fn = $context->module->getNamedFunction($name); + if (null === $fn) { + $fn = $context->module->addFunction( + $name, + $context->context->functionType($ret, $vararg, ...$params) + ); + } + $context->registerFunction($name, $fn); } } } diff --git a/lib/JIT/Builtin/ParseUrlRuntime.php b/lib/JIT/Builtin/ParseUrlRuntime.php index 1a1dedc95d7..4a125924221 100644 --- a/lib/JIT/Builtin/ParseUrlRuntime.php +++ b/lib/JIT/Builtin/ParseUrlRuntime.php @@ -232,9 +232,15 @@ private static function ensureExternal(Context $context, string $name, $ft): voi { try { $context->lookupFunction($name); + + return; } catch (\Throwable) { - $context->registerFunction($name, $context->module->addFunction($name, $ft)); } + $fn = $context->module->getNamedFunction($name); + if (null === $fn) { + $fn = $context->module->addFunction($name, $ft); + } + $context->registerFunction($name, $fn); } private static function registerLinkedRuntime(Context $context): void diff --git a/lib/JIT/Builtin/SprintfSnprintfRuntime.php b/lib/JIT/Builtin/SprintfSnprintfRuntime.php index 72c059979e9..c50663132ca 100644 --- a/lib/JIT/Builtin/SprintfSnprintfRuntime.php +++ b/lib/JIT/Builtin/SprintfSnprintfRuntime.php @@ -192,17 +192,15 @@ private static function ensureDecls(Context $context): void { $double = $context->getTypeFromString('double'); $i64 = $context->getTypeFromString('int64'); - $i32 = $context->getTypeFromString('int32'); - $charPtr = $context->getTypeFromString('char*'); $sizeT = $context->getTypeFromString('size_t'); $i8p = $context->getTypeFromString('int8*'); $strPtr = $context->getTypeFromString('__string__*'); $valuePtr = $context->getTypeFromString('__value__*'); $voidTy = $context->getTypeFromString('void'); + LibcExtern::ensureSnprintf($context); foreach ( [ - 'snprintf' => [$i32, true, [$charPtr, $sizeT, $charPtr]], '__mm__malloc' => [$i8p, false, [$sizeT]], '__mm__free' => [$voidTy, false, [$i8p]], '__string__init' => [$strPtr, false, [$i64, $i8p]], @@ -214,10 +212,18 @@ private static function ensureDecls(Context $context): void ) { try { $context->lookupFunction($name); + continue; } catch (\Throwable) { - $ft = $context->context->functionType($ret, $vararg, ...$params); - $context->registerFunction($name, $context->module->addFunction($name, $ft)); } + // Reuse the module symbol when lookupFunction misses the registry (#32122 / #31894). + $fn = $context->module->getNamedFunction($name); + if (null === $fn) { + $fn = $context->module->addFunction( + $name, + $context->context->functionType($ret, $vararg, ...$params) + ); + } + $context->registerFunction($name, $fn); } LibcExtern::ensureMemcpyImplemented($context); } diff --git a/lib/JIT/Builtin/ZendDoubleStringRuntime.php b/lib/JIT/Builtin/ZendDoubleStringRuntime.php index 16a1b385d13..477fe73dee8 100644 --- a/lib/JIT/Builtin/ZendDoubleStringRuntime.php +++ b/lib/JIT/Builtin/ZendDoubleStringRuntime.php @@ -739,9 +739,11 @@ private static function snprintfCall( $buf = $context->builder->call($context->lookupFunction('__mm__malloc'), $bufSize); $bufChar = $context->builder->pointerCast($buf, $charPtr); $fmtPtr = $context->builder->pointerCast($context->constantFromString($fmt), $charPtr); + // snprintf(3) via LibcExtern::ensureSnprintf after always-on drop (#32092). + // Always reuse the named decl — addFunction('snprintf') without getNamedFunction + // silently creates snprintf.1 and Module.php:180 aborts (#32122 / #31894). + LibcExtern::ensureSnprintf($context); if (null === $precisionArg) { - // snprintf(3) via LibcExtern::ensureSnprintf after always-on drop (#32092). - LibcExtern::ensureSnprintf($context); $written = $context->builder->call( $context->lookupFunction('snprintf'), $bufChar, @@ -772,8 +774,6 @@ private static function snprintfCall( private static function ensureDecls(Context $context): void { - $double = $context->getTypeFromString('double'); - $i32 = $context->getTypeFromString('int32'); $charPtr = $context->getTypeFromString('char*'); $sizeT = $context->getTypeFromString('size_t'); $i8p = $context->getTypeFromString('int8*'); @@ -781,9 +781,9 @@ private static function ensureDecls(Context $context): void $strPtr = $context->getTypeFromString('__string__*'); $voidTy = $context->getTypeFromString('void'); + LibcExtern::ensureSnprintf($context); foreach ( [ - 'snprintf' => [$i32, true, [$charPtr, $sizeT, $charPtr]], '__mm__malloc' => [$i8p, false, [$sizeT]], '__mm__free' => [$voidTy, false, [$i8p]], '__string__init' => [$strPtr, false, [$i64, $charPtr]], @@ -792,10 +792,18 @@ private static function ensureDecls(Context $context): void ) { try { $context->lookupFunction($name); + continue; } catch (\Throwable) { - $ft = $context->context->functionType($ret, $vararg, ...$params); - $context->registerFunction($name, $context->module->addFunction($name, $ft)); } + // Reuse the module symbol when lookupFunction misses the registry (#32122 / #31894). + $fn = $context->module->getNamedFunction($name); + if (null === $fn) { + $fn = $context->module->addFunction( + $name, + $context->context->functionType($ret, $vararg, ...$params) + ); + } + $context->registerFunction($name, $fn); } } } diff --git a/test/unit/SnprintfRuntimeShrinkTest.php b/test/unit/SnprintfRuntimeShrinkTest.php index 0c831a60d6e..a3007e7db18 100644 --- a/test/unit/SnprintfRuntimeShrinkTest.php +++ b/test/unit/SnprintfRuntimeShrinkTest.php @@ -40,6 +40,45 @@ public function testNumberFormatAndDateRouteSnprintfThroughEnsure(): void $this->assertStringNotContainsString('function ensureSnprintf', $date); } + public function testFloatFormattersReuseNamedSnprintfNotSnprintfDotOne(): void + { + $files = [ + __DIR__.'/../../lib/JIT/Builtin/ZendDoubleStringRuntime.php', + __DIR__.'/../../lib/JIT/Builtin/SprintfSnprintfRuntime.php', + __DIR__.'/../../lib/JIT/Builtin/NumberFormatRuntime.php', + __DIR__.'/../../lib/JIT/Builtin/BackedEnumFromRuntime.php', + ]; + foreach ($files as $path) { + $src = (string) file_get_contents($path); + $this->assertStringContainsString('LibcExtern::ensureSnprintf', $src, $path); + $this->assertStringContainsString('#32122', $src, $path); + $this->assertStringNotContainsString( + "'snprintf' => [\$i32, true, [\$charPtr, \$sizeT, \$charPtr]]", + $src, + $path + ); + } + $enum = (string) file_get_contents(__DIR__.'/../../lib/JIT/Builtin/BackedEnumFromRuntime.php'); + $this->assertStringNotContainsString("['snprintf', \$i32, [\$charPtr, \$sizeT, \$charPtr]]", $enum); + } + + public function testGcInternalsRegisterExistingNamedFunctions(): void + { + $gc = (string) file_get_contents(__DIR__.'/../../lib/JIT/Builtin/GcCollectCyclesRuntime.php'); + $this->assertStringContainsString('$context->registerFunction($name, $fn);', $gc); + $this->assertStringNotContainsString( + "if (null !== \$context->module->getNamedFunction(\$name)) {\n continue;", + $gc + ); + $standalone = (string) file_get_contents( + __DIR__.'/../../ext/standard/JitGcCollectCyclesStandaloneKernel.php' + ); + $this->assertStringContainsString('$context->registerFunction($name, $fn);', $standalone); + $parseUrl = (string) file_get_contents(__DIR__.'/../../lib/JIT/Builtin/ParseUrlRuntime.php'); + $this->assertStringContainsString('getNamedFunction($name)', $parseUrl); + $this->assertStringContainsString('$context->registerFunction($name, $fn);', $parseUrl); + } + public function testWeakRefRegistryUsesCanonicalSnprintfPrototype(): void { $source = (string) file_get_contents(__DIR__.'/../../lib/JIT/Builtin/WeakRefRegistryRuntime.php');