/* dirty_ram_interp.h — interpret-on-dispatch for install-at-runtime RAM. * * See CLAUDE.md Rule 18 and docs/dynamic_handler_install.md for the full * rationale. The PS1 BIOS dynamically writes 4-instruction dispatch stubs * into kernel RAM (notably RAM 0xCF0 for the SIO data-byte handler). A * static recompiler can't see those bytes at compile time, so a small MIPS * interpreter here runs them at dispatch time on the same CPUState. * * Scope: this is NOT a fallback for code the recompiler failed to translate. * It runs only against PCs in pages that have been written-to since boot. * Static-recompiled code continues to handle ROM-resident code and game * RAM. See docs/dynamic_handler_install.md for the inline note about a * potential future migration to runtime JIT (Option B). */ #ifndef PSXRECOMP_DIRTY_RAM_INTERP_H #define PSXRECOMP_DIRTY_RAM_INTERP_H #include #include #include "psx_memory.h" #include "cpu_state.h" #ifdef __cplusplus extern "C" { #endif /* Returns 1 if `addr` lies in a dirty kernel-RAM page and the interpreter * ran a basic block at that PC. Returns 0 if `addr` is clean (caller must * fall back to the static dispatch table). On return with 1, cpu->pc is * either 0 (block ended on jr $ra style return) or the target of a tail * jump that the dispatch trampoline should re-enter. * * `stop_addr` is the dispatch loop's return contract (psx_dispatch_call's * return_addr; 0 for plain tail dispatches). The interpreter's overlay * local-flow chaining MUST NOT run past it: when a transfer (or fall- * through) reaches stop_addr, the interpreter exits with cpu->pc == * stop_addr so the dispatch loop returns into the suspended native * caller. Skipping this check lets the interp execute the native * caller's tail itself; the suspended native host frame then resumes and * double-executes that tail against a moved guest stack, restoring * garbage callee-saved registers (the pig-throw blue-screen/MMIO-fatal * corruption, 2026-06-10). */ int dirty_ram_dispatch(CPUState* cpu, uint32_t addr, uint32_t stop_addr); /* Replay [start_pc, stop_pc) of already-loaded guest code from the CPU state * given: every PC in the range is interpreted from its live RAM bytes, even * where a compiled body or a compiled resume point (the return address of a * call) begins, so a span can start in the middle of a compiled function. * Calls the span makes run on their normal backend and return into it. * Returns 1 when control reaches stop_pc, 0 when it leaves the range any other * way (a return, a jump out, a nested span) or runs past max_insns * interpreted instructions. The range is half-open, CODE identity, one span * at a time. A span is a replay, not a function entry: it fires no * function-entry hooks at its own PCs and stays out of capture, alias seeding * and per-PC entry statistics. Instruction hooks fire as in compiled code. * Mods reach it through psx_mod_run_guest_span (render passes only). */ int dirty_ram_run_span(CPUState* cpu, uint32_t start_pc, uint32_t stop_pc, uint64_t max_insns); /* Why the last span that failed stopped: 1 returned (pc 0), 2 left the range, * 3 call-bail unwind, 4 instruction budget, 5 the interpreter declined the PC, * 6 bad arguments or a span already open, 7 a call did not return to the * span (`after`). `pc` is where control was. */ typedef struct DirtyRamSpanFailure { uint32_t reason, pc, start_pc, stop_pc; uint32_t ra, after; /* $ra at the exit; pc after a call that did not return */ uint64_t insns; } DirtyRamSpanFailure; void dirty_ram_span_last_failure(DirtyRamSpanFailure *out); /* The open span (0,0 = none), for landings that longjmp past its exit. */ void dirty_ram_span_get(uint32_t *lo, uint32_t *hi); void dirty_ram_span_set(uint32_t lo, uint32_t hi); /* Retire a deferred R3000A load-delay writeback (see dirty_ram_interp.c). The * interpreter defers a load's destination-register write past the delay-slot * instruction, as hardware does; call this anywhere control leaves the * interpreter or an exception is delivered, so no register write is dropped. */ void dirty_ram_ld_delay_flush(CPUState* cpu); /* Drop a deferred load-delay writeback WITHOUT applying it. Required on * savestate/RB restore: the snap already has architectural GPRs, and a * host-static pending load from the pre-load timeline must not stomp them * on the next interp step (selfcheck MotK v0=countdown vs v0=1 forks). */ void dirty_ram_ld_delay_discard(void); /* Save / put back the deferred load-delay writeback exactly. For a landing * that longjmps out of interpreted code and then resumes the interrupted * code (the render-pass watchdog abort, render_pass.c): the pass's pending * load must not reach the resumed timeline, and the interrupted one's must * survive. */ typedef struct DirtyRamLoadDelay { uint32_t rt, val, age; int armed; } DirtyRamLoadDelay; void dirty_ram_ld_delay_save(DirtyRamLoadDelay *out); void dirty_ram_ld_delay_restore(const DirtyRamLoadDelay *in); /* Re-anchor host-only dirty IRQ pump ambient (entry-poll stride + 4096-insn * gap) after snap restore. Not in boot_state — peers that drifted through FMV * otherwise entered the post-FMV dirty wait on opposite poll phases. */ void dirty_ram_irq_ambient_resync_after_restore(void); /* Overlay-cache windows — the address ranges eligible for capture, offline * recompilation, and per-entry-validated native execution (Rule 18 code that * does not exist in any compile-time image): * * Kernel RAM [0x00000, 0x10000): the BIOS part-2 image relocated to RAM * plus install-at-runtime stubs (e.g. the SIO data-byte stub at 0xCF0). * Dirty-tracked per CPU store (dirty_ram_mark_kernel_write). * Overlay region [OVERLAY_REGION_FLOOR, live RAM end): game overlays loaded by * CD DMA (dirty_ram_mark_executable_range). * * Main-EXE text [0x10000, OVERLAY_REGION_FLOOR): CLEAN pages are statically * recompiled and never captured. But games that discard their boot/init code * load gameplay overlays OVER this region (Tomba 2: hot gameplay code at * 0x1DA88/0x24548/0x3116C, well below its 0x38800 floor). Post-baseline * (dirty_ram_clear_image_baseline) a DIRTY page here is by definition live * overlay code — the compiled image is stale for it — so dirty boot-text * pages ARE a capture/candidate window (2026-07-06; before this they could * only interpret, which dominated wall time at ~300ns/insn). * * The interpreter's LOCAL-FLOW gates (is_local_dirty_target / * phys_is_overlay_flow_region) use DIRTY_RAM_KERNEL_WINDOW_END alone: only * the kernel window stays per-block (it runs in exception context where the * verified dispatch cadence is delicate). Native coverage for the kernel * window comes from the overlay loader, not from interp chaining. */ #define DIRTY_RAM_KERNEL_WINDOW_END 0x00010000u /* The overlay-region floor is the END of THIS game's statically-recompiled * main-EXE text (phys). Everything at/above it is runtime-loaded overlay code, * eligible for in-interpreter local-flow chaining and overlay-cache capture. * * It is a RUNTIME value (g_overlay_region_floor), NOT a compile-time constant, * because it differs per game: Tomba 1's text ends at 0x98000 (0x10000+0x88000) * but Tomba 2's boot EXE is only 0x28800 (text ends at 0x38800). A hardcoded * 0x98000 misclassified Tomba 2's overlays (loaded at 0x85000+) as main-EXE * text, forcing them onto the slow block-by-block dispatch path AND through the * bail-prone non-local-call contract — the Whoopee-Camp splash freeze. main.cpp * sets g_overlay_region_floor = (load_address + text_size) & 0x1FFFFFFF at game * load; OVERLAY_REGION_FLOOR_DEFAULT applies for BIOS-only runs. */ #define OVERLAY_REGION_FLOOR_DEFAULT 0x00098000u extern uint32_t g_overlay_region_floor; #define OVERLAY_REGION_FLOOR (g_overlay_region_floor) /* The text-image BASE (phys) = this game's main-EXE load address. The floor * above only bounds the text from ABOVE; on its own it encodes the assumption * that the boot EXE sits at the bottom of usable RAM, so that [KERNEL_END, * FLOOR) is text and [FLOOR, RAM) is overlay. That holds for a load_address of * 0x80010000 (text base == KERNEL_END, so the region below text is empty) but * is FALSE for a boot EXE that loads HIGH and streams its gameplay code into * the RAM BELOW itself: Klonoa loads at 0x80180000 (text 0x180000-0x18B000, * overlays at 0x10000-0x130000), Street Fighter Alpha 3 at 0x80113B00, * Bomberman Fantasy Race at 0x8003004C. For those the sub-text RAM was * misclassified as main-EXE text — clear_image_baseline() wiped its dirty bits * and the dispatch admit-heuristic refused it, so a JALR into an overlay page * the CD DMA'd there fell through to psx_unknown_dispatch and fail-fast * exit(1) (Klonoa, frame 687, target 0x80123D00). * * The overlay region is therefore BOTH sides of the text image, not just above * it. main.cpp pins g_text_image_lo = load_address & 0x1FFFFFFF at game load; * the default equals DIRTY_RAM_KERNEL_WINDOW_END so the below-text clause is * empty for BIOS-only runs and for every bottom-loading game. */ extern uint32_t g_text_image_lo; /* 1 iff phys is runtime-loaded overlay RAM rather than main-EXE text — either * side of the text image. Identical to `phys >= FLOOR` whenever the game loads * at the bottom of RAM (g_text_image_lo == KERNEL_WINDOW_END). */ static inline int phys_is_overlay_region(uint32_t phys) { return phys >= g_overlay_region_floor || (phys >= DIRTY_RAM_KERNEL_WINDOW_END && phys < g_text_image_lo); } /* Test whether a given physical kernel-RAM address is in a page that was * written-to since boot. Defined in memory.c. */ int dirty_ram_is_dirty(uint32_t phys); /* Kernel-image bless (memory.c). A dispatch key in the relocated kernel * window (RAM [0x500,0x8500), the BIOS's boot-time copy of ROM 0x1FC10000+) * may run its statically-recompiled native function IFF the live RAM bytes * of the code reachable from it (emitter-supplied body extent) byte-match * the ROM source. Lazily verified, cached per entry, invalidated precisely * on writes into the body. Runtime-patched bodies (pad/SIO install stubs) * never verify and keep interpreting — faithful either way. */ int psx_kernel_bless_dispatchable(uint32_t phys); /* True when a declared kernel patch range ENDS at this RAM address. The * emitter registered that PC as a continuation key, so the interpreter hands * straight-line flow back to static dispatch there and only the guest's * patched words interpret (memory.c psx_bios_kernel_patch_ranges). */ int psx_kernel_patch_range_ends_at(uint32_t phys); void psx_kernel_bless_note_range(uint32_t phys, uint32_t len); void psx_kernel_bless_stats(uint64_t out[8]); void psx_kernel_bless_resync_after_restore(void); /* Soft-return rematch / BIOS switch: drop latched SCPH↔OpenBIOS window + * CLEAN/MISMATCH so the next kbless_on() re-reads psx_bios_image. */ void psx_kernel_bless_reset_for_boot(void); /* Capture/candidate window membership. Kernel window and overlay region are * unconditional; main-EXE text [KERNEL_WINDOW_END, FLOOR) is included only * when the page is dirty — i.e. a gameplay overlay overwrote the boot text * after the game-start baseline, so the bytes there are runtime overlay code * (see the window model note above). Clean boot text never enters the window * (it runs compiled; capturing it would violate the static-first design). */ static inline int overlay_cache_window_contains(uint32_t phys) { return phys < DIRTY_RAM_KERNEL_WINDOW_END || phys >= OVERLAY_REGION_FLOOR || dirty_ram_is_dirty(phys); } uint32_t dirty_ram_get_bitmap(void); uint32_t dirty_ram_get_bitmap_word(uint32_t word_index); uint32_t dirty_ram_get_bitmap_word_count(void); void dirty_ram_set_bitmap_words(const uint32_t* words, uint32_t count); /* Rematch / session_reboot: wipe host dirty tracking so dig0 matches a cold * process (memory_init clears RAM but used to leave these bitmaps sticky). */ void dirty_ram_reset_for_boot(void); /* After bulk RAM restore (savestate): bump overlay page gens + lazy-miss epoch * so native overlays re-hash against restored bytes; also drop sticky * text_diverged/modified bitmaps (host-only — restored RAM may match ref). */ void overlay_watch_invalidate_after_ram_restore(void); void dirty_ram_text_guard_resync_after_restore(void); void dirty_ram_mark_executable_range(uint32_t phys, uint32_t len); void dirty_ram_register_text_image(uint32_t phys_lo, const uint8_t *bytes, uint32_t len); int dirty_ram_text_native_ok(uint32_t phys); /* Exact CFG ranges; exec_pc clips ranges that end before the resume PC. */ int dirty_ram_text_native_ok_ranges_from(const uint32_t *lo_len_pairs, uint32_t count, uint32_t exec_pc); int dirty_ram_text_native_ok_ranges(const uint32_t *lo_len_pairs, uint32_t count); int dirty_ram_text_image_registered(void); /* Bless an intentional runtime data patch (e.g. text_xlate string/glyph tables) * into the text reference image so it is not mistaken for self-modifying code. */ void dirty_ram_text_bless(uint32_t phys, const uint8_t *bytes, uint32_t len); uint64_t dirty_ram_text_native_blocked(void); uint32_t dirty_ram_text_diverged_pages(void); /* Counters for visibility / TCP debug. Increment in interpreter; expose * via debug_server.c if helpful. */ extern uint64_t g_dirty_ram_blocks_run; /* basic blocks interpreted */ extern uint64_t g_dirty_ram_insns_run; /* instructions interpreted */ extern uint64_t g_dirty_window_dispatches; /* interp dispatches inside a * capture window (autocapture * pressure signal, step 2.8) */ extern uint64_t g_dirty_ram_aborts; /* unsupported-opcode aborts */ extern uint64_t g_dirty_ram_guard_yields; /* long dirty loops yielded */ extern uint64_t g_dirty_ram_unsupported_midblock; extern uint32_t g_dirty_ram_last_unsupported_entry; extern uint32_t g_dirty_ram_last_unsupported_entry_ra; extern uint32_t g_dirty_ram_last_unsupported_entry_sp; extern uint32_t g_dirty_ram_last_unsupported_insns; extern uint32_t g_dirty_ram_last_unsupported_pc; extern uint32_t g_dirty_ram_last_unsupported_insn; extern const char *g_dirty_ram_last_unsupported_reason; /* Per-entry-PC counters. Aggregate counters above hide which install * stubs actually fire — a single noisy spurious-dispatch site can mask * a legitimate handler that never runs. This open-addressed table keys * on the entry PC of each interpreted block. */ /* 65536 -> 262144 (2026-07-03): Tomba2's Trolley attract demo interprets a * working set past 64K distinct PCs; a full table degraded every lookup to a * full-table scan (0.3 fps host burn in pc_table_get_or_insert_in) and * silently truncated overlay-capture seed lists (MAX_CAPTURE_PCS aliases * this). Probe length is bounded in pc_table_get_or_insert_in either way. */ #define DIRTY_RAM_PC_TABLE_SIZE 262144 typedef struct { uint32_t pc; /* entry PC, 0 = empty slot */ uint64_t hits; /* number of times dispatched here */ uint64_t insns; /* total instructions executed across hits */ uint64_t entry_hits; /* dispatches that arrived from NATIVE code (call / * dispatch loop), not from the interpreter's own * block-to-block chaining. `hits` conflates the two: * 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 * coverage only needs presence, not a hit count, so record it in a direct * bitmap instead of probing a large hash table for every retired instruction. * This covers every word of the host RAM backing (the old 262K-entry hash * could saturate) -- retail 2 MiB and the opt-in 8 MiB map alike -- and is * also the execution-verified seed source used by overlay_capture. */ #define DIRTY_RAM_EXEC_WORD_COUNT (PSX_MAIN_RAM_BACKING_BYTES / 4u) #define DIRTY_RAM_EXEC_BITMAP_WORDS ((DIRTY_RAM_EXEC_WORD_COUNT + 31u) / 32u) extern uint32_t g_dirty_ram_exec_pc_bitmap[DIRTY_RAM_EXEC_BITMAP_WORDS]; /* One bit per 4 KiB page. RAM writes use this as a one-test stale-evidence * guard; they clear that page's capture bits rather than serializing from the * universal store hot path. */ #define DIRTY_RAM_EXEC_PAGE_BITMAP_WORDS \ (((PSX_MAIN_RAM_BACKING_BYTES / 4096u) + 31u) / 32u) extern uint32_t g_dirty_ram_exec_page_bitmap[DIRTY_RAM_EXEC_PAGE_BITMAP_WORDS]; /* Presence-only companion for interpreted block/dispatch entries. The richer * counter table remains for telemetry, while capture can snapshot/reset this * compact evidence independently at overlay-generation boundaries. */ extern uint32_t g_dirty_ram_dispatch_pc_bitmap[DIRTY_RAM_EXEC_BITMAP_WORDS]; /* The segment each interpreted dispatch entered through * (docs/SEGMENT_AWARE_CODE.md §5.7). The bytes are segment-free, but a PC is * not: a KUSEG, KSEG0 or KSEG1 entry of the same word links, traps and fetches * in its own segment, so each needs its own compiled shard. The bitmap above * keeps its meaning (dispatched in any segment); these siblings record which * segments, one bit per word per segment, set at the same interpreted * dispatch. The execution bitmap needs no segment: direct edges keep the * entry's segment. Only the three segments that map physical memory have a * sibling; a PC elsewhere (0x20000000-0x7FFFFFFF, KSEG2) addresses no RAM in * Beetle and is never recorded. */ #define PSX_CODE_SEGMENT_COUNT 3 static inline int psx_code_segment_index(uint32_t pc) { switch (pc & 0xE0000000u) { case 0x00000000u: return 0; /* KUSEG */ case 0x80000000u: return 1; /* KSEG0 */ case 0xA0000000u: return 2; /* KSEG1 */ default: return -1; } } static inline uint32_t psx_code_segment_base(int index) { return index == 0 ? 0x00000000u : index == 1 ? 0x80000000u : 0xA0000000u; } /* The names the capture JSON and the per-segment cache directories use. */ static inline const char *psx_code_segment_name(int index) { return index == 0 ? "kuseg" : index == 1 ? "kseg0" : "kseg1"; } extern uint32_t g_dirty_ram_dispatch_seg_bitmap[PSX_CODE_SEGMENT_COUNT] [DIRTY_RAM_EXEC_BITMAP_WORDS]; /* Clear dispatch evidence (the any-segment bitmap and its segment siblings) * for `nwords` bitmap words from `first_word`. Every site that drops dispatch * evidence goes through this, so a stale segment bit can never outlive its * any-segment bit. */ static inline void dirty_ram_dispatch_evidence_clear(uint32_t first_word, uint32_t nwords) { if (first_word >= DIRTY_RAM_EXEC_BITMAP_WORDS) return; if (nwords > DIRTY_RAM_EXEC_BITMAP_WORDS - first_word) nwords = DIRTY_RAM_EXEC_BITMAP_WORDS - first_word; memset(&g_dirty_ram_dispatch_pc_bitmap[first_word], 0, (size_t)nwords * sizeof(uint32_t)); for (int s = 0; s < PSX_CODE_SEGMENT_COUNT; s++) memset(&g_dirty_ram_dispatch_seg_bitmap[s][first_word], 0, (size_t)nwords * sizeof(uint32_t)); } /* Block-entry ring buffer. Records every dispatch into dirty RAM with the * caller's RA at entry, plus argument context — answers * "who tried to JALR into this RAM stub, with what args". * Always-on, eviction keeps memory bounded; callers query the window of * interest (CLAUDE.md global rule on ring buffers). * * Limitation: this captures dispatches into RAM-resident code only. ROM * recompiled-C → recompiled-C calls (direct C function calls in * generated dispatch table) are NOT captured here. For BIOS shell * investigation that's fine — shell code lives in RAM (0x800XXXXX). * * `ra` is cpu->gpr[31] at dispatch time. For normal JAL-style calls, * (ra - 8) gives the caller's PC. For J/JR/tail-calls it's only a * heuristic — treat ra as "ra_callsite_guess", not authoritative. */ #ifdef PSX_NO_DEBUG_TOOLS /* Keep ABI-visible one-element sentinels so crash/debug stubs still link, while * Release does not reserve the diagnostic rings' ~227 MiB of address space. */ #define DIRTY_RAM_BLOCK_LOG_CAP 1u #else #define DIRTY_RAM_BLOCK_LOG_CAP (1u << 22) /* 4M entries (~224 MiB at 56 B each). * At ~580K dispatches/s during boot, this retains ~7s of history; at * ~10K/s during modal idle, ~400s. Sized for retroactive press-window * analysis without the prior 16K ring's 28-ms eviction problem. */ #endif typedef struct { uint64_t seq; /* monotonic, unique per entry */ uint32_t target; /* entry PC (RAM address) */ uint32_t ra; /* cpu->gpr[31] at dispatch — caller's return target */ uint32_t a0; /* cpu->gpr[4] at dispatch */ uint32_t a1; /* cpu->gpr[5] at dispatch */ uint32_t a2; /* cpu->gpr[6] at dispatch */ uint32_t a3; /* cpu->gpr[7] at dispatch */ uint32_t t0; /* cpu->gpr[8] at dispatch */ uint32_t t1; /* cpu->gpr[9] at dispatch */ uint32_t t2; /* cpu->gpr[10] at dispatch */ uint32_t sp; /* cpu->gpr[29] at dispatch */ uint32_t frame; /* s_frame_count at the time of dispatch */ } DirtyRamBlockLogEntry; extern DirtyRamBlockLogEntry g_dirty_ram_block_log[DIRTY_RAM_BLOCK_LOG_CAP]; extern uint64_t g_dirty_ram_block_log_seq; #ifdef PSX_NO_DEBUG_TOOLS #define DIRTY_RAM_FLOW_LOG_CAP 1u #else #define DIRTY_RAM_FLOW_LOG_CAP (1u << 16) #endif typedef struct { uint64_t seq; uint32_t pc; uint32_t target; uint32_t ra; uint32_t a0; uint32_t a1; uint32_t a2; uint32_t a3; uint32_t sp; uint32_t frame; } DirtyRamFlowLogEntry; extern DirtyRamFlowLogEntry g_dirty_ram_flow_log[DIRTY_RAM_FLOW_LOG_CAP]; extern uint64_t g_dirty_ram_flow_log_seq; #ifdef PSX_NO_DEBUG_TOOLS #define DIRTY_RAM_INSN_LOG_CAP 1u #else #define DIRTY_RAM_INSN_LOG_CAP (1u << 16) #endif typedef struct { uint64_t seq; uint32_t pc; uint32_t insn; uint32_t next_pc; uint32_t target; uint32_t before_s0; uint32_t after_s0; uint32_t sp; uint32_t ra; uint32_t v0; uint32_t v1; uint32_t a0; uint32_t a1; uint32_t a2; uint32_t a3; uint32_t t0; uint32_t t1; uint32_t t2; /* Kernel scratch registers. $at is the assembler-temp that hand-written * kernel asm uses to stash a base across a load-delay slot, and $k0/$k1 * are the exception handlers' only free registers — i.e. exactly the state * that matters when debugging BIOS exception/handler code, which is what * this interpreter mostly runs. */ uint32_t at; uint32_t k0; uint32_t k1; uint32_t current_tcb; uint32_t task_ptr; uint32_t task_mode; uint32_t task_submode; uint32_t frame; uint8_t transferred; uint8_t pad[3]; } DirtyRamInsnLogEntry; extern DirtyRamInsnLogEntry g_dirty_ram_insn_log[DIRTY_RAM_INSN_LOG_CAP]; extern uint64_t g_dirty_ram_insn_log_seq; #ifdef __cplusplus } #endif #endif /* PSXRECOMP_DIRTY_RAM_INTERP_H */