Files
psxrecomp/runtime/tests/test_psx_segment_miss.c
Shokunin 94be8f6386 recompiler, runtime: compile BIOS segment variants; SCPH-1001 enters its kernel at 0xA0000500
SCPH-1001's reset code enters its relocated kernel through the uncached
alias (lui $a0,0xA000; addiu $a0,$a0,0x500; jr $a0 at ROM 0xBFC0044C).
Beetle charges those four instructions as uncached fetches, 20 cycles; the
BIOS dispatch normalized 0xA0000500 to the kernel body compiled for runtime
0x00000500, which charges cached fetches, 11. That is the -9 cycles left at
SCPH-1001's kernel entry since #429 (docs/SEGMENT_AWARE_CODE.md §3.3).

A BIOS seed that is a runtime PC inside a relocated copy window, in another
segment than the window runs in, now asks for a segment variant (§5.4): the
function at those ROM bytes and its direct-edge closure (branch and J/JAL
targets that leave it, the call return and fall-through past its end) are
emitted again with the seed's segment. Every PC a variant computes from its
own PCs is in that segment (links, fetch tags and the uncached test, IRQ
resume, branch, J and fallthrough targets, store and exception PCs), a KSEG1
variant charges a fetch per instruction, and a jump-table case in another
segment is dispatched instead of continuing locally. Variants and their
continuations get exact-PC rows in a segment_variant_table. A variant seed
that names no emitted function entry stops the build, and so does one in a
segment that does not map physical memory (0x20000000-0x7FFFFFFF, KSEG2).

The dispatch still finds the word by its normalized key (and applies the
kernel-bless guard); a new psx_bios_hit_body() then picks the body for the
exact PC. A variant compiled for that PC runs. Any other PC in another
segment than the one the word's home body was compiled for still runs the
home body, as before, but is now recorded as a BIOS segment miss in the
segment-miss ring (kind "bios", with the home PC), so a new alias entry is
loud instead of silent (§5.5). psx_bios_key_home_pc(), emitted from the
address model, gives each key's home runtime PC: its copy window's runtime
address, or the ROM run in place. The ring, TCP segment_misses and the exit
report carry each entry's kind; tools/collect_game_misses.py lists a BIOS
segment miss for the BIOS seeds instead of writing it into the game's.

SCPH-1001's seeds gain 0xA0000500 (kernel_entry_kseg1): a 4-instruction
KSEG1 variant of kernel_trampoline_0. Every BIOS's dispatch C gains the two
helpers and calls psx_bios_hit_body() where it called the home body. A BIOS
without variant seeds (OpenBIOS, SCPH-101, SCPH-5552) keeps its full C;
SCPH-1001's full C gains the variant body and its dispatch C the one-row
segment_variant_table.

New bios_segment_variants test: OpenBIOS with KSEG1 variants of kernel
0x12BC (closure 0x6F8 by jal and 0x70C by fall-through) and 0x13F4 (0x1424
by a conditional branch only) and a KSEG0 variant of kernel memset 0x540 (a
KUSEG jump table), plus a static check of SCPH-1001's seed. It compiles the
emitted lookup, with and without variants, and queries every variant row,
every home key's home PC (checked against the PC each function body was
compiled for) and every other segment's alias of it. Seeds in a segment
that does not map physical memory stop the build, and a runtime PC in its
window's own segment stays a discovery seed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 00:24:07 -07:00

134 lines
6.6 KiB
C

/* Segment-miss classification and record (docs/SEGMENT_AWARE_CODE.md §5.5).
*
* The game dispatch table is keyed by the full PC. dirty_ram_dispatch_inner
* hands every clean game-text miss to psx_segment_miss_note(), which asks
* psx_segment_miss_home() whether the PC, having no row of its own, has a
* compiled row in another segment at the same physical word; if so the PC is
* a segment miss: interpreted, and recorded here with the full PC and the row
* that exists. test_segment_miss_wiring.py pins the call site.
*
* The fake table below is a KUSEG-linked image (rows at 0x000100E4 and
* 0x000100F0) plus one KSEG1 row, the shape PR D's variants add. */
#include "psx_segment_miss.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
static int failures = 0;
#define CHECK(cond, msg) do { if (!(cond)) { fprintf(stderr, "FAIL: %s\n", msg); failures++; } } while (0)
static int is_entry(uint32_t addr)
{
return addr == 0x000100E4u || addr == 0x000100F0u || addr == 0xA0010200u;
}
int main(void)
{
/* Classification: another segment's row at the same word. */
CHECK(psx_segment_miss_home(0x800100E4u, is_entry) == 0x000100E4u,
"a KSEG0 alias of a KUSEG row is a segment miss");
CHECK(psx_segment_miss_home(0xA00100F0u, is_entry) == 0x000100F0u,
"a KSEG1 alias of a KUSEG row is a segment miss");
CHECK(psx_segment_miss_home(0x00010200u, is_entry) == 0xA0010200u,
"a KUSEG alias of a KSEG1 row names the KSEG1 row");
CHECK(psx_segment_miss_home(0x000100E4u, is_entry) == 0,
"a PC's own row is not consulted");
CHECK(psx_segment_miss_home(0x800100E8u, is_entry) == 0,
"an interior PC with no row in any segment is not a segment miss");
CHECK(psx_segment_miss_home(0x800100E4u, NULL) == 0,
"no dispatch table, no segment miss");
/* The dispatch hook (dirty_ram_dispatch_inner, on a clean game-text
* miss): records only a PC with no row of its own whose alias has one. */
psx_segment_miss_reset();
CHECK(psx_segment_miss_note(0x000100E4u, is_entry, 1u, 2u, 3u) == 0,
"a PC with its own row (bytes diverged) is not a segment miss");
CHECK(psx_segment_miss_note(0x800100E8u, is_entry, 1u, 2u, 3u) == 0,
"an interior PC with no row in any segment is not a segment miss");
CHECK(psx_segment_miss_note(0x800100E4u, NULL, 1u, 2u, 3u) == 0,
"no dispatch table, no segment miss");
CHECK(psx_segment_miss_total() == 0, "non-segment misses record nothing");
CHECK(psx_segment_miss_note(0xA00100E4u, is_entry, 0x00010018u, 0x001FFF00u, 7u) ==
0x000100E4u, "an alias of a row is noted with that row");
{
PsxSegmentMissEntry e = psx_segment_miss_get(0);
CHECK(psx_segment_miss_total() == 1 && e.addr == 0xA00100E4u &&
e.home == 0x000100E4u && e.ra == 0x00010018u && e.sp == 0x001FFF00u &&
e.frame == 7u, "the noted miss is recorded with the full PC, $ra, $sp, frame");
}
/* Record: totals, unique PCs, ring order, summary order. */
psx_segment_miss_reset();
CHECK(psx_segment_miss_total() == 0 && psx_segment_miss_unique() == 0, "reset");
for (int i = 0; i < 3; ++i)
psx_segment_miss_record(0xA00100F0u, 0x000100F0u, 0x00010040u, 0x001FFF00u, 10u + i);
psx_segment_miss_record(0x800100E4u, 0x000100E4u, 0x00010018u, 0x001FFF00u, 20u);
CHECK(psx_segment_miss_total() == 4, "every miss counts");
CHECK(psx_segment_miss_unique() == 2, "distinct full PCs are unique entries");
{
PsxSegmentMissEntry e = psx_segment_miss_get(3);
CHECK(e.seq == 3 && e.addr == 0x800100E4u && e.home == 0x000100E4u &&
e.ra == 0x00010018u && e.frame == 20u && e.kind == PSX_SEGMENT_MISS_GAME,
"the ring keeps the full PC in order, kind game");
}
{
uint32_t addrs[4], homes[4], kinds[4];
uint64_t counts[4];
uint32_t n = psx_segment_miss_summary(addrs, homes, counts, kinds, 4);
CHECK(n == 2, "summary has one row per PC");
CHECK(addrs[0] == 0xA00100F0u && homes[0] == 0x000100F0u && counts[0] == 3 &&
kinds[0] == PSX_SEGMENT_MISS_GAME,
"summary is highest count first");
CHECK(addrs[1] == 0x800100E4u && counts[1] == 1, "summary second row");
CHECK(psx_segment_miss_summary(addrs, homes, counts, NULL, 1) == 1,
"summary honours max");
}
/* A BIOS segment miss (the compiled BIOS ran a window's home body for an
* alias PC, §5.4) keeps its kind in the ring and in the summary. */
psx_segment_miss_record_kind(0x80000500u, 0x00000500u, 0xBFC06F0Cu, 0x801FFF00u, 3u,
PSX_SEGMENT_MISS_BIOS);
{
PsxSegmentMissEntry e = psx_segment_miss_get(4);
uint32_t addrs[4], kinds[4];
uint32_t n = psx_segment_miss_summary(addrs, NULL, NULL, kinds, 4);
CHECK(e.addr == 0x80000500u && e.home == 0x00000500u && e.frame == 3u &&
e.kind == PSX_SEGMENT_MISS_BIOS, "a BIOS segment miss is recorded as kind bios");
/* Counts 3, 1, 1; the tie orders by PC. */
CHECK(n == 3 && addrs[1] == 0x80000500u && kinds[1] == PSX_SEGMENT_MISS_BIOS &&
kinds[0] == PSX_SEGMENT_MISS_GAME && kinds[2] == PSX_SEGMENT_MISS_GAME,
"the summary carries each PC's kind");
CHECK(strcmp(psx_segment_miss_kind_name(PSX_SEGMENT_MISS_BIOS), "bios") == 0 &&
strcmp(psx_segment_miss_kind_name(PSX_SEGMENT_MISS_GAME), "game") == 0,
"kind names");
}
/* Equal counts order by PC, and the summary visits each PC once. */
psx_segment_miss_reset();
psx_segment_miss_record(0xA0010300u, 0x00010300u, 0, 0, 0);
psx_segment_miss_record(0x80010300u, 0x00010300u, 0, 0, 0);
{
uint32_t addrs[4];
uint32_t n = psx_segment_miss_summary(addrs, NULL, NULL, NULL, 4);
CHECK(n == 2 && addrs[0] == 0x80010300u && addrs[1] == 0xA0010300u,
"ties order by PC");
}
/* The ring wraps; the total keeps counting. */
psx_segment_miss_reset();
for (uint32_t i = 0; i < PSX_SEGMENT_MISS_RING_CAP + 5u; ++i)
psx_segment_miss_record(0x80010000u + 4u * (i % 7u), 0x00010000u, 0, 0, i);
CHECK(psx_segment_miss_total() == PSX_SEGMENT_MISS_RING_CAP + 5u, "total past the ring");
CHECK(psx_segment_miss_unique() == 7, "unique past the ring");
{
PsxSegmentMissEntry e = psx_segment_miss_get(PSX_SEGMENT_MISS_RING_CAP + 4u);
CHECK(e.frame == PSX_SEGMENT_MISS_RING_CAP + 4u, "the newest entry survives the wrap");
}
if (failures) {
fprintf(stderr, "psx_segment_miss_test: %d failure(s)\n", failures);
return 1;
}
puts("psx_segment_miss_test: PASS");
return 0;
}