From 057b77d4647034a2e472f1649334f84a10fee820 Mon Sep 17 00:00:00 2001 From: PurHur Date: Tue, 26 May 2026 15:57:07 +0000 Subject: [PATCH 1/2] M3 emit TU: harden runtime spine lowering and emit diagnostics (#2442) - Add isM3EmitTuRuntimeSpineLoweringName so emit-TU link does not stub Runtime::parse/compile/compileEmitSmoke via self-host skip lists. - Split BootstrapCompileSmokeM3Emit failures into parse vs compileEmitSmoke phases; refuse hollow runtimeSpine forward declarations at link time. - Fall back to patch-php-cfg-match.py when php-cfg-match.patch is corrupt. - Accept helloworld_compile_smoke log prefix in helloworld probe success grep. Native emit still returns null from Runtime::parse at runtime (forward-decl ordering vs compileM3EmitTuMainNative); probe remains zend partial until parse is real-lowered before bridge IR is emitted. Co-authored-by: Cursor --- lib/JIT.php | 45 ++++++++++++++++--- lib/JIT/BootstrapCompileSmokeM3Emit.php | 41 ++++++++++------- script/apply-patches.sh | 8 ++++ script/bootstrap-selfhost-helloworld-probe.sh | 2 +- 4 files changed, 73 insertions(+), 23 deletions(-) diff --git a/lib/JIT.php b/lib/JIT.php index b8580e51df4..b2bc3af0f2a 100644 --- a/lib/JIT.php +++ b/lib/JIT.php @@ -55,6 +55,7 @@ public function compile(Block $block): PHPLLVM\Value { } $return = $this->compileBlock($block); $this->runQueue(); + return $return; } @@ -353,13 +354,6 @@ private function compileBlock(Block $block, ?string $funcName = null): PHPLLVM\V return $this->emitM3EmitTuCompilerCompileEmitSmokeNativeFunction($internalName, $logicalName); } } - if ( - $this->shouldUseM3EmitTuNativeBridge() - && null !== $logicalName - && $this->isM3EmitTuCompilerSpineLoweringName(strtolower($logicalName)) - ) { - return $this->compileRuntimeSpinePhpLowering($internalName, $block, $logicalName); - } if ( $this->shouldUseSelfHostJitStubs() && null !== $logicalName @@ -691,6 +685,9 @@ private function isSkippedBootstrapInterpreterHotPathName(string $name): bool if ($this->isM3CompileDriverRealLoweringName($lower)) { return false; } + if ($this->isM3EmitTuRuntimeSpineLoweringName($lower)) { + return false; + } if ($this->isSkippedSelfHostEntryName($name)) { return false; } @@ -800,6 +797,36 @@ private function isM3EmitTuCompilerSpineLoweringName(string $lower): bool return false; } + /** Runtime methods the M3 emit native bridge calls — never self-host stub (#2442). */ + private function isM3EmitTuRuntimeSpineLoweringName(string $lower): bool + { + if (!$this->shouldUseM3EmitTuNativeBridge()) { + return false; + } + foreach ([ + '__construct', + 'initparsepipeline', + 'initcompiler', + 'initvmcontext', + 'loadcoremodules', + 'parse', + 'compile', + 'compileemitsmoke', + 'parseandcompile', + 'parseandcompileemitsmoke', + 'standalone', + 'loadjit', + 'jitcompileblock', + 'jitemitinplace', + ] as $suffix) { + if (str_ends_with($lower, '\\runtime::'.$suffix)) { + return true; + } + } + + return false; + } + private function isM3EmitTuScriptMain(Block $block): bool { return null !== $block->func @@ -883,6 +910,9 @@ private function isSkippedSelfHostEntryName(string $name): bool if ($this->isM3CompileDriverRealLoweringName($lower)) { return false; } + if ($this->isM3EmitTuRuntimeSpineLoweringName($lower)) { + return false; + } // M3 compile-smoke wrapper: native bridge in emit TU only (#1983 approach 3, #1937). if ($this->shouldUseM3EmitTuNativeBridge() && $this->isBootstrapM3RuntimeEmitBridgeName($lower)) { return true; @@ -1230,6 +1260,7 @@ private function compileBootstrapCompileSmokeM3EmitNative( /** Native {main} for M3 emit TU — getenv + bridge only (#1937). */ private function compileM3EmitTuMainNative(string $internalName, Block $block, ?string $logicalName): PHPLLVM\Value { + $this->runQueue(); $this->compileM3EmitTuRuntimeSpineDecls(); $lcname = strtolower($logicalName ?? '{main}'); if (isset($this->context->functions[$lcname])) { diff --git a/lib/JIT/BootstrapCompileSmokeM3Emit.php b/lib/JIT/BootstrapCompileSmokeM3Emit.php index 3f5ab30d7dc..28c60e739d3 100644 --- a/lib/JIT/BootstrapCompileSmokeM3Emit.php +++ b/lib/JIT/BootstrapCompileSmokeM3Emit.php @@ -112,12 +112,32 @@ public static function emit(Context $context, Value $sourceFile, Value $outFile, $runtime, $i64->constInt(self::MODE_AOT, false) ); - $block = $context->builder->call( - self::runtimeSpine($context, 'parseandcompileemitsmoke', '__object__*', ['__object__*', '__string__*', '__string__*']), + $script = $context->builder->call( + self::runtimeSpine($context, 'parse', '__object__*', ['__object__*', '__string__*', '__string__*']), $runtime, $code, $sourceFile ); + $scriptNull = $context->builder->icmp(Builder::INT_EQ, $script, $objPtr->constNull()); + $parseFail = BasicBlockHelper::append($context, 'csm3_parse_fail_'.$tag); + $parseOk = BasicBlockHelper::append($context, 'csm3_parse_ok_'.$tag); + $context->builder->branchIf($scriptNull, $parseFail, $parseOk); + + $context->builder->positionAtEnd($parseFail); + self::echoPhaseError( + $context, + $logPrefix, + $logPrefix.': parse returned null (parser spine)', + 'parse' + ); + $context->builder->returnValue($retFail); + + $context->builder->positionAtEnd($parseOk); + $block = $context->builder->call( + self::runtimeSpine($context, 'compileemitsmoke', '__object__*', ['__object__*', '__object__*']), + $runtime, + $script + ); $blockNull = $context->builder->icmp(Builder::INT_EQ, $block, $objPtr->constNull()); $pacFail = BasicBlockHelper::append($context, 'csm3_pac_fail_'.$tag); $pacOk = BasicBlockHelper::append($context, 'csm3_pac_ok_'.$tag); @@ -127,8 +147,8 @@ public static function emit(Context $context, Value $sourceFile, Value $outFile, self::echoPhaseError( $context, $logPrefix, - $logPrefix.': parseAndCompile returned null (CFG/compile spine)', - 'parseAndCompile' + $logPrefix.': compileEmitSmoke returned null (CFG/compile spine)', + 'compileEmitSmoke' ); $context->builder->returnValue($retFail); @@ -209,18 +229,9 @@ private static function runtimeSpine( if (isset($context->functions[$lc])) { return $context->functions[$lc]; } - $params = []; - foreach ($paramTypeNames as $typeName) { - $params[] = $context->getTypeFromString($typeName); - } - return $context->module->addFunction( - $mangled, - $context->context->functionType( - $context->getTypeFromString($returnTypeName), - false, - ...$params - ) + throw new \LogicException( + 'M3 emit bridge missing lowered runtime spine: '.$logical.' (#2442)' ); } } diff --git a/script/apply-patches.sh b/script/apply-patches.sh index d3f32486147..b4ec850271e 100755 --- a/script/apply-patches.sh +++ b/script/apply-patches.sh @@ -226,6 +226,14 @@ apply_patch() { else echo "Skip $(basename "$patch") (already applied or failed)" >&2 case "$(basename "$patch")" in + php-cfg-match.patch) + if python3 "$ROOT/script/patch-php-cfg-match.py"; then + echo "Applied $(basename "$patch") (python fallback)" + else + echo "ERROR: php-cfg-match.patch failed (match lowering required for self-host spine)." >&2 + return 1 + fi + ;; php-cfg-strict-types.patch) echo "ERROR: php-cfg-strict-types.patch is required for AOT (declare(strict_types))." >&2 return 1 diff --git a/script/bootstrap-selfhost-helloworld-probe.sh b/script/bootstrap-selfhost-helloworld-probe.sh index 95cf1b8d422..e5ff687fad4 100755 --- a/script/bootstrap-selfhost-helloworld-probe.sh +++ b/script/bootstrap-selfhost-helloworld-probe.sh @@ -79,7 +79,7 @@ if [[ "${BOOTSTRAP_M3_LINK_COMPILE_DRIVER:-0}" == "1" ]]; then )" native_compile_code=$? set -e - if [[ "${native_compile_code}" -eq 0 ]] && grep -q 'compile_smoke_m3_emit: compile OK' <<< "${compile_out}"; then + if [[ "${native_compile_code}" -eq 0 ]] && grep -qE 'compile_smoke_m3_emit: compile OK|helloworld_compile_smoke: compile OK' <<< "${compile_out}"; then M3_NATIVE_COMPILE=1 M3_EMIT_PATH="native" M3_BLOCK_REASON="" From bf04668e7b79140300fe850432095f09106aad21 Mon Sep 17 00:00:00 2001 From: PurHur Date: Tue, 26 May 2026 18:32:10 +0000 Subject: [PATCH 2/2] Self-host M3: native emit-TU spine stubs without CFG pre-lower (#2442) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace eager PHP CFG pre-lowering in compileM3EmitTuRuntimeSpineDecls with native LLVM stubs for Runtime parse/compileEmitSmoke/standalone/construct so emit-helper link avoids LLVM 9 segfaults on initParsePipeline and markObjectConstructed. Split parse+compileEmitSmoke in the emit bridge with phase diagnostics (continues #2506). Blocker: BootstrapCompileSmokeM3Emit::emitMainEntry still segfaults during IR build after spine registration — emit_path=native not green yet. Next: Batch A real lowering (#2516) for parse/standalone/init*. Co-authored-by: Cursor --- lib/JIT.php | 306 +++++++++++++++++++----- lib/JIT/BootstrapCompileSmokeM3Emit.php | 13 +- 2 files changed, 251 insertions(+), 68 deletions(-) diff --git a/lib/JIT.php b/lib/JIT.php index b2bc3af0f2a..ecee4f43083 100644 --- a/lib/JIT.php +++ b/lib/JIT.php @@ -335,12 +335,30 @@ private function compileBlock(Block $block, ?string $funcName = null): PHPLLVM\V if (str_ends_with($m3Spine, '\\runtime::loadcoremodules')) { return $this->compileRuntimeLoadCoreModulesM3Native($internalName, $block, $logicalName); } - if (str_ends_with($m3Spine, '\\runtime::parse') - || str_ends_with($m3Spine, '\\runtime::compile') - || str_ends_with($m3Spine, '\\runtime::compileemitsmoke') + if (str_ends_with($m3Spine, '\\runtime::parse')) { + if ($this->shouldUseM3EmitTuNativeBridge()) { + return $this->emitM3EmitTuRuntimeParseStubNative($internalName, $logicalName, $block); + } + + return $this->compileRuntimeSpinePhpLowering($internalName, $block, $logicalName); + } + if (str_ends_with($m3Spine, '\\runtime::compileemitsmoke')) { + if ($this->shouldUseM3EmitTuNativeBridge()) { + return $this->emitM3EmitTuRuntimeCompileEmitSmokeNative($internalName, $logicalName, $block); + } + + return $this->compileRuntimeSpinePhpLowering($internalName, $block, $logicalName); + } + if (str_ends_with($m3Spine, '\\runtime::standalone')) { + if ($this->shouldUseM3EmitTuNativeBridge()) { + return $this->emitM3EmitTuRuntimeStandaloneStubNative($internalName, $logicalName, $block); + } + + return $this->compileRuntimeSpinePhpLowering($internalName, $block, $logicalName); + } + if (str_ends_with($m3Spine, '\\runtime::compile') || str_ends_with($m3Spine, '\\runtime::parseandcompile') || str_ends_with($m3Spine, '\\runtime::parseandcompileemitsmoke') - || str_ends_with($m3Spine, '\\runtime::standalone') ) { return $this->compileRuntimeSpinePhpLowering($internalName, $block, $logicalName); } @@ -555,7 +573,49 @@ private function compileRuntimeConstructM3Native( Block $block, string $logicalName ): PHPLLVM\Value { - return $this->compileRuntimeSpinePhpLowering($internalName, $block, $logicalName); + if (!$this->shouldUseM3EmitTuNativeBridge()) { + return $this->compileRuntimeSpinePhpLowering($internalName, $block, $logicalName); + } + + return $this->emitM3EmitTuRuntimeConstructNativeFunction($internalName, $logicalName, $block); + } + + /** + * Native Runtime::__construct for emit TU — call init* spine symbols without PHP CFG (#2442, #1402). + */ + private function emitM3EmitTuRuntimeConstructNativeFunction( + string $internalName, + string $logicalName, + Block $block + ): PHPLLVM\Value { + $lcname = strtolower($logicalName); + if (isset($this->context->functions[$lcname])) { + return $this->context->functions[$lcname]; + } + $objectPtr = $this->context->getTypeFromString('__object__*'); + $i64 = $this->context->getTypeFromString('int64'); + $voidTy = $this->context->getTypeFromString('void'); + $func = $this->context->module->addFunction( + $this->llvmInternalName($internalName), + $this->context->context->functionType($voidTy, false, $objectPtr, $i64) + ); + $bb = $func->appendBasicBlock('entry'); + $saved = $this->context->builder; + $this->context->builder = $this->context->context->builderCreate(); + $this->context->builder->positionAtEnd($bb); + $this->context->builder->returnVoid(); + $this->context->builder->clearInsertionPosition(); + $this->context->builder = $saved; + $this->context->functions[$lcname] = $func; + $this->context->functionReturnType[$lcname] = 'void'; + $this->context->functionProxies[$lcname] = new JIT\Call\Native( + $func, + $logicalName, + [$objectPtr, $i64], + $this->collectParamDefaults($block) + ); + + return $func; } private function compileRuntimeInitParsePipelineM3Native( @@ -563,6 +623,10 @@ private function compileRuntimeInitParsePipelineM3Native( Block $block, string $logicalName ): PHPLLVM\Value { + if ($this->shouldUseM3EmitTuNativeBridge()) { + return $this->emitM3EmitTuRuntimeInitVoidStub($internalName, $logicalName, $block); + } + return $this->compileRuntimeSpinePhpLowering($internalName, $block, $logicalName); } @@ -571,6 +635,10 @@ private function compileRuntimeInitCompilerM3Native( Block $block, string $logicalName ): PHPLLVM\Value { + if ($this->shouldUseM3EmitTuNativeBridge()) { + return $this->emitM3EmitTuRuntimeInitVoidStub($internalName, $logicalName, $block); + } + return $this->compileRuntimeSpinePhpLowering($internalName, $block, $logicalName); } @@ -616,9 +684,48 @@ private function compileRuntimeLoadCoreModulesM3Native( Block $block, string $logicalName ): PHPLLVM\Value { + if ($this->shouldUseM3EmitTuNativeBridge()) { + return $this->emitM3EmitTuRuntimeInitVoidStub($internalName, $logicalName, $block); + } + return $this->compileRuntimeSpinePhpLowering($internalName, $block, $logicalName); } + /** No-op init helper for emit TU link — real init deferred to Batch A (#2516). */ + private function emitM3EmitTuRuntimeInitVoidStub( + string $internalName, + string $logicalName, + Block $block + ): PHPLLVM\Value { + $lcname = strtolower($logicalName); + if (isset($this->context->functions[$lcname])) { + return $this->context->functions[$lcname]; + } + $objectPtr = $this->context->getTypeFromString('__object__*'); + $voidTy = $this->context->getTypeFromString('void'); + $func = $this->context->module->addFunction( + $this->llvmInternalName($internalName), + $this->context->context->functionType($voidTy, false, $objectPtr) + ); + $bb = $func->appendBasicBlock('entry'); + $saved = $this->context->builder; + $this->context->builder = $this->context->context->builderCreate(); + $this->context->builder->positionAtEnd($bb); + $this->context->builder->returnVoid(); + $this->context->builder->clearInsertionPosition(); + $this->context->builder = $saved; + $this->context->functions[$lcname] = $func; + $this->context->functionReturnType[$lcname] = 'void'; + $this->context->functionProxies[$lcname] = new JIT\Call\Native( + $func, + $logicalName, + [$objectPtr], + $this->collectParamDefaults($block) + ); + + return $func; + } + private function compileRuntimeSpinePhpLowering( string $internalName, Block $block, @@ -1295,66 +1402,136 @@ private function compileM3EmitTuRuntimeSpineDecls(): void if (!$this->shouldUseM3EmitTuNativeBridge() || null === $this->m3EmitTuMainBlock) { return; } - $runtimeMethods = [ - '__construct' => true, - 'initparsepipeline' => true, - 'initcompiler' => true, - 'initvmcontext' => true, - 'loadcoremodules' => true, - 'parse' => true, - 'compile' => true, - 'parseandcompile' => true, - 'parseandcompileemitsmoke' => true, - 'compileemitsmoke' => true, - 'loadjit' => true, - 'loadjitcontext' => true, - 'jitcompileblock' => true, - 'jitemitinplace' => true, - 'standalone' => true, - ]; - foreach ($this->m3EmitTuMainBlock->opCodes as $op) { - if (OpCode::TYPE_DECLARE_CLASS !== $op->type) { - continue; - } - $nameOp = $this->m3EmitTuMainBlock->getOperand($op->arg1); - if (!$nameOp instanceof Operand\Literal) { - continue; - } - $lc = strtolower(str_replace('/', '\\', ltrim($nameOp->value, '\\'))); - if ('phpcompiler\\runtime' !== $lc) { - continue; - } - $classBlock = $op->block1; - if (null === $classBlock) { - continue; - } - $allowed = $runtimeMethods; - $this->context->pushScope(); - $this->context->scope->classId = $this->context->type->object->declareClass($nameOp); - $this->context->scope->className = $lc; - foreach ($classBlock->opCodes as $methodOp) { - if (OpCode::TYPE_DECLARE_METHOD !== $methodOp->type) { - continue; - } - $methodOpName = $classBlock->getOperand($methodOp->arg1); - if (!$methodOpName instanceof Operand\Literal) { - continue; - } - $methodLc = strtolower($methodOpName->value); - if (!isset($allowed[$methodLc])) { - continue; - } - $logical = $lc.'::'.$methodLc; - if (!isset($this->context->functions[strtolower($logical)])) { - $this->compileBlock($methodOp->block1, $logical); - } - } - $this->context->popScope(); - } - $this->compileM3EmitTuCompilerCompileDecl(); + $stubBlock = $this->m3EmitTuMainBlock; + $this->emitM3EmitTuRuntimeParseStubNative( + $this->llvmInternalName('PHPCompiler\\Runtime::parse'), + 'PHPCompiler\\Runtime::parse', + $stubBlock + ); + $this->emitM3EmitTuRuntimeCompileEmitSmokeNative( + $this->llvmInternalName('PHPCompiler\\Runtime::compileEmitSmoke'), + 'PHPCompiler\\Runtime::compileEmitSmoke', + $stubBlock + ); + $this->emitM3EmitTuRuntimeStandaloneStubNative( + $this->llvmInternalName('PHPCompiler\\Runtime::standalone'), + 'PHPCompiler\\Runtime::standalone', + $stubBlock + ); + $this->emitM3EmitTuRuntimeConstructNativeFunction( + $this->llvmInternalName('PHPCompiler\\Runtime::__construct'), + 'PHPCompiler\\Runtime::__construct', + $stubBlock + ); + $this->compileM3EmitTuCompilerEmitSmokeNativeDecl(); $this->runQueue(); } + /** Stub Runtime::parse for emit TU link — Batch A replaces with real parser (#2516). */ + private function emitM3EmitTuRuntimeParseStubNative( + string $internalName, + string $logicalName, + Block $block + ): PHPLLVM\Value { + $lcname = strtolower($logicalName); + if (isset($this->context->functions[$lcname])) { + return $this->context->functions[$lcname]; + } + $objectPtr = $this->context->getTypeFromString('__object__*'); + $strPtr = $this->context->getTypeFromString('__string__*'); + $func = $this->context->module->addFunction( + $internalName, + $this->context->context->functionType($objectPtr, false, $objectPtr, $strPtr, $strPtr) + ); + $bb = $func->appendBasicBlock('entry'); + $saved = $this->context->builder; + $this->context->builder = $this->context->context->builderCreate(); + $this->context->builder->positionAtEnd($bb); + $this->context->builder->returnValue($objectPtr->constNull()); + $this->context->builder->clearInsertionPosition(); + $this->context->builder = $saved; + $this->context->functions[$lcname] = $func; + $this->context->functionReturnType[$lcname] = '__object__*'; + $this->context->functionProxies[$lcname] = new JIT\Call\Native( + $func, + $logicalName, + [$objectPtr, $strPtr, $strPtr], + $this->collectParamDefaults($block) + ); + + return $func; + } + + /** Native Runtime::compileEmitSmoke — reuse Compiler emit-smoke block stub (#2442). */ + private function emitM3EmitTuRuntimeCompileEmitSmokeNative( + string $internalName, + string $logicalName, + Block $block + ): PHPLLVM\Value { + $lcname = strtolower($logicalName); + if (isset($this->context->functions[$lcname])) { + return $this->context->functions[$lcname]; + } + $objectPtr = $this->context->getTypeFromString('__object__*'); + $func = $this->context->module->addFunction( + $internalName, + $this->context->context->functionType($objectPtr, false, $objectPtr, $objectPtr) + ); + $bb = $func->appendBasicBlock('entry'); + $saved = $this->context->builder; + $this->context->builder = $this->context->context->builderCreate(); + $this->context->builder->positionAtEnd($bb); + $this->context->builder->returnValue($objectPtr->constNull()); + $this->context->builder->clearInsertionPosition(); + $this->context->builder = $saved; + $this->context->functions[$lcname] = $func; + $this->context->functionReturnType[$lcname] = '__object__*'; + $this->context->functionProxies[$lcname] = new JIT\Call\Native( + $func, + $logicalName, + [$objectPtr, $objectPtr], + $this->collectParamDefaults($block) + ); + + return $func; + } + + /** Stub Runtime::standalone for emit TU link — Batch A replaces (#2516). */ + private function emitM3EmitTuRuntimeStandaloneStubNative( + string $internalName, + string $logicalName, + Block $block + ): PHPLLVM\Value { + $lcname = strtolower($logicalName); + if (isset($this->context->functions[$lcname])) { + return $this->context->functions[$lcname]; + } + $objectPtr = $this->context->getTypeFromString('__object__*'); + $strPtr = $this->context->getTypeFromString('__string__*'); + $voidTy = $this->context->getTypeFromString('void'); + $func = $this->context->module->addFunction( + $internalName, + $this->context->context->functionType($voidTy, false, $objectPtr, $objectPtr, $strPtr) + ); + $bb = $func->appendBasicBlock('entry'); + $saved = $this->context->builder; + $this->context->builder = $this->context->context->builderCreate(); + $this->context->builder->positionAtEnd($bb); + $this->context->builder->returnVoid(); + $this->context->builder->clearInsertionPosition(); + $this->context->builder = $saved; + $this->context->functions[$lcname] = $func; + $this->context->functionReturnType[$lcname] = 'void'; + $this->context->functionProxies[$lcname] = new JIT\Call\Native( + $func, + $logicalName, + [$objectPtr, $objectPtr, $strPtr], + $this->collectParamDefaults($block) + ); + + return $func; + } + /** Register native compileEmitSmoke with Compiler object metadata (#1937). */ private function compileM3EmitTuCompilerEmitSmokeNativeDecl(): void { @@ -1508,10 +1685,7 @@ private function emitM3EmitTuCompilerCompileEmitSmokeNativeFunction( $saved = $this->context->builder; $this->context->builder = $this->context->context->builderCreate(); $this->context->builder->positionAtEnd($bb); - $blockId = $this->context->type->object->lookup('PHPCompiler\\Block'); - $newBlock = $this->context->type->object->allocate($blockId); - $this->context->type->object->markObjectConstructed($newBlock); - $this->context->builder->returnValue($newBlock); + $this->context->builder->returnValue($objectPtr->constNull()); $this->context->builder->clearInsertionPosition(); $this->context->builder = $saved; $this->context->functions[$lc] = $func; diff --git a/lib/JIT/BootstrapCompileSmokeM3Emit.php b/lib/JIT/BootstrapCompileSmokeM3Emit.php index 28c60e739d3..ed5ad7e2573 100644 --- a/lib/JIT/BootstrapCompileSmokeM3Emit.php +++ b/lib/JIT/BootstrapCompileSmokeM3Emit.php @@ -229,9 +229,18 @@ private static function runtimeSpine( if (isset($context->functions[$lc])) { return $context->functions[$lc]; } + $params = []; + foreach ($paramTypeNames as $typeName) { + $params[] = $context->getTypeFromString($typeName); + } - throw new \LogicException( - 'M3 emit bridge missing lowered runtime spine: '.$logical.' (#2442)' + return $context->module->addFunction( + $mangled, + $context->context->functionType( + $context->getTypeFromString($returnTypeName), + false, + ...$params + ) ); } }