Merge pull request #541 from RetroPortingToolKit/feat/mod-uncharged-guest-call

Add uncharged guest calls: run a guest function in zero guest time
This commit is contained in:
Matt Stanley
2026-10-06 11:13:08 -07:00
committed by GitHub
12 changed files with 331 additions and 39 deletions
+17
View File
@@ -144,6 +144,23 @@ void psx_mod_write_code_word(uint32_t address, uint32_t value);
uint32_t psx_mod_call_guest(struct CPUState* cpu, uint32_t function,
uint32_t return_address, uint32_t a0, uint32_t a1,
uint32_t a2, uint32_t a3);
/* psx_mod_call_guest in zero guest time, for CPU/RAM work a game spends guest
* time on while it loads (allocators, decoders, table builds). Every RAM,
* register and device effect of the callee is kept, as if it executed in an
* instant at the call's guest cycle: none of its cycles are charged, no
* device advances and no interrupt is taken while it runs, and the caller's
* pending mult/div, GTE and load-pipeline timing is as it was before the
* call. Device work the callee starts (a DMA, a CD command) proceeds in guest
* time after the return. A callee that executes more than budget_cycles
* (0 = no limit) is charged in full from then on, so one that waits on a
* device still completes; *charged (optional) is then set to 1. Inside a
* render pass or another uncharged call, time is already frozen and this is
* psx_mod_call_guest. The outcome depends only on guest state, so netplay
* peers and rollback re-simulation agree. Returns v0. */
uint32_t psx_mod_call_guest_uncharged(struct CPUState* cpu, uint32_t function,
uint32_t return_address, uint32_t a0,
uint32_t a1, uint32_t a2, uint32_t a3,
uint32_t budget_cycles, int* charged);
/* Deliver disc sectors into RAM exactly as a completed CD-ROM DMA would
* (overlay capture, executable-page invalidation, CD DMA log when lba >= 0).
* Word-aligned address and length. Returns 0 when the span leaves RAM. */
+18 -1
View File
@@ -1,7 +1,8 @@
#ifndef PSX_CYCLE_FREEZE_H
#define PSX_CYCLE_FREEZE_H
/* Render-pass time freeze (render_pass.c, docs/RENDER_PASSES.md).
/* Guest-time freeze: render passes (render_pass.c, docs/RENDER_PASSES.md)
* and uncharged guest calls (mod_plugins.h psx_mod_call_guest_uncharged).
*
* Runtime-only: generated and overlay code never sees this header. It is kept
* out of psx_cycles.h on purpose, because psx_cycles.h is part of the
@@ -24,6 +25,10 @@ extern "C" {
#endif
extern int g_psx_render_pass_active;
/* Set while guest time is frozen, by a render pass or an uncharged call:
* devices are not serviced and no interrupt is delivered. Pass-only effects
* (rolled-back stores, synchronous DMA) key on g_psx_render_pass_active. */
extern int g_psx_guest_time_frozen;
typedef struct PsxCycleFreeze {
uint64_t cycle_count;
uint64_t next_service;
@@ -42,6 +47,18 @@ int psx_cycle_freeze_begin(PsxCycleFreeze *save, uint64_t watchdog_cycles,
void (*overrun)(void));
void psx_cycle_freeze_end(const PsxCycleFreeze *save);
/* Uncharged span: the same freeze without the pass's rollback. Code run
* between begin and end keeps every effect on RAM, registers and devices, as
* if it executed in an instant at the start cycle: end() puts the clock back,
* so none of its cycles are charged, no device advanced and no interrupt was
* taken meanwhile. A span that executes more than `budget_cycles` (0 = no
* limit) thaws: from then on it runs on the live clock and all its cycles are
* charged, so a callee that waits on a device still completes. begin()
* returns 0 while time is already frozen (the caller then simply runs the
* code). end() returns 1 when the span stayed uncharged. Spans do not nest. */
int psx_cycle_uncharged_begin(PsxCycleFreeze *save, uint64_t budget_cycles);
int psx_cycle_uncharged_end(const PsxCycleFreeze *save);
#ifdef __cplusplus
}
#endif