mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
94be8f6386
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>
93 lines
4.0 KiB
C
93 lines
4.0 KiB
C
#ifndef PSX_SEGMENT_MISS_H
|
|
#define PSX_SEGMENT_MISS_H
|
|
|
|
/* Segment misses in static game code (docs/SEGMENT_AWARE_CODE.md §5.5; §10
|
|
* decision 2: interpret loudly until the title is regenerated), and in the
|
|
* compiled BIOS (§5.4).
|
|
*
|
|
* A PC carries a segment (KUSEG 0x0..., KSEG0 0x8..., KSEG1 0xA...), and the
|
|
* game dispatch table is keyed by the full PC: each compiled body bakes the
|
|
* links, EPCs, fetch tags and store PCs of the segment it was compiled for.
|
|
* A PC whose physical word has a row only in another segment (a KSEG1 alias
|
|
* of KUSEG-linked text, a KSEG0 call into it) therefore has no body of its
|
|
* own. Dispatch interprets it through the clean-text-miss path and records it
|
|
* here, with the full PC and the row that exists, so a run names each
|
|
* (segment, entry) that needs a segment-qualified seed (§5.4). Visible over
|
|
* TCP (`segment_misses`, `dispatch_stats`) and in psx_last_run_report.json. */
|
|
|
|
#include <stdint.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
typedef struct {
|
|
uint64_t seq;
|
|
uint32_t addr; /* the PC that has no body of its own */
|
|
uint32_t home; /* the compiled row at the same physical word */
|
|
uint32_t ra;
|
|
uint32_t sp;
|
|
uint32_t frame;
|
|
uint32_t kind; /* PSX_SEGMENT_MISS_GAME or PSX_SEGMENT_MISS_BIOS */
|
|
} PsxSegmentMissEntry;
|
|
|
|
#define PSX_SEGMENT_MISS_RING_CAP 4096u /* power of two */
|
|
#define PSX_SEGMENT_MISS_UNIQUE_CAP 1024u
|
|
|
|
/* The other segment's PC with a compiled row at addr's physical word, or 0
|
|
* when there is none (addr itself is not consulted). KUSEG, KSEG0 and KSEG1
|
|
* are tried in that order. is_entry is the game dispatch's
|
|
* psx_game_is_function_entry. */
|
|
uint32_t psx_segment_miss_home(uint32_t addr, int (*is_entry)(uint32_t));
|
|
|
|
/* What a segment miss ran. GAME: static game text, interpreted at its own
|
|
* PC (§5.5). BIOS: a relocated BIOS window (kernel, shell) or the ROM entered
|
|
* in another segment than the one its body was compiled for, with no segment
|
|
* variant of its own. The BIOS dispatch is keyed by the normalized address,
|
|
* so the window's home body ran, with its own segment's links, EPCs, fetch
|
|
* tags and store PCs; the fix is a segment-qualified seed in the BIOS seeds
|
|
* file (§5.4), not in the game's. */
|
|
#define PSX_SEGMENT_MISS_GAME 0u
|
|
#define PSX_SEGMENT_MISS_BIOS 1u
|
|
|
|
/* Record one segment miss of static game code (kind GAME). */
|
|
void psx_segment_miss_record(uint32_t addr, uint32_t home, uint32_t ra,
|
|
uint32_t sp, uint32_t frame);
|
|
|
|
/* Record one segment miss of either kind. A PC keeps the kind it was first
|
|
* recorded with in the per-PC counts. */
|
|
void psx_segment_miss_record_kind(uint32_t addr, uint32_t home, uint32_t ra,
|
|
uint32_t sp, uint32_t frame, uint32_t kind);
|
|
|
|
/* The dispatch hook: dirty_ram_dispatch_inner calls this for every clean
|
|
* game-text miss (a PC in the EXE's text that no compiled body ran). It
|
|
* records a segment miss when addr has no row of its own but another
|
|
* segment's alias does, and returns that row; otherwise (addr has a row, e.g.
|
|
* its bytes diverged, or no segment has one: an interior PC) it records
|
|
* nothing and returns 0. */
|
|
uint32_t psx_segment_miss_note(uint32_t addr, int (*is_entry)(uint32_t),
|
|
uint32_t ra, uint32_t sp, uint32_t frame);
|
|
|
|
uint64_t psx_segment_miss_total(void);
|
|
uint32_t psx_segment_miss_unique(void);
|
|
|
|
/* The i-th recorded entry, i < total; only the last RING_CAP are kept. */
|
|
PsxSegmentMissEntry psx_segment_miss_get(uint64_t seq);
|
|
|
|
/* Per-PC counts, highest first: up to max rows, returns the number written.
|
|
* Counts are exact for the first UNIQUE_CAP distinct PCs; later new PCs are
|
|
* counted in the total and the ring only. Any output array may be NULL. */
|
|
uint32_t psx_segment_miss_summary(uint32_t *addrs, uint32_t *homes,
|
|
uint64_t *counts, uint32_t *kinds, uint32_t max);
|
|
|
|
/* "game" or "bios", for reports. */
|
|
const char *psx_segment_miss_kind_name(uint32_t kind);
|
|
|
|
void psx_segment_miss_reset(void);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* PSX_SEGMENT_MISS_H */
|