Files
psxrecomp/runtime/tests/test_segment_miss_wiring.py
2026-10-02 12:21:53 -07:00

89 lines
4.6 KiB
Python

"""Segment misses are recorded at dispatch and reach every surface.
docs/SEGMENT_AWARE_CODE.md §5.5 (§10 decision 2): a PC in the EXE's text whose
physical word has a compiled row only in another segment is a segment miss. It
runs interpreted through dirty_ram_dispatch_inner's clean-text-miss path and is
recorded, so the run names each (segment, entry) that needs a seed.
The classification and the ring are unit-tested (test_psx_segment_miss.c,
psx_segment_miss_note included). What a unit test cannot see is the wiring
inside the runtime, which only runs in a full build: deleting the dispatch
hook, the TCP command or a report field would leave every other test green
and make segment misses silent again. This guard pins that wiring and the
`segment_misses` row shape, whose `kind` tells a game segment miss from a
BIOS one (the compiled BIOS ran a window's home body for another segment's
alias PC; bios_segment_variants compiles and queries that emitted check).
"""
from pathlib import Path
import re
ROOT = Path(__file__).resolve().parents[2]
INTERP = (ROOT / 'runtime/src/dirty_ram_interp.c').read_text(encoding='utf-8')
DEBUG = (ROOT / 'runtime/src/debug_server.c').read_text(encoding='utf-8')
CRASH = (ROOT / 'runtime/src/crash_trace.c').read_text(encoding='utf-8')
CMAKE = (ROOT / 'runtime/runtime.cmake').read_text(encoding='utf-8')
def body(source, signature):
start = source.index(signature)
depth, pos = 0, source.index('{', start)
while True:
if source[pos] == '{':
depth += 1
elif source[pos] == '}':
depth -= 1
if depth == 0:
return source[start:pos]
pos += 1
def main():
assert 'runtime/src/psx_segment_miss.c' in CMAKE, 'the runtime does not link psx_segment_miss.c'
# The hook: every clean game-text miss goes to psx_segment_miss_note with
# the full PC, inside the game-dispatch block that sets the flag.
inner = body(INTERP, 'static int dirty_ram_dispatch_inner(CPUState* cpu, uint32_t addr, '
'uint32_t stop_addr) {')
block = inner[inner.index('#ifdef PSX_HAS_GAME_DISPATCH'):]
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'
# 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(), \
'the segment-miss note must follow the compiled-dispatch attempt'
# TCP: the command, and the counters on dispatch_stats and ping.
assert re.search(r'\{\s*"segment_misses",\s*handle_segment_misses\s*\}', DEBUG), \
'TCP segment_misses is not registered'
seg = body(DEBUG, 'static void handle_segment_misses(int id, const char *json)')
# The summary row shape (the seed tools read `pc` and `kind`: a "bios"
# row belongs in the BIOS seeds, not the game's).
assert '\\"summary\\":[' in seg and \
('{\\"pc\\":\\"0x%08X\\",\\"home\\":\\"0x%08X\\",\\"count\\":%llu,"\n'
' "\\"kind\\":\\"%s\\"}"') in seg and \
'psx_segment_miss_kind_name(kinds[i])' in seg, \
'segment_misses summary rows changed shape'
assert '\\"kind\\":\\"%s\\"}' in seg and 'psx_segment_miss_kind_name(e.kind)' in seg, \
'segment_misses tail entries lost their kind'
stats = body(DEBUG, 'static void handle_dispatch_stats(int id, const char *json)')
assert 'segment_miss_total' in stats and 'psx_segment_miss_total()' in stats
assert 'segment_miss_unique' in stats and 'psx_segment_miss_unique()' in stats
ping = body(DEBUG, 'static void handle_ping(int id, const char *json)')
assert 'segment_miss_total' in ping and 'psx_segment_miss_total()' in ping, \
'ping no longer surfaces segment misses'
# Exit report (psx_last_run_report.json).
dump = body(CRASH, 'void psx_crash_trace_dump(const char *reason, void *seh_info) {')
assert '\\"segment_misses\\"' in dump and 'psx_segment_miss_summary(' in dump, \
'the exit report lost its segment_misses section'
assert 'psx_segment_miss_kind_name(kinds[i])' in dump, \
'the exit report lost the segment-miss kind'
print('segment-miss wiring guards passed')
if __name__ == '__main__':
main()