From a507df5dd1c7420318d57cbadf23f8dc9433eba4 Mon Sep 17 00:00:00 2001 From: PurHur Date: Tue, 1 Sep 2026 19:37:41 +0000 Subject: [PATCH] Split-TU: fast-exit after SPINE_CHUNK AOT emit (#36155) Spine hub/consumer chunk compiles set PHP_COMPILER_SPINE_CHUNK=1 but bin/compile.php classifies build/micro/chunk paths as user-script AOT and clears SELFHOST_AOT, so post-emit LLVM teardown could spin 15+ min after -o was already linked (#31726). Extend AotEmitFastExit and the compile.php belt-and-suspenders path to treat SPINE_CHUNK like self-host emit success. Co-authored-by: Cursor --- bin/compile.php | 5 ++++- lib/AOT/AotEmitFastExit.php | 7 ++++++- test/unit/SelfhostAotFastExitTest.php | 17 +++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/bin/compile.php b/bin/compile.php index b5dbda1311f..93a726db790 100755 --- a/bin/compile.php +++ b/bin/compile.php @@ -587,7 +587,10 @@ function phpc_run_command(string $command, ?array $env = null): ?array // `free(): invalid pointer` (exit 134) or spins after a successful link // (#21925, #31726). Prefer Runtime::standalone() fast-exit (before $context // dtor); this is belt-and-suspenders if that return is reached. - if ($aotEmitOk && '' !== $normalized && str_contains($normalized, 'test/selfhost/')) { + if ($aotEmitOk && '' !== $normalized && ( + str_contains($normalized, 'test/selfhost/') + || \PHPCompiler\AOT\ExternalMethodBind::spineChunkMode() + )) { \PHPCompiler\AOT\AotEmitFastExit::exitAfterSuccessfulSelfhostEmit($filename, (string) $options['-o']); } } diff --git a/lib/AOT/AotEmitFastExit.php b/lib/AOT/AotEmitFastExit.php index 9d739846129..507d714a6bf 100644 --- a/lib/AOT/AotEmitFastExit.php +++ b/lib/AOT/AotEmitFastExit.php @@ -68,8 +68,13 @@ public static function exitAfterSuccessfulSelfhostEmit( $selfhostEnv = getenv('PHP_COMPILER_SELFHOST_AOT'); $isSelfhostPath = '' !== $normalized && str_contains($normalized, 'test/selfhost/'); $isSelfhostEnv = '1' === $selfhostEnv || 'true' === strtolower((string) $selfhostEnv); + $spineChunkEnv = getenv(ExternalMethodBind::ENV_SPINE_CHUNK); + $isSpineChunk = '1' === $spineChunkEnv || 'true' === strtolower((string) $spineChunkEnv); // Match #31741: selfhost path or explicit SELFHOST_AOT (compile_driver argv). - if (!$isSelfhostPath && !$isSelfhostEnv) { + // Spine split-TU hub/consumer chunks compile under SPINE_CHUNK=1 but are not + // test/selfhost paths; bin/compile.php treats them as user-script AOT and clears + // SELFHOST_AOT — same post-emit LLVM teardown hang (#31726, #36155 Phase B). + if (!$isSelfhostPath && !$isSelfhostEnv && !$isSpineChunk) { return; } if (null !== $outfile && '' !== $outfile) { diff --git a/test/unit/SelfhostAotFastExitTest.php b/test/unit/SelfhostAotFastExitTest.php index 50ebe4884ce..38f2438e8ac 100644 --- a/test/unit/SelfhostAotFastExitTest.php +++ b/test/unit/SelfhostAotFastExitTest.php @@ -4,6 +4,7 @@ namespace PHPCompiler\test\unit; +use PHPCompiler\AOT\ExternalMethodBind; use PHPUnit\Framework\TestCase; /** @@ -88,4 +89,20 @@ public function testZendCompileInvokeHasPostEmitHangWatchdog(): void $this->assertStringContainsString('runtime_standalone_compiletofile_done', $script); $this->assertStringContainsString('#31726', $script); } + + /** Spine split-TU hub chunks hit the same post-emit hang without fast-exit (#36155). */ + public function testAotEmitFastExitIncludesSpineChunkMode(): void + { + $root = dirname(__DIR__, 2); + $helper = (string) file_get_contents($root.'/lib/AOT/AotEmitFastExit.php'); + $this->assertStringContainsString('ExternalMethodBind::ENV_SPINE_CHUNK', $helper); + $this->assertStringContainsString('isSpineChunk', $helper); + } + + public function testCompilePhpFastExitsAfterSpineChunkEmit(): void + { + $root = dirname(__DIR__, 2); + $compile = (string) file_get_contents($root.'/bin/compile.php'); + $this->assertStringContainsString('ExternalMethodBind::spineChunkMode()', $compile); + } }