#include "overlay_api.h" #include "ws_cull_edge.h" /* psx_ws_cull_bgez / psx_ws_clip_edge_x math */ /* ---- Overlay dispatch shim (inserted by compile_overlays.py) ----------- */ static OverlayCallbacks g_cbs; static uint32_t s_pending_cycles; PSX_OVERLAY_EXPORT void overlay_flush_cycles(void) { uint32_t cycles = s_pending_cycles; s_pending_cycles = 0; if (cycles && g_cbs.advance_cycles) g_cbs.advance_cycles(cycles); } /* Call-contract state (ABI v2): generated code reads/bumps the runtime's * bail state through these pointers (cpu_state.h PSX_OVERLAY_DLL_BUILD * branch). They start at local dummies so a pre-init call can't fault. */ static int s_bail_dummy; static uint64_t s_ctr_dummy0, s_ctr_dummy1; int *g_psx_call_bail_p = &s_bail_dummy; uint64_t *g_psx_bail_first_p = &s_ctr_dummy0; uint64_t *g_psx_bail_resolved_p = &s_ctr_dummy1; /* g_debug_last_store_pc (ABI v24): cpu_state.h maps the emitter's writes to * *g_psx_last_store_pc_p. The runtime reads the value in every build (memory.c * store filters, write attribution), so overlay_init points this at the host's * copy; the local one only catches a store made before init or on a NULL host. */ static uint32_t s_last_store_pc_local; uint32_t *g_psx_last_store_pc_p = &s_last_store_pc_local; PSX_OVERLAY_EXPORT int overlay_abi(void) { return PSX_OVERLAY_ABI_VERSION | (PSX_OVERLAY_FLAVOR << 16); } PSX_OVERLAY_EXPORT void overlay_init(const OverlayCallbacks *cbs) { g_cbs = *cbs; if (g_cbs.call_bail_flag) g_psx_call_bail_p = g_cbs.call_bail_flag; if (g_cbs.bail_first) g_psx_bail_first_p = g_cbs.bail_first; if (g_cbs.bail_resolved) g_psx_bail_resolved_p = g_cbs.bail_resolved; g_psx_last_store_pc_p = g_cbs.last_store_pc ? g_cbs.last_store_pc : &s_last_store_pc_local; } void psx_dispatch_call(CPUState *cpu, uint32_t addr, uint32_t ra) { overlay_flush_cycles(); (void)ra; g_cbs.dispatch_call(cpu, addr, cpu->gpr[31]); } void psx_check_interrupts(CPUState *cpu) { overlay_flush_cycles(); g_cbs.check_interrupts(cpu); } void psx_check_interrupts_at(CPUState *cpu, uint32_t resume_pc) { overlay_flush_cycles(); if (g_cbs.check_interrupts_at) g_cbs.check_interrupts_at(cpu, resume_pc); else g_cbs.check_interrupts(cpu); } void psx_advance_cycles(uint32_t cycles) { if (cycles > UINT32_MAX - s_pending_cycles) overlay_flush_cycles(); s_pending_cycles += cycles; } void gte_execute(CPUState *cpu, uint32_t cmd) { overlay_flush_cycles(); g_cbs.gte_execute(cpu, cmd); } int psx_syscall(CPUState *cpu, uint32_t code) { overlay_flush_cycles(); return g_cbs.psx_syscall(cpu, code); } void psx_native_bad_entry(CPUState *cpu, uint32_t owner, uint32_t pc) { overlay_flush_cycles(); if (g_cbs.psx_native_bad_entry) g_cbs.psx_native_bad_entry(cpu, owner, pc); } void psx_unknown_dispatch(CPUState *cpu, uint32_t addr, uint32_t phys) { overlay_flush_cycles(); g_cbs.psx_unknown_dispatch(cpu, addr, phys); } void debug_server_log_call_entry(uint32_t func_addr) { if (g_cbs.log_call_entry) g_cbs.log_call_entry(func_addr); } 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(); } /* 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 * v9 DLL stays safe on a host that predates a given callback. */ uint32_t psx_cyc_load_word(CPUState *cpu, uint32_t addr, uint32_t rt, uint32_t reg_mask) { overlay_flush_cycles(); return g_cbs.cyc_load_word ? g_cbs.cyc_load_word(cpu, addr, rt, reg_mask) : cpu->read_word(addr); } uint16_t psx_cyc_load_half(CPUState *cpu, uint32_t addr, uint32_t rt, uint32_t reg_mask) { overlay_flush_cycles(); return g_cbs.cyc_load_half ? g_cbs.cyc_load_half(cpu, addr, rt, reg_mask) : cpu->read_half(addr); } uint8_t psx_cyc_load_byte(CPUState *cpu, uint32_t addr, uint32_t rt, uint32_t reg_mask) { overlay_flush_cycles(); return g_cbs.cyc_load_byte ? g_cbs.cyc_load_byte(cpu, addr, rt, reg_mask) : cpu->read_byte(addr); } uint32_t psx_cyc_lwc2_read(CPUState *cpu, uint32_t addr) { overlay_flush_cycles(); return g_cbs.cyc_lwc2_read ? g_cbs.cyc_lwc2_read(cpu, addr) : cpu->read_word(addr); } void psx_icache_fetch(CPUState *cpu, uint32_t addr) { overlay_flush_cycles(); if (g_cbs.icache_fetch) g_cbs.icache_fetch(cpu, addr); } void psx_muldiv_set(CPUState *cpu, uint32_t latency) { overlay_flush_cycles(); if (g_cbs.muldiv_set) g_cbs.muldiv_set(cpu, latency); } void psx_muldiv_stall(CPUState *cpu) { overlay_flush_cycles(); if (g_cbs.muldiv_stall) g_cbs.muldiv_stall(cpu); } uint32_t psx_mult_latency_s(uint32_t rs) { return g_cbs.mult_latency_s ? g_cbs.mult_latency_s(rs) : 0u; } uint32_t psx_mult_latency_u(uint32_t rs) { return g_cbs.mult_latency_u ? g_cbs.mult_latency_u(rs) : 0u; } void psx_gte_stall(CPUState *cpu) { overlay_flush_cycles(); if (g_cbs.gte_stall) g_cbs.gte_stall(cpu); } void psx_gte_read(CPUState *cpu, uint32_t rt) { overlay_flush_cycles(); if (g_cbs.gte_read) g_cbs.gte_read(cpu, rt); } int psx_slice_block(CPUState *cpu, uint32_t block_addr, uint32_t bcyc, int side_effects) { overlay_flush_cycles(); return g_cbs.slice_block ? g_cbs.slice_block(cpu, block_addr, bcyc, side_effects) : 0; } /* GTE special-register accessors (ABI v10). The emitter routes masked, * sign-extended, aliased/FIFO, read-only, precision-sensitive, flag/IR, and * derived registers through these as DIRECT calls, so any * GTE-heavy overlay (Tomba2 lava / Trolley attract demos) fails to link * without them — undefined gte_read_ctrl was the signature that silently * blocked all new overlay coverage. GTE state is SHARED runtime state; a * local fallback would fork that timeline, so these forward * unconditionally (ABI v10 hosts always supply them; the v10 gate rejects * DLL/host mixes that don't). * Proven bit-transparent registers retain direct CPUState-array fast paths; * the shared emitter classification header is the source of truth. */ uint32_t gte_read_data(CPUState *cpu, uint8_t reg) { overlay_flush_cycles(); return g_cbs.gte_read_data(cpu, reg); } uint32_t gte_read_ctrl(CPUState *cpu, uint8_t reg) { overlay_flush_cycles(); return g_cbs.gte_read_ctrl(cpu, reg); } void gte_write_data(CPUState *cpu, uint8_t reg, uint32_t value) { overlay_flush_cycles(); g_cbs.gte_write_data(cpu, reg, value); } void gte_write_ctrl(CPUState *cpu, uint8_t reg, uint32_t value) { overlay_flush_cycles(); g_cbs.gte_write_ctrl(cpu, reg, value); } /* Widescreen hooks (ABI v3): forward to the runtime's live widescreen state. * Identity fallback if the host predates the callbacks (NULL) — so a v3 DLL * stays correct (4:3) on an older host. */ int psx_ws_backdrop_x(int x) { return g_cbs.ws_backdrop_x ? g_cbs.ws_backdrop_x(x) : (int)(short)x; } int psx_ws_x_margin(void) { return g_cbs.ws_x_margin ? g_cbs.ws_x_margin() : 0; } /* Shared render-funnel screen-X cull widening ([widescreen.cull] auto_screen_x). * The recompiler emits `rt = psx_ws_cull_sltiu(sx, imm)` at every width-compare * cull site, INCLUDING in overlay-resident code, so the overlay DLL must define * it or it fails to link (undefined reference) -> no native shard -> that overlay * variant runs interpreted forever (the dwarf-village-style lag). Self-contained: * depends only on psx_ws_x_margin() above. Identity at 4:3 (margin 0). MUST stay * byte-for-byte identical to gpu.c psx_ws_cull_sltiu. */ int psx_ws_cull_sltiu(uint32_t sx, uint32_t imm) { int m = psx_ws_x_margin(); return ((uint32_t)((int32_t)(int16_t)(uint16_t)sx + m) < (uint32_t)((int32_t)imm + 2 * m)) ? 1 : 0; } /* Signed-funnel variants (min/max + center±halfwidth idioms). Same contract: * self-contained, identity at margin 0, MUST stay byte-for-byte identical to * the gpu.c implementations. */ int psx_ws_cull_slti(uint32_t sx, uint32_t imm) { return ((int32_t)sx < (int32_t)imm + psx_ws_x_margin()) ? 1 : 0; } int psx_ws_cull_slti_lower(uint32_t sx, uint32_t imm) { int32_t bound = (int32_t)(int16_t)(uint16_t)imm; return ((int32_t)sx < bound - psx_ws_x_margin()) ? 1 : 0; } int psx_ws_cull_bltz(uint32_t v) { return ((int32_t)v < -psx_ws_x_margin()) ? 1 : 0; } /* [widescreen.cull] bgez_sites and clip_edge_x_load_sites. The emitter writes * these at a listed address whose instruction matches in overlay code too, so * the shard must define them. Same math as gpu.c: both call the pure helpers * in ws_cull_edge.h (stdint-only), over psx_ws_x_margin() above. */ int psx_ws_cull_bgez(uint32_t v) { return psx_ws_cull_bgez_value((int32_t)v, psx_ws_x_margin()); } uint32_t psx_ws_clip_edge_x(uint32_t v, uint32_t w) { return psx_ws_clip_edge_x_value(v, w, psx_ws_x_margin()); } /* [widescreen.cull] screen_x_sites on `lui` and `bltz` (a screen X in the high * half of a register). Emitted in overlay code at a listed address whose * instruction matches, so the shard must define them. Same math as gpu.c. */ uint32_t psx_ws_cull_lui_hi(uint32_t imm) { return psx_ws_cull_lui_hi_value(imm, psx_ws_x_margin()); } int psx_ws_cull_bltz_hi(uint32_t v) { return psx_ws_cull_bltz_hi_value(v, psx_ws_x_margin()); } int psx_ws_cull_vxrange(uint32_t x, uint32_t imm) { int32_t margin = psx_ws_x_margin(); uint32_t bound = (uint32_t)(int32_t)(int16_t)(uint16_t)imm; return (((x + (uint32_t)margin) & 0xFFFFu) < (bound + 2u * (uint32_t)margin)) ? 1 : 0; } void psx_ws_sprite_tag(CPUState *cpu) { if (g_cbs.ws_sprite_tag) g_cbs.ws_sprite_tag(cpu); } /* Widescreen backdrop column-preload value (ABI v4). Identity fallback if the * host predates the callback, so the DLL stays correct (4:3) on an older host. */ uint32_t psx_ws_backdrop_value(uint32_t orig, int is_end, int window_cols) { return g_cbs.ws_backdrop_value ? g_cbs.ws_backdrop_value(orig, is_end, window_cols) : orig; } /* Typed native-wide signed player-X bound (ABI v13). The emitter rewrites a * configured [widescreen] signed_x_bound LUI site into * psx_ws_player_x_bound(vanilla) (Einhander player bounds), including in * overlay-resident code, so the DLL must define it or the shard fails to link * (undefined reference — the rfe/gte_read_ctrl class). Computes against the * host's live widescreen state; identity fallback (the vanilla constant, i.e. * 4:3 behavior) if the host predates the callback. */ int32_t psx_ws_player_x_bound(int32_t vanilla) { return g_cbs.ws_player_x_bound ? g_cbs.ws_player_x_bound(vanilla) : vanilla; } int32_t psx_ws_screen_x_bound(int32_t vanilla) { return g_cbs.ws_screen_x_bound ? g_cbs.ws_screen_x_bound(vanilla) : vanilla; } void psx_mod_instruction(CPUState *cpu, uint32_t address, uint32_t word) { overlay_flush_cycles(); if (g_cbs.mod_instruction) g_cbs.mod_instruction(cpu, address, word); } int psx_mod_function_entry(CPUState *cpu, uint32_t address) { overlay_flush_cycles(); return g_cbs.mod_function_entry ? g_cbs.mod_function_entry(cpu, address) : 0; } int32_t psx_ws_depth_bound(int32_t imm) { return g_cbs.ws_depth_bound ? g_cbs.ws_depth_bound(imm) : imm; } int32_t psx_ws_plane_nx(int32_t nx) { return g_cbs.ws_plane_nx ? g_cbs.ws_plane_nx(nx) : nx; } uint32_t psx_ws_xclip_bound(uint32_t vanilla) { return g_cbs.ws_xclip_bound ? g_cbs.ws_xclip_bound(vanilla) : vanilla; } uint32_t psx_ws_cull_keep_result(uint32_t vanilla, uint32_t forced) { return g_cbs.ws_cull_keep_result ? g_cbs.ws_cull_keep_result(vanilla, forced) : vanilla; } int psx_ws_masked_reject(uint32_t flags, uint32_t mask) { return g_cbs.ws_masked_reject ? g_cbs.ws_masked_reject(flags, mask) : flags != 0u; } int psx_ws_nclip_branch(uint32_t pc, uint32_t instr, int32_t mac0, int vanilla) { return g_cbs.ws_nclip_branch ? g_cbs.ws_nclip_branch(pc, instr, mac0, vanilla) : vanilla; } uint32_t psx_ws_aspect_cone_result(uint32_t site, uint32_t vanilla, uint32_t object, int32_t x, int32_t z, int32_t y) { return g_cbs.ws_aspect_cone_result ? g_cbs.ws_aspect_cone_result(site, vanilla, object, x, z, y) : vanilla; } uint32_t psx_ws_angle_widen(uint32_t vanilla) { return g_cbs.ws_angle_widen ? g_cbs.ws_angle_widen(vanilla) : vanilla; } int32_t psx_ws_cull_scale(int32_t bound, int32_t half_extent) { return g_cbs.ws_cull_scale ? g_cbs.ws_cull_scale(bound, half_extent) : bound; } int32_t gte_nclip_exact_sign(int32_t native_mac0, uint32_t pc) { return g_cbs.nclip_exact_sign ? g_cbs.nclip_exact_sign(native_mac0, pc) : (native_mac0 < 0 ? -1 : (native_mac0 > 0 ? 1 : 0)); } /* Sub-pixel GTE precision-store tracker (ABI v14). The emitter emits a direct * gte_precision_store_word() call for every swc2 (GTE store-word), including in * overlay-resident code, so the DLL must define it or the shard fails to link * (undefined reference — the rfe/gte_read_ctrl class). Forwards to the host's * stateful tracker (gte.cpp); no-op if the host predates the callback * (precision tracking is a widescreen enhancement, off by default — 4:3 * behavior is unaffected). */ void gte_precision_store_word(uint32_t addr, uint8_t reg) { if (g_cbs.gte_precision_store_word) g_cbs.gte_precision_store_word(addr, reg); } #if defined(PSX_PGXP) && PSX_PGXP /* PGXP dataflow-shadowing hooks (pgxp_hooks.h; docs/ENHANCEMENTS.md G1.10). Only a * pgxp-flavour shard (compiled -DPSX_PGXP=1, flavor bit set) references these * — base-flavour shards preprocess the PGXP_*() macros away, so this block is * compiled out of them entirely. Forwards to the host engine through the * appended-last table; every hook no-ops on a host that predates it * (precision shadowing is visual-only, never load-bearing). */ void psx_pgxp_load(struct CPUState *cpu, uint32_t instr, uint32_t addr, uint32_t value) { if (g_cbs.pgxp && g_cbs.pgxp->load) g_cbs.pgxp->load(cpu, instr, addr, value); } void psx_pgxp_store(struct CPUState *cpu, uint32_t instr, uint32_t addr, uint32_t value) { if (g_cbs.pgxp && g_cbs.pgxp->store) g_cbs.pgxp->store(cpu, instr, addr, value); } void psx_pgxp_alu(struct CPUState *cpu, uint32_t instr, uint32_t result, uint32_t s1, uint32_t s2) { if (g_cbs.pgxp && g_cbs.pgxp->alu) g_cbs.pgxp->alu(cpu, instr, result, s1, s2); } void psx_pgxp_muldiv(struct CPUState *cpu, uint32_t instr, uint32_t hi, uint32_t lo, uint32_t s1, uint32_t s2) { if (g_cbs.pgxp && g_cbs.pgxp->muldiv) g_cbs.pgxp->muldiv(cpu, instr, hi, lo, s1, s2); } void psx_pgxp_cop2(struct CPUState *cpu, uint32_t instr, uint32_t value, uint32_t addr) { if (g_cbs.pgxp && g_cbs.pgxp->cop2) g_cbs.pgxp->cop2(cpu, instr, value, addr); } #endif /* PSX_PGXP */ /* ----------------------------------------------------------------------- */