recompiler: the image-edge fallthrough hole is at the BLOCK level, not the function tail

3b69868 patched the two function-tail sites, but the truncated tail is
emitted by the block-level fallthrough branch: a block with exit type
None and no successor whose next PC is not a known function emitted the
interrupt check and nothing else — execution fell off the C function
with pc==0 (verified: the re-baked overlays_static.c still carried the
truncated block_800D7FC0 tail; zero image-edge transfers were emitted).
Publish the continuation there too. The function-tail guards stay as
belt-and-braces.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
tetrisgm
2026-08-19 14:00:04 -07:00
parent 3b6986827e
commit 95a56e236e
+15
View File
@@ -2475,7 +2475,22 @@ std::string CodeGenerator::translate_basic_block(
<< fmt::format("func_{:08X}(cpu); return; /* fallthrough to split piece */\n",
next_addr);
} else {
// No successor block and no known function owns the next PC —
// the block runs off the region/image edge. Emitting only the
// interrupt check here let execution FALL OFF the C function with
// the entry-switch's consumed pc==0: the dispatcher returned
// "handled" with a null PC and the top-level trampoline read it
// as "program ended" (WipEout 3 static bake: the 0xD5000 region
// variant's truncated block_800D7FC0, continuation 0x800D8004,
// died the moment CD streaming loaded content matching that
// variant — the deterministic boot exit at ~frame 1221). Publish
// the continuation instead: the dispatcher routes it to whoever
// owns it (a neighboring region variant, AOT text, or the
// interpreter).
ss << emit_interrupt_check(next_addr, config_.indent);
ss << config_.indent
<< fmt::format("cpu->pc = 0x{:08X}u; return; /* image-edge fallthrough: tail-transfer */\n",
next_addr);
}
}