From c564ccd1f1a57e04e800bf9da0beef5e8f34968c Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Sat, 26 Sep 2026 06:31:33 -0700 Subject: [PATCH] runtime: route every RAM consumer through one live 2/8 MiB geometry Co-Authored-By: Claude Opus 5.5 (1M context) --- recompiler/src/bios_address_model.cpp | 7 +- recompiler/src/full_function_emitter.cpp | 4 +- recompiler/src/main_psx.cpp | 7 +- .../tests/test_kuseg_dispatch_lookup.py | 11 ++ runtime/CMakeLists.txt | 17 +- runtime/include/dirty_ram_interp.h | 13 +- runtime/include/mod_memory.h | 6 +- runtime/include/psx_cyc.h | 2 +- runtime/include/psx_memory.h | 108 ++++++++++--- runtime/include/psx_ram.h | 75 --------- runtime/include/ws_primitive_roles.h | 5 +- runtime/runtime.cmake | 1 + runtime/src/bios_hle.c | 3 +- runtime/src/boot_state.c | 4 +- runtime/src/cosim_state.c | 12 +- runtime/src/crash_trace.c | 6 +- runtime/src/data_shards.c | 7 +- runtime/src/debug_server.c | 29 ++-- runtime/src/dirty_ram_interp.c | 26 +-- runtime/src/dma.c | 93 ++++++----- runtime/src/freeze_heartbeat.c | 9 +- runtime/src/gpu.c | 47 +++--- runtime/src/interrupts.c | 7 +- runtime/src/memory.c | 151 ++++-------------- runtime/src/mod_runtime.cpp | 2 +- runtime/src/overlay_capture.c | 18 +-- runtime/src/overlay_loader.c | 76 +++++---- runtime/src/pgxp.cpp | 11 +- runtime/src/psx_netplay_rb.c | 5 +- runtime/src/psx_ram_geometry.c | 123 ++++++++++++++ runtime/src/psx_selfcheck.c | 3 +- runtime/src/savestate.c | 3 +- runtime/src/text_xlate.cpp | 10 +- runtime/src/traps.c | 5 +- .../tests/test_dirty_text_admission_guards.py | 2 +- runtime/tests/test_gpu_upload_history.py | 7 +- runtime/tests/test_gte_register_access.cpp | 1 - .../tests/test_overlay_decodable_fallback.py | 2 +- runtime/tests/test_psx_memory_geometry.c | 29 +++- runtime/tests/test_psx_ram_runtime_map.c | 126 ++++++++++++--- tools/compile_overlays.py | 2 +- 41 files changed, 649 insertions(+), 426 deletions(-) delete mode 100644 runtime/include/psx_ram.h create mode 100644 runtime/src/psx_ram_geometry.c diff --git a/recompiler/src/bios_address_model.cpp b/recompiler/src/bios_address_model.cpp index 495f224f..0b78c7cb 100644 --- a/recompiler/src/bios_address_model.cpp +++ b/recompiler/src/bios_address_model.cpp @@ -266,10 +266,13 @@ uint32_t BiosAddressModel::rom_keyed_ram_hi_incl() const { std::string BiosAddressModel::emit_normalize_c() const { std::string out; + out += "extern uint32_t psx_ram_canon_code_addr(uint32_t addr);\n"; out += "static uint32_t normalize(uint32_t addr) {\n"; out += " uint32_t phys = addr & 0x1FFFFFFFu;\n"; - out += " /* PSX main RAM: 2 MiB mirrored through physical 0x007FFFFF. */\n"; - out += " if (phys < 0x00800000u) phys &= 0x001FFFFFu;\n"; + out += " /* PSX main RAM decode window [0, 0x00800000): retail 2 MiB mirrors\n"; + out += " * fold onto the low bank; the opt-in 8 MB map keeps registered high\n"; + out += " * pages unique. Only high-window keys need the live-geometry call. */\n"; + out += " if (phys - 0x00200000u < 0x00600000u) phys = psx_ram_canon_code_addr(phys);\n"; for (const BiosAddrCopy& c : copies_) { if (c.key_is_ram) { out += fmt::format(" /* {}: ROM 0x{:X}+ -> RAM 0x{:X}+ */\n", diff --git a/recompiler/src/full_function_emitter.cpp b/recompiler/src/full_function_emitter.cpp index 2928ac05..ef860e63 100644 --- a/recompiler/src/full_function_emitter.cpp +++ b/recompiler/src/full_function_emitter.cpp @@ -2190,7 +2190,9 @@ void FullFunctionEmitter::emit_dispatch( out += " * physical 0x30000-0x5AFFF. If the target belongs to the\n"; out += " * active game text range, route it through the game/dirty-RAM\n"; out += " * path before normalizing it to shell ROM. */\n"; - out += " uint32_t game_addr = psx_ram_canon_code_addr(addr);\n"; + out += " /* Live-geometry code address: only 2nd-4th-MiB keys can fold. */\n"; + out += " uint32_t game_addr = ((addr & 0x1FFFFFFFu) - 0x00200000u < 0x00600000u)\n"; + out += " ? psx_ram_canon_code_addr(addr) : addr;\n"; out += " uint32_t game_phys = game_addr & 0x1FFFFFFFu;\n"; out += " /* Class-A shell-window collision fix: post-game-start the BIOS shell\n"; out += " * copy at RAM 0x30000-0x5AFFF is DEAD (overwritten by the game EXE,\n"; diff --git a/recompiler/src/main_psx.cpp b/recompiler/src/main_psx.cpp index 11f0b835..8fe469a7 100644 --- a/recompiler/src/main_psx.cpp +++ b/recompiler/src/main_psx.cpp @@ -1483,7 +1483,9 @@ int main(int argc, char** argv) { uint32_t game_text_end = game_text_start + exe->code_size(); ds << "int psx_game_address_in_text(uint32_t addr) {\n"; ds << " extern uint32_t psx_ram_canon_code_addr(uint32_t);\n"; - ds << " uint32_t phys = psx_ram_canon_code_addr(addr) & 0x1FFFFFFFu;\n"; + ds << " uint32_t phys = addr & 0x1FFFFFFFu;\n"; + ds << " if (phys - 0x00200000u < 0x00600000u)\n"; + ds << " phys = psx_ram_canon_code_addr(addr) & 0x1FFFFFFFu;\n"; ds << fmt::format(" return phys >= 0x{:08X}u && phys < 0x{:08X}u;\n", game_text_start, game_text_end); ds << "}\n\n"; @@ -1641,7 +1643,8 @@ int main(int argc, char** argv) { ds << " * the table is sorted by the same masked key. */\n"; ds << "static const PsxGameDispatchEntry* psx_game_find_entry(uint32_t addr) {\n"; ds << " extern uint32_t psx_ram_canon_code_addr(uint32_t);\n"; - ds << " addr = psx_ram_canon_code_addr(addr);\n"; + ds << " if ((addr & 0x1FFFFFFFu) - 0x00200000u < 0x00600000u)\n"; + ds << " addr = psx_ram_canon_code_addr(addr);\n"; ds << " const uint32_t want = addr & 0x1FFFFFFFu;\n"; if (indexed_lookup) { ds << fmt::format(" const uint32_t offset = want - 0x{:08X}u;\n", lookup_lo); diff --git a/recompiler/tests/test_kuseg_dispatch_lookup.py b/recompiler/tests/test_kuseg_dispatch_lookup.py index 96854c75..b2142b58 100644 --- a/recompiler/tests/test_kuseg_dispatch_lookup.py +++ b/recompiler/tests/test_kuseg_dispatch_lookup.py @@ -141,6 +141,14 @@ static void psx_check_interrupts_dispatch_entry(CPUState* cpu, uint32_t a) { (void)cpu; (void)a; irqs++; } int psx_vsync_query_hle_try(CPUState* cpu, uint32_t a) { (void)cpu; (void)a; return 0; } +/* Retail live geometry (runtime psx_ram_geometry.c with the 8 MB mod off): + * 2nd-4th MiB of the decode window fold onto the low 2 MiB. */ +uint32_t psx_ram_canon_code_addr(uint32_t addr) { + uint32_t phys = addr & 0x1FFFFFFFu; + if (phys >= 0x00200000u && phys < 0x00800000u) + return (addr & 0xE0000000u) | (phys & 0x001FFFFFu); + return addr; +} ''' harness += "\n".join(f"#define {fn} dummy" for fn in funcs) + "\n" + fragment harness += r''' @@ -155,6 +163,9 @@ int main(void) { expected = &k_psx_game_dispatch[i]; uint32_t addr = aliases[a] | phys; assert(psx_game_find_entry(addr) == expected); + // Retail RAM mirrors resolve to the same compiled entry. + for (uint32_t m = 1; m < 4; ++m) + assert(psx_game_find_entry(addr + m * 0x00200000u) == expected); if (!expected) continue; // A previously resolved PC must NOT cache its live-byte verdict. allowed = 0; cpu.pc = 0xDEADBEEFu; diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 32573949..13bc6e52 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -35,7 +35,8 @@ if(BUILD_TESTING) target_compile_options(ws_world_scene_test PRIVATE -O2 -flto -ffunction-sections -fdata-sections) target_link_options(ws_world_scene_test PRIVATE -flto -Wl,--gc-sections) add_test(NAME ws_world_scene_test COMMAND ws_world_scene_test) - add_executable(mod_memory_snapshot_test tests/test_mod_memory_snapshot.c) + add_executable(mod_memory_snapshot_test tests/test_mod_memory_snapshot.c + src/psx_ram_geometry.c) target_include_directories(mod_memory_snapshot_test PRIVATE include src) target_compile_options(mod_memory_snapshot_test PRIVATE -O2 -flto -ffunction-sections -fdata-sections) target_link_options(mod_memory_snapshot_test PRIVATE -flto -Wl,--gc-sections) @@ -140,14 +141,14 @@ if(BUILD_TESTING) PRIVATE include) if(_ram_geometry STREQUAL "expanded") target_compile_definitions(psx_memory_geometry_${_ram_geometry}_test - PRIVATE PSX_MAIN_RAM_BYTES=0x00800000u) + PRIVATE PSX_MEMORY_GEOMETRY_TEST_EXPANDED=1) endif() add_test(NAME psx_memory_geometry_${_ram_geometry}_test COMMAND psx_memory_geometry_${_ram_geometry}_test) endforeach() add_executable(psx_ram_runtime_map_test - tests/test_psx_ram_runtime_map.c) + tests/test_psx_ram_runtime_map.c src/psx_ram_geometry.c) target_include_directories(psx_ram_runtime_map_test PRIVATE include) add_test(NAME psx_ram_runtime_map_test COMMAND psx_ram_runtime_map_test) @@ -205,6 +206,7 @@ if(BUILD_TESTING) tests/test_gte_register_access.cpp src/gte.cpp src/pgxp.cpp + src/psx_ram_geometry.c ) target_include_directories(gte_register_access_test PRIVATE include) target_compile_definitions(gte_register_access_test PRIVATE @@ -219,7 +221,8 @@ if(BUILD_TESTING) # suppression bracket, invalidate-all, and GetPreciseVertex safeguards # (truncation agreement + tolerance clamp). White-box: includes src/pgxp.cpp # and stubs the gte.cpp fallback-cache extern. - add_executable(pgxp_test tests/test_pgxp.cpp src/pgxp.cpp) + add_executable(pgxp_test tests/test_pgxp.cpp src/pgxp.cpp + src/psx_ram_geometry.c) target_include_directories(pgxp_test PRIVATE include) add_test(NAME pgxp_test COMMAND pgxp_test) @@ -327,7 +330,8 @@ if(BUILD_TESTING) add_executable(overlay_capture_retry_test tests/test_overlay_capture_retry.cpp - src/overlay_capture.c) + src/overlay_capture.c + src/psx_ram_geometry.c) target_include_directories(overlay_capture_retry_test PRIVATE include ${PSX_SDL_INCLUDE_DIRS}) target_compile_definitions(overlay_capture_retry_test PRIVATE @@ -427,7 +431,8 @@ if(BUILD_TESTING) add_executable(text_xlate_fix_only_test tests/test_text_xlate_fix_only.cpp - src/text_xlate.cpp) + src/text_xlate.cpp + src/psx_ram_geometry.c) target_include_directories(text_xlate_fix_only_test PRIVATE include ../recompiler/lib/toml11) diff --git a/runtime/include/dirty_ram_interp.h b/runtime/include/dirty_ram_interp.h index 24cfe449..d96cc5c6 100644 --- a/runtime/include/dirty_ram_interp.h +++ b/runtime/include/dirty_ram_interp.h @@ -16,6 +16,7 @@ #define PSXRECOMP_DIRTY_RAM_INTERP_H #include +#include "psx_memory.h" #include "cpu_state.h" #ifdef __cplusplus @@ -64,7 +65,7 @@ void dirty_ram_irq_ambient_resync_after_restore(void); * Kernel RAM [0x00000, 0x10000): the BIOS part-2 image relocated to RAM * plus install-at-runtime stubs (e.g. the SIO data-byte stub at 0xCF0). * Dirty-tracked per CPU store (dirty_ram_mark_kernel_write). - * Overlay region [OVERLAY_REGION_FLOOR, RAM_SIZE): game overlays loaded by + * Overlay region [OVERLAY_REGION_FLOOR, live RAM end): game overlays loaded by * CD DMA (dirty_ram_mark_executable_range). * * Main-EXE text [0x10000, OVERLAY_REGION_FLOOR): CLEAN pages are statically @@ -258,15 +259,17 @@ extern DirtyRamPcEntry g_dirty_ram_pc_table[DIRTY_RAM_PC_TABLE_SIZE]; /* Every aligned main-RAM word is a possible instruction PC. Execution * coverage only needs presence, not a hit count, so record it in a direct * bitmap instead of probing a large hash table for every retired instruction. - * This covers all 524,288 RAM words (the old 262K-entry hash could saturate) - * and is also the execution-verified seed source used by overlay_capture. */ -#define DIRTY_RAM_EXEC_WORD_COUNT ((2u * 1024u * 1024u) / 4u) + * This covers every word of the host RAM backing (the old 262K-entry hash + * could saturate) -- retail 2 MiB and the opt-in 8 MiB map alike -- and is + * also the execution-verified seed source used by overlay_capture. */ +#define DIRTY_RAM_EXEC_WORD_COUNT (PSX_MAIN_RAM_BACKING_BYTES / 4u) #define DIRTY_RAM_EXEC_BITMAP_WORDS ((DIRTY_RAM_EXEC_WORD_COUNT + 31u) / 32u) extern uint32_t g_dirty_ram_exec_pc_bitmap[DIRTY_RAM_EXEC_BITMAP_WORDS]; /* One bit per 4 KiB page. RAM writes use this as a one-test stale-evidence * guard; they clear that page's capture bits rather than serializing from the * universal store hot path. */ -#define DIRTY_RAM_EXEC_PAGE_BITMAP_WORDS 16u +#define DIRTY_RAM_EXEC_PAGE_BITMAP_WORDS \ + (((PSX_MAIN_RAM_BACKING_BYTES / 4096u) + 31u) / 32u) extern uint32_t g_dirty_ram_exec_page_bitmap[DIRTY_RAM_EXEC_PAGE_BITMAP_WORDS]; /* Presence-only companion for interpreted block/dispatch entries. The richer * counter table remains for telemetry, while capture can snapshot/reset this diff --git a/runtime/include/mod_memory.h b/runtime/include/mod_memory.h index 2bfea7aa..1fbc254b 100644 --- a/runtime/include/mod_memory.h +++ b/runtime/include/mod_memory.h @@ -2,7 +2,7 @@ #define PSXRECOMP_MOD_MEMORY_H #include -#include "psx_ram.h" +#include "psx_memory.h" #ifdef __cplusplus extern "C" { @@ -44,7 +44,9 @@ static inline uint32_t psx_mod_gpu_dma_resolve_address_for( if (psx_mod_gpu_dma_aperture_offset_for( canonical, 4u, used, (uint32_t *)0)) return canonical; - return psx_ram_map_read(canonical) & ~3u; + /* 24-bit tag bits above the 8 MiB decode window are ignored, then the + * live geometry folds (retail: exactly the DMAC's 0x1FFFFC). */ + return psx_ram_canonical_offset(canonical) & ~3u; } uint32_t psx_mod_gpu_dma_memory_alloc(uint32_t size, uint32_t alignment); diff --git a/runtime/include/psx_cyc.h b/runtime/include/psx_cyc.h index 3642d519..354fed8a 100644 --- a/runtime/include/psx_cyc.h +++ b/runtime/include/psx_cyc.h @@ -34,7 +34,7 @@ #endif #include "cpu_state.h" /* CPUState (guard-safe: cpu_state.h includes us last) */ #include "psx_cycles.h" /* inline psx_advance_cycles */ -#include "psx_ram.h" +#include "psx_memory.h" #ifdef __cplusplus extern "C" { diff --git a/runtime/include/psx_memory.h b/runtime/include/psx_memory.h index df150f66..7ed0afc0 100644 --- a/runtime/include/psx_memory.h +++ b/runtime/include/psx_memory.h @@ -7,41 +7,111 @@ extern "C" { #endif -/* One compile-time main-RAM geometry contract for every runtime subsystem. - * A target selects retail 2 MiB mirroring or unique 8 MiB decoding. */ +/* One main-RAM geometry contract for every runtime subsystem. + * + * Retail DRAM is 2 MiB, mirrored four times across the 8 MiB decode window + * (physical 0x00000000-0x007FFFFF of every segment). The opt-in 8 MB main RAM + * enhancement (builtin mod psx.enhancement.8mb-ram) switches the LIVE geometry + * to unique decoding of registered high pages before memory_init(); with the + * mod off the live geometry is retail and every helper below folds exactly as + * retail hardware does. + * + * Host storage indexed by a RAM offset is sized with PSX_MAIN_RAM_BACKING_BYTES + * (the largest geometry); bounds that describe the guest-visible RAM use the + * live size (psx_ram_live_bytes()). Serialized/digested RAM state covers the + * live size only, so retail savestates and netplay digests are unchanged. */ #define PSX_MAIN_RAM_RETAIL_BYTES 0x00200000u #define PSX_MAIN_RAM_EXPANDED_BYTES 0x00800000u +#define PSX_MAIN_RAM_WINDOW_BYTES 0x00800000u +#define PSX_MAIN_RAM_BACKING_BYTES PSX_MAIN_RAM_EXPANDED_BYTES -#ifndef PSX_MAIN_RAM_BYTES -#define PSX_MAIN_RAM_BYTES PSX_MAIN_RAM_RETAIL_BYTES -#endif +/* Live geometry (memory.c). Retail until a trusted activation plugin requests + * expanded RAM (psx_mod_set_main_ram_8mb) and memory_init() applies it. */ +extern uint32_t g_psx_ram_size; +extern uint32_t g_psx_ram_mask; -#if PSX_MAIN_RAM_BYTES != PSX_MAIN_RAM_RETAIL_BYTES && \ - PSX_MAIN_RAM_BYTES != PSX_MAIN_RAM_EXPANDED_BYTES -#error "PSX_MAIN_RAM_BYTES must select 2 MiB or 8 MiB" -#endif +uint32_t memory_get_ram_bytes(void); +int psx_ram_8mb_active(void); +void psx_ram_reset_size_request(void); +/* memory_init(): latch the requested geometry for this boot. */ +void psx_ram_apply_size_request(void); -#define PSX_MAIN_RAM_MASK (PSX_MAIN_RAM_BYTES - 1u) -#define PSX_MAIN_RAM_WORD_MASK (PSX_MAIN_RAM_MASK & ~3u) +/* High pages [2 MiB, 8 MiB) decode uniquely only while registered; unregistered + * high pages keep aliasing the low 2 MiB even in expanded mode. */ +#define PSX_RAM_HIGH_PAGE0 (PSX_MAIN_RAM_RETAIL_BYTES >> 12) +#define PSX_RAM_HIGH_PAGES \ + ((PSX_MAIN_RAM_EXPANDED_BYTES - PSX_MAIN_RAM_RETAIL_BYTES) >> 12) +#define PSX_RAM_HIGH_BITWORDS ((PSX_RAM_HIGH_PAGES + 31u) / 32u) +extern uint32_t g_psx_ram_high_unique[PSX_RAM_HIGH_BITWORDS]; -/* Strip KUSEG/KSEG0/KSEG1 and canonicalize within the 8 MiB DRAM decode - * window. Retail targets fold all four aliases; expanded targets preserve all - * 23 address bits. */ -static inline uint32_t psx_ram_canonical_offset(uint32_t address) { - return (address & 0x1FFFFFFFu) & PSX_MAIN_RAM_MASK; +static inline uint32_t psx_ram_live_bytes(void) { return g_psx_ram_size; } + +static inline int psx_ram_high_page_unique(uint32_t page) { + uint32_t i, bit; + if (page < PSX_RAM_HIGH_PAGE0 || + page >= (PSX_MAIN_RAM_EXPANDED_BYTES >> 12)) + return 1; + i = page - PSX_RAM_HIGH_PAGE0; + bit = 1u << (i & 31u); + return (g_psx_ram_high_unique[i >> 5] & bit) != 0; } +/* Canonical RAM offset of a physical address inside the decode window. + * Addresses at/above the window are returned unchanged (not RAM). */ +static inline uint32_t psx_ram_map_read(uint32_t phys) { + phys &= 0x1FFFFFFFu; + if (phys >= PSX_MAIN_RAM_WINDOW_BYTES) + return phys; + if (g_psx_ram_size <= PSX_MAIN_RAM_RETAIL_BYTES) + return phys & (PSX_MAIN_RAM_RETAIL_BYTES - 1u); + if (phys < PSX_MAIN_RAM_RETAIL_BYTES) + return phys; + if (psx_ram_high_page_unique(phys >> 12)) + return phys; + return phys & (PSX_MAIN_RAM_RETAIL_BYTES - 1u); +} + +static inline uint32_t psx_ram_map_write(uint32_t phys) { + return psx_ram_map_read(phys); +} + +/* Strip KUSEG/KSEG0/KSEG1 and canonicalize within the 8 MiB DRAM decode + * window through the live map. Retail folds all four aliases; expanded mode + * preserves every decoded bit of a registered high page. */ +static inline uint32_t psx_ram_canonical_offset(uint32_t address) { + return psx_ram_map_read((address & 0x1FFFFFFFu) & + (PSX_MAIN_RAM_WINDOW_BYTES - 1u)); +} + +/* Resolve [address, address+width) to a live RAM offset. Fails for anything + * outside the decode window, zero width, or a span past the live RAM end. */ static inline int psx_ram_resolve(uint32_t address, uint32_t width, uint32_t *offset) { const uint32_t phys = address & 0x1FFFFFFFu; uint32_t off; - if (phys >= PSX_MAIN_RAM_EXPANDED_BYTES || width == 0u) return 0; - off = phys & PSX_MAIN_RAM_MASK; - if (width > PSX_MAIN_RAM_BYTES - off) return 0; + if (phys >= PSX_MAIN_RAM_WINDOW_BYTES || width == 0u) return 0; + off = psx_ram_map_read(phys); + if (off >= g_psx_ram_size || width > g_psx_ram_size - off) return 0; if (offset) *offset = off; return 1; } +/* Code-address form: keep the segment bits, fold only aliased mirrors. */ +static inline uint32_t psx_ram_canon_code_addr_inline(uint32_t addr) { + uint32_t seg = addr & 0xE0000000u; + uint32_t phys = addr & 0x1FFFFFFFu; + if (phys < PSX_MAIN_RAM_WINDOW_BYTES && phys >= PSX_MAIN_RAM_RETAIL_BYTES && + (g_psx_ram_size <= PSX_MAIN_RAM_RETAIL_BYTES || + !psx_ram_high_page_unique(phys >> 12))) + phys &= (PSX_MAIN_RAM_RETAIL_BYTES - 1u); + return seg | phys; +} + +void psx_ram_register_unique(uint32_t addr, uint32_t len); +uint32_t psx_ram_canon_code_addr(uint32_t addr); +/* After a bulk RAM restore into `ram` (the host backing). */ +void psx_ram_resync_high_after_restore(const uint8_t *ram); + #ifdef __cplusplus } #endif diff --git a/runtime/include/psx_ram.h b/runtime/include/psx_ram.h deleted file mode 100644 index c149dcd6..00000000 --- a/runtime/include/psx_ram.h +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef PSX_RAM_H -#define PSX_RAM_H - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * Retail DRAM is 2 MiB, mirrored 4x across the 8 MiB decode window. The - * opt-in 8 MB RAM enhancement keeps an 8 MiB host backing and switches the - * live map to unique high pages only after a trusted package requests it. - */ -#define PSX_RAM_2MB 0x00200000u -#define PSX_RAM_8MB 0x00800000u -#define PSX_RAM_CAPACITY PSX_RAM_8MB -#define PSX_RAM_WINDOW 0x00800000u - -extern uint32_t g_psx_ram_size; -extern uint32_t g_psx_ram_mask; - -uint32_t memory_get_ram_bytes(void); -int psx_ram_8mb_active(void); -void psx_ram_reset_size_request(void); - -#define PSX_RAM_HIGH_PAGE0 (PSX_RAM_2MB >> 12) -#define PSX_RAM_HIGH_PAGES ((PSX_RAM_8MB - PSX_RAM_2MB) >> 12) -#define PSX_RAM_HIGH_BITWORDS ((PSX_RAM_HIGH_PAGES + 31u) / 32u) -extern uint32_t g_psx_ram_high_unique[PSX_RAM_HIGH_BITWORDS]; - -static inline int psx_ram_high_page_unique(uint32_t page) { - uint32_t i, bit; - if (page < PSX_RAM_HIGH_PAGE0 || page >= (PSX_RAM_8MB >> 12)) - return 1; - i = page - PSX_RAM_HIGH_PAGE0; - bit = 1u << (i & 31u); - return (g_psx_ram_high_unique[i >> 5] & bit) != 0; -} - -static inline uint32_t psx_ram_map_read(uint32_t phys) { - phys &= 0x1FFFFFFFu; - if (phys >= PSX_RAM_WINDOW) - return phys; - if (g_psx_ram_size <= PSX_RAM_2MB) - return phys & (PSX_RAM_2MB - 1u); - if (phys < PSX_RAM_2MB) - return phys; - if (psx_ram_high_page_unique(phys >> 12)) - return phys; - return phys & (PSX_RAM_2MB - 1u); -} - -static inline uint32_t psx_ram_map_write(uint32_t phys) { - return psx_ram_map_read(phys); -} - -static inline uint32_t psx_ram_canon_code_addr_inline(uint32_t addr) { - uint32_t seg = addr & 0xE0000000u; - uint32_t phys = addr & 0x1FFFFFFFu; - if (phys < PSX_RAM_WINDOW && phys >= PSX_RAM_2MB && - !psx_ram_high_page_unique(phys >> 12)) - phys &= (PSX_RAM_2MB - 1u); - return seg | phys; -} - -void psx_ram_register_unique(uint32_t addr, uint32_t len); -uint32_t psx_ram_canon_code_addr(uint32_t addr); -void psx_ram_resync_high_after_restore(void); - -#ifdef __cplusplus -} -#endif - -#endif /* PSX_RAM_H */ diff --git a/runtime/include/ws_primitive_roles.h b/runtime/include/ws_primitive_roles.h index 7fcac2df..e3ba25a0 100644 --- a/runtime/include/ws_primitive_roles.h +++ b/runtime/include/ws_primitive_roles.h @@ -2,6 +2,7 @@ #define PSX_WS_PRIMITIVE_ROLES_H #include #include +#include "psx_memory.h" /* Title-supplied packet identity, independent of draw order and sprite tags. * Keys name PsyQ P_TAG headers; GPU lookup names the following command word. */ @@ -14,7 +15,7 @@ typedef struct { static inline WsPrimitiveRole* ws_role_write(WsPrimitiveRole* tags, uint32_t primitive, uint32_t now) { - uint32_t key = primitive & 0x1FFFFCu; + uint32_t key = psx_ram_canonical_offset(primitive) & ~3u; if (!key) return NULL; uint32_t slot = (key >> 2) & (WS_ROLE_BUCKETS - 1u), victim = slot; for (unsigned i = 0; i < 4; ++i) { @@ -35,7 +36,7 @@ static inline WsPrimitiveRole* ws_role_write(WsPrimitiveRole* tags, static inline const WsPrimitiveRole* ws_role_read(const WsPrimitiveRole* tags, uint32_t source, uint32_t now) { if (source == 0xFFFFFFFFu) return NULL; - uint32_t key = (source - 4u) & 0x1FFFFCu; + uint32_t key = psx_ram_canonical_offset(source - 4u) & ~3u; if (!key) return NULL; uint32_t slot = (key >> 2) & (WS_ROLE_BUCKETS - 1u); for (unsigned i = 0; i < 4; ++i) { diff --git a/runtime/runtime.cmake b/runtime/runtime.cmake index 11462f09..2c02ac1b 100644 --- a/runtime/runtime.cmake +++ b/runtime/runtime.cmake @@ -330,6 +330,7 @@ set(PSXRECOMP_RUNTIME_SOURCES ${PSXRECOMP_ROOT}/runtime/src/psx_sdl_audio.cpp ${PSXRECOMP_ROOT}/runtime/src/psx_stick.c ${PSXRECOMP_ROOT}/runtime/src/memory.c + ${PSXRECOMP_ROOT}/runtime/src/psx_ram_geometry.c ${PSXRECOMP_ROOT}/runtime/src/kernel_patch_ranges.c ${PSXRECOMP_ROOT}/runtime/src/guest_tty.c ${PSXRECOMP_ROOT}/runtime/src/gpu.c diff --git a/runtime/src/bios_hle.c b/runtime/src/bios_hle.c index 11a875f4..0d82b7b9 100644 --- a/runtime/src/bios_hle.c +++ b/runtime/src/bios_hle.c @@ -33,6 +33,7 @@ */ #include "bios_hle.h" +#include "psx_memory.h" #include "cpu_state.h" #include "psx_cycles.h" @@ -100,7 +101,7 @@ static void hle_record(uint32_t vector, uint32_t fn, const CPUState* cpu, * load-bearing for event-consumption races (MMX6 card check). */ uint32_t pcb = cpu->read_word(0xA0000108u); uint32_t pcb_phys = pcb & 0x1FFFFFFFu; - uint32_t tcb = (pcb != 0u && pcb_phys < 0x200000u) + uint32_t tcb = (pcb != 0u && pcb_phys < psx_ram_live_bytes()) ? cpu->read_word(0xA0000000u | pcb_phys) : 0u; extern int psx_get_in_exception(void); uint8_t in_exc = (uint8_t)(psx_get_in_exception() ? 1u : 0u); diff --git a/runtime/src/boot_state.c b/runtime/src/boot_state.c index 0adf892b..c0221002 100644 --- a/runtime/src/boot_state.c +++ b/runtime/src/boot_state.c @@ -9,7 +9,7 @@ #include "interrupts.h" #include "psx_cycles.h" #include "psx_icache.h" /* g_psx_icache_tv — fetch-cost tags in BS_SEC_ICACHE */ -#include "psx_ram.h" +#include "psx_memory.h" #include "pst_wire.h" #include #include @@ -550,7 +550,7 @@ static int apply_section(uint32_t tag, const uint8_t* p, uint32_t len, { extern void psx_kernel_bless_note_range(uint32_t phys, uint32_t l); psx_kernel_bless_note_range(0, memory_get_ram_bytes()); - psx_ram_resync_high_after_restore(); + psx_ram_resync_high_after_restore(memory_get_ram_ptr()); } return 1; case BS_SEC_SPAD: diff --git a/runtime/src/cosim_state.c b/runtime/src/cosim_state.c index ea792673..9a04f8a2 100644 --- a/runtime/src/cosim_state.c +++ b/runtime/src/cosim_state.c @@ -13,6 +13,7 @@ * and compiled-vs-compiled gates. */ #include "cosim_state.h" +#include "psx_memory.h" #include #include @@ -50,18 +51,21 @@ extern uint64_t interrupts_cosim_hash(uint64_t seed); /* renderer-agnostic VRAM readback (sw renderer = cheap memcpy). */ extern void gr_vram_transfer_out(int x, int y, int w, int h, uint16_t *dst); -#define RAM_SIZE (2u * 1024u * 1024u) +/* Hash the live RAM (retail 2 MiB unless the 8 MB mod is on); page tables are + * sized for the host backing. */ +#define RAM_SIZE psx_ram_live_bytes() #define SPAD_SIZE (1024u) #define PAGE 4096u -#define RAM_PAGES (RAM_SIZE / PAGE) /* 512 */ +#define RAM_PAGES_CAP (PSX_MAIN_RAM_BACKING_BYTES / PAGE) +#define RAM_PAGES (RAM_SIZE / PAGE) #define VRAM_W 1024 #define VRAM_H 512 #define VRAM_SIZE ((uint32_t)(VRAM_W * VRAM_H * 2)) /* 1 MB */ #define VRAM_PAGES (VRAM_SIZE / PAGE) /* 256 */ /* ---- incremental RAM page hashes ---- */ -static uint64_t s_ram_page_h[RAM_PAGES]; -static uint8_t s_ram_page_dirty[RAM_PAGES]; +static uint64_t s_ram_page_h[RAM_PAGES_CAP]; +static uint8_t s_ram_page_dirty[RAM_PAGES_CAP]; static int s_ram_all_dirty = 1; /* ---- VRAM: full-recompute when dirty (draws are periodic, not per-block) ---- */ diff --git a/runtime/src/crash_trace.c b/runtime/src/crash_trace.c index c9f8b04c..f64465af 100644 --- a/runtime/src/crash_trace.c +++ b/runtime/src/crash_trace.c @@ -38,6 +38,7 @@ #include #include "cpu_state.h" +#include "psx_memory.h" #include "psx_bss.h" #include "crash_trace.h" #include "autocompile.h" /* autocompile_degraded_reason — stamp a degraded @@ -166,8 +167,9 @@ static int crash_peek_guest(uint32_t vaddr, uint8_t *dst, int len) { return len; } if (phys < 0x00800000u && g_psx_ram) { - uint32_t folded = phys & 0x1FFFFFu; - if (folded + (uint32_t)len > 0x200000u) len = (int)(0x200000u - folded); + uint32_t folded = psx_ram_canonical_offset(phys); + if (folded + (uint32_t)len > psx_ram_live_bytes()) + len = (int)(psx_ram_live_bytes() - folded); if (len <= 0) return 0; memcpy(dst, g_psx_ram + folded, (size_t)len); return len; diff --git a/runtime/src/data_shards.c b/runtime/src/data_shards.c index 6c3be641..ccd7c668 100644 --- a/runtime/src/data_shards.c +++ b/runtime/src/data_shards.c @@ -30,6 +30,7 @@ * functions are data transforms; a GTE-using function must not be listed). */ #include "data_shards.h" +#include "psx_memory.h" #include "psx_cycles.h" #include "interrupts.h" #include "crc32.h" @@ -47,7 +48,9 @@ extern void psx_write_word(uint32_t addr, uint32_t val); extern void psx_write_byte(uint32_t addr, uint8_t val); extern void dirty_ram_mark_executable_range(uint32_t addr, uint32_t len); -#define DS_RAM_BYTES (2u * 1024u * 1024u) +/* Unified index space: RAM offsets cover the host backing (the opt-in 8 MB + * map's unique high banks included), then the scratchpad. */ +#define DS_RAM_BYTES PSX_MAIN_RAM_BACKING_BYTES #define DS_SP_BYTES 1024u #define DS_SPACE (DS_RAM_BYTES + DS_SP_BYTES) /* unified index space */ #define DS_PAGE_SHIFT 12 @@ -139,7 +142,7 @@ void ds_init(const char* cache_dir, const char* game_id) { /* virtual addr -> unified index, or -1 (untracked: ROM/open bus), or -2 (MMIO) */ static int64_t ds_index_of(uint32_t addr) { uint32_t phys = addr & 0x1FFFFFFFu; - if (phys < 0x00800000u) return (int64_t)(phys & (DS_RAM_BYTES - 1u)); + if (phys < PSX_MAIN_RAM_WINDOW_BYTES) return (int64_t)psx_ram_canonical_offset(phys); if (phys >= 0x1F800000u && phys < 0x1F800000u + DS_SP_BYTES) return (int64_t)(DS_RAM_BYTES + (phys - 0x1F800000u)); if (phys >= 0x1F801000u && phys < 0x1F803000u) return -2; /* MMIO */ diff --git a/runtime/src/debug_server.c b/runtime/src/debug_server.c index 12c86b3a..138e4904 100644 --- a/runtime/src/debug_server.c +++ b/runtime/src/debug_server.c @@ -41,6 +41,7 @@ #include "psx_cycles.h" #include "timers.h" #include "dirty_ram_interp.h" +#include "psx_memory.h" #include "card_read_summary.h" #include "card_data_writes.h" #include "crash_trace.h" @@ -162,7 +163,7 @@ uint64_t s_frame_count = 0; * frame, found in O(1) instead of O(n) function guesses. wr_hash vs pc_hash * classifies the fork: pc differs but wr matches => same writes via a different * control path; wr differs => actual state divergence. Reusable for any title. - * Hashed over main RAM (phys < 0x200000) only — game state lives there; MMIO/ + * Hashed over live main RAM (phys < live RAM end) only — game state lives there; MMIO/ * scratchpad churn (device polling) would add benign cross-backend noise. */ uint64_t g_fp_wr_hash = 1469598103934665603ULL; /* FNV-1a-style seed (main RAM) */ uint64_t g_fp_pc_hash = 1469598103934665603ULL; /* store-PC path sig (main RAM) */ @@ -179,7 +180,7 @@ static PSX_BSS FpEntry s_fp_ring[FP_RING_CAP]; static uint32_t s_fp_head = 0; static uint64_t s_fp_total = 0; -/* Record a guest WRITE into the per-frame fingerprint. Main RAM (phys<0x200000) +/* Record a guest WRITE into the per-frame fingerprint. Live main RAM * feeds the proven wr/pc hashes. Scratchpad (0x1F800000..0x1F8003FF) feeds a * SEPARATE sp_hash — it was previously dropped entirely (the blind spot that * hid a possible pre-1823 scratchpad-state fork), but folding it into wr_hash @@ -196,7 +197,7 @@ static inline void fp_record_write(uint32_t phys, uint32_t val, uint32_t pc) g_fp_sp_count++; return; } - if (phys >= 0x200000u) return; /* main RAM only */ + if (phys >= psx_ram_live_bytes()) return; /* main RAM only */ uint64_t h = g_fp_wr_hash; h = (h ^ (uint64_t)phys) * 1099511628211ULL; h = (h ^ (uint64_t)val) * 1099511628211ULL; @@ -747,7 +748,7 @@ static uint32_t trace_read_word(CPUState *cpu, uint32_t addr) { (void)cpu; uint32_t phys = addr & 0x1FFFFFFFu; - if (phys > 0x001FFFFCu && + if (phys > psx_ram_live_bytes() - 4u && (phys < 0x1F800000u || phys > 0x1F8003FCu)) { return 0; } @@ -764,7 +765,7 @@ static uint32_t trace_read_half(CPUState *cpu, uint32_t addr) { (void)cpu; uint32_t phys = addr & 0x1FFFFFFFu; - if (phys > 0x001FFFFEu && + if (phys > psx_ram_live_bytes() - 2u && (phys < 0x1F800000u || phys > 0x1F8003FEu)) { return 0; } @@ -1367,7 +1368,7 @@ static void evcb_snapshot_capture(EvCBTag tag, uint64_t fn_entry_seq) { /* Convert RAM base ptr to RAM offset (kernel uses cached + uncached * mirrors; mask off region bits). */ - uint32_t base_ram = base_ptr & 0x001FFFFFu; + uint32_t base_ram = psx_ram_canonical_offset(base_ptr); EvCBSnapshot *e = &s_evcb_ring[s_evcb_ring_seq % EVCB_RING_CAP]; e->seq = s_evcb_ring_seq; @@ -1391,7 +1392,7 @@ static void evcb_snapshot_capture(EvCBTag tag, uint64_t fn_entry_seq) { for (uint32_t i = 0; i < n_entries; i++) { uint32_t off = base_ram + i * EVCB_ENTRY_SIZE; - if (off + EVCB_ENTRY_SIZE > 0x00200000u) break; + if (off + EVCB_ENTRY_SIZE > psx_ram_live_bytes()) break; e->entries[i].cls = evcb_read_u32_ram(off + 0); e->entries[i].status = evcb_read_u32_ram(off + 4); e->entries[i].spec = evcb_read_u32_ram(off + 8); @@ -1702,7 +1703,7 @@ static void call_focus_record(uint32_t func_addr) e->obj_18 = psx_read_word(phys + 0x18u); e->obj_30 = psx_read_word(phys + 0x30u); uint32_t out_phys = e->obj_30 & 0x1FFFFFFFu; - if (out_phys < 0x00200000u - 1u) { + if (out_phys < psx_ram_live_bytes() - 1u) { e->obj_30_0 = psx_read_byte(out_phys); e->obj_30_1 = psx_read_byte(out_phys + 1u); } @@ -5171,9 +5172,9 @@ static void handle_read_ram(int id, const char *json) uint32_t addr = hex_to_u32(addr_str); int len = json_get_int(json, "len", 1); if (len < 1) len = 1; - /* Effectively the entire 2 MB RAM in one shot. Response uses a heap- - * sized envelope so we don't truncate. */ - if (len > 0x200000) len = 0x200000; + /* Effectively the entire live RAM (2 MB, or 8 MB with the opt-in map) in + * one shot. Response uses a heap-sized envelope so we don't truncate. */ + if (len > (int)psx_ram_live_bytes()) len = (int)psx_ram_live_bytes(); /* Heap buffer for hex chars + JSON envelope. Each byte = 2 hex chars. */ size_t env = 256; @@ -7278,13 +7279,13 @@ static void handle_evcb_snapshot(int id, const char *json) snap.flag_755A = (uint32_t)psx_read_byte(0x0000755A); snap.flag_75C0 = evcb_read_u32_ram(0x000075C0); snap.frame = (uint32_t)s_frame_count; - uint32_t base_ram = base_ptr & 0x001FFFFFu; + uint32_t base_ram = psx_ram_canonical_offset(base_ptr); uint32_t n_entries = (total_bytes / EVCB_ENTRY_SIZE); if (n_entries > EVCB_MAX_ENTRIES) n_entries = EVCB_MAX_ENTRIES; snap.entry_count = n_entries; for (uint32_t i = 0; i < n_entries; i++) { uint32_t off = base_ram + i * EVCB_ENTRY_SIZE; - if (off + EVCB_ENTRY_SIZE > 0x00200000u) break; + if (off + EVCB_ENTRY_SIZE > psx_ram_live_bytes()) break; snap.entries[i].cls = evcb_read_u32_ram(off + 0); snap.entries[i].status = evcb_read_u32_ram(off + 4); snap.entries[i].spec = evcb_read_u32_ram(off + 8); @@ -9589,7 +9590,7 @@ void debug_server_trace_write_check(uint32_t phys, uint32_t old_val, fp_record_write(phys, new_val, g_debug_last_store_pc); { uint32_t ra = debug_cpu_ptr ? debug_cpu_ptr->gpr[31] : 0; - if (phys < 0x200000u) + if (phys < psx_ram_live_bytes()) rec_event(REC_KIND_RAM_W, phys, new_val, g_debug_last_store_pc, ra); else if (phys >= 0x1F800000u && phys <= 0x1F8003FFu) rec_event(REC_KIND_SP_W, phys, new_val, g_debug_last_store_pc, ra); diff --git a/runtime/src/dirty_ram_interp.c b/runtime/src/dirty_ram_interp.c index 8b5af50c..51be8421 100644 --- a/runtime/src/dirty_ram_interp.c +++ b/runtime/src/dirty_ram_interp.c @@ -30,7 +30,7 @@ #include "psx_cycles.h" #include "psx_icache.h" #include "psx_instr_cost.h" /* psx_instr_base_cycles — single-source cycle cost */ -#include "psx_ram.h" +#include "psx_memory.h" #include "gpu.h" /* psx_ws_is_backdrop_site / psx_ws_backdrop_x (interp hook) */ #include "ws_backdrop_detect.h" /* shared backdrop-window detector (auto_backdrop) */ #include "lockstep.h" @@ -257,7 +257,7 @@ static DirtyRamPcEntry *pc_table_get_or_insert(uint32_t pc) { * cache-unfriendly open-addressed lookup on every guest instruction. */ static inline void exec_pc_table_record(uint32_t pc) { uint32_t phys = pc & 0x1FFFFFFFu; - if (phys < 2u * 1024u * 1024u && (phys & 3u) == 0u) { + if (phys < psx_ram_live_bytes() && (phys & 3u) == 0u) { uint32_t word = phys >> 2; uint32_t mask = 1u << (word & 31u); uint32_t *slot = &g_dirty_ram_exec_pc_bitmap[word >> 5]; @@ -508,13 +508,16 @@ static inline uint32_t imm16_field (uint32_t i) { return i & 0xFFFFu; } static inline int32_t simm16_field(uint32_t i) { return (int32_t)(int16_t)imm16_field(i); } static inline uint32_t target26 (uint32_t i) { return i & 0x03FFFFFFu; } -/* Read a 32-bit instruction word from kernel RAM at the given physical addr. - * Caller has already verified the address is in dirty kernel RAM. */ +/* Read a 32-bit instruction word from main RAM at the given physical addr. + * Caller has already verified the address is RAM. The fetch decodes through + * the live geometry exactly like a CPU load: a retail PC in the 2nd-4th mirror + * fetches the folded 2 MiB bytes, an opt-in 8 MB PC its unique high page. */ static inline uint32_t fetch_word(uint32_t phys) { /* Main RAM is a process-lifetime static allocation. Cache its address so * instruction fetch does not cross translation units for every guest op. */ static const uint8_t *ram; if (!ram) ram = memory_get_ram_ptr(); + phys = psx_ram_map_read(phys); return (uint32_t)ram[phys] | ((uint32_t)ram[phys + 1] << 8) | ((uint32_t)ram[phys + 2] << 16) @@ -552,7 +555,7 @@ static int ws_cull_site(uint32_t pc) { return cache[slot].flag; uint32_t lo = (phys > (uint32_t)(WIN * 4)) ? phys - (uint32_t)(WIN * 4) : 0u; uint32_t hi = phys + (uint32_t)(WIN * 4); - if (hi > 0x200000u) hi = 0x200000u; /* 2 MB main RAM */ + if (hi > psx_ram_live_bytes()) hi = psx_ram_live_bytes(); static uint32_t words[2 * WIN + 1]; int n = 0; for (uint32_t a = lo; a + 4u <= hi && n < (int)(2 * WIN + 1); a += 4u) @@ -578,7 +581,7 @@ static int ws_cull_bltz_site(uint32_t pc) { return cache[slot].flag; uint32_t lo = (phys > (uint32_t)(WIN * 4)) ? phys - (uint32_t)(WIN * 4) : 0u; uint32_t hi = phys + (uint32_t)(WIN * 4); - if (hi > 0x200000u) hi = 0x200000u; /* 2 MB main RAM */ + if (hi > psx_ram_live_bytes()) hi = psx_ram_live_bytes(); static uint32_t words[2 * WIN + 1]; int n = 0; for (uint32_t a = lo; a + 4u <= hi && n < (int)(2 * WIN + 1); a += 4u) @@ -615,7 +618,7 @@ static int ws_backdrop_site_kind(uint32_t pc, int *out_cols) { } uint32_t lo = (phys > (uint32_t)(WIN * 4)) ? phys - (uint32_t)(WIN * 4) : 0u; uint32_t hi = phys + (uint32_t)(WIN * 4 + 4); - if (hi > 0x200000u) hi = 0x200000u; /* 2 MB main RAM */ + if (hi > psx_ram_live_bytes()) hi = psx_ram_live_bytes(); static uint32_t words[2 * WIN + 2]; int n = 0; for (uint32_t a = lo; a + 4u <= hi && n < (int)(2 * WIN + 2); a += 4u) @@ -2874,7 +2877,7 @@ static int dirty_ram_dispatch_inner(CPUState* cpu, uint32_t addr, uint32_t stop_ * a JALR into a CD-DMA'd overlay page then fell through to * psx_unknown_dispatch and fail-fast exit(1). Data and invalid targets * still fail closed via the decodability check. */ - if (phys < (2u * 1024u * 1024u) && + if (phys < psx_ram_live_bytes() && phys_is_overlay_region(phys) && dirty_ram_word_looks_decodable(fetch_word(phys))) { dirty_ram_mark_executable_range(phys, 4u); @@ -3831,7 +3834,7 @@ void ls_func_enter(uint32_t entry_pc, CPUState *cpu) { return; } uint32_t phys = entry_pc & 0x1FFFFFFFu; - if (phys < 0x00010000u || phys >= 0x00200000u) return; + if (phys < 0x00010000u || phys >= psx_ram_live_bytes()) return; s_lsf_dispatch_entry = entry_pc; s_lsf_entry = entry_pc; @@ -3913,9 +3916,10 @@ void ls_at_leader(uint32_t leader_phys, CPUState *cpu) { * (per-instruction, for the cycle ruler); skip those — they're already * interpreted (clean game text dispatched FROM the dirty path still runs * compiled, so we can't use g_dirty_interp_active here). - * - [0x10000, 0x200000): game EXE text in main RAM (above the low-RAM + * - [0x10000, live RAM end): game EXE/overlay text in main RAM (above the low-RAM * kernel/relocated-BIOS area, which isn't the regression locus). */ - if (!g_ls_dirty_observe && leader_phys >= 0x00010000u && leader_phys < 0x00200000u) { + if (!g_ls_dirty_observe && leader_phys >= 0x00010000u && + leader_phys < psx_ram_live_bytes()) { s_ls_R0 = *cpu; s_ls_block = leader_phys; s_ls_trace_n = 0; s_ls_trace_idx = 0; s_ls_overflow = 0; s_ls_mismatch = 0; diff --git a/runtime/src/dma.c b/runtime/src/dma.c index 3b426050..85ea2e05 100644 --- a/runtime/src/dma.c +++ b/runtime/src/dma.c @@ -22,6 +22,7 @@ #include "mod_memory.h" #include "overlay_capture.h" #include "psx_cycles.h" +#include "psx_memory.h" #include "spu.h" #include "audio_trace.h" #include "event_ring.h" @@ -30,6 +31,10 @@ #include #include +/* Word-aligned main-RAM offset of a DMA address (MADR / stepped cursor) + * through the live geometry: retail folds to 2 MiB exactly as the DMAC's + * 0x1FFFFC decode does; the opt-in 8 MB map addresses its unique high pages. */ +#define DMA_RAM_ADDR(a) (psx_ram_canonical_offset(a) & ~3u) /* Memory access — defined in memory.c */ extern uint32_t psx_read_word(uint32_t addr); extern void psx_write_word(uint32_t addr, uint32_t val); @@ -256,7 +261,7 @@ static void start_cdrom_dma_capture(uint32_t requested_words) { memset(&cdrom_dma_active_entry, 0, sizeof(cdrom_dma_active_entry)); cdrom_dma_active_entry.seq = cdrom_dma_history_seq++; cdrom_dma_active_entry.frame_start = (uint32_t)s_frame_count; - cdrom_dma_active_entry.start_addr = channels[3].madr & 0x1FFFFCu; + cdrom_dma_active_entry.start_addr = DMA_RAM_ADDR(channels[3].madr); cdrom_dma_active_entry.final_addr = cdrom_dma_active_entry.start_addr; cdrom_dma_active_entry.requested_words = requested_words; cdrom_dma_active_entry.bcr = channels[3].bcr; @@ -303,7 +308,7 @@ static void finish_cdrom_dma_capture(uint32_t final_addr, uint8_t completed) { cdrom_debug_snapshot(&s); cdrom_dma_active_entry.frame_end = (uint32_t)s_frame_count; - cdrom_dma_active_entry.final_addr = final_addr & 0x1FFFFCu; + cdrom_dma_active_entry.final_addr = DMA_RAM_ADDR(final_addr); cdrom_dma_active_entry.dicr_end = dicr_read_value(dicr); cdrom_dma_active_entry.i_stat_end = i_stat; cdrom_dma_active_entry.sector_read_pos_end = s.sector_read_pos; @@ -400,7 +405,7 @@ static void cancel_async_transfer(int ch) { mdec_async[ch].cycles_accum = 0; } if (ch == 3) { - finish_cdrom_dma_capture(channels[3].madr & 0x1FFFFCu, 0); + finish_cdrom_dma_capture(DMA_RAM_ADDR(channels[3].madr), 0); cdrom_async.active = 0; cdrom_async.debug_started = 0; cdrom_async.total_words = 0; @@ -471,9 +476,9 @@ static void start_async_mdec_transfer(int ch) { a->cycles_accum = 0; if (ch == 0) { - mdec_debug_dma_in_start(channels[0].madr & 0x1FFFFCu, a->remaining_words); + mdec_debug_dma_in_start(DMA_RAM_ADDR(channels[0].madr), a->remaining_words); } else { - mdec_debug_dma_out_start(channels[1].madr & 0x1FFFFCu, a->remaining_words); + mdec_debug_dma_out_start(DMA_RAM_ADDR(channels[1].madr), a->remaining_words); } a->debug_started = 1; } @@ -497,11 +502,11 @@ static void start_async_cdrom_transfer(void) { a->total_words = transfer_word_count(3); a->remaining_words = a->total_words; a->cycles_accum = 0; - a->start_addr = channels[3].madr & 0x1FFFFCu; + a->start_addr = DMA_RAM_ADDR(channels[3].madr); start_cdrom_dma_capture(a->total_words); if (a->total_words == 0) { - finish_cdrom_dma_capture(channels[3].madr & 0x1FFFFCu, 1); + finish_cdrom_dma_capture(DMA_RAM_ADDR(channels[3].madr), 1); cancel_async_transfer(3); complete_transfer(3); } @@ -516,12 +521,13 @@ static void finish_async_cdrom_transfer(uint32_t final_addr) { /* Log and capture game-data transfers only. * Transfers to 0x1C0000+ are FMV/streaming buffers; skip them. */ - if (!step && size > 0 && load_start < 0x1C0000u) { - /* The DMA word loop wraps inside the 2 MB RAM mask; the capture + if (!step && size > 0 && + (load_start < 0x1C0000u || load_start >= PSX_MAIN_RAM_RETAIL_BYTES)) { + /* The DMA word loop wraps inside the live RAM mask; the capture * path below takes a flat ram+offset span and must not follow the * wrap past the end of host RAM. */ - if (size > 0x200000u - load_start) - size = 0x200000u - load_start; + if (size > psx_ram_live_bytes() - load_start) + size = psx_ram_live_bytes() - load_start; int lba = cdrom_get_setloc_lba(); if (lba >= 0) cd_dma_log_push(lba, load_start, size); @@ -551,7 +557,7 @@ static void advance_mdec_channel(int ch, uint32_t cycles) { if ((ch == 0 && direction == 0) || (ch == 1 && direction != 0)) { uint32_t words = a->total_words; - uint32_t addr = channels[ch].madr & 0x1FFFFCu; + uint32_t addr = DMA_RAM_ADDR(channels[ch].madr); finish_async_mdec_transfer(ch, addr, words); return; } @@ -571,7 +577,7 @@ static void advance_mdec_channel(int ch, uint32_t cycles) { uint32_t words_budget = a->cycles_accum / cycles_per_word; if (words_budget == 0) return; - uint32_t addr = channels[ch].madr & 0x1FFFFCu; + uint32_t addr = DMA_RAM_ADDR(channels[ch].madr); uint32_t moved = 0; /* ch1 (MDEC→RAM) writes guest RAM here in a deferred step; mark it as * DMA-sourced so write-provenance tags these writes with ch1 + the kick PC @@ -592,13 +598,13 @@ static void advance_mdec_channel(int ch, uint32_t cycles) { if (!mdec_dma_write_ready()) break; uint32_t n = a->remaining_words < words_budget ? a->remaining_words : words_budget; - uint32_t max_by_ram = (0x200000u - addr) / 4u; + uint32_t max_by_ram = (psx_ram_live_bytes() - addr) / 4u; if (max_by_ram == 0) break; if (n > max_by_ram) n = max_by_ram; uint32_t got = mdec_dma_write_words((const uint32_t *)(ram + addr), n); if (got == 0) break; - addr = (addr + got * 4u) & 0x1FFFFCu; + addr = DMA_RAM_ADDR((addr + got * 4u)); a->remaining_words -= got; words_budget -= got; moved += got; @@ -614,7 +620,7 @@ static void advance_mdec_channel(int ch, uint32_t cycles) { psx_write_word(addr, mdec_dma_read_word()); } - addr = (addr + addr_step) & 0x1FFFFCu; + addr = DMA_RAM_ADDR((addr + addr_step)); a->remaining_words--; words_budget--; moved++; @@ -637,26 +643,26 @@ static void execute_ch0_mdec_in(void) { uint32_t direction = chcr & 1; /* 1=from RAM to MDEC */ uint32_t step = (chcr >> 1) & 1; uint32_t total_words = transfer_word_count(0); - uint32_t addr = channels[0].madr & 0x1FFFFCu; + uint32_t addr = DMA_RAM_ADDR(channels[0].madr); int32_t addr_step = step ? -4 : 4; if (direction != 0) { mdec_debug_dma_in_start(addr, total_words); if (addr_step == 4 && total_words > 0 && - addr + total_words * 4u <= 0x200000u) { + addr + total_words * 4u <= psx_ram_live_bytes()) { extern uint8_t *memory_get_ram_ptr(void); uint32_t got = mdec_dma_write_words( (const uint32_t *)(memory_get_ram_ptr() + addr), total_words); - addr = (addr + got * 4u) & 0x1FFFFCu; + addr = DMA_RAM_ADDR((addr + got * 4u)); /* If the FIFO stalled mid-burst, finish any remainder word-wise. */ for (uint32_t i = got; i < total_words; i++) { mdec_dma_write_word(psx_read_word(addr)); - addr = (addr + 4u) & 0x1FFFFCu; + addr = DMA_RAM_ADDR((addr + 4u)); } } else { for (uint32_t i = 0; i < total_words; i++) { mdec_dma_write_word(psx_read_word(addr)); - addr = (addr + addr_step) & 0x1FFFFCu; + addr = DMA_RAM_ADDR((addr + addr_step)); } } mdec_debug_dma_in_end(addr, total_words); @@ -671,14 +677,14 @@ static void execute_ch1_mdec_out(void) { uint32_t direction = chcr & 1; /* 0=from MDEC to RAM */ uint32_t step = (chcr >> 1) & 1; uint32_t total_words = transfer_word_count(1); - uint32_t addr = channels[1].madr & 0x1FFFFCu; + uint32_t addr = DMA_RAM_ADDR(channels[1].madr); int32_t addr_step = step ? -4 : 4; if (direction == 0) { mdec_debug_dma_out_start(addr, total_words); for (uint32_t i = 0; i < total_words; i++) { psx_write_word(addr, mdec_dma_read_word()); - addr = (addr + addr_step) & 0x1FFFFCu; + addr = DMA_RAM_ADDR((addr + addr_step)); } mdec_debug_dma_out_end(addr, total_words); channels[1].madr = addr; @@ -768,12 +774,12 @@ static uint32_t execute_ch2_gpu(void) { uint32_t block_size = channels[2].bcr & 0xFFFF; uint32_t block_count = (channels[2].bcr >> 16) & 0xFFFF; uint32_t total_words = block_size * block_count; - uint32_t addr = channels[2].madr & 0x1FFFFCu; + uint32_t addr = DMA_RAM_ADDR(channels[2].madr); int32_t addr_step = step ? -4 : 4; for (uint32_t i = 0; i < total_words; i++) { uint32_t pixel_data = gpu_read_gpuread(); psx_write_word(addr, pixel_data); - addr = (addr + addr_step) & 0x1FFFFCu; + addr = DMA_RAM_ADDR((addr + addr_step)); } channels[2].madr = addr; actual_words = total_words; @@ -787,14 +793,14 @@ static uint32_t execute_ch2_gpu(void) { uint32_t block_size = channels[2].bcr & 0xFFFF; uint32_t block_count = (channels[2].bcr >> 16) & 0xFFFF; uint32_t total_words = block_size * block_count; - uint32_t addr = channels[2].madr & 0x1FFFFCu; /* mask to RAM, word-aligned */ + uint32_t addr = DMA_RAM_ADDR(channels[2].madr); /* mask to RAM, word-aligned */ int32_t addr_step = step ? -4 : 4; for (uint32_t i = 0; i < total_words; i++) { uint32_t word = psx_read_word(addr); gpu_set_gp0_source(addr); gpu_write_gp0(word); - addr = (addr + addr_step) & 0x1FFFFCu; + addr = DMA_RAM_ADDR((addr + addr_step)); } channels[2].madr = addr; actual_words = total_words; @@ -806,14 +812,14 @@ static uint32_t execute_ch2_gpu(void) { /* Burst mode (sync_mode == 0) */ uint32_t word_count = channels[2].bcr & 0xFFFF; if (word_count == 0) word_count = 0x10000; /* 0 means 0x10000 */ - uint32_t addr = channels[2].madr & 0x1FFFFCu; + uint32_t addr = DMA_RAM_ADDR(channels[2].madr); int32_t addr_step = step ? -4 : 4; for (uint32_t i = 0; i < word_count; i++) { uint32_t word = psx_read_word(addr); gpu_set_gp0_source(addr); gpu_write_gp0(word); - addr = (addr + addr_step) & 0x1FFFFCu; + addr = DMA_RAM_ADDR((addr + addr_step)); } channels[2].madr = addr; actual_words = word_count; @@ -843,18 +849,18 @@ static uint32_t execute_ch4_spu(void) { uint32_t direction = chcr & 1; /* 1=from RAM to SPU, 0=SPU to RAM */ uint32_t step = (chcr >> 1) & 1; uint32_t total_words = transfer_word_count(4); - uint32_t addr = channels[4].madr & 0x1FFFFCu; + uint32_t addr = DMA_RAM_ADDR(channels[4].madr); int32_t addr_step = step ? -4 : 4; if (direction != 0) { for (uint32_t i = 0; i < total_words; i++) { spu_dma_write(psx_read_word(addr)); - addr = (addr + addr_step) & 0x1FFFFCu; + addr = DMA_RAM_ADDR((addr + addr_step)); } /* One aggregated event per SPU-bound transfer (per-word would flood * the ring: a full sound-bank upload is ~128k words). */ audio_trace_event(AUDIO_EV_DMA_WRITE, total_words, - channels[4].madr & 0x1FFFFCu); + DMA_RAM_ADDR(channels[4].madr)); } else { /* SPU RAM -> CPU RAM. This direction previously zero-filled the * destination, which is not a transfer at all: SPU RAM is readable @@ -865,10 +871,10 @@ static uint32_t execute_ch4_spu(void) { * fell back to a cold-boot path. */ for (uint32_t i = 0; i < total_words; i++) { psx_write_word(addr, spu_dma_read()); - addr = (addr + addr_step) & 0x1FFFFCu; + addr = DMA_RAM_ADDR((addr + addr_step)); } audio_trace_event(AUDIO_EV_DMA_READ, total_words, - channels[4].madr & 0x1FFFFCu); + DMA_RAM_ADDR(channels[4].madr)); } channels[4].madr = addr; @@ -882,7 +888,7 @@ static void execute_ch6_otc(void) { * BCR bits 0-15 = number of entries. */ uint32_t num_entries = channels[6].bcr & 0xFFFF; if (num_entries == 0) num_entries = 0x10000; - uint32_t addr = channels[6].madr & 0x1FFFFCu; + uint32_t addr = DMA_RAM_ADDR(channels[6].madr); for (uint32_t i = 0; i < num_entries; i++) { uint32_t val; @@ -894,7 +900,7 @@ static void execute_ch6_otc(void) { val = (addr - 4) & 0x00FFFFFFu; } psx_write_word(addr, val); - addr = (addr - 4) & 0x1FFFFCu; + addr = DMA_RAM_ADDR((addr - 4)); } complete_transfer(6); @@ -910,20 +916,20 @@ static void execute_ch5_pio(void) { uint32_t direction = chcr & 1u; uint32_t step = (chcr >> 1) & 1u; uint32_t total_words = transfer_word_count(5); - uint32_t addr = channels[5].madr & 0x1FFFFCu; + uint32_t addr = DMA_RAM_ADDR(channels[5].madr); int32_t addr_step = step ? -4 : 4; if (direction == 1) { /* from RAM to device: just read and discard */ for (uint32_t i = 0; i < total_words; i++) { (void)psx_read_word(addr); - addr = (addr + addr_step) & 0x1FFFFCu; + addr = DMA_RAM_ADDR((addr + addr_step)); } } else { /* to RAM from device: write zeros (device not emulated) */ for (uint32_t i = 0; i < total_words; i++) { psx_write_word(addr, 0); - addr = (addr + addr_step) & 0x1FFFFCu; + addr = DMA_RAM_ADDR((addr + addr_step)); } } channels[5].madr = addr; @@ -1144,7 +1150,7 @@ void dma_advance(uint32_t cycles) { } uint32_t words_budget = a->cycles_accum / DMA_CDROM_CYCLES_PER_WORD; - uint32_t addr = channels[3].madr & 0x1FFFFCu; + uint32_t addr = DMA_RAM_ADDR(channels[3].madr); uint32_t moved = 0; g_dma_cur_ch = 3; g_dma_cur_bcr = channels[3].bcr; g_dma_initiator_pc = s_dma_ch_initiator_pc[3]; /* deferred: restore kick PC */ @@ -1153,9 +1159,10 @@ void dma_advance(uint32_t cycles) { * word. Scheduling-time capture was too early because guest code * can continue executing while the CD device is not ready. */ if (a->remaining_words == a->total_words && words_budget > 0 && - addr < 0x1C0000u) { + (addr < 0x1C0000u || addr >= PSX_MAIN_RAM_RETAIL_BYTES)) { uint32_t bytes = a->total_words * 4u; - if (bytes > 0x200000u - addr) bytes = 0x200000u - addr; + if (bytes > psx_ram_live_bytes() - addr) + bytes = psx_ram_live_bytes() - addr; overlay_capture_before_dma(addr, bytes); } while (a->remaining_words > 0 && words_budget > 0 && cdrom_dma_ready()) { @@ -1164,7 +1171,7 @@ void dma_advance(uint32_t cycles) { psx_write_word(addr, word); record_cdrom_dma_word(word); dirty_ram_mark_executable_range(addr, 4); - addr = (addr + addr_step) & 0x1FFFFCu; + addr = DMA_RAM_ADDR((addr + addr_step)); a->remaining_words--; words_budget--; moved++; diff --git a/runtime/src/freeze_heartbeat.c b/runtime/src/freeze_heartbeat.c index 56e78e22..faac7761 100644 --- a/runtime/src/freeze_heartbeat.c +++ b/runtime/src/freeze_heartbeat.c @@ -1,6 +1,7 @@ /* freeze_heartbeat.c — see header for rationale. */ #include "mdec.h" /* mdec_recently_active: FMV parks the guest in BIOS MDEC code */ +#include "psx_memory.h" #include "freeze_heartbeat.h" #include "freeze_dump_policy.h" #include "debug_server.h" @@ -347,10 +348,10 @@ static int hb_append_ram_peek(char *out, int n, size_t cap, uint32_t vaddr, int ncopy = (int)(0x400u - off); } } else if (phys < 0x00800000u && g_psx_ram) { - uint32_t folded = phys & 0x1FFFFFu; + uint32_t folded = psx_ram_canonical_offset(phys); src = g_psx_ram + folded; - if (folded + (uint32_t)ncopy > 0x200000u) - ncopy = (int)(0x200000u - folded); + if (folded + (uint32_t)ncopy > psx_ram_live_bytes()) + ncopy = (int)(psx_ram_live_bytes() - folded); } if (!src) ncopy = 0; int m = snprintf(out + n, cap - (size_t)n, @@ -376,7 +377,7 @@ static uint32_t hb_jal_target_from_ra(uint32_t ra) { uint8_t *sp = memory_get_scratchpad_ptr(); if (sp) src = sp + (phys - 0x1F800000u); } else if (phys < 0x00800000u && g_psx_ram) { - src = g_psx_ram + (phys & 0x1FFFFFu); + src = g_psx_ram + psx_ram_canonical_offset(phys); } if (!src) return 0; memcpy(&insn, src, 4); diff --git a/runtime/src/gpu.c b/runtime/src/gpu.c index 3988c095..30141902 100644 --- a/runtime/src/gpu.c +++ b/runtime/src/gpu.c @@ -16,6 +16,7 @@ #include "display_scanout.h" #include "pgxp.h" #include "mod_memory.h" +#include "psx_memory.h" #include "gpu_primitive_reject.h" #include "gpu_sw_renderer.h" #include "gpu_vram_dirty.h" @@ -44,6 +45,10 @@ #include #include +/* Word-aligned main-RAM key for a primitive/OT address through the live + * geometry (retail: the DMAC's 0x1FFFFC fold). */ +#define GPU_RAM_KEY(a) (psx_ram_canonical_offset(a) & ~3u) + extern uint16_t psx_read_half(uint32_t addr); extern uint8_t psx_read_byte(uint32_t addr); extern uint32_t psx_read_word(uint32_t addr); @@ -1880,7 +1885,7 @@ void gpu_ws_configure(int aspect_num, int aspect_den, * start (the first character of a frame would be suppressed forever). */ void psx_ws_sprite_tag(CPUState* cpu) { if (!ws_engaged() || !ws_anchor_addr) return; - uint32_t key = cpu->gpr[4] & 0x1FFFFCu; + uint32_t key = GPU_RAM_KEY(cpu->gpr[4]); if (!key) return; uint32_t sxy = cpu->read_word(ws_anchor_addr); int32_t ax = (int32_t)(int16_t)(sxy & 0xFFFFu); @@ -1904,7 +1909,7 @@ static int ws_hud_command_words(uint32_t command_addr, uint32_t *words, if (!words || !out_count) return 0; if ((command_addr & 3u) != 0) return 0; uint32_t phys = command_addr & 0x1FFFFFFFu; - if (phys > 0x00200000u - 4u) return 0; + if (phys > psx_ram_live_bytes() - 4u) return 0; words[0] = psx_read_word(command_addr); uint8_t op = (uint8_t)(words[0] >> 24); @@ -1912,7 +1917,7 @@ static int ws_hud_command_words(uint32_t command_addr, uint32_t *words, int count = gp0_command_word_count(op); if (count <= 0 || count > 12) return 0; uint32_t bytes = (uint32_t)count * 4u; - if (phys > 0x00200000u - bytes) return 0; + if (phys > psx_ram_live_bytes() - bytes) return 0; for (int i = 1; i < count; i++) words[i] = psx_read_word(command_addr + (uint32_t)i * 4u); @@ -1932,7 +1937,7 @@ void gpu_ws_tag_hud_prim(uint32_t prim, int anchor) { WsPrepassPacketGuard guard = ws_prepass_packet_guard(words, word_count); ws_hud_anchor_insert(ws_hud_anchor_tags, WS_HUD_ANCHOR_TABLE_SIZE, - command_addr & 0x1FFFFCu, anchor, &guard, + GPU_RAM_KEY(command_addr), anchor, &guard, (uint32_t)s_frame_count); } @@ -1945,7 +1950,7 @@ void gpu_ws_tag_black_reveal_rect(uint32_t prim) { if ((words[0] >> 24) != 0x64u && (words[0] >> 24) != 0x65u) return; WsPrepassPacketGuard guard = ws_prepass_packet_guard(words, count); ws_hud_anchor_insert(ws_reveal_clear_tags, WS_HUD_ANCHOR_TABLE_SIZE, - (prim + 4u) & 0x1FFFFCu, 0, &guard, + GPU_RAM_KEY((prim + 4u)), 0, &guard, (uint32_t)s_frame_count); } @@ -1960,7 +1965,7 @@ void gpu_ws_tag_repeat_rect(uint32_t prim, int32_t period) { if (!w || w > 256u || (words[2] & 255u) + w > 256u || !h || h > 511u) return; WsPrepassPacketGuard guard = ws_prepass_packet_guard(words, count); - ws_repeat_rect_tag_insert(ws_repeat_rect_tags, (prim + 4u) & 0x1FFFFCu, + ws_repeat_rect_tag_insert(ws_repeat_rect_tags, GPU_RAM_KEY((prim + 4u)), period, &guard, (uint32_t)s_frame_count); } @@ -1971,7 +1976,7 @@ static int ws_nw_explicit_hud_delta(int32_t *out_delta) { if (gp0_words_needed <= 0 || gp0_words_needed > 12) return 0; uint32_t command_addr = gp0_cmd_source_addr & 0x1FFFFFFFu; - if (command_addr >= 0x00200000u || (command_addr & 3u)) return 0; + if (command_addr >= psx_ram_live_bytes() || (command_addr & 3u)) return 0; int anchor = 0; if (!ws_hud_anchor_lookup(ws_hud_anchor_tags, WS_HUD_ANCHOR_TABLE_SIZE, command_addr, gp0_cmd_buf, @@ -1991,7 +1996,7 @@ static int ws_tagged_anchor(int32_t *out_ax) { if (!ws_active() || gp0_cmd_source_addr == 0xFFFFFFFFu) return 0; uint32_t now = (uint32_t)s_frame_count; for (int variant = 0; variant < 2; variant++) { - uint32_t key = (gp0_cmd_source_addr - (variant ? 0u : 4u)) & 0x1FFFFCu; + uint32_t key = GPU_RAM_KEY((gp0_cmd_source_addr - (variant ? 0u : 4u))); uint32_t idx = (key >> 2) & (WS_TAG_BUCKETS - 1); for (int i = 0; i < WS_TAG_PROBES; i++) { WsTag *t = &ws_tags[(idx + i) & (WS_TAG_BUCKETS - 1)]; @@ -2132,7 +2137,7 @@ int psx_ws_prim_is_tagged(void) { if (!ws_engaged() || gp0_cmd_source_addr == 0xFFFFFFFFu) return 0; uint32_t now = (uint32_t)s_frame_count; for (int variant = 0; variant < 2; variant++) { - uint32_t key = (gp0_cmd_source_addr - (variant ? 0u : 4u)) & 0x1FFFFCu; + uint32_t key = GPU_RAM_KEY((gp0_cmd_source_addr - (variant ? 0u : 4u))); uint32_t idx = (key >> 2) & (WS_TAG_BUCKETS - 1); for (int i = 0; i < WS_TAG_PROBES; i++) { WsTag *t = &ws_tags[(idx + i) & (WS_TAG_BUCKETS - 1)]; @@ -2228,7 +2233,7 @@ static int ws_auto_ui_anchor(int32_t *out_anchor) { if (!ws_auto_ui_squash || !ws_active() || gp0_cmd_source_addr == 0xFFFFFFFFu) return 0; - uint32_t src = gp0_cmd_source_addr & 0x1FFFFCu; + uint32_t src = GPU_RAM_KEY(gp0_cmd_source_addr); for (uint32_t i = 0; i < ws_ui_prepass_count; i++) { if (ws_ui_prepass[i].src_addr != src) continue; if (!ws_prepass_packet_matches(&ws_ui_prepass[i].packet_guard, @@ -2770,7 +2775,7 @@ static void ws_clear_tagged_rect_reveal(int y, int h) { if (!ws_native_wide_active() || h <= 0 || gp0_cmd_source_addr == UINT32_MAX) return; uint32_t address = gp0_cmd_source_addr & 0x1FFFFFFFu; - if (address >= 0x00200000u || (address & 3u) || + if (address >= psx_ram_live_bytes() || (address & 3u) || !ws_hud_anchor_lookup(ws_reveal_clear_tags, WS_HUD_ANCHOR_TABLE_SIZE, address, gp0_cmd_buf, (uint32_t)gp0_words_needed, (uint32_t)s_frame_count, NULL)) return; @@ -3653,7 +3658,7 @@ static void prepare_texture_triangle(int i0, int i1, int i2) { int indices[3] = { i0, i1, i2 }; uint16_t z[3]; for (int i = 0; i < 3; i++) { - uint32_t addr = (gp0_cmd_source_addr + (uint32_t)indices[i] * 4u) & 0x1FFFFCu; + uint32_t addr = GPU_RAM_KEY((gp0_cmd_source_addr + (uint32_t)indices[i] * 4u)); if (!gte_precision_load_word(addr, gp0_cmd_buf[indices[i]], NULL, NULL, &z[i]) || z[i] == 0) { s_texcorr.no_depth++; @@ -4384,7 +4389,7 @@ static void ws_repeat_textured_rect_reveal(int x, int y, int w, int h, draw_area_right - draw_area_left + 1u != (uint32_t)width) return; uint32_t address = gp0_cmd_source_addr & 0x1FFFFFFFu; - if (address >= 0x00200000u || (address & 3u)) return; + if (address >= psx_ram_live_bytes() || (address & 3u)) return; int32_t period = ws_repeat_rect_tag_lookup(ws_repeat_rect_tags, address, gp0_cmd_buf, (uint32_t)gp0_words_needed, (uint32_t)s_frame_count); if (!period) return; @@ -4778,8 +4783,8 @@ static void gp0_exec_cpu_to_vram(void) { a0_history[slot].s2_val = debug_cpu_ptr->gpr[18]; /* $s2 = source ptr */ a0_history[slot].a0_val = debug_cpu_ptr->gpr[4]; /* $a0 */ a0_history[slot].a1_val = debug_cpu_ptr->gpr[5]; /* $a1 */ - uint32_t sp_phys = sp & 0x1FFFFFu; - for (int si = 0; si < 10 && sp_phys + (si + 1) * 4 <= 0x200000u; si++) + uint32_t sp_phys = psx_ram_canonical_offset(sp); + for (int si = 0; si < 10 && sp_phys + (si + 1) * 4 <= psx_ram_live_bytes(); si++) a0_history[slot].stack[si] = psx_read_word(sp + si * 4); } a0_capture_slot = slot; @@ -5022,7 +5027,7 @@ static void ws_ui_prepass_add(const uint32_t *words, uint32_t word_count, item->group.height = height; item->group.anchor = 0; item->group.root = ws_ui_prepass_count - 1u; - item->src_addr = source_addr & 0x1FFFFCu; + item->src_addr = GPU_RAM_KEY(source_addr); item->ot_rank = rank; item->y = min_y; item->h = height; @@ -5068,7 +5073,7 @@ void gpu_ws_prepass_linked_list(uint32_t start_addr) { } WsUiPrepassNode *node = &ws_ui_prepass_nodes[ws_ui_prepass_node_count++]; - node->addr = addr & 0x1FFFFCu; + node->addr = GPU_RAM_KEY(addr); node->header = header; node->payload_guard = ws_prepass_packet_guard(payload, num_words); @@ -5165,7 +5170,7 @@ void gpu_ws_validate_linked_list_header(uint32_t addr, uint32_t header) { if (ws_ui_prepass_count == 0) return; uint32_t resolved = - psx_mod_gpu_dma_resolve_address(addr) & 0x1FFFFCu; + GPU_RAM_KEY(psx_mod_gpu_dma_resolve_address(addr)); for (uint32_t i = 0; i < ws_ui_prepass_node_count; i++) { if (ws_ui_prepass_nodes[i].addr != resolved) continue; if (ws_ui_prepass_nodes[i].header != header) @@ -5179,7 +5184,7 @@ void gpu_ws_validate_linked_list_node(uint32_t addr, uint32_t num_words) { if (ws_ui_prepass_count == 0) return; uint32_t resolved = - psx_mod_gpu_dma_resolve_address(addr) & 0x1FFFFCu; + GPU_RAM_KEY(psx_mod_gpu_dma_resolve_address(addr)); const WsUiPrepassNode *node = NULL; for (uint32_t i = 0; i < ws_ui_prepass_node_count; i++) { if (ws_ui_prepass_nodes[i].addr == resolved) { @@ -5233,11 +5238,11 @@ static void gp0_capture_builder_chain(uint32_t out[6]) { for (int i = 0; i < 6; i++) out[i] = 0; uint8_t *ram = memory_get_ram_ptr(); if (!ram) return; - const uint32_t RMASK = 0x001FFFFFu; /* 2 MB, mirror-folded */ + const uint32_t RMASK = g_psx_ram_mask; /* live RAM, mirror-folded */ uint32_t rawsp = debug_guest_sp(); g_gp0_last_copy_sp = rawsp; if ((rawsp & 0x1FFFFFFFu) >= 0x00800000u) return; /* not in RAM/mirror */ - uint32_t sp = rawsp & RMASK & ~3u; /* fold to 2MB, word-align */ + uint32_t sp = psx_ram_canonical_offset(rawsp) & ~3u; /* fold, word-align */ /* Two-pass: prefer words that are genuine return addresses (call at ret-8), * but if guest code validation yields none, fall back to any code-range * stack word so the chain is never silently empty. */ diff --git a/runtime/src/interrupts.c b/runtime/src/interrupts.c index e3e6c143..9376ecd6 100644 --- a/runtime/src/interrupts.c +++ b/runtime/src/interrupts.c @@ -26,6 +26,7 @@ */ #include "interrupts.h" +#include "psx_memory.h" #include "sio.h" #include "timers.h" #include "gpu.h" @@ -1597,12 +1598,12 @@ irq_deliver_eval: if (psx_scheduler_top_level_resume_active() && cpu->pc != 0u && (cpu->pc & 3u) == 0u) { uint32_t phys = cpu->pc & 0x1FFFFFFFu; - if (phys < 0x00200000u || + if (phys < psx_ram_live_bytes() || (phys >= 0x1FC00000u && phys < 0x1FC80000u)) real_pc = cpu->pc; } } - /* Accept the real resume PC from guest RAM (<2MB) OR the BIOS ROM + /* Accept the real resume PC from live guest RAM OR the BIOS ROM * window. The old RAM-only guard rejected ROM-space block leaders * (e.g. OpenBIOS mcWaitForStatus spinning at 0xBFC076xx during a * card op), forcing EVERY such delivery onto the legacy sentinel. @@ -1617,7 +1618,7 @@ irq_deliver_eval: * ROM pc still falls back to the sentinel (pre-fix behavior); * RAM acceptance is unchanged byte-for-byte. */ uint32_t real_phys = real_pc & 0x1FFFFFFFu; - int resume_in_ram = real_phys < 0x00200000u; + int resume_in_ram = real_phys < psx_ram_live_bytes(); int resume_in_rom = real_phys >= 0x1FC00000u && real_phys < 0x1FC80000u && psx_is_dispatchable(real_pc); if (real_pc != 0u && (real_pc & 0x3u) == 0u && diff --git a/runtime/src/memory.c b/runtime/src/memory.c index 03f6a704..9de37127 100644 --- a/runtime/src/memory.c +++ b/runtime/src/memory.c @@ -25,14 +25,14 @@ #include "dirty_ram_interp.h" #include "guest_tty.h" #include "psx_cycles.h" -#include "psx_ram.h" +#include "psx_memory.h" #include "starvation_ring.h" #include #include #include #include -#define RAM_SIZE PSX_RAM_CAPACITY +#define RAM_SIZE PSX_MAIN_RAM_BACKING_BYTES #define SCRATCHPAD_SIZE 1024 #define BIOS_ROM_SIZE (512 * 1024) #define MOD_MEMORY_BASE 0x1F000000u @@ -45,67 +45,6 @@ static uint8_t mod_memory[MOD_MEMORY_SIZE]; static uint32_t mod_memory_used; static uint8_t mod_gpu_dma_memory[PSX_MOD_GPU_DMA_APERTURE_SIZE]; static uint32_t mod_gpu_dma_memory_used; -static int s_ram_8mb_requested; - -uint32_t g_psx_ram_size = PSX_RAM_2MB; -uint32_t g_psx_ram_mask = PSX_RAM_2MB - 1u; -static uint32_t s_ram_high_registered[PSX_RAM_HIGH_BITWORDS]; -uint32_t g_psx_ram_high_unique[PSX_RAM_HIGH_BITWORDS]; - -static inline void psx_ram_high_page_mark(uint32_t page) { - uint32_t i, bit; - if (page < PSX_RAM_HIGH_PAGE0 || page >= (PSX_RAM_8MB >> 12)) - return; - i = page - PSX_RAM_HIGH_PAGE0; - bit = 1u << (i & 31u); - g_psx_ram_high_unique[i >> 5] |= bit; - s_ram_high_registered[i >> 5] |= bit; -} - -static void psx_ram_apply_registered_bitmap(void) { - memcpy(g_psx_ram_high_unique, s_ram_high_registered, - sizeof(g_psx_ram_high_unique)); -} - -void psx_ram_register_unique(uint32_t addr, uint32_t len) { - uint32_t phys, end, page, last; - if (len == 0u) - return; - phys = addr & 0x1FFFFFFFu; - if (phys >= PSX_RAM_WINDOW) - return; - end = phys + len; - if (end < phys || end > PSX_RAM_WINDOW) - end = PSX_RAM_WINDOW; - if (end <= PSX_RAM_2MB) - return; - if (phys < PSX_RAM_2MB) - phys = PSX_RAM_2MB; - page = phys >> 12; - last = (end - 1u) >> 12; - for (; page <= last; page++) - psx_ram_high_page_mark(page); -} - -uint32_t psx_ram_canon_code_addr(uint32_t addr) { - return psx_ram_canon_code_addr_inline(addr); -} - -void psx_ram_resync_high_after_restore(void) { - uint32_t page; - psx_ram_apply_registered_bitmap(); - if (g_psx_ram_size <= PSX_RAM_2MB) - return; - for (page = PSX_RAM_HIGH_PAGE0; page < (PSX_RAM_8MB >> 12); page++) { - uint32_t dst = page << 12; - uint32_t src = dst & (PSX_RAM_2MB - 1u); - if (psx_ram_high_page_unique(page)) - continue; - if (memcmp(ram + dst, ram + src, 4096u) != 0) - psx_ram_high_page_mark(page); - } -} - uint32_t psx_mod_memory_snapshot_bytes(void) { return mod_memory_used || mod_gpu_dma_memory_used ? 16u + mod_memory_used + mod_gpu_dma_memory_used : 0u; @@ -205,59 +144,19 @@ int g_psx_load_delay = -1; * registered high pages uniquely and keeps unregistered high pages aliased. */ static inline uint32_t psx_phys_addr(uint32_t addr) { uint32_t phys = addr & 0x1FFFFFFFu; - if (phys < PSX_RAM_WINDOW) phys = psx_ram_map_read(phys); + if (phys < PSX_MAIN_RAM_WINDOW_BYTES) phys = psx_ram_map_read(phys); return phys; } static inline uint32_t psx_phys_addr_store(uint32_t addr) { uint32_t phys = addr & 0x1FFFFFFFu; - if (phys < PSX_RAM_WINDOW) phys = psx_ram_map_write(phys); + if (phys < PSX_MAIN_RAM_WINDOW_BYTES) phys = psx_ram_map_write(phys); return phys; } /* Expose RAM pointer for oracle comparison (find_first_divergence). */ uint8_t *memory_get_ram_ptr(void) { return ram; } uint8_t *memory_get_scratchpad_ptr(void) { return scratchpad; } -uint32_t memory_get_ram_bytes(void) { return g_psx_ram_size; } -int psx_ram_8mb_active(void) { return g_psx_ram_size > PSX_RAM_2MB; } - -void psx_ram_reset_size_request(void) { - s_ram_8mb_requested = 0; - memset(s_ram_high_registered, 0, sizeof(s_ram_high_registered)); - memset(g_psx_ram_high_unique, 0, sizeof(g_psx_ram_high_unique)); -} - -int psx_mod_set_main_ram_8mb(int enabled) { - s_ram_8mb_requested = enabled ? 1 : 0; - memset(s_ram_high_registered, 0, sizeof(s_ram_high_registered)); - memset(g_psx_ram_high_unique, 0, sizeof(g_psx_ram_high_unique)); - if (enabled) - psx_ram_register_unique(PSX_RAM_2MB, PSX_RAM_8MB - PSX_RAM_2MB); - return 1; -} - -static int psx_ram_any_high_registered(void) { - uint32_t i; - for (i = 0; i < PSX_RAM_HIGH_BITWORDS; i++) { - if (s_ram_high_registered[i] != 0u) - return 1; - } - return 0; -} - -static void psx_ram_apply_size_request(void) { - if (s_ram_8mb_requested) { - g_psx_ram_size = PSX_RAM_8MB; - g_psx_ram_mask = PSX_RAM_8MB - 1u; - if (!psx_ram_any_high_registered()) - psx_ram_register_unique(PSX_RAM_2MB, PSX_RAM_8MB - PSX_RAM_2MB); - } else { - g_psx_ram_size = PSX_RAM_2MB; - g_psx_ram_mask = PSX_RAM_2MB - 1u; - memset(g_psx_ram_high_unique, 0, sizeof(g_psx_ram_high_unique)); - } -} - void memory_clear_low_boot_scratch(void) { memset(ram, 0, 0x10u); } @@ -287,6 +186,11 @@ void memory_clear_low_boot_scratch(void) { #define DIRTY_RAM_PAGE_SHIFT 12 /* 4 KB pages */ #define DIRTY_RAM_PAGE_COUNT (RAM_SIZE >> DIRTY_RAM_PAGE_SHIFT) #define DIRTY_RAM_BITMAP_WORDS ((DIRTY_RAM_PAGE_COUNT + 31u) / 32u) +/* Guest-visible extent (retail 2 MiB unless the 8 MB mod is live). Host arrays + * are sized for the backing capacity; bounds and serialization use this. */ +#define RAM_LIVE (g_psx_ram_size) +#define DIRTY_RAM_LIVE_BITMAP_WORDS \ + (((RAM_LIVE >> DIRTY_RAM_PAGE_SHIFT) + 31u) / 32u) static uint32_t dirty_ram_bitmap[DIRTY_RAM_BITMAP_WORDS]; /* Monotonic generation for RAM-resident CODE changes (kernel install-stub @@ -297,7 +201,7 @@ static uint32_t dirty_ram_bitmap[DIRTY_RAM_BITMAP_WORDS]; uint32_t g_dirty_ram_code_gen = 1; static inline void dirty_ram_mark_page(uint32_t phys) { - if (phys >= RAM_SIZE) return; + if (phys >= RAM_LIVE) return; uint32_t page = phys >> DIRTY_RAM_PAGE_SHIFT; uint32_t bit = 1u << (page & 31u); /* Generation bumps on the clean->dirty TRANSITION only: kernel-window data @@ -563,7 +467,7 @@ extern uint32_t g_overlay_region_floor; void dirty_ram_clear_image_baseline(void) { uint32_t floor = g_overlay_region_floor; if (floor <= DIRTY_RAM_KERNEL_TRACK_BYTES) return; - if (floor > RAM_SIZE) floor = RAM_SIZE; + if (floor > RAM_LIVE) floor = RAM_LIVE; uint32_t base = g_text_image_lo; if (base < DIRTY_RAM_KERNEL_TRACK_BYTES) base = DIRTY_RAM_KERNEL_TRACK_BYTES; if (base >= floor) return; @@ -605,8 +509,8 @@ static uint32_t g_text_exact_last_ref = 0; void dirty_ram_register_text_image(uint32_t phys_lo, const uint8_t *bytes, uint32_t len) { - if (!bytes || len == 0 || phys_lo >= RAM_SIZE) return; - if (len > RAM_SIZE - phys_lo) len = RAM_SIZE - phys_lo; + if (!bytes || len == 0 || phys_lo >= RAM_LIVE) return; + if (len > RAM_LIVE - phys_lo) len = RAM_LIVE - phys_lo; text_ref_image = (uint8_t *)bytes; /* runtime-owned mutable heap buffer */ text_ref_lo = phys_lo; text_ref_hi = phys_lo + len; @@ -799,10 +703,10 @@ uint32_t dirty_ram_text_diverged_bitmap_word(uint32_t word_index) { } void dirty_ram_mark_executable_range(uint32_t phys, uint32_t len) { - if (len == 0 || phys >= RAM_SIZE) return; + if (len == 0 || phys >= RAM_LIVE) return; psx_kernel_bless_note_range(phys, len); uint32_t end = phys + len - 1u; - if (end >= RAM_SIZE || end < phys) end = RAM_SIZE - 1u; + if (end >= RAM_LIVE || end < phys) end = RAM_LIVE - 1u; uint32_t first_page = phys >> DIRTY_RAM_PAGE_SHIFT; uint32_t last_page = end >> DIRTY_RAM_PAGE_SHIFT; @@ -846,7 +750,7 @@ static int dirty_ram_shellwin_interp(void) { } int dirty_ram_is_dirty(uint32_t phys) { - if (phys >= RAM_SIZE) return 0; + if (phys >= RAM_LIVE) return 0; if (dirty_ram_force_interp() && phys >= DIRTY_RAM_KERNEL_TRACK_BYTES) return 1; if (dirty_ram_shellwin_interp() && phys >= 0x00030000u && phys <= 0x0005AFFFu) return 1; /* Experimental fallback for overlays copied into their final location by @@ -868,13 +772,16 @@ uint32_t dirty_ram_get_bitmap_word(uint32_t word_index) { return dirty_ram_bitmap[word_index]; } +/* Serialized (savestate / netplay digest) extent: the LIVE RAM's pages only, + * so a retail session's dirty-bitmap section is unchanged by the 8 MiB host + * backing. */ uint32_t dirty_ram_get_bitmap_word_count(void) { - return DIRTY_RAM_BITMAP_WORDS; + return DIRTY_RAM_LIVE_BITMAP_WORDS; } void dirty_ram_set_bitmap_words(const uint32_t* words, uint32_t count) { memset(dirty_ram_bitmap, 0, sizeof(dirty_ram_bitmap)); - if (count > DIRTY_RAM_BITMAP_WORDS) count = DIRTY_RAM_BITMAP_WORDS; + if (count > DIRTY_RAM_LIVE_BITMAP_WORDS) count = DIRTY_RAM_LIVE_BITMAP_WORDS; for (uint32_t i = 0; i < count; i++) dirty_ram_bitmap[i] = words[i]; /* Bitmap replace bypasses clean→dirty transitions; bump so interpreter @@ -914,9 +821,9 @@ void dirty_ram_reset_for_boot(void) { } void overlay_watch_set_range(uint32_t phys, uint32_t len) { - if (len == 0 || phys >= RAM_SIZE) return; + if (len == 0 || phys >= RAM_LIVE) return; uint32_t end = phys + len - 1u; - if (end >= RAM_SIZE || end < phys) end = RAM_SIZE - 1u; + if (end >= RAM_LIVE || end < phys) end = RAM_LIVE - 1u; uint32_t fp = phys >> DIRTY_RAM_PAGE_SHIFT; uint32_t lp = end >> DIRTY_RAM_PAGE_SHIFT; for (uint32_t pg = fp; pg <= lp; pg++) @@ -924,9 +831,9 @@ void overlay_watch_set_range(uint32_t phys, uint32_t len) { } void overlay_watch_clear_range(uint32_t phys, uint32_t len) { - if (len == 0 || phys >= RAM_SIZE) return; + if (len == 0 || phys >= RAM_LIVE) return; uint32_t end = phys + len - 1u; - if (end >= RAM_SIZE || end < phys) end = RAM_SIZE - 1u; + if (end >= RAM_LIVE || end < phys) end = RAM_LIVE - 1u; uint32_t fp = phys >> DIRTY_RAM_PAGE_SHIFT; uint32_t lp = end >> DIRTY_RAM_PAGE_SHIFT; for (uint32_t pg = fp; pg <= lp; pg++) @@ -937,9 +844,9 @@ void overlay_watch_clear_range(uint32_t phys, uint32_t len) { * loader stores this at validation time and compares on dispatch; any change * means a watched page in the range was written. */ uint32_t overlay_watch_pagegen_sum(uint32_t phys, uint32_t len) { - if (len == 0 || phys >= RAM_SIZE) return 0; + if (len == 0 || phys >= RAM_LIVE) return 0; uint32_t end = phys + len - 1u; - if (end >= RAM_SIZE || end < phys) end = RAM_SIZE - 1u; + if (end >= RAM_LIVE || end < phys) end = RAM_LIVE - 1u; uint32_t fp = phys >> DIRTY_RAM_PAGE_SHIFT; uint32_t lp = end >> DIRTY_RAM_PAGE_SHIFT; uint32_t sum = 0; @@ -981,7 +888,7 @@ void dirty_ram_text_guard_resync_after_restore(void) { uint32_t hi = lo + (1u << DIRTY_RAM_PAGE_SHIFT); if (lo < text_ref_lo) lo = text_ref_lo; if (hi > text_ref_hi) hi = text_ref_hi; - if (hi > RAM_SIZE) hi = RAM_SIZE; + if (hi > RAM_LIVE) hi = RAM_LIVE; if (hi <= lo) continue; if (memcmp(ram + lo, text_ref_image + (lo - text_ref_lo), hi - lo) != 0) text_modified_bitmap[p >> 5] |= (1u << (p & 31u)); @@ -1002,7 +909,7 @@ void overlay_watch_invalidate_after_ram_restore(void) { static inline void overlay_watch_note_write(uint32_t phys, uint32_t size) { uint32_t pg = phys >> DIRTY_RAM_PAGE_SHIFT; - if (pg >= DIRTY_RAM_PAGE_COUNT) return; + if (pg >= (RAM_LIVE >> DIRTY_RAM_PAGE_SHIFT)) return; /* Never attach pre-write PC evidence to post-write bytes, including for * completely unknown/self-modifying code. This is deliberately a compact * page clear, not a capture: serializing snapshots from this universal diff --git a/runtime/src/mod_runtime.cpp b/runtime/src/mod_runtime.cpp index 63226032..d9082741 100644 --- a/runtime/src/mod_runtime.cpp +++ b/runtime/src/mod_runtime.cpp @@ -5,7 +5,7 @@ #include "mod_packages.h" #include "mod_plugins.h" #include "gpu.h" -#include "psx_ram.h" +#include "psx_memory.h" #include "psx_sha256.h" #if defined(RECOMP_LAUNCHER) diff --git a/runtime/src/overlay_capture.c b/runtime/src/overlay_capture.c index 0ca510e7..6db3fa61 100644 --- a/runtime/src/overlay_capture.c +++ b/runtime/src/overlay_capture.c @@ -355,14 +355,14 @@ static void write_json_window(FILE *f, uint32_t win_lo_page, * boot/overlay capture-window boundary is required: those windows * stabilize region keys, but they are not MIPS execution barriers. * Adjacent dirty pages were already folded into this run. */ - const uint32_t ram_size = 2u * 1024u * 1024u; + const uint32_t ram_size = psx_ram_live_bytes(); uint32_t guard_bytes = 0u; if (phys <= ram_size && size <= ram_size - phys && phys + size <= ram_size - 4u) { size += 4u; guard_bytes = 4u; } - uint32_t virt = 0x80000000u | (phys & 0x1FFFFFu); + uint32_t virt = 0x80000000u | psx_ram_canonical_offset(phys); in_run = 0; /* Seeds: only per-PC interpreter hits — execution-verified. */ @@ -801,7 +801,7 @@ static void capture_executed_pages(uint32_t *bitmap, uint32_t bw, uint32_t scope_lo, uint32_t scope_hi, int include_halo) { - const uint32_t ram_size = 2u * 1024u * 1024u; + const uint32_t ram_size = psx_ram_live_bytes(); memset(bitmap, 0, (size_t)bw * sizeof(uint32_t)); if (scope_hi > ram_size) scope_hi = ram_size; if (scope_lo >= scope_hi) return; @@ -855,16 +855,16 @@ void overlay_capture_write_json(void) * can otherwise merge them into a giant region that changes every run. * Final/manual captures use the same executed-page scope as autocapture. */ (void)overlay_capture_write_current("shutdown-or-manual", - 0, 2u * 1024u * 1024u, 0); + 0, psx_ram_live_bytes(), 0); } void overlay_capture_before_dma(uint32_t load_addr, uint32_t size) { if (!s_enabled || !s_active || size == 0) return; - uint32_t lo = load_addr & 0x1FFFFFu; + uint32_t lo = psx_ram_canonical_offset(load_addr); uint32_t hi = lo + size; - if (lo >= 2u * 1024u * 1024u) return; - if (hi > 2u * 1024u * 1024u || hi < lo) hi = 2u * 1024u * 1024u; + if (lo >= psx_ram_live_bytes()) return; + if (hi > psx_ram_live_bytes() || hi < lo) hi = psx_ram_live_bytes(); /* Snapshot complete pages touched by this DMA. Page scope prevents sticky * dirty runs from turning a small sector replacement into a multi-megabyte @@ -1191,7 +1191,7 @@ static AutocapWriteJob *capture_snapshot_create(uint32_t scope_lo, int scoped) { extern uint8_t *memory_get_ram_ptr(void); - const size_t ram_size = 2u * 1024u * 1024u; + const size_t ram_size = psx_ram_live_bytes(); uint32_t bw = dirty_ram_get_bitmap_word_count(); AutocapWriteJob *job = (AutocapWriteJob *)calloc(1, sizeof(*job)); if (!job) return NULL; @@ -1229,7 +1229,7 @@ static int autocap_write_start(void) * RAM. Evidence-scoping keeps the background manifest proportional to live * coverage and avoids multi-megabyte rewrites every cooldown. */ AutocapWriteJob *job = capture_snapshot_create( - 0, 2u * 1024u * 1024u, 1); + 0, psx_ram_live_bytes(), 1); if (!job) return 0; if (!autocap_write_launch(job)) { s_autocap_write_job = NULL; diff --git a/runtime/src/overlay_loader.c b/runtime/src/overlay_loader.c index df7a2c33..8fa3aa52 100644 --- a/runtime/src/overlay_loader.c +++ b/runtime/src/overlay_loader.c @@ -10,6 +10,7 @@ #include "psx_cycles.h" #include "lockstep.h" #include "overlay_posix.h" +#include "psx_memory.h" #include #include #include @@ -116,7 +117,7 @@ static int s_cand_n = 0; * scales catastrophically once a warmed cache contains hundreds of variant * DLLs. Index candidates by the 4 KiB RAM pages touched by their code ranges; * a continuation then examines only candidates that could contain its PC. */ -#define RANGE_PAGE_COUNT (2u * 1024u * 1024u / 4096u) +#define RANGE_PAGE_COUNT (PSX_MAIN_RAM_BACKING_BYTES / 4096u) #define RANGE_LINK_CAP (CAND_CAP * 8) typedef struct { int cand, next; } RangeLink; static int s_range_page_head[RANGE_PAGE_COUNT]; @@ -182,7 +183,7 @@ static uint32_t s_exact_entry_bitmap[DIRTY_RAM_EXEC_BITMAP_WORDS]; static void exact_entry_set(uint32_t phys) { phys &= 0x1FFFFFFFu; - if (phys < 2u * 1024u * 1024u && (phys & 3u) == 0u) { + if (phys < PSX_MAIN_RAM_BACKING_BYTES && (phys & 3u) == 0u) { uint32_t word = phys >> 2; s_exact_entry_bitmap[word >> 5] |= 1u << (word & 31u); } @@ -190,7 +191,7 @@ static void exact_entry_set(uint32_t phys) { static int exact_entry_has(uint32_t phys) { phys &= 0x1FFFFFFFu; - if (phys >= 2u * 1024u * 1024u || (phys & 3u) != 0u) return 0; + if (phys >= PSX_MAIN_RAM_BACKING_BYTES || (phys & 3u) != 0u) return 0; uint32_t word = phys >> 2; return (s_exact_entry_bitmap[word >> 5] >> (word & 31u)) & 1u; } @@ -574,6 +575,15 @@ typedef struct { } StaticMatchCache; static StaticMatchCache s_static_match_cache[STATIC_MATCH_CACHE_CAP]; + +/* Main-RAM offset of a variant code range, through the one geometry contract + * (psx_memory.h). Retail targets fold the 2nd-4th mirrors of the 8 MiB KSEG + * decode window and expanded targets decode it uniquely -- the same fold the + * CPU applies, so an image a game places in a mirror (a mod engine copied to + * 0x80780000 on 2 MiB hardware) is gated on exactly the bytes that execute. */ +static int static_range_offset(uint32_t lo, uint32_t len, uint32_t *off) { + return len != 0u && psx_ram_resolve(lo, len, off); +} static uint64_t s_static_match_rehashes = 0; static uint64_t s_static_match_crc_misses = 0; static uint64_t s_static_match_gen_fastpath = 0; @@ -589,14 +599,13 @@ int psx_overlay_static_code_matches(const uint32_t *lo_len_pairs, uint32_t gen_sum = 0; for (uint32_t i = 0; i < count; i++) { - uint32_t lo = lo_len_pairs[i * 2u] & 0x1FFFFFFFu; + uint32_t off = 0; uint32_t len = lo_len_pairs[i * 2u + 1u]; - if (len == 0u || lo >= 2u * 1024u * 1024u || - len > 2u * 1024u * 1024u - lo) { + if (!static_range_offset(lo_len_pairs[i * 2u], len, &off)) { s_static_match_crc_misses++; return 0; } - gen_sum += overlay_watch_pagegen_sum(lo, len); + gen_sum += overlay_watch_pagegen_sum(off, len); } uintptr_t raw = (uintptr_t)lo_len_pairs; @@ -632,15 +641,16 @@ int psx_overlay_static_code_matches(const uint32_t *lo_len_pairs, * a band that swapped occupants would keep dispatching the stale variant. * Arming only sets bitmap bits -- it does not advance any generation, so * the gen_sum computed above stays valid for the cache write below. */ - for (uint32_t i = 0; i < count; i++) - overlay_watch_set_range(lo_len_pairs[i * 2u] & 0x1FFFFFFFu, - lo_len_pairs[i * 2u + 1u]); - uint32_t crc = 0xFFFFFFFFu; + uint32_t lo_min = 0xFFFFFFFFu, hi_max = 0; for (uint32_t i = 0; i < count; i++) { - uint32_t lo = lo_len_pairs[i * 2u] & 0x1FFFFFFFu; + uint32_t off = 0; uint32_t len = lo_len_pairs[i * 2u + 1u]; - crc = crc32_update(crc, ram + lo, len); + (void)static_range_offset(lo_len_pairs[i * 2u], len, &off); /* checked above */ + overlay_watch_set_range(off, len); + crc = crc32_update(crc, ram + off, len); + if (off < lo_min) lo_min = off; + if (off + len > hi_max) hi_max = off + len; } crc ^= 0xFFFFFFFFu; int matches = (crc == expected_crc) ? 1 : 0; @@ -653,13 +663,6 @@ int psx_overlay_static_code_matches(const uint32_t *lo_len_pairs, entry->expected_crc = expected_crc; entry->gen_sum = gen_sum; entry->matches = matches; - uint32_t lo_min = 0xFFFFFFFFu, hi_max = 0; - for (uint32_t i = 0; i < count; i++) { - uint32_t lo = lo_len_pairs[i * 2u] & 0x1FFFFFFFu; - uint32_t hi = lo + lo_len_pairs[i * 2u + 1u]; - if (lo < lo_min) lo_min = lo; - if (hi > hi_max) hi_max = hi; - } entry->lo_min = lo_min; entry->hi_max = hi_max; } @@ -687,17 +690,22 @@ int psx_overlay_static_code_matches(const uint32_t *lo_len_pairs, * to a capture / .EMI section offline. Called only on EXTERNAL interp * entries, so a 4096-slot scan is fine. */ uint32_t psx_overlay_resident_crc_at(uint32_t phys, int *valid) { - phys &= 0x1FFFFFFFu; uint32_t stale = 0; + if (!psx_ram_resolve(phys, 1u, &phys)) { + if (valid) *valid = 0; + return s_last_crc; + } for (uint32_t i = 0; i < STATIC_MATCH_CACHE_CAP; i++) { const StaticMatchCache *e = &s_static_match_cache[i]; if (!e->ranges || phys < e->lo_min || phys >= e->hi_max) continue; if (e->matches) { uint32_t gen_sum = 0; - for (uint32_t k = 0; k < e->count; k++) - gen_sum += overlay_watch_pagegen_sum(e->ranges[k * 2u] & 0x1FFFFFFFu, - e->ranges[k * 2u + 1u]); + for (uint32_t k = 0; k < e->count; k++) { + uint32_t off = 0; + (void)static_range_offset(e->ranges[k * 2u], e->ranges[k * 2u + 1u], &off); + gen_sum += overlay_watch_pagegen_sum(off, e->ranges[k * 2u + 1u]); + } if (gen_sum == e->gen_sum) { if (valid) *valid = 1; return e->expected_crc; @@ -737,7 +745,11 @@ typedef struct { int n; } ManFn; -#define OVERLAY_RAM_SIZE (2u * 1024u * 1024u) +/* Manifests are parsed at loader init, before memory_init() latches the live + * geometry, so structural validity is the whole KSEG0 decode window. Whether + * the bytes there are live code is decided per dispatch by the range CRC. */ +#define OVERLAY_RAM_SIZE PSX_MAIN_RAM_WINDOW_BYTES +#define OVERLAY_KSEG0_WINDOW_MASK (~(PSX_MAIN_RAM_WINDOW_BYTES - 1u)) #define MANIFEST_LINE_MAX 128u #define MANIFEST_PHYSICAL_LINE_MAX 159u #define MANIFEST_PROVENANCE_PREFIX "# psxrecomp overlay provenance " @@ -751,12 +763,12 @@ enum { static int man_structurally_valid(const ManFn *m) { if (!m || !m->has_crc || m->n < 1 || m->n > MAX_CODE_RANGES) return 0; - if ((m->entry & 0xFFE00000u) != 0x80000000u) return 0; + if ((m->entry & OVERLAY_KSEG0_WINDOW_MASK) != 0x80000000u) return 0; uint32_t entry = m->entry & 0x1FFFFFFFu; if ((entry & 3u) != 0u || entry >= OVERLAY_RAM_SIZE) return 0; int entry_covered = 0; for (int r = 0; r < m->n; r++) { - if ((m->lo[r] & 0xFFE00000u) != 0x80000000u) return 0; + if ((m->lo[r] & OVERLAY_KSEG0_WINDOW_MASK) != 0x80000000u) return 0; uint32_t lo = m->lo[r] & 0x1FFFFFFFu; uint32_t len = m->len[r]; if ((lo & 3u) != 0u || (len & 3u) != 0u || len < 4u || @@ -970,7 +982,7 @@ static int mips_control_kind(uint32_t instr) { static int ranges_contain_word(const uint32_t *lo_list, const uint32_t *len_list, int n, uint32_t phys) { - if ((phys & 3u) != 0u || phys > (2u * 1024u * 1024u) - 4u) return 0; + if ((phys & 3u) != 0u || phys > OVERLAY_RAM_SIZE - 4u) return 0; for (int r = 0; r < n; r++) { uint32_t lo = lo_list[r] & 0x1FFFFFFFu; uint32_t len = len_list[r]; @@ -986,7 +998,7 @@ static int ranges_contain_word(const uint32_t *lo_list, static int ranges_delay_slots_hashed(const uint32_t *lo_list, const uint32_t *len_list, int n) { const uint8_t *ram = memory_get_ram_ptr(); - const uint32_t ram_size = 2u * 1024u * 1024u; + const uint32_t ram_size = psx_ram_live_bytes(); if (!ram || n < 1 || n > MAX_CODE_RANGES) return 0; for (int r = 0; r < n; r++) { uint32_t lo = lo_list[r] & 0x1FFFFFFFu; @@ -4078,9 +4090,11 @@ int overlay_fp_enabled(void) { * so the comparison isolates COMPUTATION (and is longjmp-safe). A divergence * here = a real codegen bug (function + exact register/RAM). Zero divergence = * computation is correct and the fault is timing/interrupt-ordering. */ -#define SHADOW_RAM_SIZE (2u * 1024u * 1024u) +/* Snapshots cover the live RAM (retail 2 MiB unless the 8 MB mod is on). */ +#define SHADOW_RAM_SIZE psx_ram_live_bytes() #define SHADOW_SPAD_SIZE 1024u -static uint8_t s_ram0[SHADOW_RAM_SIZE], s_ramN[SHADOW_RAM_SIZE], s_ramI[SHADOW_RAM_SIZE]; +static uint8_t s_ram0[PSX_MAIN_RAM_BACKING_BYTES], s_ramN[PSX_MAIN_RAM_BACKING_BYTES], + s_ramI[PSX_MAIN_RAM_BACKING_BYTES]; static uint8_t s_spad0[SHADOW_SPAD_SIZE], s_spadI[SHADOW_SPAD_SIZE]; static uint64_t s_shadow_skipped_dev = 0; /* unsafe/incomplete traces skipped */ /* s_diff_mode / s_in_shadow declared above (before dispatch). */ diff --git a/runtime/src/pgxp.cpp b/runtime/src/pgxp.cpp index 52c57bca..c2906c9e 100644 --- a/runtime/src/pgxp.cpp +++ b/runtime/src/pgxp.cpp @@ -34,6 +34,7 @@ #include "pgxp.h" #include "pgxp_hooks.h" #include "cpu_state.h" +#include "psx_memory.h" #include #include @@ -61,12 +62,14 @@ struct PGXPValue { uint32_t gen; /* valid iff == s_gen (O(1) invalidate-all) */ }; -#define PGXP_RAM_WORDS (0x200000u >> 2) /* 2 MB RAM */ +/* Shadow covers the host RAM backing so the opt-in 8 MB map tracks its high + * banks; retail sessions only ever touch the low 2 MiB of it. */ +#define PGXP_RAM_WORDS (PSX_MAIN_RAM_BACKING_BYTES >> 2) #define PGXP_SCRATCH_WORDS (0x400u >> 2) /* 1 KB scratchpad */ #define PGXP_REG_HI 32 #define PGXP_REG_LO 33 -static PGXPValue *s_ram = nullptr; /* lazily allocated, ~10 MB */ +static PGXPValue *s_ram = nullptr; /* lazily allocated, ~40 MB VA */ static PGXPValue s_scratch[PGXP_SCRATCH_WORDS]; static PGXPValue s_gpr[34]; /* 32 GPRs + HI + LO */ static PGXPValue s_gte[32]; /* GTE data registers */ @@ -153,8 +156,8 @@ extern "C" void pgxp_get_stats(PGXPStats *out) { /* Guest address -> shadow slot, or NULL for BIOS/MMIO/KSEG2 (untrackable). */ static inline PGXPValue *pgxp_ptr(uint32_t addr) { uint32_t m = addr & 0x1FFFFFFFu; - if (m < 0x00800000u) /* RAM + its mirrors */ - return s_ram ? &s_ram[(m & 0x1FFFFCu) >> 2] : nullptr; + if (m < PSX_MAIN_RAM_WINDOW_BYTES) /* RAM + its mirrors (live map) */ + return s_ram ? &s_ram[psx_ram_canonical_offset(m) >> 2] : nullptr; if ((m & 0xFFFFFC00u) == 0x1F800000u) /* scratchpad */ return &s_scratch[(m & 0x3FCu) >> 2]; return nullptr; diff --git a/runtime/src/psx_netplay_rb.c b/runtime/src/psx_netplay_rb.c index c6b28665..1da3dc7b 100644 --- a/runtime/src/psx_netplay_rb.c +++ b/runtime/src/psx_netplay_rb.c @@ -1,4 +1,5 @@ #include "psx_netplay_rb.h" +#include "psx_memory.h" #include "cpu_state.h" /* full CPUState — stub path assigns *out = *in */ #if !defined(PSX_HAS_RECOMP_NET) @@ -1579,8 +1580,8 @@ static int rb_resume_pc_ok(uint32_t pc) if ((pc & 0xfff00000u) == 0xbfc00000u) return 1; /* BIOS ROM */ phys = pc & 0x1fffffffu; - /* 2MB main RAM; skip the low scratch page (catches 0xB0 / 0x800000B0). */ - if (phys >= 0x1000u && phys < 0x200000u) + /* Live main RAM; skip the low scratch page (catches 0xB0 / 0x800000B0). */ + if (phys >= 0x1000u && phys < psx_ram_live_bytes()) return 1; return 0; } diff --git a/runtime/src/psx_ram_geometry.c b/runtime/src/psx_ram_geometry.c new file mode 100644 index 00000000..2b514e9a --- /dev/null +++ b/runtime/src/psx_ram_geometry.c @@ -0,0 +1,123 @@ +/* psx_ram_geometry.c -- live main-RAM geometry state (see psx_memory.h). + * + * Retail 2 MiB mirroring is the default and the only geometry reachable + * without the opt-in 8 MB main RAM mod. A trusted activation plugin requests + * expanded RAM (psx_mod_set_main_ram_8mb) before memory_init(), which applies + * the request exactly once per boot (psx_ram_apply_size_request). Keeping the + * state machine here, free of memory.c's device dependencies, lets the retail + * guarantee be unit-tested against the same code the runtime links. */ +#include "psx_memory.h" +#include "mod_plugins.h" + +#include +#include + +uint32_t g_psx_ram_size = PSX_MAIN_RAM_RETAIL_BYTES; +uint32_t g_psx_ram_mask = PSX_MAIN_RAM_RETAIL_BYTES - 1u; +uint32_t g_psx_ram_high_unique[PSX_RAM_HIGH_BITWORDS]; + +static int s_ram_8mb_requested; +static uint32_t s_ram_high_registered[PSX_RAM_HIGH_BITWORDS]; + +static inline void psx_ram_high_page_mark(uint32_t page) { + uint32_t i, bit; + if (page < PSX_RAM_HIGH_PAGE0 || + page >= (PSX_MAIN_RAM_EXPANDED_BYTES >> 12)) + return; + i = page - PSX_RAM_HIGH_PAGE0; + bit = 1u << (i & 31u); + g_psx_ram_high_unique[i >> 5] |= bit; + s_ram_high_registered[i >> 5] |= bit; +} + +void psx_ram_register_unique(uint32_t addr, uint32_t len) { + uint32_t phys, end, page, last; + if (len == 0u) + return; + phys = addr & 0x1FFFFFFFu; + if (phys >= PSX_MAIN_RAM_WINDOW_BYTES) + return; + end = phys + len; + if (end < phys || end > PSX_MAIN_RAM_WINDOW_BYTES) + end = PSX_MAIN_RAM_WINDOW_BYTES; + if (end <= PSX_MAIN_RAM_RETAIL_BYTES) + return; + if (phys < PSX_MAIN_RAM_RETAIL_BYTES) + phys = PSX_MAIN_RAM_RETAIL_BYTES; + page = phys >> 12; + last = (end - 1u) >> 12; + for (; page <= last; page++) + psx_ram_high_page_mark(page); +} + +uint32_t psx_ram_canon_code_addr(uint32_t addr) { + return psx_ram_canon_code_addr_inline(addr); +} + +void psx_ram_resync_high_after_restore(const uint8_t *ram) { + uint32_t page; + if (g_psx_ram_size <= PSX_MAIN_RAM_RETAIL_BYTES) { + memset(g_psx_ram_high_unique, 0, sizeof(g_psx_ram_high_unique)); + return; + } + memcpy(g_psx_ram_high_unique, s_ram_high_registered, + sizeof(g_psx_ram_high_unique)); + if (!ram) return; + for (page = PSX_RAM_HIGH_PAGE0; + page < (PSX_MAIN_RAM_EXPANDED_BYTES >> 12); page++) { + uint32_t dst = page << 12; + uint32_t src = dst & (PSX_MAIN_RAM_RETAIL_BYTES - 1u); + if (psx_ram_high_page_unique(page)) + continue; + if (memcmp(ram + dst, ram + src, 4096u) != 0) + psx_ram_high_page_mark(page); + } +} + +uint32_t memory_get_ram_bytes(void) { return g_psx_ram_size; } +int psx_ram_8mb_active(void) { + return g_psx_ram_size > PSX_MAIN_RAM_RETAIL_BYTES; +} + +void psx_ram_reset_size_request(void) { + s_ram_8mb_requested = 0; + memset(s_ram_high_registered, 0, sizeof(s_ram_high_registered)); + memset(g_psx_ram_high_unique, 0, sizeof(g_psx_ram_high_unique)); +} + +int psx_mod_set_main_ram_8mb(int enabled) { + s_ram_8mb_requested = enabled ? 1 : 0; + memset(s_ram_high_registered, 0, sizeof(s_ram_high_registered)); + memset(g_psx_ram_high_unique, 0, sizeof(g_psx_ram_high_unique)); + if (enabled) + psx_ram_register_unique(PSX_MAIN_RAM_RETAIL_BYTES, + PSX_MAIN_RAM_EXPANDED_BYTES - + PSX_MAIN_RAM_RETAIL_BYTES); + return 1; +} + +static int psx_ram_any_high_registered(void) { + uint32_t i; + for (i = 0; i < PSX_RAM_HIGH_BITWORDS; i++) { + if (s_ram_high_registered[i] != 0u) + return 1; + } + return 0; +} + +void psx_ram_apply_size_request(void) { + if (s_ram_8mb_requested) { + g_psx_ram_size = PSX_MAIN_RAM_EXPANDED_BYTES; + g_psx_ram_mask = PSX_MAIN_RAM_EXPANDED_BYTES - 1u; + if (!psx_ram_any_high_registered()) + psx_ram_register_unique(PSX_MAIN_RAM_RETAIL_BYTES, + PSX_MAIN_RAM_EXPANDED_BYTES - + PSX_MAIN_RAM_RETAIL_BYTES); + memcpy(g_psx_ram_high_unique, s_ram_high_registered, + sizeof(g_psx_ram_high_unique)); + } else { + g_psx_ram_size = PSX_MAIN_RAM_RETAIL_BYTES; + g_psx_ram_mask = PSX_MAIN_RAM_RETAIL_BYTES - 1u; + memset(g_psx_ram_high_unique, 0, sizeof(g_psx_ram_high_unique)); + } +} diff --git a/runtime/src/psx_selfcheck.c b/runtime/src/psx_selfcheck.c index a35f840c..0f0899be 100644 --- a/runtime/src/psx_selfcheck.c +++ b/runtime/src/psx_selfcheck.c @@ -23,6 +23,7 @@ */ #include "psx_selfcheck.h" +#include "psx_memory.h" #include #include @@ -925,7 +926,7 @@ static int sc_resume_pc_ok(uint32_t pc) if ((pc & 0xfff00000u) == 0xbfc00000u) return 1; phys = pc & 0x1fffffffu; - if (phys >= 0x1000u && phys < 0x200000u) + if (phys >= 0x1000u && phys < psx_ram_live_bytes()) return 1; return 0; } diff --git a/runtime/src/savestate.c b/runtime/src/savestate.c index 8667a01b..3b4faa63 100644 --- a/runtime/src/savestate.c +++ b/runtime/src/savestate.c @@ -582,7 +582,8 @@ int savestate_read_slot(int slot, uint8_t** data_out, size_t* size_out) { if (!f) return 0; if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return 0; } sz = ftell(f); - if (sz <= 0 || (size_t)sz > 8u * 1024u * 1024u) { fclose(f); return 0; } + /* Raw 8 MB-RAM states run ~11.5 MiB (RAM + VRAM + SPU + dirty maps). */ + if (sz <= 0 || (size_t)sz > 16u * 1024u * 1024u) { fclose(f); return 0; } if (fseek(f, 0, SEEK_SET) != 0) { fclose(f); return 0; } buf = (uint8_t*)malloc((size_t)sz); if (!buf) { fclose(f); return 0; } diff --git a/runtime/src/text_xlate.cpp b/runtime/src/text_xlate.cpp index dbd6b5b2..653c1a07 100644 --- a/runtime/src/text_xlate.cpp +++ b/runtime/src/text_xlate.cpp @@ -2,6 +2,7 @@ * See text_xlate.h + docs/STRING_TRANSLATION.md. */ #include "text_xlate.h" +#include "psx_memory.h" #include "cpu_state.h" #include @@ -36,14 +37,13 @@ namespace { namespace fs = std::filesystem; // --------------------------------------------------------------------------- -// Guest RAM access (little-endian, no swizzle). Main RAM is 2 MB, mirrored -// across [0,0x800000). Returns 0 / no-op for out-of-range. +// Guest RAM access (little-endian, no swizzle). The 8 MiB decode window +// [0,0x800000) folds through the live geometry (retail: 2 MiB mirrored four +// times). Returns 0 / no-op for out-of-range. // --------------------------------------------------------------------------- -constexpr uint32_t kRamSize = 2u * 1024u * 1024u; - inline bool ram_fold(uint32_t va, uint32_t* pa_out) { uint32_t p = va & 0x1FFFFFFFu; - if (p < 0x00800000u) { *pa_out = p & (kRamSize - 1u); return true; } + if (p < PSX_MAIN_RAM_WINDOW_BYTES) { *pa_out = psx_ram_canonical_offset(p); return true; } return false; // I/O / BIOS / scratchpad — not translatable text storage } inline uint8_t grb(uint8_t* ram, uint32_t va) { diff --git a/runtime/src/traps.c b/runtime/src/traps.c index d0145a19..479b8f34 100644 --- a/runtime/src/traps.c +++ b/runtime/src/traps.c @@ -5,6 +5,7 @@ */ #include "cpu_state.h" +#include "psx_memory.h" #include "psx_bss.h" #include "psx_runtime.h" /* fix B: psx_exc_escape_reason_t + g_exc_escape_reason */ #include "debug_server.h" @@ -1209,7 +1210,7 @@ void psx_unknown_dispatch(CPUState* cpu, uint32_t addr, uint32_t phys) { int hits = 0; pos += snprintf(buf + pos, sizeof(buf) - (size_t)pos, "\n RAM scan for 0x%08X:\n", needles[n]); - for (uint32_t off = 0; off + 4 <= 2u * 1024u * 1024u; off += 4) { + for (uint32_t off = 0; off + 4 <= psx_ram_live_bytes(); off += 4) { uint32_t word; memcpy(&word, ram + off, sizeof(word)); if (word != needles[n]) continue; @@ -1220,7 +1221,7 @@ void psx_unknown_dispatch(CPUState* cpu, uint32_t addr, uint32_t phys) { } if (!hits) pos += snprintf(buf + pos, sizeof(buf) - (size_t)pos, - " (not found in 2MB PSX RAM)\n"); + " (not found in live PSX RAM)\n"); } } diff --git a/runtime/tests/test_dirty_text_admission_guards.py b/runtime/tests/test_dirty_text_admission_guards.py index c39a5285..e4ebead9 100644 --- a/runtime/tests/test_dirty_text_admission_guards.py +++ b/runtime/tests/test_dirty_text_admission_guards.py @@ -42,7 +42,7 @@ def main() -> int: read = cd_slice.index("uint32_t word = cdrom_dma_read();") store = cd_slice.index("psx_write_word(addr, word);", read) admit = cd_slice.index("dirty_ram_mark_executable_range(addr, 4);", store) - advance = cd_slice.index("addr = (addr + addr_step)", admit) + advance = cd_slice.index("addr = DMA_RAM_ADDR((addr + addr_step))", admit) if not read < store < admit < advance: raise AssertionError("CD DMA overlay admission is not coupled to each RAM word") diff --git a/runtime/tests/test_gpu_upload_history.py b/runtime/tests/test_gpu_upload_history.py index 2e31767d..24d4f4b7 100644 --- a/runtime/tests/test_gpu_upload_history.py +++ b/runtime/tests/test_gpu_upload_history.py @@ -20,6 +20,9 @@ harness=r""" #include #include #include +#include "psx_memory.h" +uint32_t g_psx_ram_size=PSX_MAIN_RAM_RETAIL_BYTES,g_psx_ram_mask=PSX_MAIN_RAM_RETAIL_BYTES-1u; +uint32_t g_psx_ram_high_unique[PSX_RAM_HIGH_BITWORDS]; static uint32_t gp0_cmd_buf[16],g_debug_current_func_addr; static uint16_t vram_write_x,vram_write_y,vram_write_w,vram_write_h,vram_write_col,vram_write_row; static unsigned vram_write_remaining,s_frame_count; @@ -55,5 +58,7 @@ with tempfile.TemporaryDirectory() as d: root=Path(d);c=root/'history.c';c.write_text(harness) for opt in ('-O0','-O2'): exe=root/(opt+'.exe') - subprocess.run([a.cc,opt,'-std=c11','-Wall','-Wextra','-Werror',str(c),'-o',str(exe)],check=True) + subprocess.run([a.cc,opt,'-std=c11','-Wall','-Wextra','-Werror', + '-I',str(a.source.resolve().parents[1]/'include'), + str(c),'-o',str(exe)],check=True) subprocess.run([str(exe)],check=True) diff --git a/runtime/tests/test_gte_register_access.cpp b/runtime/tests/test_gte_register_access.cpp index 8fd223de..0d6415d7 100644 --- a/runtime/tests/test_gte_register_access.cpp +++ b/runtime/tests/test_gte_register_access.cpp @@ -49,7 +49,6 @@ extern "C" int gpu_ws_precise_nclip_enabled(void) { return g_test_precise_nclip_enabled; } extern "C" void gpu_pgxp_rederive_enable(void) {} -extern "C" uint32_t memory_get_ram_bytes(void) { return 2u * 1024u * 1024u; } extern "C" void psx_ws_note_gte_project(int) {} extern "C" { uint64_t s_frame_count = 0; diff --git a/runtime/tests/test_overlay_decodable_fallback.py b/runtime/tests/test_overlay_decodable_fallback.py index 774b5821..59e047ae 100644 --- a/runtime/tests/test_overlay_decodable_fallback.py +++ b/runtime/tests/test_overlay_decodable_fallback.py @@ -60,7 +60,7 @@ def main() -> int: dirty_gate = body.find("!dirty_ram_is_dirty(phys) && !clean_game_text_miss") region_gate = body.find("phys_is_overlay_region(phys)", dirty_gate) - ram_gate = body.find("phys < (2u * 1024u * 1024u)", dirty_gate) + ram_gate = body.find("phys < psx_ram_live_bytes()", dirty_gate) decode_gate = body.find("dirty_ram_word_looks_decodable(fetch_word(phys))", dirty_gate) mark = body.find("dirty_ram_mark_executable_range(phys, 4u)", dirty_gate) if min(dirty_gate, region_gate, ram_gate, decode_gate, mark) < 0: diff --git a/runtime/tests/test_psx_memory_geometry.c b/runtime/tests/test_psx_memory_geometry.c index df5bd345..81f0a803 100644 --- a/runtime/tests/test_psx_memory_geometry.c +++ b/runtime/tests/test_psx_memory_geometry.c @@ -1,6 +1,13 @@ #include "psx_memory.h" #include +#include + +/* The live geometry normally lives in memory.c; this test owns it so each + * build of the test pins one geometry the way memory_init() would. */ +uint32_t g_psx_ram_size = PSX_MAIN_RAM_RETAIL_BYTES; +uint32_t g_psx_ram_mask = PSX_MAIN_RAM_RETAIL_BYTES - 1u; +uint32_t g_psx_ram_high_unique[PSX_RAM_HIGH_BITWORDS]; static int failures; @@ -16,6 +23,14 @@ static void check(int condition, const char *name) { int main(void) { uint32_t off = 0xFFFFFFFFu; +#if defined(PSX_MEMORY_GEOMETRY_TEST_EXPANDED) + /* What the 8 MB mod leaves behind after memory_init(): expanded size and + * every high page registered unique. */ + g_psx_ram_size = PSX_MAIN_RAM_EXPANDED_BYTES; + g_psx_ram_mask = PSX_MAIN_RAM_EXPANDED_BYTES - 1u; + memset(g_psx_ram_high_unique, 0xFF, sizeof(g_psx_ram_high_unique)); +#endif + check(psx_ram_resolve(0x80000000u, 4u, &off) && off == 0u, "KSEG0 aliases physical RAM"); check(psx_ram_resolve(0xA0000100u, 4u, &off) && off == 0x100u, @@ -24,23 +39,31 @@ int main(void) { "first byte beyond DRAM decode is rejected"); check(!psx_ram_resolve(0x1F801000u, 4u, &off), "MMIO is never mistaken for DRAM"); - check(!psx_ram_resolve(PSX_MAIN_RAM_BYTES - 2u, 4u, &off), + check(!psx_ram_resolve(psx_ram_live_bytes() - 2u, 4u, &off), "cross-geometry word is rejected"); -#if PSX_MAIN_RAM_BYTES == PSX_MAIN_RAM_RETAIL_BYTES +#if !defined(PSX_MEMORY_GEOMETRY_TEST_EXPANDED) check(psx_ram_canonical_offset(0x80612340u) == 0x00012340u, "retail canonicalization folds KSEG0 mirrors"); check(psx_ram_resolve(0x00212340u, 4u, &off) && off == 0x00012340u, "retail geometry folds the second mirror"); check(psx_ram_resolve(0x807FFFFCu, 4u, &off) && off == 0x001FFFFCu, "retail geometry folds the fourth mirror top"); -#elif PSX_MAIN_RAM_BYTES == PSX_MAIN_RAM_EXPANDED_BYTES + check(psx_ram_canon_code_addr_inline(0x80780000u) == 0x80180000u, + "retail code address folds the fourth mirror"); + check(psx_ram_live_bytes() == PSX_MAIN_RAM_RETAIL_BYTES, + "retail live size is 2 MiB"); +#else check(psx_ram_canonical_offset(0x80612340u) == 0x00612340u, "expanded canonicalization preserves all decoded address bits"); check(psx_ram_resolve(0x00212340u, 4u, &off) && off == 0x00212340u, "expanded geometry uniquely decodes the second bank"); check(psx_ram_resolve(0x807FFFFCu, 4u, &off) && off == 0x007FFFFCu, "expanded geometry uniquely decodes the eighth MiB top"); + check(psx_ram_canon_code_addr_inline(0x80780000u) == 0x80780000u, + "expanded code address stays in the high bank"); + check(psx_ram_live_bytes() == PSX_MAIN_RAM_EXPANDED_BYTES, + "expanded live size is 8 MiB"); #endif return failures ? 1 : 0; diff --git a/runtime/tests/test_psx_ram_runtime_map.c b/runtime/tests/test_psx_ram_runtime_map.c index 1e470cee..e2d2d61a 100644 --- a/runtime/tests/test_psx_ram_runtime_map.c +++ b/runtime/tests/test_psx_ram_runtime_map.c @@ -1,41 +1,131 @@ -#include "psx_ram.h" +/* Live main-RAM geometry: links the runtime's own psx_ram_geometry.c so the + * state machine memory_init() drives is the thing under test. + * + * The load-bearing claim is the opt-in guarantee: unless the 8 MB mod asks for + * expanded RAM before memory_init(), every address in the 8 MiB decode window + * folds onto the 2 MiB retail DRAM exactly as hardware mirrors it, for loads, + * stores, code addresses and span resolution alike. */ +#include "psx_memory.h" +#include "mod_plugins.h" #include #include +#include -uint32_t g_psx_ram_size = PSX_RAM_2MB; -uint32_t g_psx_ram_mask = PSX_RAM_2MB - 1u; -uint32_t g_psx_ram_high_unique[PSX_RAM_HIGH_BITWORDS]; +static uint8_t s_ram[PSX_MAIN_RAM_BACKING_BYTES]; + +static int failures; static int check(int cond, const char *label) { if (!cond) { fprintf(stderr, "FAIL: %s\n", label); + failures++; return 0; } return 1; } +/* Every page of the decode window, at its first, middle and last word, through + * all three CPU segments: the retail fold must be exact everywhere. */ +static void check_retail_everywhere(const char *phase) { + static const uint32_t segs[3] = { 0x00000000u, 0x80000000u, 0xA0000000u }; + static const uint32_t offs[3] = { 0x000u, 0x7F0u, 0xFFCu }; + uint32_t page, s, o, bad = 0; + for (page = 0; page < (PSX_MAIN_RAM_WINDOW_BYTES >> 12); page++) { + for (o = 0; o < 3; o++) { + uint32_t phys = (page << 12) | offs[o]; + uint32_t want = phys & (PSX_MAIN_RAM_RETAIL_BYTES - 1u); + uint32_t off = 0xFFFFFFFFu; + if (psx_ram_map_read(phys) != want) bad++; + if (psx_ram_map_write(phys) != want) bad++; + for (s = 0; s < 3; s++) { + uint32_t va = segs[s] | phys; + if (psx_ram_canonical_offset(va) != want) bad++; + if (!psx_ram_resolve(va, 4u, &off) || off != want) bad++; + if (psx_ram_canon_code_addr(va) != (segs[s] | want)) bad++; + } + } + } + if (bad) fprintf(stderr, " %s: %u retail-fold violations\n", phase, bad); + check(bad == 0u, phase); + check(memory_get_ram_bytes() == PSX_MAIN_RAM_RETAIL_BYTES, phase); + check(!psx_ram_8mb_active(), phase); + check(!psx_ram_resolve(0x801FFFFEu, 4u, 0), "retail span past 2 MiB end"); +} + +static void check_expanded_everywhere(const char *phase) { + uint32_t page, bad = 0; + for (page = 0; page < (PSX_MAIN_RAM_WINDOW_BYTES >> 12); page++) { + uint32_t phys = (page << 12) | 0x7F0u; + uint32_t off = 0xFFFFFFFFu; + if (psx_ram_map_read(phys) != phys) bad++; + if (psx_ram_map_write(phys) != phys) bad++; + if (psx_ram_canonical_offset(0x80000000u | phys) != phys) bad++; + if (!psx_ram_resolve(0x80000000u | phys, 4u, &off) || off != phys) bad++; + if (psx_ram_canon_code_addr(0x80000000u | phys) != (0x80000000u | phys)) + bad++; + } + if (bad) fprintf(stderr, " %s: %u expanded-decode violations\n", phase, bad); + check(bad == 0u, phase); + check(memory_get_ram_bytes() == PSX_MAIN_RAM_EXPANDED_BYTES, phase); + check(psx_ram_8mb_active(), phase); +} + int main(void) { const uint32_t high = 0x00201000u; const uint32_t high_page = high >> 12; uint32_t i = high_page - PSX_RAM_HIGH_PAGE0; - int ok = 1; - ok &= check(psx_ram_map_read(high) == 0x00001000u, - "retail maps high mirror to low RAM"); - ok &= check(psx_ram_canon_code_addr_inline(0x80201000u) == 0x80001000u, - "retail folds high code PC to low mirror"); + /* Process start: nothing requested. */ + check_retail_everywhere("default geometry is retail 2 MiB mirroring"); - g_psx_ram_size = PSX_RAM_8MB; - g_psx_ram_mask = PSX_RAM_8MB - 1u; - ok &= check(psx_ram_map_read(high) == 0x00001000u, - "unregistered 8 MB high page still mirrors low RAM"); + /* memory_init() with no activation plugin. */ + psx_ram_apply_size_request(); + check_retail_everywhere("memory_init without the mod stays retail"); + /* mod_runtime_activate_plugins() always resets first; a plan without the + * 8 MB package must leave retail in place. */ + psx_ram_reset_size_request(); + psx_ram_apply_size_request(); + check_retail_everywhere("reset + apply without the mod stays retail"); + + /* The mod requests expanded RAM; nothing changes until memory_init(). */ + check(psx_mod_set_main_ram_8mb(1) == 1, "8 MB request accepted"); + check_retail_everywhere("8 MB request is inert before memory_init"); + psx_ram_apply_size_request(); + check_expanded_everywhere("8 MB request applies unique 8 MiB decode"); + + /* A later boot whose plan drops the mod returns to retail mirroring. */ + psx_ram_reset_size_request(); + psx_ram_apply_size_request(); + check_retail_everywhere("reboot without the mod restores retail"); + + /* Savestate restore in retail never leaves unique pages behind. */ + memset(g_psx_ram_high_unique, 0xFF, sizeof(g_psx_ram_high_unique)); + psx_ram_resync_high_after_restore(s_ram); + check_retail_everywhere("retail restore clears unique high pages"); + + /* Expanded geometry with partial registration: unregistered high pages + * keep aliasing the low 2 MiB; registered ones decode uniquely. */ + psx_mod_set_main_ram_8mb(1); + psx_ram_apply_size_request(); + memset(g_psx_ram_high_unique, 0, sizeof(g_psx_ram_high_unique)); + check(psx_ram_map_read(high) == 0x00001000u, + "unregistered 8 MB high page still mirrors low RAM"); g_psx_ram_high_unique[i >> 5] |= 1u << (i & 31u); - ok &= check(psx_ram_map_read(high) == high, - "registered 8 MB high page maps uniquely"); - ok &= check(psx_ram_canon_code_addr_inline(0x80201000u) == 0x80201000u, - "registered high code PC remains unique"); + check(psx_ram_map_read(high) == high, + "registered 8 MB high page maps uniquely"); + check(psx_ram_canon_code_addr_inline(0x80201000u) == 0x80201000u, + "registered high code PC remains unique"); - return ok ? 0 : 1; + /* Expanded restore resync re-applies the registered (all-high) map. */ + memset(g_psx_ram_high_unique, 0, sizeof(g_psx_ram_high_unique)); + psx_mod_set_main_ram_8mb(1); + psx_ram_apply_size_request(); + memset(g_psx_ram_high_unique, 0, sizeof(g_psx_ram_high_unique)); + psx_ram_resync_high_after_restore(s_ram); + check_expanded_everywhere("expanded restore resync keeps unique decode"); + + if (failures == 0) printf("PASS psx_ram_runtime_map (%s)\n", "all phases"); + return failures ? 1 : 0; } diff --git a/tools/compile_overlays.py b/tools/compile_overlays.py index 2230de2d..27a2ee3f 100644 --- a/tools/compile_overlays.py +++ b/tools/compile_overlays.py @@ -545,7 +545,7 @@ NON_AUTHORITY_MANIFEST_PROVENANCES = { HOSTED_MANIFEST_MARKER = ( f'# psxrecomp overlay provenance {HOSTED_MANIFEST_PROVENANCE}') HOSTED_UNIQUE_GUARDED_BYTE_CAP = 1024 * 1024 -# Full 8 MiB host capacity (PSX_RAM_CAPACITY): 8 MB-mod captures carry +# Full 8 MiB host capacity (PSX_MAIN_RAM_BACKING_BYTES): 8 MB-mod captures carry # high-bank enhancement code and their ranges must validate. PSX_RAM_SIZE = 8 * 1024 * 1024