From cf6ac8a580a10e2dfdb61448049374c5e2ddf4d5 Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Fri, 2 Oct 2026 12:21:53 -0700 Subject: [PATCH] Runtime tests: link gpu_timeline where needed, no-debug ring stub, guards for span replay Co-Authored-By: Claude Opus 5.5 (1M context) --- runtime/CMakeLists.txt | 1 + runtime/src/gpu.c | 1 + runtime/tests/test_gpu_gp0_history_guard.py | 7 +++++-- runtime/tests/test_interp_mod_entry_guards.py | 5 ++++- runtime/tests/test_segment_miss_wiring.py | 3 ++- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 1a799d02..f139b5ec 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -255,6 +255,7 @@ if(BUILD_TESTING) tests/psx_ram_runtime_map_stubs.c src/memory.c src/dma.c + src/gpu_timeline.c src/boot_state.c src/psx_icache.c src/psx_ram_geometry.c) diff --git a/runtime/src/gpu.c b/runtime/src/gpu.c index 47d063b8..82ef3efa 100644 --- a/runtime/src/gpu.c +++ b/runtime/src/gpu.c @@ -6081,6 +6081,7 @@ static void gp0_ring_record(const uint32_t *words, int n) { (void)n; } +void gpu_gp0_ring_set_frozen(int frozen) { (void)frozen; } uint64_t gpu_gp0_ring_total(void) { return 0; } uint32_t gpu_gp0_ring_capacity(void) { return 0; } uint32_t gpu_gp0_ring_max_words(void) { return GPU_GP0_RING_MAX_WORDS; } diff --git a/runtime/tests/test_gpu_gp0_history_guard.py b/runtime/tests/test_gpu_gp0_history_guard.py index 5a98d829..bf8f2949 100644 --- a/runtime/tests/test_gpu_gp0_history_guard.py +++ b/runtime/tests/test_gpu_gp0_history_guard.py @@ -18,6 +18,7 @@ import tempfile ROOT = pathlib.Path(__file__).resolve().parents[2] GPU_C = ROOT / "runtime" / "src" / "gpu.c" +GPU_TIMELINE_C = ROOT / "runtime" / "src" / "gpu_timeline.c" DEFAULT_GCC = pathlib.Path(r"C:\msys64\mingw64\bin\gcc.exe") DRIVER_C = r""" @@ -201,6 +202,7 @@ STUBS_C = r""" #include uint64_t s_frame_count = 0; +uint64_t psx_cycle_count = 0; uint32_t g_debug_last_store_pc = 0x80012340u; uint32_t g_psx_ram_size = 0x00200000u; uint32_t g_psx_ram_mask = 0x001FFFFFu; @@ -242,8 +244,8 @@ int ws_cull_should_keep(uint32_t addr) { (void)addr; return 1; } int ws_ui_group_should_keep(uint32_t addr) { (void)addr; return 1; } uint32_t psx_mod_gpu_dma_resolve_address(uint32_t address) { return address; } void ws_ui_group_assign(WsUiGroupItem *items, size_t count, - int32_t display_width, int dense_menu) -{ (void)items; (void)count; (void)display_width; (void)dense_menu; } + int32_t display_width, int dense_menu, int in_place) +{ (void)items; (void)count; (void)display_width; (void)dense_menu; (void)in_place; } int32_t ws_ui_anchor_for_bounds(int32_t x, int32_t width, int32_t display_width) { (void)x; (void)width; return display_width / 2; } int gte_geometry_correction_enabled(void) { return 0; } @@ -436,6 +438,7 @@ def build(cc: str, work: pathlib.Path, name: str, prod: bool) -> pathlib.Path: cmd.extend([ str(work / "driver.c"), str(GPU_C), + str(GPU_TIMELINE_C), str(work / "stubs.c"), ]) if platform.system() == "Darwin": diff --git a/runtime/tests/test_interp_mod_entry_guards.py b/runtime/tests/test_interp_mod_entry_guards.py index 10ed543c..b6b6fc1a 100644 --- a/runtime/tests/test_interp_mod_entry_guards.py +++ b/runtime/tests/test_interp_mod_entry_guards.py @@ -21,7 +21,10 @@ def main(): assert source.count('psx_mod_function_entry(cpu, addr)') == 1 # Every interpreted entry reaches these calls: without an active hook they # must cost one load, never a call into the plugin table. - assert 'if (g_psx_mod_function_entry_hooks && psx_mod_function_entry(cpu, addr)) return 1;' in source + # A forced span replay (psx_mod_run_guest_span) runs inside a hook + # already: it must not re-enter the entry hooks, so the check leads. + assert ('if (!forced && g_psx_mod_function_entry_hooks && ' + 'psx_mod_function_entry(cpu, addr))\n return 1;') in source assert 'if (g_psx_mod_function_entry_hooks &&\n' in source local_start = source.index('if (allow_local_dirty_flow && target != 0 &&') local_end = source.index('current_page = target_phys >> 12;', local_start) diff --git a/runtime/tests/test_segment_miss_wiring.py b/runtime/tests/test_segment_miss_wiring.py index c4b1457f..fd0aa979 100644 --- a/runtime/tests/test_segment_miss_wiring.py +++ b/runtime/tests/test_segment_miss_wiring.py @@ -48,7 +48,8 @@ def main(): block = block[:block.index('#endif\n\n /* B-2')] assert re.search(r'clean_game_text_miss = psx_game_address_in_text\(addr\) \? 1 : 0;', block), \ 'a compiled-dispatch miss in game text no longer sets clean_game_text_miss' - hook = re.search(r'if \(clean_game_text_miss\)\s*\(void\)psx_segment_miss_note\(addr, ' + # A forced span replay interprets compiled text on purpose: not a miss. + hook = re.search(r'if \(clean_game_text_miss && !forced\)\s*\(void\)psx_segment_miss_note\(addr, ' r'psx_game_is_function_entry,\s*cpu->gpr\[31\], cpu->gpr\[29\],', block) assert hook, 'dirty_ram_dispatch_inner no longer notes segment misses on a clean-text miss' assert block.index('psx_dispatch_game_compiled(cpu, addr)') < hook.start(), \