Files
psxrecomp/runtime/include/psx_cycles.h
T
2026-08-06 00:52:14 -04:00

196 lines
7.2 KiB
C

#ifndef PSXRECOMP_PSX_CYCLES_H
#define PSXRECOMP_PSX_CYCLES_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* PSX guest CPU cycle clock — the single source of truth for guest-
* visible time. Peripherals derive all schedules from this counter. */
extern uint64_t psx_cycle_count;
/* Deadline-device model bookkeeping (written by psx_cycles.c). Hot path
* reads these from the inlined psx_advance_cycles below. */
extern uint64_t psx_next_service_cycle; /* absolute; 0 = dirty / recompute */
extern int psx_in_device_service; /* re-entrancy guard */
extern int g_event_step_conservative;
/* Diagnostic replay clock: advance the CPU-visible guest clock without
* servicing devices, then restore the authoritative live clock afterward.
* Used only while g_ls_replay_active is set. */
int psx_cycle_replay_begin(uint64_t start_cycle);
uint64_t psx_cycle_replay_end(void);
/* Transactional I-cache view for the overlay differential harness. The
* interpreter keeps the authoritative post-call cache; native replay starts
* from the saved entry tags, mutates a temporary view, then restores live. */
int psx_icache_shadow_record_begin(void);
int psx_icache_shadow_replay_begin(void);
void psx_icache_shadow_replay_end(void);
void psx_icache_shadow_abort(void);
/* Event-deadline device model: catch every device up to the charged
* guest-cycle position and force a deadline recompute. memory.c calls
* this at the top of every device-MMIO read/write. */
void psx_devices_mmio_sync(void);
void psx_devices_service_to_now(void);
/* Rare/slow advance path (COSIM, conservative 1-cycle stepping, lockstep). */
void psx_advance_cycles_slow(uint32_t cycles);
/* Sparse throttle fires (watchdog / PC sample) — not on the per-charge path. */
extern uint32_t psx_watchdog_throttle;
extern uint32_t psx_pc_sample_throttle;
void psx_cycles_watchdog_fire(void);
void psx_cycles_pc_sample_fire(void);
/* Lockstep replay flag (defined in dirty_ram_interp.c). */
extern int g_ls_replay_active;
/* Deferred under-deadline charges (MotK VLC load-charge batching).
* psx_cyc_charge accumulates here; publish via psx_cyc_batch_flush /
* psx_advance_cycles before IRQ checks, MMIO, or any cycle read that must
* match the published counter. Guest totals at those barriers are unchanged. */
extern uint32_t g_psx_cyc_batch;
extern uint32_t g_psx_cyc_batch_limit;
/* GCC/Clang-generated functions can defer deadline probes within a basic
* block. Interrupt/MMIO edges still publish the accumulated guest cycles. */
extern int g_psx_cyc_bb_defer;
/* Emitter-level VLC load-charge batching: when non-NULL, psx_cyc_charge
* accumulates into *g_psx_cyc_local_acc instead of g_psx_cyc_batch. Publish
* via psx_cyc_local_publish / psx_cyc_batch_flush before IRQ/MMIO barriers. */
extern uint32_t *g_psx_cyc_local_acc;
/* Advance guest time. Overlay DLLs forward this through their callback shim;
* normal runtime/generated code keeps the common production path inlined. */
#if defined(PSX_OVERLAY_DLL_BUILD)
void psx_advance_cycles(uint32_t cycles);
#else
/* The common production path is inlined: bump the
* counter and only service devices when the next event deadline is due.
* Guest-visible timing is unchanged (service_to_now replays exact events).
*
* Watchdog / PC-sample throttles live in psx_devices_service_to_now (fired
* on the HARD_CAP / event cadence, ≥ every 16K guest cycles) — not on every
* per-instruction charge. MotK VLC issues millions of advances/s; two add+
* branch pairs there were pure host tax. */
static inline void psx_advance_cycles(uint32_t cycles) {
#if !defined(PSX_COSIM)
if (g_psx_cyc_batch) {
uint32_t b = g_psx_cyc_batch;
g_psx_cyc_batch = 0;
g_psx_cyc_batch_limit = 0;
if (cycles <= UINT32_MAX - b) cycles += b;
else {
/* Extreme: publish b first, then continue with cycles. */
psx_cycle_count += (uint64_t)b;
if (!psx_in_device_service &&
(psx_next_service_cycle == 0u ||
psx_cycle_count >= psx_next_service_cycle)) {
psx_devices_service_to_now();
}
}
}
#endif
if (cycles == 0u) return;
#if defined(PSX_COSIM)
psx_advance_cycles_slow(cycles);
return;
#else
#if defined(__GNUC__) || defined(__clang__)
if (__builtin_expect(g_ls_replay_active | g_event_step_conservative, 0)) {
#else
if (g_ls_replay_active || g_event_step_conservative) {
#endif
psx_advance_cycles_slow(cycles);
return;
}
if (psx_in_device_service) {
psx_cycle_count += (uint64_t)cycles;
return;
}
psx_cycle_count += (uint64_t)cycles;
if (psx_next_service_cycle == 0u ||
psx_cycle_count >= psx_next_service_cycle) {
psx_devices_service_to_now();
}
#endif
}
#endif
/* Publish deferred charges (IRQ edge / MMIO / savestate). Overlay DLLs keep
* their pending total in the callback shim rather than these host globals. */
#if defined(PSX_OVERLAY_DLL_BUILD)
void overlay_flush_cycles(void);
static inline void psx_cyc_local_publish(void) { }
static inline void psx_cyc_batch_flush(void) { overlay_flush_cycles(); }
#else
/* Publish function-local charges into the normal batch/advance path while
* keeping the local pointer installed (nested charges resume locally). */
static inline void psx_cyc_local_publish(void) {
#if !defined(PSX_COSIM)
uint32_t *acc = g_psx_cyc_local_acc;
if (!acc) return;
uint32_t v = *acc;
if (!v) return;
*acc = 0;
g_psx_cyc_local_acc = NULL;
psx_advance_cycles(v);
g_psx_cyc_local_acc = acc;
#endif
}
static inline void psx_cyc_batch_flush(void) {
#if !defined(PSX_COSIM)
psx_cyc_local_publish();
uint32_t b = g_psx_cyc_batch;
if (!b) return;
g_psx_cyc_batch = 0;
g_psx_cyc_batch_limit = 0;
psx_advance_cycles(b);
#endif
}
#endif
/* Read accessor for telemetry (includes deferred batch). */
uint64_t psx_get_cycle_count(void);
/* Idle-loop cycle skip (see psx_cycles.c "Idle-loop cycle skip"). */
struct CPUState;
void psx_idle_note_check(struct CPUState *cpu, uint32_t check_pc);
int psx_idle_skip_is_enabled(void);
extern int g_idle_skip_enabled;
extern uint64_t g_idle_skip_count;
extern uint64_t g_idle_skip_cycles;
extern uint32_t g_idle_skip_last_pc;
extern uint32_t g_idle_skip_last_quantum;
/* Post-load probe cycle diagnostics (optional; main.cpp soft-load tooling). */
extern int g_plp_cycle_diag;
extern uint64_t g_plp_adv_calls;
extern uint32_t g_plp_adv_max_chunk;
extern uint64_t g_plp_adv_sum;
extern uint64_t g_plp_svc_calls;
/* Save-state restore: re-anchor the deadline device model after psx_cycle_count
* is overwritten from a snapshot. Pass the live CPU so GTE/muldiv completion
* stamps and load-absorb give-back are rewound with the guest clock. */
void psx_cycles_resync_after_restore(struct CPUState *cpu);
/* Soft rematch / session_reboot: zero the guest clock and deadline bookkeeping.
* Soft-exit longjmps out of vblank (inside psx_devices_service_to_now) leave
* psx_in_device_service stuck at 1 and a huge leftover cycle count — without
* this reset the next match never services devices or fires vblanks. */
void psx_cycles_reset_for_boot(void);
#ifdef __cplusplus
}
#endif
#endif /* PSXRECOMP_PSX_CYCLES_H */