Files
psxrecomp/runtime/src/event_ring.c
T
Matthew Stanley 5041712a05 runtime: phase-5 checkpoint — event-timeline ring, instant-disc rate fix, overlay diagnostics
Accumulated working-tree state for the overlay-JIT-cache phase, committed as a
checkpoint before the Bug A fix (next commit):

- event_ring.{c,h}: always-on unified event-timeline ring (IRQ deliver/gate/
  raise, I_STAT changes, DMA kick/sched/done, ENQ/DEQ per source, exec-mode
  tagged). Edge-recorded, eviction-bounded, survives a blue screen; dump via
  event_ring_dump/_tail/_clear.
- cdrom.c: disc_speed="instant" no longer collapses every sector read to the
  500cy floor (which flooded ~1100 DMA3 completions/frame and starved main
  code). CDROM_INSTANT_PERIOD bounds sector IRQs to CDROM_INSTANT_MAX_PER_FRAME
  (32)/frame. Known caveat: a transient ~1-2s reentry storm at boot frame ~210
  still trips the watchdog once, then recovers.
- interrupts.c: claimed-IRQ cooldown is now per-source — SIO stays 0 (card
  reads need back-to-back IRQs); DMA/VBLANK/timer get CLAIMED_PROGRESS_QUANTUM
  (8) blocks of guaranteed main-code progress between deliveries. Root cause of
  the instant-disc freeze: cooldown=0 let the block leader re-take the
  exception before its body ran (pinned at the 0x8005A1D8 field loop).
- gpu/dma/memory/sio/timers/psx_cycles: event-ring instrumentation at raise/
  kick/complete/schedule sites; g_event_step_conservative (single-cycle event
  stepping A/B used to refute the event-granularity hypothesis for Bug A).
- overlay_capture.c, overlay_loader.h, crc32: capture/manifest plumbing for
  per-entry validity (Inc3).
- recompiler: overlay-mode emission updates (code_generator, main_psx).
- tools: compile_overlays.py audit/patch updates; tomba_loadtest.py.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-09 12:24:40 -07:00

134 lines
4.8 KiB
C

/*
* event_ring.c — always-on unified event-timeline ring. See event_ring.h.
*
* OBSERVABILITY ONLY. Records edges/events from the IRQ-decision path, I_STAT
* raise sites, and DMA kick/complete, each tagged with cycle/pc/func/mode/
* overlay-fn so two runs of the same route can be diffed for the first
* interrupt/DMA ordering divergence.
*/
#include "event_ring.h"
#include "psx_cycles.h"
#include <stdio.h>
#include <string.h>
/* ---- runtime globals captured at record time ---- */
extern uint32_t i_stat; /* memory.c */
extern uint32_t i_mask; /* memory.c */
extern uint32_t g_debug_current_func_addr; /* debug_server */
extern uint32_t g_debug_last_store_pc; /* debug_server */
/* Execution-mode sources (see overlay_loader.c / dirty_ram_interp.c). */
extern uint32_t overlay_loader_get_inprogress(void); /* in-progress native ovl fn, 0 if none */
extern int dirty_ram_interp_is_active(void); /* 1 while interpreting a dirty block */
static EventEntry s_ring[EVENT_RING_CAP];
static uint64_t s_seq; /* total events ever recorded (monotonic) */
static uint8_t current_mode(void) {
if (overlay_loader_get_inprogress() != 0) return MODE_NATIVE_OVERLAY;
if (dirty_ram_interp_is_active()) return MODE_INTERP;
return MODE_STATIC;
}
void event_ring_record_aux(uint16_t kind, uint8_t detail, uint32_t aux) {
#if EVENT_RING_ENABLED
EventEntry *e = &s_ring[s_seq & (EVENT_RING_CAP - 1u)];
e->seq = s_seq++;
e->cycle = psx_get_cycle_count();
e->pc = g_debug_last_store_pc;
e->func = g_debug_current_func_addr;
e->overlay_fn = overlay_loader_get_inprogress();
e->i_stat = i_stat;
e->i_mask = i_mask;
e->aux = aux;
e->kind = kind;
e->mode = current_mode();
e->detail = detail;
#else
(void)kind; (void)detail; (void)aux;
#endif
}
void event_ring_record(uint16_t kind, uint8_t detail) {
event_ring_record_aux(kind, detail, 0u);
}
void event_ring_clear(void) {
s_seq = 0;
memset(s_ring, 0, sizeof(s_ring));
}
static const char *kind_name(uint16_t k) {
switch (k) {
case EV_IRQ_DELIVER: return "IRQ_DELIVER";
case EV_IRQ_GATE: return "IRQ_GATE";
case EV_ISTAT_RAISE: return "ISTAT_RAISE";
case EV_ISTAT_CHANGE: return "ISTAT_CHANGE";
case EV_DMA_KICK: return "DMA_KICK";
case EV_DMA_DONE: return "DMA_DONE";
case EV_DMA_SCHED: return "DMA_SCHED";
case EV_ENQ: return "ENQ";
case EV_DEQ: return "DEQ";
case EV_MODE: return "MODE";
default: return "NONE";
}
}
static const char *mode_name(uint8_t m) {
switch (m) {
case MODE_STATIC: return "STATIC";
case MODE_NATIVE_OVERLAY: return "NATIVE_OVERLAY";
case MODE_INTERP: return "INTERP";
default: return "UNKNOWN";
}
}
static int format_entry(char *out, int cap, const EventEntry *e) {
return snprintf(out, cap,
"{\"seq\":%llu,\"cycle\":%llu,\"kind\":\"%s\",\"mode\":\"%s\","
"\"pc\":\"0x%08X\",\"func\":\"0x%08X\",\"ovl\":\"0x%08X\","
"\"istat\":\"0x%08X\",\"imask\":\"0x%08X\",\"detail\":%u,\"aux\":\"0x%08X\"}",
(unsigned long long)e->seq, (unsigned long long)e->cycle,
kind_name(e->kind), mode_name(e->mode),
e->pc, e->func, e->overlay_fn, e->i_stat, e->i_mask,
(unsigned)e->detail, e->aux);
}
int event_ring_dump_file(const char *path) {
FILE *f = fopen(path ? path : "event_ring.json", "w");
if (!f) return -1;
uint64_t total = s_seq;
uint64_t start = (total > EVENT_RING_CAP) ? (total - EVENT_RING_CAP) : 0;
fputc('[', f);
char buf[512];
int first = 1, count = 0;
for (uint64_t s = start; s < total; s++) {
const EventEntry *e = &s_ring[s & (EVENT_RING_CAP - 1u)];
format_entry(buf, (int)sizeof(buf), e);
fprintf(f, "%s%s", first ? "" : ",\n", buf);
first = 0; count++;
}
fputs("]\n", f);
fclose(f);
return count;
}
int event_ring_dump_json(char *out, int cap, int max_entries) {
uint64_t total = s_seq;
uint64_t want = (max_entries > 0) ? (uint64_t)max_entries : 256;
uint64_t start = (total > want) ? (total - want) : 0;
int n = 0;
n += snprintf(out + n, cap - n,
"{\"total\":%llu,\"events\":[", (unsigned long long)total);
int first = 1;
char buf[512];
for (uint64_t s = start; s < total && n < cap - 600; s++) {
const EventEntry *e = &s_ring[s & (EVENT_RING_CAP - 1u)];
format_entry(buf, (int)sizeof(buf), e);
n += snprintf(out + n, cap - n, "%s%s", first ? "" : ",", buf);
first = 0;
}
n += snprintf(out + n, cap - n, "]}");
return n;
}