From 709e04d228b1aa4a7e5febab985cd152e0a63da8 Mon Sep 17 00:00:00 2001 From: Shokunin <30949000+tetrisgm@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:47:05 -0700 Subject: [PATCH] Overlay shards: define psx_ws_cull_bgez and psx_ws_clip_edge_x The emitter writes both helpers at a listed bgez_sites or clip_edge_x_load_sites address whose instruction matches in overlay code too, but overlay_dispatch_preamble.c.inc did not define them, so such a shard failed to link (macOS, Windows) or to load (Linux dlopen RTLD_NOW) and its overlay stayed interpreted. The preamble now defines both over psx_ws_x_margin(), with the same pure math gpu.c uses (ws_cull_edge.h, stdint-only, staged with the other runtime headers). ws_cull_edge.h joins the shard compile surface in codegen_hash_sources.cmake. overlay_shim_compile_contract links a shim that calls both with the emitter's extern declarations, and overlay_widescreen_callbacks runs them in an executable: identity with no margin callback, widened at margin 120, interior clip edges unchanged. Both fail without the definitions. Co-Authored-By: Claude Opus 5.5 --- recompiler/tests/test_overlay_shim_compile.py | 8 ++++++++ runtime/codegen_hash_sources.cmake | 4 +++- runtime/include/overlay_dispatch_preamble.c.inc | 11 +++++++++++ tools/tests/test_overlay_widescreen_callbacks.py | 14 ++++++++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/recompiler/tests/test_overlay_shim_compile.py b/recompiler/tests/test_overlay_shim_compile.py index 03f57e85..dae099d9 100644 --- a/recompiler/tests/test_overlay_shim_compile.py +++ b/recompiler/tests/test_overlay_shim_compile.py @@ -30,6 +30,10 @@ def main() -> int: source = r'''#include "cpu_state.h" #include "overlay_dispatch_preamble.c.inc" +/* As CodeGenerator::emit_runtime_externs declares them in every shard. */ +extern int psx_ws_cull_bgez(uint32_t v); +extern uint32_t psx_ws_clip_edge_x(uint32_t v, uint32_t w); + void func_80010000(CPUState *cpu) { psx_cyc_bb_defer_begin(); psx_cyc_charge(1u); @@ -37,6 +41,10 @@ void func_80010000(CPUState *cpu) { cpu->gpr[2] = psx_cyc_load_word(cpu, cpu->gpr[4], 2u, 0u); cpu->gpr[3] = psx_cyc_load_half(cpu, cpu->gpr[5], 3u, 0u); cpu->gpr[6] = psx_ws_cull_slti_lower(cpu->gpr[7], 0xff00u); + /* [widescreen.cull] bgez_sites / clip_edge_x_load_sites: the emitter + * writes both in overlay code, so the shim must define them. */ + cpu->gpr[8] = (uint32_t)psx_ws_cull_bgez(cpu->gpr[9]); + cpu->gpr[10] = psx_ws_clip_edge_x(cpu->gpr[11], 0x140u); psx_cyc_bb_defer_flush(); psx_cyc_bb_defer_end(); if (psx_slice_block(cpu, 0x80010000u, 1u, 0)) return; diff --git a/runtime/codegen_hash_sources.cmake b/runtime/codegen_hash_sources.cmake index 61ce3601..37a9621e 100644 --- a/runtime/codegen_hash_sources.cmake +++ b/runtime/codegen_hash_sources.cmake @@ -63,10 +63,12 @@ set(PSXRECOMP_CODEGEN_HASH_SRCS # runtime (the recurring "header change broke the shards" class). Folding the # compile surface in makes such a change reshard SAME-TREE instead of breaking # silently. overlay_api.h pulls cpu_state.h and its cycle-helper headers; the - # .inc is the shim every shard links against. + # .inc is the shim every shard links against; ws_cull_edge.h is the edge-cull + # math the .inc includes. ${PSXRECOMP_CODEGEN_HASH_ROOT}/runtime/include/overlay_api.h ${PSXRECOMP_CODEGEN_HASH_ROOT}/runtime/include/cpu_state.h ${PSXRECOMP_CODEGEN_HASH_ROOT}/runtime/include/pgxp_hooks.h ${PSXRECOMP_CODEGEN_HASH_ROOT}/runtime/include/psx_cyc.h ${PSXRECOMP_CODEGEN_HASH_ROOT}/runtime/include/psx_cycles.h + ${PSXRECOMP_CODEGEN_HASH_ROOT}/runtime/include/ws_cull_edge.h ${PSXRECOMP_CODEGEN_HASH_ROOT}/runtime/include/overlay_dispatch_preamble.c.inc) diff --git a/runtime/include/overlay_dispatch_preamble.c.inc b/runtime/include/overlay_dispatch_preamble.c.inc index c062cf1f..6cf58e48 100644 --- a/runtime/include/overlay_dispatch_preamble.c.inc +++ b/runtime/include/overlay_dispatch_preamble.c.inc @@ -1,4 +1,5 @@ #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; @@ -206,6 +207,16 @@ int psx_ws_cull_slti_lower(uint32_t sx, uint32_t imm) { 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()); +} 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; diff --git a/tools/tests/test_overlay_widescreen_callbacks.py b/tools/tests/test_overlay_widescreen_callbacks.py index b68aabae..c1628be6 100644 --- a/tools/tests/test_overlay_widescreen_callbacks.py +++ b/tools/tests/test_overlay_widescreen_callbacks.py @@ -20,6 +20,7 @@ class OverlayWidescreenCallbacks(unittest.TestCase): static CPUState *seen_cpu; static uint32_t seen_address, cycles; static int32_t bound(int32_t x) { return x * 2; } +static int margin120(void) { return 120; } static void advance(uint32_t n) { cycles += n; } static void entry(CPUState *cpu, uint32_t address) { assert(cycles == 17); seen_cpu = cpu; seen_address = address; @@ -42,6 +43,19 @@ int main(void) { assert(psx_ws_screen_x_bound(-256) == -256); psx_mod_function_entry(&cpu, 0); assert(seen_address == 0x80045770); + /* bgez_sites / clip_edge_x_load_sites in overlay code: identity with no + * margin callback (4:3 host), widened by the live margin with one. */ + assert(psx_ws_cull_bgez(0u) == 1 && psx_ws_cull_bgez((uint32_t)-1) == 0); + assert(psx_ws_clip_edge_x(0u, 0x140u) == 0u); + assert(psx_ws_clip_edge_x(0x140u, 0x140u) == 0x140u); + callbacks.ws_x_margin = margin120; + overlay_init(&callbacks); + assert(psx_ws_cull_bgez((uint32_t)-120) == 1); + assert(psx_ws_cull_bgez((uint32_t)-121) == 0); + assert(psx_ws_clip_edge_x(0u, 0x140u) == (uint32_t)-120); + assert(psx_ws_clip_edge_x(0x140u, 0x140u) == 0x140u + 120u); + assert(psx_ws_clip_edge_x(98u, 0x140u) == 98u); /* interior viewport edge */ + assert(psx_ws_clip_edge_x(222u, 0x140u) == 222u); return 0; } '''