diff --git a/recompiler/src/main_psx.cpp b/recompiler/src/main_psx.cpp index 5d6e45df..3f6e36f9 100644 --- a/recompiler/src/main_psx.cpp +++ b/recompiler/src/main_psx.cpp @@ -1458,6 +1458,7 @@ int main(int argc, char** argv) { ds << "#include \"psx_runtime.h\"\n\n"; ds << "extern void psx_check_interrupts_dispatch_entry(CPUState* cpu, uint32_t resume_pc);\n\n"; ds << "extern int dirty_ram_text_native_ok_ranges_from(const uint32_t* lo_len_pairs, uint32_t count, uint32_t exec_pc);\n\n"; + ds << "extern int dirty_ram_text_native_ok_ranges(const uint32_t* lo_len_pairs, uint32_t count);\n\n"; // Forward declarations ds << "/* Forward declarations for all recompiled functions */\n"; @@ -1619,6 +1620,14 @@ int main(int argc, char** argv) { ds << " &k_psx_game_code_ranges[entry->range_index].lo, entry->range_count, addr);\n"; ds << "}\n\n"; + ds << "/* Full-range validity for straight-line interpreter-to-AOT handoff. */\n"; + ds << "int psx_game_text_native_ok_full(uint32_t addr) {\n"; + ds << " const PsxGameDispatchEntry* entry = psx_game_find_entry(addr);\n"; + ds << " if (!entry || entry->range_count == 0) return 0;\n"; + ds << " return dirty_ram_text_native_ok_ranges(\n"; + ds << " &k_psx_game_code_ranges[entry->range_index].lo, entry->range_count);\n"; + ds << "}\n\n"; + ds << "/* Maps PS1 address to compiled game code. Returns 1 if dispatched, 0 if unknown. */\n"; ds << "int psx_dispatch_game_compiled(CPUState* cpu, uint32_t addr) {\n"; ds << " const PsxGameDispatchEntry* entry = psx_game_find_entry(addr);\n"; diff --git a/recompiler/tests/test_reachable_discovery_codegen.py b/recompiler/tests/test_reachable_discovery_codegen.py index db87e2be..744249f2 100644 --- a/recompiler/tests/test_reachable_discovery_codegen.py +++ b/recompiler/tests/test_reachable_discovery_codegen.py @@ -139,6 +139,10 @@ def main(): failures.append("dispatch guard does not accept a continuation PC") if dispatch.count("entry->range_count, addr)") != 2: failures.append("dispatch guards do not validate from their resume PC") + if "int psx_game_text_native_ok_full(uint32_t addr)" not in dispatch: + failures.append("full-range straight-line handoff guard was not emitted") + if "dirty_ram_text_native_ok_ranges(" not in dispatch: + failures.append("full-range handoff guard lost exact range validation") if "psx_native_bad_entry(" in full: failures.append("reachable main image incorrectly used overlay codegen") if "=== Exact-Entry Function Analysis ===" not in output: diff --git a/runtime/runtime.cmake b/runtime/runtime.cmake index 5946a3a9..a8520439 100644 --- a/runtime/runtime.cmake +++ b/runtime/runtime.cmake @@ -877,6 +877,13 @@ function(psxrecomp_add_runtime_target target) if(game_dispatch_native_ok_decl) set(has_game_dispatch_native_ok TRUE) endif() + file(STRINGS "${PSXRT_GAME_GENERATED_DISPATCH_C}" + game_dispatch_native_ok_full_decl + REGEX "int[ \t]+psx_game_text_native_ok_full\\(" + LIMIT_COUNT 1) + if(game_dispatch_native_ok_full_decl) + set(has_game_dispatch_native_ok_full TRUE) + endif() endif() endif() # Layer B: statically-compiled overlay dispatch. Inert unless a game @@ -1296,6 +1303,12 @@ function(psxrecomp_add_runtime_target target) APPEND PROPERTY COMPILE_DEFINITIONS PSX_GAME_DISPATCH_HAS_NATIVE_OK=1) endif() + if(has_game_dispatch_native_ok_full) + set_property(SOURCE + ${PSXRECOMP_ROOT}/runtime/src/game_dispatch_compat.c + APPEND PROPERTY COMPILE_DEFINITIONS + PSX_GAME_DISPATCH_HAS_NATIVE_OK_FULL=1) + endif() if(has_overlay_dispatch) target_compile_definitions(${target} PRIVATE PSX_HAS_OVERLAY_DISPATCH=1) endif() diff --git a/runtime/src/dirty_ram_interp.c b/runtime/src/dirty_ram_interp.c index 377f35aa..5ec7fd01 100644 --- a/runtime/src/dirty_ram_interp.c +++ b/runtime/src/dirty_ram_interp.c @@ -474,6 +474,7 @@ extern int psx_dispatch_game_compiled(CPUState* cpu, uint32_t addr); extern int psx_game_address_in_text(uint32_t addr); extern int psx_game_is_function_entry(uint32_t addr); /* non-destructive entry test */ extern int psx_game_text_native_ok(uint32_t addr); +extern int psx_game_text_native_ok_full(uint32_t addr); #endif extern void psx_dispatch_call(CPUState* cpu, uint32_t addr, uint32_t return_addr); @@ -3152,10 +3153,14 @@ static int dirty_ram_dispatch_inner(CPUState* cpu, uint32_t addr, uint32_t stop_ } pc = next_pc; #ifdef PSX_HAS_GAME_DISPATCH - /* Guest call returns advance without transferred set. Re-check the - * resume PC so a patched entry can hand its unchanged tail back to - * compiled code without adding probes to ordinary dirty overlay runs. */ - if (clean_game_text_miss && interp_enter_compiled(cpu, pc)) { + /* Guest call returns advance without transferred set. A suffix-only + * continuation check is insufficient here: a split compiled piece can + * fall through by a direct host call into another piece without a new + * RAM-byte check. Require the continuation's complete emitted range to + * match before this straight-line handoff. Control-transfer handoffs + * retain suffix validation because they are explicit guest entries. */ + if (clean_game_text_miss && psx_game_text_native_ok_full(pc) && + interp_enter_compiled(cpu, pc)) { g_dirty_ram_native_handoffs++; g_dirty_ram_blocks_run++; if (pc_entry) pc_entry->insns += (uint64_t)insns_executed; diff --git a/runtime/src/game_dispatch_compat.c b/runtime/src/game_dispatch_compat.c index 315b535a..00530faf 100644 --- a/runtime/src/game_dispatch_compat.c +++ b/runtime/src/game_dispatch_compat.c @@ -21,6 +21,25 @@ int psx_game_text_native_ok(uint32_t addr) return psx_game_address_in_text(addr) && dirty_ram_text_native_ok(phys); } +int psx_game_text_native_ok_full(uint32_t addr) +{ + (void)addr; + return 0; +} + +#elif defined(PSX_HAS_GAME_DISPATCH) && \ + !defined(PSX_GAME_DISPATCH_HAS_NATIVE_OK_FULL) + +/* A suffix-aware dispatcher cannot prove that a later direct host fallthrough + * in the same compiled entry still matches guest RAM. Decline straight-line + * handoff until regeneration provides exact whole-entry validation. */ + +int psx_game_text_native_ok_full(uint32_t addr) +{ + (void)addr; + return 0; +} + #elif !defined(PSX_HAS_GAME_DISPATCH) /* The BIOS-only runtime still links shared dispatch observability. There is no @@ -37,4 +56,10 @@ int psx_game_text_native_ok(uint32_t addr) return 0; } +int psx_game_text_native_ok_full(uint32_t addr) +{ + (void)addr; + return 0; +} + #endif /* PSX_HAS_GAME_DISPATCH */ diff --git a/runtime/tests/test_dirty_text_continuation_guards.py b/runtime/tests/test_dirty_text_continuation_guards.py index 4cf1a24e..56aa21ea 100644 --- a/runtime/tests/test_dirty_text_continuation_guards.py +++ b/runtime/tests/test_dirty_text_continuation_guards.py @@ -9,6 +9,8 @@ def main() -> int: root = Path(__file__).resolve().parents[2] memory = (root / "runtime/src/memory.c").read_text(encoding="utf-8") interp = (root / "runtime/src/dirty_ram_interp.c").read_text(encoding="utf-8") + compat = (root / "runtime/src/game_dispatch_compat.c").read_text(encoding="utf-8") + runtime_cmake = (root / "runtime/runtime.cmake").read_text(encoding="utf-8") start = memory.index("int dirty_ram_text_native_ok_ranges_from(") end = memory.index("\n/* Preserve the generated-code ABI", start) @@ -29,12 +31,22 @@ def main() -> int: raise AssertionError("legacy generated-code ABI is not preserved") handoff = "clean_game_text_miss && interp_enter_compiled(cpu, " - if interp.count(handoff) != 2: - raise AssertionError("expected transfer and call-return continuation handoffs") + if interp.count(handoff) != 1: + raise AssertionError("expected one suffix-validated transfer handoff") if "interp_enter_compiled(cpu, target)" not in interp: raise AssertionError("missing transfer-boundary continuation handoff") + if "psx_game_text_native_ok_full(pc) &&" not in interp: + raise AssertionError("straight-line handoff lacks full-range validation") if "interp_enter_compiled(cpu, pc)" not in interp: raise AssertionError("missing call-return continuation handoff") + if "PSX_GAME_DISPATCH_HAS_NATIVE_OK_FULL" not in compat: + raise AssertionError("older generated dispatchers lost full-guard compatibility") + if compat.count("int psx_game_text_native_ok_full(uint32_t addr)") != 3: + raise AssertionError("full-guard compatibility branches are incomplete") + if compat.count("int psx_game_text_native_ok_full(uint32_t addr)\n{\n (void)addr;\n return 0;\n}") != 3: + raise AssertionError("a dispatcher without the full ABI may accept an unsafe handoff") + if "has_game_dispatch_native_ok_full" not in runtime_cmake: + raise AssertionError("runtime does not detect the generated full-range ABI") print("dirty-text continuation guards: ok") return 0