mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
dirty_ram per_pc: stamp resident-occupant CRC and caller RA on external entries
Make a bare interpreted PC explainable from the session-long per_pc snapshot alone. DirtyRamPcEntry gains occ_crc / occ_ok (the static overlay variant whose code ranges span the PC, via a per-PC scan of the static-match cache, and whether it validated at that moment) and last_ext_ra (gpr[31] at the most recent external entry, i.e. the caller that reached this interior). Both are stamped only on external entries -- arrivals from native dispatch, not interp block chaining -- and emitted per row in dirty_ram_stats.per_pc as occ_crc, occ_ok and ext_ra. Reading the pair: occ_ok=1 is an interior gap inside live native code (an alias seed fixes it); occ_ok=0 with a CRC means the spanning piece is resident but CRC-missing, so it was compiled from another section's bytes or rewritten at run time and no seed helps; 0 means nothing compiled spans the PC. psx_overlay_resident_crc_at() records each static-match cache entry's code span (lo_min/hi_max) so the scan is a range test; DLL-path titles fall back to the loader's last residency hash. The debug-server reply buffer grows 32 -> 64 KiB for the wider rows; the tail reserve still cuts between rows. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -243,6 +243,43 @@ vector observation there.
|
||||
|
||||
---
|
||||
|
||||
## `dirty_ram_stats` `per_pc` — interpreted-PC table
|
||||
|
||||
Snapshot of the open-addressed per-entry-PC table
|
||||
(`g_dirty_ram_pc_table`, `dirty_ram_interp.c`): one object per interpreted
|
||||
entry PC. Read-only; safe to poll while the game runs.
|
||||
|
||||
- `pc` — physical entry PC.
|
||||
- `hits` — dispatches here (interp block chaining conflated in).
|
||||
- `insns` — instructions executed across those hits.
|
||||
- `entries` — dispatches that arrived from NATIVE code (a call or a fresh
|
||||
dispatch), not interp block-to-block chaining. `entries > 0` marks a real
|
||||
interior entry point — the seed stream for overlay-capture aliasing.
|
||||
- `occ_crc` / `occ_ok` — enrichment (2026-09-05): at the last external
|
||||
entry, the manifest CRC of the static overlay variant whose code ranges
|
||||
span this PC (`psx_overlay_resident_crc_at`; DLL-path titles get the last
|
||||
residency hash instead), and whether it validated then. The CRC is the one
|
||||
the variant was compiled from, so it maps straight back to a capture /
|
||||
`.EMI` section offline. Read the pair as: `occ_ok = 1` — an interior gap
|
||||
inside live native code, an alias seed will make it native; `occ_ok = 0`
|
||||
with a CRC — the band's compiled occupant is resident but CRC-missing (data
|
||||
inside the code range is rewritten at run time, or the section differs from
|
||||
the compiled one), so the whole band runs interpreted and no seed helps;
|
||||
`0x00000000` — nothing compiled spans this PC (BIOS / kernel / boot EXE).
|
||||
- `ext_ra` — enrichment: `$ra` at the most recent external entry, i.e. the
|
||||
caller that reached this interior. A PC reached only through a
|
||||
function-pointer table shows the dispatcher's `ra` here, so the table's
|
||||
call site is recoverable from the snapshot alone (the LOGO effect-handler
|
||||
case) without a live `overlay_fp_log` window. `ext_ra == pc` means the
|
||||
entry was a `jr ra` *return* from native code into an interpreted
|
||||
continuation, not a call.
|
||||
|
||||
The array is emitted inline and truncates before the trailing bitmap
|
||||
diagnostics when the response buffer fills; a partial list is still valid JSON
|
||||
(the cut lands between rows). Poll periodically to accumulate coverage.
|
||||
|
||||
---
|
||||
|
||||
## Rule when the server can't answer your question
|
||||
|
||||
If an inspection need isn't covered by the existing commands, **do not fall back to printf or log files**. Instead:
|
||||
|
||||
@@ -224,6 +224,30 @@ typedef struct {
|
||||
* below the local-flow floor every taken branch
|
||||
* re-dispatches, so chain blocks dominate. entry_hits
|
||||
* is the evidence stream for interior-alias seeds. */
|
||||
/* Enrichment (2026-09-05, BoF3): make a bare interpreted PC explainable
|
||||
* from a session-long per_pc snapshot alone, with no offline join and no
|
||||
* live-ring window. Stamped only on EXTERNAL entries (arrived from native
|
||||
* dispatch, addr != chain target), which is where the value is. */
|
||||
uint32_t occ_crc; /* tier 1: psx_overlay_resident_crc_at(pc) at the last
|
||||
* external entry -- the manifest CRC of the static
|
||||
* variant whose code ranges span this PC (DLL path:
|
||||
* last hash taken). Disambiguates a mixed band
|
||||
* (0x801D0C00 carries BATTLE/ETC/SCENARIO/WORLD
|
||||
* occupants) to the one actually loaded, which the
|
||||
* offline enrich_pcs join cannot. 0 = nothing compiled
|
||||
* spans this PC (BIOS / kernel / boot EXE). */
|
||||
uint8_t occ_ok; /* 1 = that variant validated at the time (interior
|
||||
* gap inside live native code: an Axis B seed);
|
||||
* 0 = it is resident but CRC-missing (data inside the
|
||||
* code range rewritten, or a different section than
|
||||
* the one compiled) -- the whole band runs
|
||||
* interpreted and no seed will fix it. */
|
||||
uint32_t last_ext_ra;/* tier 2: gpr[31] (caller RA) at the most recent
|
||||
* external entry — names the call site that reaches a
|
||||
* function-pointer-table interior. This is the §9 (LOGO
|
||||
* effect-handler tables) diagnosis made durable and
|
||||
* session-long instead of ring-bounded; the full
|
||||
* call/jalr/jr transfer split stays in the fp-log ring. */
|
||||
} DirtyRamPcEntry;
|
||||
extern DirtyRamPcEntry g_dirty_ram_pc_table[DIRTY_RAM_PC_TABLE_SIZE];
|
||||
/* Every aligned main-RAM word is a possible instruction PC. Execution
|
||||
|
||||
@@ -2906,7 +2906,11 @@ static void handle_dirty_ram_stats(int id, const char *json)
|
||||
uint32_t out[5]);
|
||||
(void)json;
|
||||
|
||||
char buf[32 * 1024];
|
||||
/* 64 KiB (was 32): the per_pc array is emitted inline and each row grew by
|
||||
* the occ_crc / ext_ra enrichment fields (2026-09-05). The tail reserve
|
||||
* below still truncates the row list before the fixed diagnostics, so a
|
||||
* larger buffer only means more per_pc rows fit before that cut. */
|
||||
char buf[64 * 1024];
|
||||
int n = snprintf(buf, sizeof(buf),
|
||||
"{\"id\":%d,\"ok\":true,\"blocks_run\":%llu,"
|
||||
"\"insns_run\":%llu,\"aborts\":%llu,"
|
||||
@@ -2926,12 +2930,16 @@ static void handle_dirty_ram_stats(int id, const char *json)
|
||||
if (e->pc == 0 || e->hits == 0) continue;
|
||||
n += snprintf(buf + n, sizeof(buf) - n,
|
||||
"%s{\"pc\":\"0x%08X\",\"hits\":%llu,\"insns\":%llu,"
|
||||
"\"entries\":%llu}",
|
||||
"\"entries\":%llu,\"occ_crc\":\"0x%08X\","
|
||||
"\"occ_ok\":%u,\"ext_ra\":\"0x%08X\"}",
|
||||
first ? "" : ",",
|
||||
(unsigned)e->pc,
|
||||
(unsigned long long)e->hits,
|
||||
(unsigned long long)e->insns,
|
||||
(unsigned long long)e->entry_hits);
|
||||
(unsigned long long)e->entry_hits,
|
||||
(unsigned)e->occ_crc,
|
||||
(unsigned)e->occ_ok,
|
||||
(unsigned)e->last_ext_ra);
|
||||
first = 0;
|
||||
/* Reserve enough tail room for all bitmap/guard diagnostics below. */
|
||||
if (n >= (int)sizeof(buf) - 2048) break;
|
||||
|
||||
@@ -2891,7 +2891,19 @@ static int dirty_ram_dispatch_inner(CPUState* cpu, uint32_t addr, uint32_t stop_
|
||||
* not a new entry. Anything else arrived from native code (a call or
|
||||
* a fresh dispatch) and is real interior-entry evidence for alias
|
||||
* seeding. */
|
||||
if (pc_entry && addr != g_dirty_interp_chain_target) pc_entry->entry_hits++;
|
||||
if (pc_entry && addr != g_dirty_interp_chain_target) {
|
||||
pc_entry->entry_hits++;
|
||||
/* Enrichment, external entries only (this is a real native/dispatch
|
||||
* arrival, not interp block chaining). occ_crc names which overlay is
|
||||
* resident in this band; last_ext_ra names the caller that reached
|
||||
* this interior. Both durable in the per_pc snapshot — no ring window,
|
||||
* no offline join. See DirtyRamPcEntry. */
|
||||
extern uint32_t psx_overlay_resident_crc_at(uint32_t phys, int *valid);
|
||||
int occ_ok = 0;
|
||||
pc_entry->occ_crc = psx_overlay_resident_crc_at(phys, &occ_ok);
|
||||
pc_entry->occ_ok = (uint8_t)occ_ok;
|
||||
pc_entry->last_ext_ra = cpu->gpr[31];
|
||||
}
|
||||
g_dirty_interp_chain_target = 0;
|
||||
|
||||
/* Block-entry ring buffer — answers "who tried to JALR into this RAM
|
||||
|
||||
@@ -504,6 +504,10 @@ static uint64_t s_diffgate_interp = 0; /* CPS interior re-entries sent to the
|
||||
/* interp because their candidate is */
|
||||
/* still inside the diff verify budget */
|
||||
static uint32_t s_last_crc = 0;
|
||||
/* psx_overlay_resident_crc_at(phys, &valid) -- the occupant fingerprint the dirty
|
||||
* interpreter stamps on each interpreted PC (DirtyRamPcEntry.occ_crc) -- is
|
||||
* defined with the static-match cache below; s_last_crc is its DLL-path
|
||||
* fallback. */
|
||||
static uint32_t s_no_manifest = 0; /* exports skipped (no manifest range)*/
|
||||
static uint64_t s_cand_overflow = 0; /* registrations dropped at CAND_CAP */
|
||||
static uint64_t s_pair_aliases = 0; /* validated complete pairs deduped */
|
||||
@@ -565,6 +569,8 @@ typedef struct {
|
||||
uint32_t expected_crc;
|
||||
uint32_t gen_sum;
|
||||
int matches;
|
||||
uint32_t lo_min; /* span of the variant's code ranges (phys), for */
|
||||
uint32_t hi_max; /* psx_overlay_resident_crc_at() */
|
||||
} StaticMatchCache;
|
||||
|
||||
static StaticMatchCache s_static_match_cache[STATIC_MATCH_CACHE_CAP];
|
||||
@@ -647,10 +653,62 @@ int psx_overlay_static_code_matches(const uint32_t *lo_len_pairs,
|
||||
entry->expected_crc = expected_crc;
|
||||
entry->gen_sum = gen_sum;
|
||||
entry->matches = matches;
|
||||
uint32_t lo_min = 0xFFFFFFFFu, hi_max = 0;
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
uint32_t lo = lo_len_pairs[i * 2u] & 0x1FFFFFFFu;
|
||||
uint32_t hi = lo + lo_len_pairs[i * 2u + 1u];
|
||||
if (lo < lo_min) lo_min = lo;
|
||||
if (hi > hi_max) hi_max = hi;
|
||||
}
|
||||
entry->lo_min = lo_min;
|
||||
entry->hi_max = hi_max;
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
/* Occupant fingerprint for an interpreted PC (tier-1 enrichment,
|
||||
* DirtyRamPcEntry.occ_crc / occ_ok). Scans the static-match cache for the
|
||||
* variant whose code ranges span `phys`:
|
||||
* - one that matched and whose pages are unchanged since (same
|
||||
* page-generation test the dispatch fast path uses) -> its manifest CRC,
|
||||
* *valid = 1. An interior the static walk missed, interpreted inside a
|
||||
* validating variant: the normal Axis B seed.
|
||||
* - otherwise the spanning variant most recently consulted -> its manifest
|
||||
* CRC, *valid = 0. "Compiled for this band, resident, but NOT validating"
|
||||
* -- a CRC-miss occupant (data inside the code range rewritten at run
|
||||
* time, or a section that differs from the one compiled). This is the
|
||||
* case a plain 0 would hide, and it is the one that explains a band that
|
||||
* is compiled yet fully interpreted (SCENA16 at the title screen,
|
||||
* 2026-09-05).
|
||||
* - no spanning variant at all -> 0 / *valid = 0 (BIOS, kernel, boot EXE,
|
||||
* or a band nothing was compiled for); DLL-path titles get the last
|
||||
* residency hash as a hint instead.
|
||||
* The CRC is the one the variant was compiled from, so it maps straight back
|
||||
* to a capture / .EMI section offline. Called only on EXTERNAL interp
|
||||
* entries, so a 4096-slot scan is fine. */
|
||||
uint32_t psx_overlay_resident_crc_at(uint32_t phys, int *valid) {
|
||||
phys &= 0x1FFFFFFFu;
|
||||
uint32_t stale = 0;
|
||||
for (uint32_t i = 0; i < STATIC_MATCH_CACHE_CAP; i++) {
|
||||
const StaticMatchCache *e = &s_static_match_cache[i];
|
||||
if (!e->ranges || phys < e->lo_min || phys >= e->hi_max)
|
||||
continue;
|
||||
if (e->matches) {
|
||||
uint32_t gen_sum = 0;
|
||||
for (uint32_t k = 0; k < e->count; k++)
|
||||
gen_sum += overlay_watch_pagegen_sum(e->ranges[k * 2u] & 0x1FFFFFFFu,
|
||||
e->ranges[k * 2u + 1u]);
|
||||
if (gen_sum == e->gen_sum) {
|
||||
if (valid) *valid = 1;
|
||||
return e->expected_crc;
|
||||
}
|
||||
}
|
||||
if (!stale) stale = e->expected_crc;
|
||||
}
|
||||
if (valid) *valid = 0;
|
||||
return stale ? stale : s_last_crc;
|
||||
}
|
||||
|
||||
void overlay_loader_static_match_stats(uint64_t *rehashes,
|
||||
uint64_t *crc_misses,
|
||||
uint64_t *gen_fastpath) {
|
||||
@@ -658,6 +716,8 @@ void overlay_loader_static_match_stats(uint64_t *rehashes,
|
||||
if (crc_misses) *crc_misses = s_static_match_crc_misses;
|
||||
if (gen_fastpath) *gen_fastpath = s_static_match_gen_fastpath;
|
||||
}
|
||||
#else
|
||||
uint32_t psx_overlay_resident_crc_at(uint32_t phys, int *valid) { (void)phys; if (valid) *valid = 0; return s_last_crc; }
|
||||
#endif
|
||||
|
||||
/* ---- Per-DLL code-range manifest --------------------------------------- */
|
||||
|
||||
Reference in New Issue
Block a user