From 95f9a46b7decb6dabb0948ec61eeafb867a643c4 Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Tue, 14 Jul 2026 10:35:39 -0700 Subject: [PATCH] overlay ABI v12: forward psx_rfe_mark_escape into overlay DLLs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- runtime/include/overlay_api.h | 13 ++++++++++++- runtime/src/overlay_loader.c | 7 +++++++ tools/compile_overlays.py | 9 +++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/runtime/include/overlay_api.h b/runtime/include/overlay_api.h index 92d8b60a..d779c423 100644 --- a/runtime/include/overlay_api.h +++ b/runtime/include/overlay_api.h @@ -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 diff --git a/runtime/src/overlay_loader.c b/runtime/src/overlay_loader.c index b0efd562..24d9eec8 100644 --- a/runtime/src/overlay_loader.c +++ b/runtime/src/overlay_loader.c @@ -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; + } } } diff --git a/tools/compile_overlays.py b/tools/compile_overlays.py index 30ed9c30..cc8b2659 100644 --- a/tools/compile_overlays.py +++ b/tools/compile_overlays.py @@ -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