mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 18:25:35 -04:00
interp: fix the REAL native-wide CPU collapse - per-instruction ws site scans
Stack-sampled (devkitPro gdb, Tomba2 16:9 attract in the 12fps wedge): the main thread sat in ws_backdrop_site_kind <- exec_one. Under native-wide, exec_one classified EVERY interpreted instruction as a potential backdrop rewrite site; the classifier scans +/-512 bytes of RAM per miss, and its 256-slot direct-mapped cache (any two hot PCs 1KB apart collide) thrashed on overlay-sized working sets, so hot interpreted loops re-scanned per executed instruction. Squash mode gates the whole path off, which is why ws_nw on=0 instantly restored 60fps while every GPU-side theory (mirror passes, FBO ping-pong, extra prims, present path) failed - the earlier GL GPU-time numbers were CPU-starvation-inflated timestamp gaps. Fixes: - exec_one pre-filters by opcode class first (addu/or/addi/addiu are the only rewrite-site forms) - detection no longer runs for ~85% of instrs. - Both site caches (cull + backdrop) grow to 8192 full-PC-tagged slots. - NEW g_dirty_ram_code_gen (memory.c): bumped on clean->dirty page transitions and every executable-range mark (overlay/EXE loads); cached classifications carry the generation, so an overlay reload re-derives them. This also fixes the latent staleness bug where both caches could serve a kind derived from a PREVIOUS overlay bytes at the same PC. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -290,11 +290,21 @@ static inline uint32_t fetch_word(uint32_t phys) {
|
||||
* function boundaries, so we scan a +/-512-byte window around the site (clamped
|
||||
* to main RAM) via the shared psx_ws_func_has_screen_cull, cached per-PC so a
|
||||
* hot render loop pays the scan once. Single-threaded interp => static buffers ok. */
|
||||
/* Per-PC site caches: FULL-PC tagged, sized for a whole overlay's working set
|
||||
* (the old 256-slot direct map thrashed on any two hot PCs 1 KB apart — the
|
||||
* ±512-byte rescan then ran per EXECUTED INSTRUCTION and collapsed native-wide
|
||||
* 16:9 to ~12fps, entirely CPU-side). Entries carry the dirty-RAM code
|
||||
* generation (memory.c g_dirty_ram_code_gen) so an overlay reload re-derives
|
||||
* every classification instead of trusting a stale kind. */
|
||||
extern uint32_t g_dirty_ram_code_gen;
|
||||
#define WS_SITE_CACHE_SLOTS 8192u /* 32 KB of code coverage per cache */
|
||||
|
||||
static int ws_cull_site(uint32_t pc) {
|
||||
enum { WIN = 128 }; /* +/- 128 words = +/- 512 bytes */
|
||||
static struct { uint32_t pc; int8_t flag; } cache[256];
|
||||
uint32_t slot = (pc >> 2) & 255u;
|
||||
if (cache[slot].pc == pc) return cache[slot].flag;
|
||||
static struct { uint32_t pc; uint32_t gen; int8_t flag; } cache[WS_SITE_CACHE_SLOTS];
|
||||
uint32_t slot = (pc >> 2) & (WS_SITE_CACHE_SLOTS - 1u);
|
||||
if (cache[slot].pc == pc && cache[slot].gen == g_dirty_ram_code_gen)
|
||||
return cache[slot].flag;
|
||||
uint32_t phys = pc & 0x1FFFFFFFu;
|
||||
uint32_t lo = (phys > (uint32_t)(WIN * 4)) ? phys - (uint32_t)(WIN * 4) : 0u;
|
||||
uint32_t hi = phys + (uint32_t)(WIN * 4);
|
||||
@@ -304,7 +314,7 @@ static int ws_cull_site(uint32_t pc) {
|
||||
for (uint32_t a = lo; a + 4u <= hi && n < (int)(2 * WIN + 1); a += 4u)
|
||||
words[n++] = fetch_word(a);
|
||||
int flag = psx_ws_func_has_screen_cull(words, n);
|
||||
cache[slot].pc = pc; cache[slot].flag = (int8_t)flag;
|
||||
cache[slot].pc = pc; cache[slot].gen = g_dirty_ram_code_gen; cache[slot].flag = (int8_t)flag;
|
||||
return flag;
|
||||
}
|
||||
|
||||
@@ -318,9 +328,12 @@ static int ws_cull_site(uint32_t pc) {
|
||||
* WS_BD_END_WIDEN. Single-threaded interp => static buffers are safe. */
|
||||
static int ws_backdrop_site_kind(uint32_t pc, int *out_cols) {
|
||||
enum { WIN = 128 }; /* +/- 128 words = +/- 512 bytes */
|
||||
static struct { uint32_t pc; int8_t kind; int16_t cols; } cache[256];
|
||||
uint32_t slot = (pc >> 2) & 255u;
|
||||
if (cache[slot].pc == pc) { if (out_cols) *out_cols = cache[slot].cols; return cache[slot].kind; }
|
||||
static struct { uint32_t pc; uint32_t gen; int8_t kind; int16_t cols; } cache[WS_SITE_CACHE_SLOTS];
|
||||
uint32_t slot = (pc >> 2) & (WS_SITE_CACHE_SLOTS - 1u);
|
||||
if (cache[slot].pc == pc && cache[slot].gen == g_dirty_ram_code_gen) {
|
||||
if (out_cols) *out_cols = cache[slot].cols;
|
||||
return cache[slot].kind;
|
||||
}
|
||||
uint32_t phys = pc & 0x1FFFFFFFu;
|
||||
uint32_t lo = (phys > (uint32_t)(WIN * 4)) ? phys - (uint32_t)(WIN * 4) : 0u;
|
||||
uint32_t hi = phys + (uint32_t)(WIN * 4 + 4);
|
||||
@@ -331,7 +344,8 @@ static int ws_backdrop_site_kind(uint32_t pc, int *out_cols) {
|
||||
words[n++] = fetch_word(a);
|
||||
int cols = 0;
|
||||
int kind = psx_ws_backdrop_kind_at(words, n, lo, phys, &cols); /* all physical-space */
|
||||
cache[slot].pc = pc; cache[slot].kind = (int8_t)kind; cache[slot].cols = (int16_t)cols;
|
||||
cache[slot].pc = pc; cache[slot].gen = g_dirty_ram_code_gen;
|
||||
cache[slot].kind = (int8_t)kind; cache[slot].cols = (int16_t)cols;
|
||||
if (out_cols) *out_cols = cols;
|
||||
return kind;
|
||||
}
|
||||
@@ -1083,7 +1097,14 @@ static int exec_one(CPUState *cpu, uint32_t pc, uint32_t *next_pc_out) {
|
||||
* complete. Gated on the runtime predicate first => 4:3 pays nothing. Each
|
||||
* rewrite is recorded to the always-on backdrop ring with the live extent
|
||||
* (s7), camera-X (scratchpad 0x176) and DL count for `ws_backdrop_ring`. */
|
||||
if (psx_ws_backdrop_preload()) {
|
||||
/* Opcode pre-filter FIRST: only addu/or/addi/addiu can be a rewrite site
|
||||
* (the detector below rewrites exactly one GPR-writing move/addiu). The old
|
||||
* order ran the ±512-byte site scan for EVERY interpreted instruction; with
|
||||
* the small direct-mapped cache thrashing, that was the mechanism of the
|
||||
* native-wide 16:9 CPU collapse (Tomba2 attract 12fps, stack-sampled). */
|
||||
if (((opc == 0x00u && (fnt == 0x21u || fnt == 0x25u)) ||
|
||||
opc == 0x09u || opc == 0x08u) &&
|
||||
psx_ws_backdrop_preload()) {
|
||||
int wcols = 0;
|
||||
int bk = ws_backdrop_site_kind(pc, &wcols);
|
||||
if (bk != WS_BD_NONE) {
|
||||
|
||||
+15
-1
@@ -63,10 +63,23 @@ uint8_t *memory_get_scratchpad_ptr(void) { return scratchpad; }
|
||||
#define DIRTY_RAM_BITMAP_WORDS ((DIRTY_RAM_PAGE_COUNT + 31u) / 32u)
|
||||
static uint32_t dirty_ram_bitmap[DIRTY_RAM_BITMAP_WORDS];
|
||||
|
||||
/* Monotonic generation for RAM-resident CODE changes (kernel install-stub
|
||||
* writes + DMA/EXE loads that mark executable ranges). Consumers that cache
|
||||
* per-PC classifications of RAM instructions (the interp's widescreen
|
||||
* cull/backdrop site caches) compare against this and re-derive after any
|
||||
* code change — a cached kind must never survive an overlay reload. */
|
||||
uint32_t g_dirty_ram_code_gen = 1;
|
||||
|
||||
static inline void dirty_ram_mark_page(uint32_t phys) {
|
||||
if (phys >= RAM_SIZE) return;
|
||||
uint32_t page = phys >> DIRTY_RAM_PAGE_SHIFT;
|
||||
dirty_ram_bitmap[page >> 5] |= (1u << (page & 31u));
|
||||
uint32_t bit = 1u << (page & 31u);
|
||||
/* Generation bumps on the clean->dirty TRANSITION only: kernel-window data
|
||||
* writes land here on every guest store, and bumping per write would
|
||||
* invalidate the per-PC site caches continuously. Overlay (re)loads always
|
||||
* bump via dirty_ram_mark_executable_range. */
|
||||
if (!(dirty_ram_bitmap[page >> 5] & bit)) g_dirty_ram_code_gen++;
|
||||
dirty_ram_bitmap[page >> 5] |= bit;
|
||||
}
|
||||
|
||||
static inline void dirty_ram_mark_kernel_write(uint32_t phys) {
|
||||
@@ -106,6 +119,7 @@ void dirty_ram_mark_executable_range(uint32_t phys, uint32_t len) {
|
||||
for (uint32_t page = first_page; page <= last_page; page++) {
|
||||
dirty_ram_bitmap[page >> 5] |= (1u << (page & 31u));
|
||||
}
|
||||
g_dirty_ram_code_gen++;
|
||||
}
|
||||
|
||||
/* Force-interp mode (tooling): PSX_FORCE_INTERP=1 makes ALL RAM above the kernel
|
||||
|
||||
Reference in New Issue
Block a user