feat(widescreen,sljit): shared cull helper across backends, cache versioning, sljit default-on

Three coupled landings on top of the sljit merge:

1. Shared widescreen render-cull helper. auto_screen_x now emits a call to one
   runtime helper psx_ws_cull_sltiu(sx,imm) (gpu.c) instead of an inline expr, and
   the sljit JIT (overlay_sljit.c) + dirty-RAM interpreter (dirty_ram_interp.c)
   route flagged render-funnel sltiu 0x140/0x141 through the SAME helper. So an
   overlay's GTE render funnel widens identically whether it runs as a gcc DLL, a
   sljit shard, or interpreted. Helper is byte-identical to vanilla at 4:3
   (margin 0), so widescreen-off stays bit-exact. psx_ws_func_has_screen_cull
   gates the widen to real render funnels (sltiu 0x140 + sltiu 0xE0 signature).

2. Versioned overlay cache path. gcc cache moves to <game_id>/gcc/<arch-abi>/cg<N>/
   where N = PSX_OVERLAY_CODEGEN_VER (overlay_api.h), parsed identically by the
   runtime (overlay_loader.c) and compile_overlays.py so they can't drift. A build
   whose emitter output changed (e.g. this auto_screen_x change) reads+writes a
   FRESH dir and never reuses a stale DLL; old versions coexist (no auto-delete).
   Pre-1.0: legacy flat/unversioned fallbacks dropped.

3. sljit default-on (validated). gcc and sljit are complementary, not exclusive:
   gcc fills the cache via prebuilt DLLs + async batch (compile_fragment NULL),
   sljit JITs gcc-absent regions synchronously on-miss. New code_provider_sljit()
   exposes sljit for the JIT-on-miss gap-fill independent of the "active" backend;
   the dispatch gate fires whenever sljit is available (head<0 == gcc-absent), and
   apply_live_policy defaults live ON when sljit is available. Priority gcc > sljit
   > interp; shards still route through the same-state diff gate (device-touch +
   diverging shards pinned to interp) so default-on does NOT reintroduce the
   save-load wedge; a declined/failing shard falls to interp (never a crash).
   Verified live: live=1, 62 shards compiled, 2 declines->interp, native:interp
   ratio ~31%->~87%, gcc still primary.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley
2026-06-15 19:36:43 -07:00
parent 52ddde7808
commit 0c1f005c35
10 changed files with 198 additions and 43 deletions
+11 -2
View File
@@ -77,10 +77,19 @@ typedef struct CodeProvider {
* at overlay-cache init, on the emu thread, before the run loop starts. */
void code_provider_init(const char *cfg_backend, int gcc_configured);
/* The active provider. Never NULL — defaults to the gcc provider before
* code_provider_init() runs, so any early caller is safe. */
/* The active/primary provider (owns the batch path + default selection). Never
* NULL — defaults to the gcc provider before code_provider_init() runs. */
const CodeProvider *code_provider_active(void);
/* The sljit provider IF it is available, else NULL. gcc and sljit are
* COMPLEMENTARY, not exclusive: gcc fills the cache via the async batch path +
* prebuilt DLLs and never JITs synchronously (compile_fragment == NULL), while
* sljit is the synchronous JIT-on-miss that fills the gaps gcc has not (yet)
* covered. The dispatch path uses this — independent of which provider is
* "active" — so on a gcc dev box sljit still JITs gcc-absent regions
* (priority gcc > sljit > interp). NULL => sljit unavailable, gaps fall to interp. */
const CodeProvider *code_provider_sljit(void);
#ifdef __cplusplus
}
#endif