Files
psxrecomp/runtime/include/psx_cycle_freeze.h
Shokunin 1a8f382085 Render passes: put host nesting back after a watchdog abort
The watchdog leaves a runaway pass by longjmp from inside guest code, which
skips the exits of every frame it leaves: generated functions' bb_defer
cleanup, overlay_loader_call_native's unit depth, the native-shard stack and
cycle-flush hook, the DMA kick's exec depth, and the dirty-RAM interpreter's
active/phase/precise flags, resume latch and pending load. The checkpoint
now saves all of it and the restore puts it back (bb_defer with the cycle
freeze), as the scheduler and exception landings do for theirs. An overlay
shard's unpublished pass cycles are flushed into the frozen clock at the
landing so they never reach the live one.

PSX_RENDER_PASS_VERIFY also checks that a pass which returned normally left
the nesting balanced, since the restore would hide that. render_pass_stats
gains nesting_repairs and the fault log names what the landing undid.

render_pass_abort_test aborts a pass from five nested frames (real
render_pass.c and psx_cycles.c, mock devices and presenter) and checks every
value, the clock and RAM; it fails without this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 21:02:47 -07:00

50 lines
1.8 KiB
C

#ifndef PSX_CYCLE_FREEZE_H
#define PSX_CYCLE_FREEZE_H
/* Render-pass time freeze (render_pass.c, docs/RENDER_PASSES.md).
*
* 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
* overlay codegen hash (runtime/codegen_hash_sources.cmake) and any change to
* it invalidates every title's overlay cache and savestates. The freeze is
* enforced in psx_cycles.c's out-of-line service paths, which generated code
* already reaches through psx_cycles.h.
*
* While g_psx_render_pass_active is set, guest cycles are still counted (GTE
* and mult/div deadlines keep working) but devices are never serviced, no
* VBlank or device event fires and no interrupt is delivered; end() puts
* every clock value back exactly, so the pass consumed no guest time. A pass
* that runs longer than `watchdog_cycles` calls `overrun` (which must not
* return into the guest; render_pass.c longjmps out of the pass). */
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
extern int g_psx_render_pass_active;
typedef struct PsxCycleFreeze {
uint64_t cycle_count;
uint64_t next_service;
uint64_t fast_limit;
uint32_t batch;
uint32_t batch_limit;
uint32_t *local_acc;
uint32_t local_acc_value;
int in_device_service;
/* Generated functions bump g_psx_cyc_bb_defer on entry and drop it in a
* cleanup handler, which a longjmp out of the pass (the watchdog) skips:
* end() puts the interrupted code's depth back. */
int bb_defer;
} PsxCycleFreeze;
int psx_cycle_freeze_begin(PsxCycleFreeze *save, uint64_t watchdog_cycles,
void (*overrun)(void));
void psx_cycle_freeze_end(const PsxCycleFreeze *save);
#ifdef __cplusplus
}
#endif
#endif