Files
psxrecomp/overlay_toolchain/include/lockstep.h
T
Matthew Stanley fe42da2744 Merge release/native-rendering-20261003 into master
Keeps both lines: master's PGXP session, draw-distance clamps, masked rejects
and saturated-winding NCLIP; the release's render-pass kit additions, verified
media/disc extents, timeline/present rings, cull-scale sites. Overlay ABI v30.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 19:27:02 -07:00

88 lines
4.1 KiB
C

/* lockstep.h — #2 lockstep "unit-test interp" comparator.
*
* Per-basic-block differential check: the compiled block runs (RECORD mode,
* its memory ops captured), then the SAME block is re-interpreted from the
* same entry state (REPLAY mode, memory ops fed from the recorded trace so the
* replay is side-effect-free and MMIO-safe). If the interp replay reads a
* different address, writes a different value, or ends with different
* registers, that block's compiled output is wrong -> the exact codegen defect.
* If NO block ever diverges, the regression is timing/IRQ-phase, not codegen.
*
* Dev tooling only: the per-block hook is debug_server_cyc_observe, which is
* PSX_NO_DEBUG_TOOLS-stripped in release builds. Window-gated (frame range) so
* it's off (full speed) outside the region of interest.
*/
#pragma once
#include <stdint.h>
#include "cpu_state.h"
#ifdef __cplusplus
extern "C" {
#endif
/* 0=off, 1=record (compiled block running), 2=replay (interp re-run). */
extern int g_ls_mode;
/* 1 while replaying: cycle/icache accounting no-ops so the replay never
* perturbs real global timing state. */
extern int g_ls_replay_active;
/* Side-effect-free whole-call trace used by the overlay differential gate.
* RECORD executes the interpreter authoritatively and records every memory
* operation. REPLAY feeds the recorded reads to native code and verifies
* writes without touching RAM, scratchpad, or MMIO a second time. These calls
* are deliberately single-owner/non-nestable; a false return fails the
* comparison closed. */
int ls_shadow_record_begin(void);
int ls_shadow_record_end(uint32_t *ops, int *saw_exception);
int ls_shadow_replay_begin(void);
int ls_shadow_replay_end(uint32_t *ops, int *mismatch_kind,
uint32_t *pc, uint32_t *addr,
uint32_t *expected, uint32_t *actual);
void ls_shadow_abort(void);
/* A recorded effect the replay cannot reproduce fails the record closed (the
* comparison is skipped, as for a device touch). memory.c calls it for a
* cache-isolated store: its I-cache effect depends on BIU state, and the
* replay neither writes the BIU nor stores. No-op outside a shadow record. */
void ls_shadow_record_unreplayable(void);
/* Memory chokepoint hooks, called from the psx_read_ and psx_write_ funcs. */
uint32_t ls_read_hook(uint32_t addr, int size, uint32_t real_val);
void ls_write_hook(uint32_t addr, int size, uint32_t val);
/* Per-basic-block-leader entry, called from debug_server_cyc_observe. */
void ls_at_leader(uint32_t leader_phys, CPUState *cpu);
/* Dispatch-segment comparator: wraps one clean psx_dispatch_game_compiled()
* invocation and replays the same callable through exec_one until it reaches
* the same returned cpu->pc. This measures the native CPS unit, not just one
* basic block. */
void ls_func_enter(uint32_t entry_pc, CPUState *cpu);
void ls_func_exit(uint32_t entry_pc, CPUState *cpu, int handled);
void ls_func_set_window(uint32_t frame_lo, uint32_t frame_hi);
void ls_func_set_record_only(int on);
/* Called at real IRQ exception entry. Function-scope comparisons skip segments
* that delivered an interrupt, keeping codegen/tooling checks separate from
* event-phase behavior. */
void ls_note_exception_entry(void);
/* Suppress lockstep memory recording while runtime/debug observers read guest
* RAM for diagnostics. These reads are not guest instructions and must not
* become part of the compiled-vs-interp trace. */
void ls_suppress_begin(void);
void ls_suppress_end(void);
/* Arm the comparator over a guest-frame window [lo,hi]. hi==0 => disabled. */
void ls_set_window(uint32_t frame_lo, uint32_t frame_hi);
/* Diagnostic: record blocks but skip the inline replay (to test whether the
* inline replay, not recording, is what perturbs the run). */
void ls_set_record_only(int on);
/* Drain the first-divergence record as JSON into buf. Returns bytes written. */
int ls_get_diverge_json(char *buf, int buflen);
int ls_get_func_json(char *buf, int buflen);
#ifdef __cplusplus
}
#endif