From c981b3a2d73d3cc10152ebc9b716100b8cfacf3c Mon Sep 17 00:00:00 2001 From: Shokunin <30949000+tetrisgm@users.noreply.github.com> Date: Tue, 29 Sep 2026 00:24:03 -0700 Subject: [PATCH] bios emitter: test cache-line starts on the runtime PC The cached line-leader rule charges a fetch at every 16-byte line start of the runtime PC. The BIOS emitter tested bits[3:0] of the ROM address instead and assumed every copy window preserves them. OpenBIOS copies its kernel from ROM 0x1FC1E4D4 to RAM 0x500, which shifts them by 4. Each kernel line crossing was charged one instruction early, where it is a hit, and the real crossing went uncharged. 1,152 kernel instruction sites differed from the interpreter. The test now uses the runtime PC that relocate_ra already computes. In the OpenBIOS kernel, 823 misplaced fetch sites go and 773 are added at runtime line starts. Retail profiles are unaffected: SCPH-1001's copy windows are 16-byte aligned and SCPH-101/5552 declare none. The uncached_fetch_charge ctest now also replays every cached OpenBIOS run from a cold cache against the interpreter's fetch path and the Beetle transcription. R4 (OpenBIOS, recompiled kernel, HLE boot) reaches the game entry 6,013 guest cycles later than with the uncached fix alone. Co-Authored-By: Claude Opus 5.5 --- docs/internal/ACCURACY_BURNDOWN.md | 8 ++++++++ docs/internal/FAITHFUL_TIMING_PLAN.md | 17 ++++++++++++++--- recompiler/src/full_function_emitter.cpp | 13 ++++++++----- recompiler/tests/test_uncached_fetch_charge.py | 13 +++++++++---- 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/docs/internal/ACCURACY_BURNDOWN.md b/docs/internal/ACCURACY_BURNDOWN.md index 7fbe727f..9b0b9aa1 100644 --- a/docs/internal/ACCURACY_BURNDOWN.md +++ b/docs/internal/ACCURACY_BURNDOWN.md @@ -136,6 +136,14 @@ observe added to the recompiler so ANY block leader is anchorable on both backen (compiled == interp fetch path == Beetle transcription, per instruction, on OpenBIOS) and `uncached_fetch_codegen_test` (game emitter). OPEN: full LLE boot (`bios_hle = false`) parity against live Beetle (no oracle binary on the Mac). +- [x] **BIOS emitter tests cache-line starts on the runtime PC (2026-09-29).** The + line-leader test used the ROM address, assuming every copy window preserves + bits[3:0]. OpenBIOS copies its kernel from ROM 0x1FC1E4D4 to RAM 0x500, so every + kernel line crossing was charged one instruction early (a hit) and the real + crossing went uncharged: 1,152 kernel instruction sites differed from the interp. + Retail profiles are unaffected (SCPH-1001's windows are 16-byte aligned; + SCPH-101/5552 declare none). Covered by ctest `uncached_fetch_charge` (cached runs + compared from a cold cache). - [ ] **HW test-ROM ruler (#2)** — Amidog CPU/GTE timing ROMs for hand-crafted single-COMPONENT isolation (div-only, load-only loops) that organic BIOS code can't give (the prologue combines div+loads in one block). Strongest validator. diff --git a/docs/internal/FAITHFUL_TIMING_PLAN.md b/docs/internal/FAITHFUL_TIMING_PLAN.md index d0bb6175..8cafa85a 100644 --- a/docs/internal/FAITHFUL_TIMING_PLAN.md +++ b/docs/internal/FAITHFUL_TIMING_PLAN.md @@ -224,9 +224,10 @@ on a fixed region -> next. psx_icache.c also uses. The A0/B0/C0 call-vector stubs charge one fetch per executed word. - Cached code keeps the leader rule. R4's regenerated game C (50 shards and the - dispatch table) is byte-identical, as are the 26 R4 overlay shards compiled in - both runs. - - The OpenBIOS diff is insertions only: 5,477 fetches plus 5 stub fetches. + dispatch table) is byte-identical. So is every R4 overlay shard compiled in + more than one run (26 of the 45 captured; runs capture different sets). + - For the uncached fix, the OpenBIOS diff is insertions only: 5,477 fetches + plus 5 stub fetches. - The codegen hash changes because the emitter sources do, and psx_instr_cost.h is now in the hash list. Every title reshards its overlay cache once. @@ -238,6 +239,16 @@ on a fixed region -> next. game entry 0x8007D8F4 moves from guest cycle 97,718,389 to 100,583,391 (+2,865,002, about 5.1 frames). The count was the same on two baseline boots. R4 reached a race with 0 dispatch misses. + - Same day, second fix in the BIOS emitter: the cached line-start test now + uses the runtime PC instead of the ROM address. OpenBIOS copies its kernel + from ROM 0x1FC1E4D4 to RAM 0x500, which shifts bits[3:0] by 4. Each kernel + line crossing was charged one instruction early (a hit), and the real + crossing went uncharged: 1,152 kernel sites differed from the interp. + In the OpenBIOS kernel, 823 misplaced fetch sites go and 773 are added at + runtime line starts. Retail profiles are unaffected: SCPH-1001's windows + are 16-byte aligned and SCPH-101/5552 declare none. With both fixes, the + R4 game entry is at 100,589,404 (+2,871,015 over master; +6,013 from this + fix). R4 reached a race with 0 dispatch misses. - Closes `bios-kseg1-fetch-charge` in the segment-aware ledger (PR #419). - OPEN: full LLE boot (`bios_hle = false`) cycle parity against live Beetle. There is no oracle binary on the Mac. diff --git a/recompiler/src/full_function_emitter.cpp b/recompiler/src/full_function_emitter.cpp index a64d4dab..143eb387 100644 --- a/recompiler/src/full_function_emitter.cpp +++ b/recompiler/src/full_function_emitter.cpp @@ -772,10 +772,13 @@ bool FullFunctionEmitter::emit_function( // to the dirty-RAM interp (cpu->pc) and Beetle. // - CACHED runtime PC: emitted only at cache-line LEADERS: a block leader (any // branch/dispatch entry — a possibly-cold cache entry; cross-function targets - // are inserted into block_leaders above) OR a 16-byte-line start (addr&0xC==0, - // a sequential line crossing). Intra-line followers reached by fall-through are - // guaranteed hits (the leader refilled the line to its end) → no call (+0). The - // relocation preserves bits[3:0], so the line-leader test is space-independent. + // are inserted into block_leaders above) OR a 16-byte-line start of the RUNTIME + // PC (pc&0xC==0, a sequential line crossing). Intra-line followers reached by + // fall-through are guaranteed hits (the leader refilled the line to its end) → + // no call (+0). The line test must use the runtime PC: a copy window need not + // preserve bits[3:0]. OpenBIOS copies its kernel from ROM 0x1FC1E4D4 to RAM + // 0x500, so a ROM-address test put every line crossing of the kernel one + // instruction early (a hit) and left the real crossing uncharged. // - UNCACHED runtime PC (psx_fetch_uncached: the ROM run in place at KSEG1): no // fetch ever fills a line, so there is no follower to elide. Every instruction // pays +4 and clears the load give-back, exactly as the interpreter (a fetch at @@ -785,7 +788,7 @@ bool FullFunctionEmitter::emit_function( if (!per_insn_cycles) return; const uint32_t pc = relocate_ra(rom_addr); if (!psx_fetch_uncached(pc) && - !(block_leaders.count(rom_addr) || (rom_addr & 0xCu) == 0)) return; + !(block_leaders.count(rom_addr) || (pc & 0xCu) == 0)) return; out += fmt::format("#ifdef PSX_ENABLE_BLOCK_CYCLES\n psx_icache_fetch(cpu, 0x{:08X}u);\n#endif\n", pc); }; diff --git a/recompiler/tests/test_uncached_fetch_charge.py b/recompiler/tests/test_uncached_fetch_charge.py index 476393f4..67a4b29f 100644 --- a/recompiler/tests/test_uncached_fetch_charge.py +++ b/recompiler/tests/test_uncached_fetch_charge.py @@ -20,9 +20,14 @@ instruction's (cycles, give-back cleared) pair must be the same both ways: model psx_icache.c (both entry points) equals a transcription of Beetle ReadInstruction on synthetic sequences: uncached repeat, cached cold then warm, KSEG0 -> KUSEG alias refill, partial refill. - bios psxrecomp-bios on bios/OpenBIOS.toml. Every straight-line KSEG1 - run (the ROM in place) is replayed, and every KSEG1 instruction - must carry a fetch naming its own runtime PC. + bios psxrecomp-bios on bios/OpenBIOS.toml. Every straight-line run from + a label is replayed from a cold cache: KSEG1 runs (ROM in place) + and cached runs (kernel at 0x500+, shell at 0x80030000+) alike. + Every KSEG1 instruction must carry a fetch naming its own runtime + PC. Cached runs pin the line-start test to the runtime PC: + OpenBIOS copies its kernel from ROM 0x1FC1E4D4 to RAM 0x500, so a + ROM-address test charged each kernel line crossing one + instruction early (a hit) and missed the real one. stubs The A0/B0/C0 call-vector native stubs charge one fetch per executed word, so a KSEG1 call pays like the interpreter. @@ -347,7 +352,7 @@ def main(): if not run["from_label"]: first = next((i for i, (_, t) in enumerate(insns) if t is not None), len(insns)) insns = [x for i, x in enumerate(insns) if i >= first or uncached(x[0])] - if not insns or not all(uncached(pc) for pc, _ in insns): + if not insns: continue for pc, tag in insns: if uncached(pc):