overlay ABI v12: forward psx_rfe_mark_escape into overlay DLLs

The emitter emits psx_rfe_mark_escape() at every rfe — including in
overlay-resident exception-context code — but the dispatch preamble
never provided it, so any rfe-containing overlay failed to LINK
(undefined reference) and ran interpreted forever: the gte_read_ctrl
silent-coverage-blocker class again (11 of Tomba's vault shards hit
it). It mutates shared RFE-pending escape state (interrupts.c), so the
shim forwards unconditionally through OverlayCallbacks; ABI bump to 12
rejects pre-shim caches.

Found live during LOAD_TIME_ZERO E0 bring-up: the user's first pig
load triggered autocapture and the compile died on this link error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley
2026-07-14 10:35:39 -07:00
parent d34f4aabcc
commit 95f9a46b7d
3 changed files with 28 additions and 1 deletions
+12 -1
View File
@@ -46,7 +46,14 @@
/* v11 batches per-instruction cycle charges inside overlay DLLs and flushes at
* block/device/store boundaries, removing a cross-DLL callback per instruction
* without changing the shared guest-cycle timeline. */
#define PSX_OVERLAY_ABI_VERSION 11
/* v12: rfe_mark_escape callback — the emitter emits psx_rfe_mark_escape() at
* every `rfe` (code_generator.cpp COP0 case), including in overlay-resident
* exception-context code (Tomba kernel-window overlays hit this). It mutates
* the runtime's shared RFE-pending escape state (interrupts.c), so the DLL
* MUST forward — a local no-op would swallow exception returns. Without the
* shim any rfe-containing overlay fails to LINK (undefined psx_rfe_mark_escape)
* and that variant runs interpreted forever — the gte_read_ctrl class again. */
#define PSX_OVERLAY_ABI_VERSION 12
/* Codegen flavor of the recompiled output the overlays + runtime were built
* against. Overlays are keyed in the cache by guest-bytes CRC, which is
@@ -196,6 +203,10 @@ typedef struct {
uint32_t (*gte_read_ctrl)(CPUState *cpu, uint8_t reg);
void (*gte_write_data)(CPUState *cpu, uint8_t reg, uint32_t value);
void (*gte_write_ctrl)(CPUState *cpu, uint8_t reg, uint32_t value);
/* RFE escape marker (ABI v12; see the version-history note above).
* Appended last; forwards unconditionally — v12 hosts always supply it
* and the ABI gate rejects DLL/host mixes that don't. */
void (*rfe_mark_escape)(void);
} OverlayCallbacks;
#ifdef __cplusplus
+7
View File
@@ -1746,6 +1746,13 @@ static void init_callbacks(void) {
s_callbacks.gte_write_data = gte_write_data;
s_callbacks.gte_write_ctrl = gte_write_ctrl;
}
/* ABI v12: rfe escape marker — emitted at every `rfe`, including in
* overlay-resident exception-context code; mutates the runtime's
* shared RFE-pending state so it must forward. */
{
extern void psx_rfe_mark_escape(void);
s_callbacks.rfe_mark_escape = psx_rfe_mark_escape;
}
}
}
+9
View File
@@ -953,6 +953,15 @@ void psx_restore_state_escape(void) {
overlay_flush_cycles();
if (g_cbs.psx_restore_state_escape) g_cbs.psx_restore_state_escape();
}
/* RFE escape marker (ABI v12): emitted at every `rfe`, including in
* overlay-resident exception-context code. Mutates the runtime's shared
* RFE-pending state, so it MUST forward (a local no-op would swallow
* exception returns). Unconditional — v12 hosts always supply it; the
* ABI gate rejects mixes that don't. */
void psx_rfe_mark_escape(void) {
overlay_flush_cycles();
g_cbs.rfe_mark_escape();
}
/* Faithful-timing functions (ABI v9): overlay code built with PSX_ENABLE_BLOCK_CYCLES
* emits these; forward to the runtime's real impls so native overlays charge cycles on
* the SAME timeline as the interp/BIOS (a local copy would diverge). NULL-guarded so a