diff --git a/lib/JIT/Builtin/AssertionErrorRaise.php b/lib/JIT/Builtin/AssertionErrorRaise.php index e563e9c837a..6a457bec23c 100644 --- a/lib/JIT/Builtin/AssertionErrorRaise.php +++ b/lib/JIT/Builtin/AssertionErrorRaise.php @@ -4,7 +4,7 @@ namespace PHPCompiler\JIT\Builtin; -use PHPCompiler\JIT\Builtin; +use PHPCompiler\JIT\BasicBlockHelper; use PHPCompiler\JIT\Context; use PHPLLVM; @@ -23,9 +23,10 @@ public static function ensureLinked(Context $context): void { self::registerPendingGlobals($context); self::registerDeclarations($context); - if (Builtin::LOAD_TYPE_STANDALONE !== $context->loadType) { - self::implementBodies($context); - } + // Standalone too — Context ensureMinimal no longer eagerly ErrorBridge (#34769 / + // peer #34732). Call-site ensureLinked must implement bodies or thin AOT mints + // declaration-only *.1 / link-fail (#31894 / #32122). + self::implementBodies($context); } public static function ensureStandaloneBodies(Context $context): void @@ -37,6 +38,7 @@ public static function ensureStandaloneBodies(Context $context): void public static function emitRaise(Context $context, string $message): void { + self::ensureLinked($context); $msgLen = $context->constantFromInteger(\strlen($message), 'size_t'); $msgCStr = $context->builder->pointerCast( $context->constantFromString($message), @@ -58,10 +60,17 @@ private static function implementBodies(Context $context): void return; } - self::registerPendingGlobals($context); - self::registerDeclarations($context); - self::implementRaiseFunction($context); - self::implementPendingHelpers($context); + // Body emission clears the insert block; restore so mid-{main} assert sites stay + // well-formed (#34769 / peer ErrorRaise #26826). + $restore = BasicBlockHelper::tryGetInsertBlock($context); + try { + self::registerPendingGlobals($context); + self::registerDeclarations($context); + self::implementRaiseFunction($context); + self::implementPendingHelpers($context); + } finally { + BasicBlockHelper::restoreInsertBlock($context, $restore); + } } private static function registerPendingGlobals(Context $context): void diff --git a/lib/JIT/Builtin/ErrorRaise.php b/lib/JIT/Builtin/ErrorRaise.php index 2fe4faf4ff8..420b931e2df 100644 --- a/lib/JIT/Builtin/ErrorRaise.php +++ b/lib/JIT/Builtin/ErrorRaise.php @@ -25,9 +25,10 @@ public static function ensureLinked(Context $context): void { self::registerPendingGlobals($context); self::registerDeclarations($context); - if (Builtin::LOAD_TYPE_STANDALONE !== $context->loadType) { - self::implementBodies($context); - } + // Standalone too — Context ensureMinimal no longer eagerly ErrorBridge (#34769 / + // peer #34732). Call-site ensureLinked must implement bodies or thin AOT mints + // declaration-only *.1 / link-fail (#31894 / #32122). + self::implementBodies($context); } public static function ensureStandaloneBodies(Context $context): void @@ -59,6 +60,7 @@ private static function implementBodies(Context $context): void public static function emitRaise(Context $context, string $message): void { + self::ensureLinked($context); self::emitPendingMessage($context, $message); } @@ -304,7 +306,8 @@ public static function emitClearForStandaloneMain(Context $context): void if (Builtin::LOAD_TYPE_STANDALONE !== $context->loadType) { return; } - self::registerDeclarations($context); + // Lazy bodies — ensureMinimal no longer ErrorBridge (#34769). + self::ensureLinked($context); $context->builder->call($context->lookupFunction('phpc_jit_error_clear_pending')); } @@ -313,7 +316,7 @@ public static function emitAbortIfPendingForStandaloneMain(Context $context): vo if (Builtin::LOAD_TYPE_STANDALONE !== $context->loadType) { return; } - self::registerDeclarations($context); + self::ensureLinked($context); $context->builder->call($context->lookupFunction('phpc_jit_abort_if_pending_error')); } diff --git a/lib/JIT/Builtin/ReadonlyRaise.php b/lib/JIT/Builtin/ReadonlyRaise.php index 5b05e27ef99..5939f3d44ac 100644 --- a/lib/JIT/Builtin/ReadonlyRaise.php +++ b/lib/JIT/Builtin/ReadonlyRaise.php @@ -440,7 +440,8 @@ public static function emitClearForStandaloneMain(Context $context): void if (Builtin::LOAD_TYPE_STANDALONE !== $context->loadType) { return; } - self::registerDeclarations($context); + // Lazy bodies — ensureMinimal no longer ErrorBridge (#34769). + self::ensureLinked($context); $context->builder->call($context->lookupFunction('phpc_jit_clear_pending_exception')); } @@ -449,7 +450,7 @@ public static function emitAbortIfPendingForStandaloneMain(Context $context): vo if (Builtin::LOAD_TYPE_STANDALONE !== $context->loadType) { return; } - self::registerDeclarations($context); + self::ensureLinked($context); $context->builder->call($context->lookupFunction('phpc_jit_abort_if_pending_logic_exception')); } diff --git a/lib/JIT/Context.php b/lib/JIT/Context.php index 10b159c7203..d8faec96522 100644 --- a/lib/JIT/Context.php +++ b/lib/JIT/Context.php @@ -2302,8 +2302,12 @@ private function ensureMinimalUserStandaloneBodies(): void // bodies before lookup (peer #34695). Thin hello-world must not NestedJIT TypeErrorRaise // / JitThrow during init — thin {main} skips ExceptionBridge clear/abort anyway. // Leftover Context NestedJIT vs Runtime ABI drift mints *.1 (#31894 / #32122). + // ErrorBridge always-on removed (#34769): ErrorRaise / AssertionErrorRaise / + // ReadonlyRaise ensureLinked + emitClear/emitAbort/emitRaise already implement + // standalone bodies before lookup (peer #34732). Thin hello-world must not NestedJIT + // pending-Error ABI during init — thin {main} skips ErrorBridge clear/abort when unused. + // Leftover Context NestedJIT vs Runtime ABI drift mints *.1 (#31894 / #32122). // Full standalone still ensureStandaloneBodies below. - ErrorBridge::ensureStandaloneBodies($this); // ErrorHandler / ExceptionHandler always-on removed (#34612): JitErrorHandler / // JitTriggerErrorKernel / JitExceptionHandler / TryCatchHelper already ensureLinked // before lookup (peer #34605). implement() paths restore builder insert mid-{main}. diff --git a/lib/JIT/ErrorBridge.php b/lib/JIT/ErrorBridge.php index a444780952c..1f462c8096b 100644 --- a/lib/JIT/ErrorBridge.php +++ b/lib/JIT/ErrorBridge.php @@ -12,6 +12,8 @@ * * Replaces lib/AOT/runtime/phpc_error_raise.c — LLVM bodies live in {@see ErrorRaise}; * readonly property guards use {@see ReadonlyBridge} (#5374). + * Standalone bodies are lazy via ensureLinked at call sites (#34769); Context + * ensureMinimal no longer eagerly NestedJITs this ABI. */ final class ErrorBridge { diff --git a/test/unit/ContextMinimalStandaloneLazyErrorBridgeRuntimeShrinkTest.php b/test/unit/ContextMinimalStandaloneLazyErrorBridgeRuntimeShrinkTest.php new file mode 100644 index 00000000000..19246df8c12 --- /dev/null +++ b/test/unit/ContextMinimalStandaloneLazyErrorBridgeRuntimeShrinkTest.php @@ -0,0 +1,156 @@ +assertStringContainsString('#34769', $context); + $minimalPos = strpos($context, 'private function ensureMinimalUserStandaloneBodies'); + $this->assertNotFalse($minimalPos); + $minimalEnd = strpos($context, 'private function ensureBootstrapAotStandaloneBodies', $minimalPos); + $this->assertNotFalse($minimalEnd); + $minimalBody = substr($context, $minimalPos, $minimalEnd - $minimalPos); + + $this->assertStringNotContainsString( + 'ErrorBridge::ensureStandaloneBodies($this)', + $minimalBody, + 'ensureMinimalUserStandaloneBodies must not eagerly ErrorBridge (#34769)' + ); + + // Essentials for thin argv / getenv stay. + foreach ([ + 'CliArgvRuntime::ensureStandaloneBodies($this)', + 'EnvLocalRuntime::ensureLinked($this)', + 'SuperglobalNameRuntime::ensureLinked($this)', + ] as $keep) { + $this->assertStringContainsString($keep, $minimalBody, "keep {$keep} in minimal (#34769)"); + } + + // ensureFull still eagerly ErrorBridge (full AOT fixture surface). + $fullPos = strpos($context, 'private function ensureFullStandaloneBodies'); + $this->assertNotFalse($fullPos); + $fullEnd = strpos($context, 'private function ', $fullPos + 1); + $fullHead = false === $fullEnd + ? substr($context, $fullPos, 2500) + : substr($context, $fullPos, min(2500, $fullEnd - $fullPos)); + $this->assertStringContainsString( + 'ErrorBridge::ensureStandaloneBodies($this)', + $fullHead, + 'ensureFullStandaloneBodies still ensures ErrorBridge (#34769)' + ); + } + + public function testErrorRaiseEnsureLinkedImplementsStandaloneBodies(): void + { + $source = (string) file_get_contents(__DIR__.'/../../lib/JIT/Builtin/ErrorRaise.php'); + $this->assertStringContainsString('#34769', $source); + $pos = strpos($source, 'public static function ensureLinked'); + $this->assertNotFalse($pos); + $next = strpos($source, 'public static function ensureStandaloneBodies', $pos + 10); + $this->assertNotFalse($next); + $body = substr($source, $pos, $next - $pos); + $this->assertStringContainsString('self::implementBodies($context)', $body); + $this->assertStringNotContainsString( + 'LOAD_TYPE_STANDALONE !== $context->loadType', + $body, + 'ensureLinked must implement bodies for STANDALONE too (#34769)' + ); + $this->assertStringContainsString( + 'BasicBlockHelper::tryGetInsertBlock', + $source, + 'implementBodies must restore insert block mid-{main} (#34769)' + ); + } + + public function testAssertionErrorRaiseEnsureLinkedImplementsStandaloneBodies(): void + { + $source = (string) file_get_contents(__DIR__.'/../../lib/JIT/Builtin/AssertionErrorRaise.php'); + $this->assertStringContainsString('#34769', $source); + $pos = strpos($source, 'public static function ensureLinked'); + $this->assertNotFalse($pos); + $next = strpos($source, 'public static function ensureStandaloneBodies', $pos + 10); + $this->assertNotFalse($next); + $body = substr($source, $pos, $next - $pos); + $this->assertStringContainsString('self::implementBodies($context)', $body); + $this->assertStringNotContainsString( + 'LOAD_TYPE_STANDALONE !== $context->loadType', + $body, + 'ensureLinked must implement bodies for STANDALONE too (#34769)' + ); + $this->assertStringContainsString( + 'BasicBlockHelper::tryGetInsertBlock', + $source, + 'implementBodies must restore insert block mid-{main} (#34769)' + ); + } + + public function testEmitClearAndAbortEnsureBeforeLookup(): void + { + foreach ([ + __DIR__.'/../../lib/JIT/Builtin/ErrorRaise.php', + __DIR__.'/../../lib/JIT/Builtin/ReadonlyRaise.php', + ] as $path) { + $source = (string) file_get_contents($path); + foreach (['emitClearForStandaloneMain', 'emitAbortIfPendingForStandaloneMain'] as $method) { + $pos = strpos($source, 'public static function '.$method); + $this->assertNotFalse($pos, $path.' '.$method); + $next = strpos($source, 'public static function ', $pos + 10); + $body = false === $next + ? substr($source, $pos) + : substr($source, $pos, $next - $pos); + $this->assertStringContainsString( + 'self::ensureLinked($context)', + $body, + basename($path).'::'.$method.' must ensureLinked before lookup (#34769)' + ); + } + } + } + + public function testEmitRaiseEnsuresBeforeLookup(): void + { + foreach ([ + __DIR__.'/../../lib/JIT/Builtin/ErrorRaise.php', + __DIR__.'/../../lib/JIT/Builtin/AssertionErrorRaise.php', + ] as $path) { + $source = (string) file_get_contents($path); + $pos = strpos($source, 'public static function emitRaise'); + $this->assertNotFalse($pos, $path); + $next = strpos($source, 'public static function ', $pos + 10); + $body = false === $next + ? substr($source, $pos) + : substr($source, $pos, $next - $pos); + $this->assertStringContainsString( + 'self::ensureLinked($context)', + $body, + basename($path).'::emitRaise must ensureLinked (#34769)' + ); + } + } + + public function testNoNewRuntimeCForMinimalErrorBridgeLazy(): void + { + $runtimeDir = dirname(__DIR__, 2).'/lib/AOT/runtime'; + $this->assertFileDoesNotExist( + $runtimeDir.'/error_bridge.c', + 'must not add error_bridge.c for #34769 — PHP JIT bridges only' + ); + $this->assertFileDoesNotExist( + $runtimeDir.'/phpc_error_raise.c', + 'must not re-add phpc_error_raise.c for #34769' + ); + } +} diff --git a/test/unit/ContextMinimalStandaloneLazyExceptionBridgeRuntimeShrinkTest.php b/test/unit/ContextMinimalStandaloneLazyExceptionBridgeRuntimeShrinkTest.php index fc5782810bd..5ccab851f0c 100644 --- a/test/unit/ContextMinimalStandaloneLazyExceptionBridgeRuntimeShrinkTest.php +++ b/test/unit/ContextMinimalStandaloneLazyExceptionBridgeRuntimeShrinkTest.php @@ -30,15 +30,19 @@ public function testEnsureMinimalDropsEagerExceptionBridge(): void 'ensureMinimalUserStandaloneBodies must not eagerly ExceptionBridge (#34732)' ); - // Essentials for thin argv / getenv / Error pending stay. + // Essentials for thin argv / getenv stay (#34769 dropped ErrorBridge). foreach ([ 'CliArgvRuntime::ensureStandaloneBodies($this)', 'EnvLocalRuntime::ensureLinked($this)', 'SuperglobalNameRuntime::ensureLinked($this)', - 'ErrorBridge::ensureStandaloneBodies($this)', ] as $keep) { - $this->assertStringContainsString($keep, $minimalBody, "keep {$keep} in minimal (#34732)"); + $this->assertStringContainsString($keep, $minimalBody, "keep {$keep} in minimal (#34769)"); } + $this->assertStringNotContainsString( + 'ErrorBridge::ensureStandaloneBodies($this)', + $minimalBody, + 'ensureMinimal must not eagerly ErrorBridge (#34769)' + ); // ensureFull still eagerly ExceptionBridge (full AOT fixture surface). $fullPos = strpos($context, 'private function ensureFullStandaloneBodies'); diff --git a/test/unit/ContextMinimalStandaloneLazyHtmlspecialcharsRuntimeShrinkTest.php b/test/unit/ContextMinimalStandaloneLazyHtmlspecialcharsRuntimeShrinkTest.php index 561e7c3f428..fcc3b7d5e9f 100644 --- a/test/unit/ContextMinimalStandaloneLazyHtmlspecialcharsRuntimeShrinkTest.php +++ b/test/unit/ContextMinimalStandaloneLazyHtmlspecialcharsRuntimeShrinkTest.php @@ -35,10 +35,14 @@ public function testEnsureMinimalDropsEagerHtmlspecialchars(): void 'CliArgvRuntime::ensureStandaloneBodies($this)', 'EnvLocalRuntime::ensureLinked($this)', 'SuperglobalNameRuntime::ensureLinked($this)', - 'ErrorBridge::ensureStandaloneBodies($this)', ] as $keep) { - $this->assertStringContainsString($keep, $minimalBody, "keep {$keep} in minimal (#34732)"); + $this->assertStringContainsString($keep, $minimalBody, "keep {$keep} in minimal (#34769)"); } + $this->assertStringNotContainsString( + 'ErrorBridge::ensureStandaloneBodies($this)', + $minimalBody, + 'ensureMinimal must not eagerly ErrorBridge (#34769)' + ); $this->assertStringNotContainsString( 'ExceptionBridge::ensureStandaloneBodies($this)', $minimalBody, diff --git a/test/unit/ContextMinimalStandaloneLazyLastErrorRuntimeShrinkTest.php b/test/unit/ContextMinimalStandaloneLazyLastErrorRuntimeShrinkTest.php index 8240ea66363..d36f61a102d 100644 --- a/test/unit/ContextMinimalStandaloneLazyLastErrorRuntimeShrinkTest.php +++ b/test/unit/ContextMinimalStandaloneLazyLastErrorRuntimeShrinkTest.php @@ -35,10 +35,14 @@ public function testEnsureMinimalDropsEagerLastError(): void 'CliArgvRuntime::ensureStandaloneBodies($this)', 'EnvLocalRuntime::ensureLinked($this)', 'SuperglobalNameRuntime::ensureLinked($this)', - 'ErrorBridge::ensureStandaloneBodies($this)', ] as $keep) { - $this->assertStringContainsString($keep, $minimalBody, "keep {$keep} in minimal (#34732)"); + $this->assertStringContainsString($keep, $minimalBody, "keep {$keep} in minimal (#34769)"); } + $this->assertStringNotContainsString( + 'ErrorBridge::ensureStandaloneBodies($this)', + $minimalBody, + 'ensureMinimal must not eagerly ErrorBridge (#34769)' + ); $this->assertStringNotContainsString( 'ExceptionBridge::ensureStandaloneBodies($this)', $minimalBody, diff --git a/test/unit/ContextMinimalStandaloneLazyObOutputRuntimeShrinkTest.php b/test/unit/ContextMinimalStandaloneLazyObOutputRuntimeShrinkTest.php index 9d74a562555..62ccfa298ba 100644 --- a/test/unit/ContextMinimalStandaloneLazyObOutputRuntimeShrinkTest.php +++ b/test/unit/ContextMinimalStandaloneLazyObOutputRuntimeShrinkTest.php @@ -35,10 +35,14 @@ public function testEnsureMinimalDropsEagerObOutput(): void 'CliArgvRuntime::ensureStandaloneBodies($this)', 'EnvLocalRuntime::ensureLinked($this)', 'SuperglobalNameRuntime::ensureLinked($this)', - 'ErrorBridge::ensureStandaloneBodies($this)', ] as $keep) { - $this->assertStringContainsString($keep, $minimalBody, "keep {$keep} in minimal (#34732)"); + $this->assertStringContainsString($keep, $minimalBody, "keep {$keep} in minimal (#34769)"); } + $this->assertStringNotContainsString( + 'ErrorBridge::ensureStandaloneBodies($this)', + $minimalBody, + 'ensureMinimal must not eagerly ErrorBridge (#34769)' + ); $this->assertStringNotContainsString( 'ExceptionBridge::ensureStandaloneBodies($this)', $minimalBody, diff --git a/test/unit/ContextMinimalStandaloneLazyReturnPendingRuntimeShrinkTest.php b/test/unit/ContextMinimalStandaloneLazyReturnPendingRuntimeShrinkTest.php index a937d1ccfb3..290ea900113 100644 --- a/test/unit/ContextMinimalStandaloneLazyReturnPendingRuntimeShrinkTest.php +++ b/test/unit/ContextMinimalStandaloneLazyReturnPendingRuntimeShrinkTest.php @@ -37,10 +37,14 @@ public function testEnsureMinimalDropsEagerJitReturnPending(): void 'CliArgvRuntime::ensureStandaloneBodies($this)', 'EnvLocalRuntime::ensureLinked($this)', 'SuperglobalNameRuntime::ensureLinked($this)', - 'ErrorBridge::ensureStandaloneBodies($this)', ] as $keep) { - $this->assertStringContainsString($keep, $minimalBody, "keep {$keep} in minimal (#34732)"); + $this->assertStringContainsString($keep, $minimalBody, "keep {$keep} in minimal (#34769)"); } + $this->assertStringNotContainsString( + 'ErrorBridge::ensureStandaloneBodies($this)', + $minimalBody, + 'ensureMinimal must not eagerly ErrorBridge (#34769)' + ); $this->assertStringNotContainsString( 'ExceptionBridge::ensureStandaloneBodies($this)', $minimalBody, diff --git a/test/unit/ContextMinimalStandaloneLazyStringTriggerErrorShrinkTest.php b/test/unit/ContextMinimalStandaloneLazyStringTriggerErrorShrinkTest.php index e17fc367e89..135079d268d 100644 --- a/test/unit/ContextMinimalStandaloneLazyStringTriggerErrorShrinkTest.php +++ b/test/unit/ContextMinimalStandaloneLazyStringTriggerErrorShrinkTest.php @@ -36,10 +36,14 @@ public function testEnsureMinimalDropsEagerStringTriggerError(): void 'CliArgvRuntime::ensureStandaloneBodies($this)', 'EnvLocalRuntime::ensureLinked($this)', 'SuperglobalNameRuntime::ensureLinked($this)', - 'ErrorBridge::ensureStandaloneBodies($this)', ] as $keep) { - $this->assertStringContainsString($keep, $minimalBody, "keep {$keep} in minimal (#34732)"); + $this->assertStringContainsString($keep, $minimalBody, "keep {$keep} in minimal (#34769)"); } + $this->assertStringNotContainsString( + 'ErrorBridge::ensureStandaloneBodies($this)', + $minimalBody, + 'ensureMinimal must not eagerly ErrorBridge (#34769)' + ); $this->assertStringNotContainsString( 'ExceptionBridge::ensureStandaloneBodies($this)', $minimalBody, diff --git a/test/unit/ErrorBridgeRuntimeShrinkTest.php b/test/unit/ErrorBridgeRuntimeShrinkTest.php new file mode 100644 index 00000000000..7c4c6b57331 --- /dev/null +++ b/test/unit/ErrorBridgeRuntimeShrinkTest.php @@ -0,0 +1,39 @@ +assertStringContainsString('AssertionErrorRaise::ensureLinked', $source); + $this->assertStringContainsString('ErrorRaise::ensureLinked', $source); + $this->assertStringContainsString('ReadonlyBridge::ensureLinked', $source); + $this->assertStringContainsString('ErrorRaise::ensureStandaloneBodies', $source); + $this->assertStringContainsString('ReadonlyBridge::ensureStandaloneBodies', $source); + } + + public function testEmitErrorEnsuresBeforeLookup(): void + { + $source = (string) file_get_contents(__DIR__.'/../../lib/JIT/ErrorBridge.php'); + $pos = strpos($source, 'function emitError'); + $this->assertNotFalse($pos); + $next = strpos($source, 'public static function ', $pos + 10); + $body = false === $next + ? substr($source, $pos) + : substr($source, $pos, $next - $pos); + $this->assertStringContainsString( + 'ErrorRaise::ensureLinked', + $body, + 'emitError must ensureLinked (#34769)' + ); + } +}