diff --git a/runtime/include/overlay_api.h b/runtime/include/overlay_api.h index 2ceda151..28a9f488 100644 --- a/runtime/include/overlay_api.h +++ b/runtime/include/overlay_api.h @@ -45,6 +45,18 @@ * masked, aliased, read-only, precision-sensitive, flag/IR, and derived * GTE regs; GTE-heavy overlays failed to LINK without shims (undefined * gte_read_ctrl), silently blocking new coverage. */ +/* v22: game_text_native_ok callback - the emitter emits a direct + * psx_game_text_native_ok() stale-static guard (code_generator.cpp, at + * block entry and before every direct jal). The function is DEFINED IN THE + * GENERATED DISPATCH SHARD, which lives in the executable, so an overlay + * DLL cannot see it: 107 of 109 shard failures on the 2026-09-02 Tomba 2 + * framework bump were `undefined reference to psx_game_text_native_ok`. + * Exactly the rfe / gte_read_ctrl / gte_precision_store_word class again - + * an emitter gained a runtime call without the overlay ABI gaining the + * shim, and the only symptom is shards that silently fail to build so the + * code runs interpreted forever. NULL-guarded: a host predating v22 makes + * the shim return 0, which routes the guest through the dispatcher instead + * of native code - slower, never wrong. */ /* 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. */ @@ -93,7 +105,7 @@ * host/DLL flavor mix — base-flavour DLLs and hosts are untouched, so * the version stays. The emit-content change (PGXP_*() macros in all * generated C) is covered by the codegen hash + CODEGEN_VER below. */ -#define PSX_OVERLAY_ABI_VERSION 21 +#define PSX_OVERLAY_ABI_VERSION 22 /* Process-lifetime overlay candidate capacity. Every accepted manifest F * record consumes one slot, even when another DLL carries an identical @@ -309,6 +321,12 @@ typedef struct { /* Aspect-scaled terrain-frustum angle helper (ABI v21). */ uint32_t (*ws_angle_widen)(uint32_t vanilla); + /* Stale-static guard (ABI v22; see the version-history note above). + * Defined by the generated dispatch shard in the executable, so overlay + * DLLs must forward. Appended last; may be NULL on a host that predates + * it - the shim then returns 0 and the guest takes the dispatcher path. */ + int (*game_text_native_ok)(uint32_t addr); + /* PGXP dataflow-shadowing hook table (pgxp_hooks.h). Only pgxp-flavour * shards (PSX_OVERLAY_FLAVOR pgxp bit, compiled with -DPSX_PGXP=1) emit * calls to psx_pgxp_*; their preamble forwards through this table. Base- diff --git a/runtime/include/overlay_dispatch_preamble.c.inc b/runtime/include/overlay_dispatch_preamble.c.inc index ac0e9fc5..fefb8165 100644 --- a/runtime/include/overlay_dispatch_preamble.c.inc +++ b/runtime/include/overlay_dispatch_preamble.c.inc @@ -93,6 +93,16 @@ void psx_rfe_mark_escape(void) { overlay_flush_cycles(); g_cbs.rfe_mark_escape(); } +/* Stale-static guard (ABI v22). The emitter emits this at block entry and + * before every direct jal, but it is DEFINED IN THE GENERATED DISPATCH SHARD + * (executable side), so an overlay DLL cannot resolve it without this shim. + * Its absence is what made 107 shards fail to link on the 2026-09-02 bump. + * NULL-guarded: returning 0 sends the guest down the dispatcher path, which is + * slower but never wrong -- the opposite choice (returning 1) would let an + * overlay run native code the guard was trying to reject. */ +int psx_game_text_native_ok(uint32_t addr) { + return g_cbs.game_text_native_ok ? g_cbs.game_text_native_ok(addr) : 0; +} /* 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 diff --git a/runtime/src/overlay_loader.c b/runtime/src/overlay_loader.c index 0bd26ccc..a2b32480 100644 --- a/runtime/src/overlay_loader.c +++ b/runtime/src/overlay_loader.c @@ -2260,6 +2260,12 @@ static void init_callbacks(void) { s_callbacks.psx_restore_state_escape = psx_restore_state_escape; /* Return-from-exception mark (ABI v12): overlay `rfe` ops forward here. */ s_callbacks.rfe_mark_escape = psx_rfe_mark_escape; + /* Stale-static guard (ABI v22): defined by the generated dispatch shard in + * this executable, so overlay DLLs forward here rather than link it. */ + { + extern int psx_game_text_native_ok(uint32_t addr); + s_callbacks.game_text_native_ok = psx_game_text_native_ok; + } /* Call-contract state (ABI v2): DLL code shares the runtime's bail * flag and counters through these pointers. */ s_callbacks.call_bail_flag = &g_psx_call_bail;