mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
196 lines
7.2 KiB
C
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 */
|