mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
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:
@@ -730,16 +730,12 @@ std::string CodeGenerator::translate_instruction(uint32_t addr, uint32_t instr)
|
||||
uint16_t uimm = get_imm16_u(instr);
|
||||
if (uimm == 0x140 || uimm == 0x141) {
|
||||
uint32_t rs = get_rs(instr), rt = get_rt(instr);
|
||||
// SX is a 16-bit GTE screen coord loaded via lhu, so an off-LEFT
|
||||
// vertex is a large unsigned value (e.g. -30 -> 0xFFE2). A plain
|
||||
// `SX <u (imm+2m)` widen therefore reveals only the RIGHT margin —
|
||||
// the left stays culled. Sign-extend SX and shift by +margin so the
|
||||
// verdict is the wide window (-margin <= SX < imm+margin): both the
|
||||
// left AND right 16:9 margins pass, and geometry past the wide
|
||||
// surface on either side is still rejected. At margin=0 this reduces
|
||||
// exactly to the vanilla `SX <u imm` verdict (byte-identical).
|
||||
return fmt::format("{} = ((uint32_t)((int32_t)(int16_t){} + psx_ws_x_margin()) "
|
||||
"< (uint32_t)((int32_t){} + 2*psx_ws_x_margin())) ? 1 : 0;"
|
||||
// Route through the shared runtime helper psx_ws_cull_sltiu so the
|
||||
// gcc emit, the sljit JIT, and the interpreter all widen identically.
|
||||
// It sign-extends SX and shifts by +margin (wide window
|
||||
// -margin <= SX < imm+margin, both edges); at margin=0 it reduces
|
||||
// bit-for-bit to the vanilla `SX <u imm` verdict (4:3 byte-identical).
|
||||
return fmt::format("{} = psx_ws_cull_sltiu({}, {});"
|
||||
" /* ws auto screen-x cull (both edges) */{}",
|
||||
reg_name(rt), reg_name(rs), (int)uimm, comment);
|
||||
}
|
||||
@@ -1760,6 +1756,7 @@ std::string CodeGenerator::generate_file(
|
||||
ss << "extern void debug_server_log_call_entry(uint32_t func_addr);\n";
|
||||
ss << "extern void psx_ws_sprite_tag(CPUState* cpu); /* widescreen prim tag (gpu.c) */\n";
|
||||
ss << "extern int psx_ws_x_margin(void); /* widescreen cull-margin term (gpu.c) */\n";
|
||||
ss << "extern int psx_ws_cull_sltiu(uint32_t sx, uint32_t imm); /* ws auto screen-x cull (gpu.c) */\n";
|
||||
ss << "extern int psx_ws_backdrop_x(int x); /* widescreen backdrop screenX squash (gpu.c) */\n";
|
||||
ss << "extern void gte_ws_set_suppress(int on); /* widescreen far-backdrop un-squash (gte.cpp) */\n\n";
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -111,6 +111,16 @@ int gpu_ws_present_native_43(void);
|
||||
* cull immediates by the recompiler ([widescreen.cull]); 0 unless stretching. */
|
||||
int psx_ws_x_margin(void);
|
||||
|
||||
/* Shared render-funnel screen-X cull widening ([widescreen.cull] auto_screen_x):
|
||||
* the gcc emit, the sljit JIT, and the interpreter all route a flagged
|
||||
* `sltiu rt, sx, 0x140/0x141` through this one helper so every overlay execution
|
||||
* path widens identically. Returns the sltiu verdict (1 = keep). 0 at 4:3. */
|
||||
int psx_ws_cull_sltiu(uint32_t sx, uint32_t imm);
|
||||
/* True if a run of instruction words carries the screen-extent reject signature
|
||||
* (a sltiu 0x140/0x141 AND a sltiu 0xE0/0xF1). Used by sljit/interp to gate the
|
||||
* widening to real render funnels. */
|
||||
int psx_ws_func_has_screen_cull(const uint32_t *words, int n);
|
||||
|
||||
/* Backdrop screen-X correction ([widescreen.backdrop] x_sites). The parallax
|
||||
* 2D backdrop layer computes screen-X without the GTE, so it misses the
|
||||
* widescreen squash; the recompiler emits this on each backdrop handler's
|
||||
|
||||
@@ -48,6 +48,19 @@
|
||||
#define PSX_OVERLAY_ABI_TAG \
|
||||
((int)((PSX_OVERLAY_ABI_VERSION & 0xFFFF) | ((PSX_OVERLAY_FLAVOR & 0xFFFF) << 16)))
|
||||
|
||||
/* Codegen version — bumped whenever the recompiler emits DIFFERENT bytes for the
|
||||
* same guest overlay WITHOUT an ABI change (the load-time overlay_abi() gate only
|
||||
* catches ABI/flavor changes; a pure-emitter change produces a different DLL the
|
||||
* gate would happily accept). The cache is namespaced by this version
|
||||
* (gcc/<arch-abi>/cg<N>/), so a build with new codegen writes + reads a FRESH
|
||||
* directory and never reuses a stale DLL; old versions coexist on disk (no
|
||||
* auto-delete — a user who downgrades still finds their matching cache). Python
|
||||
* (compile_overlays.py) parses this same constant from this header, so the two
|
||||
* sides can never drift. BUMP THIS when you change code_generator.cpp emit in a
|
||||
* way that alters overlay output (e.g. the auto_screen_x widescreen widening).
|
||||
* 1: baseline + auto_screen_x render-funnel cull via psx_ws_cull_sltiu. */
|
||||
#define PSX_OVERLAY_CODEGEN_VER 1
|
||||
|
||||
typedef struct {
|
||||
/* Core dispatch: routes call_by_address() and out-of-overlay jal */
|
||||
void (*dispatch_call)(CPUState *cpu, uint32_t addr, uint32_t ra);
|
||||
|
||||
@@ -70,3 +70,9 @@ void code_provider_init(const char *cfg_backend, int gcc_configured) {
|
||||
}
|
||||
|
||||
const CodeProvider *code_provider_active(void) { return s_active; }
|
||||
|
||||
/* sljit provider for the synchronous JIT-on-miss gap-fill, regardless of which
|
||||
* provider is "active". NULL when sljit isn't available so callers fall to interp. */
|
||||
const CodeProvider *code_provider_sljit(void) {
|
||||
return (s_sljit.available && s_sljit.available()) ? &s_sljit : NULL;
|
||||
}
|
||||
|
||||
@@ -147,6 +147,31 @@ static inline uint32_t fetch_word(uint32_t phys) {
|
||||
| ((uint32_t)ram[phys + 3] << 24);
|
||||
}
|
||||
|
||||
/* Widescreen render-funnel cull detection for the interpreter ([widescreen.cull]
|
||||
* auto_screen_x). A `sltiu …,0x140/0x141` is widened ONLY when its enclosing
|
||||
* render function also carries a `sltiu …,0xE0/0xF1` (the GTE per-vertex trivial-
|
||||
* reject signature) — a lone 0x140 elsewhere stays vanilla. The interp has no
|
||||
* function boundaries, so we scan a +/-512-byte window around the site (clamped
|
||||
* to main RAM) via the shared psx_ws_func_has_screen_cull, cached per-PC so a
|
||||
* hot render loop pays the scan once. Single-threaded interp => static buffers ok. */
|
||||
static int ws_cull_site(uint32_t pc) {
|
||||
enum { WIN = 128 }; /* +/- 128 words = +/- 512 bytes */
|
||||
static struct { uint32_t pc; int8_t flag; } cache[256];
|
||||
uint32_t slot = (pc >> 2) & 255u;
|
||||
if (cache[slot].pc == pc) return cache[slot].flag;
|
||||
uint32_t phys = pc & 0x1FFFFFFFu;
|
||||
uint32_t lo = (phys > (uint32_t)(WIN * 4)) ? phys - (uint32_t)(WIN * 4) : 0u;
|
||||
uint32_t hi = phys + (uint32_t)(WIN * 4);
|
||||
if (hi > 0x200000u) hi = 0x200000u; /* 2 MB main RAM */
|
||||
static uint32_t words[2 * WIN + 1];
|
||||
int n = 0;
|
||||
for (uint32_t a = lo; a + 4u <= hi && n < (int)(2 * WIN + 1); a += 4u)
|
||||
words[n++] = fetch_word(a);
|
||||
int flag = psx_ws_func_has_screen_cull(words, n);
|
||||
cache[slot].pc = pc; cache[slot].flag = (int8_t)flag;
|
||||
return flag;
|
||||
}
|
||||
|
||||
static void dirty_ram_log_instruction(CPUState *cpu, uint32_t pc, uint32_t insn,
|
||||
uint32_t before_s0, uint32_t next_pc,
|
||||
uint32_t target, int transferred) {
|
||||
@@ -684,7 +709,14 @@ static int exec_one(CPUState *cpu, uint32_t pc, uint32_t *next_pc_out) {
|
||||
cpu->gpr[0] = 0;
|
||||
return 0;
|
||||
case 0x0B: /* SLTIU */
|
||||
cpu->gpr[rt] = (cpu->gpr[rs] < (uint32_t)simm) ? 1u : 0u;
|
||||
/* Widescreen render-funnel cull widening (auto_screen_x): always apply
|
||||
* the shared helper for a flagged render-cull site — it is byte-identical
|
||||
* to the vanilla compare at 4:3 (margin 0) and widens at 16:9, so the one
|
||||
* code path serves both aspects (no widescreen-specific caching). */
|
||||
if ((imm == 0x140 || imm == 0x141) && ws_cull_site(pc))
|
||||
cpu->gpr[rt] = (uint32_t)psx_ws_cull_sltiu(cpu->gpr[rs], imm);
|
||||
else
|
||||
cpu->gpr[rt] = (cpu->gpr[rs] < (uint32_t)simm) ? 1u : 0u;
|
||||
cpu->gpr[0] = 0;
|
||||
return 0;
|
||||
case 0x0C: /* ANDI */
|
||||
|
||||
@@ -182,6 +182,37 @@ int psx_ws_x_margin(void) {
|
||||
return (160 * (ws_xden - ws_xnum) + ws_xnum / 2) / ws_xnum;
|
||||
}
|
||||
|
||||
/* Shared render-funnel screen-X cull widening ([widescreen.cull] auto_screen_x),
|
||||
* called identically by the gcc emit, the sljit JIT, and the interpreter so every
|
||||
* overlay execution path widens the same way. sx = the GTE screen-X as loaded by
|
||||
* the guest's lhu (low 16 bits significant); imm = the original bound (0x140 /
|
||||
* 0x141). Returns the sltiu verdict (1 = on-screen/keep). Sign-extends sx and
|
||||
* shifts by +margin so BOTH 16:9 margins pass; at 4:3 margin==0 so it reduces
|
||||
* bit-for-bit to the vanilla `(uint16)sx < imm`. */
|
||||
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;
|
||||
}
|
||||
|
||||
/* Detect the GTE per-vertex trivial-reject signature in a run of instruction
|
||||
* words: at least one `sltiu …,0x140/0x141` (width) AND one `sltiu …,0xE0/0xF1`
|
||||
* (height). Lets the sljit JIT + interpreter gate the cull-widening to real
|
||||
* render funnels (a lone sltiu 0x140 elsewhere must stay vanilla). Mirrors the
|
||||
* recompiler's func_has_screen_extent_cull. */
|
||||
int psx_ws_func_has_screen_cull(const uint32_t *words, int n) {
|
||||
int has_x = 0, has_y = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
uint32_t w = words[i];
|
||||
if ((w & 0xFC000000u) != 0x2C000000u) continue; /* sltiu */
|
||||
uint32_t imm = w & 0xFFFFu;
|
||||
if (imm == 0x140 || imm == 0x141) has_x = 1;
|
||||
else if (imm == 0xE0 || imm == 0xF1) has_y = 1;
|
||||
if (has_x && has_y) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Widescreen backdrop screen-X correction ([widescreen.backdrop] x_sites).
|
||||
* The parallax 2D backdrop layer (ocean/cloud/mountain/grass — overlay actor
|
||||
* handlers e.g. 0x801216BC) computes its screen-X in pure integer math
|
||||
|
||||
@@ -379,8 +379,11 @@ static void sljit_mark_tried(uint32_t phys) {
|
||||
* hook. Decodes from live RAM exactly as the interpreter does. */
|
||||
static void try_sljit_region(uint32_t addr) {
|
||||
uint32_t phys = addr & 0x1FFFFFFFu;
|
||||
const CodeProvider *cp = code_provider_active();
|
||||
if (!cp->compile_fragment) return;
|
||||
/* Use the sljit provider specifically (not the active one): gcc is the
|
||||
* primary/batch provider with compile_fragment == NULL, so the synchronous
|
||||
* JIT-on-miss gap-fill must go through sljit even when gcc is "active". */
|
||||
const CodeProvider *cp = code_provider_sljit();
|
||||
if (!cp || !cp->compile_fragment) return;
|
||||
if (sljit_already_tried(phys)) return;
|
||||
extern int dirty_ram_is_dirty(uint32_t phys);
|
||||
if (!dirty_ram_is_dirty(phys)) return; /* only JIT real runtime code */
|
||||
@@ -490,17 +493,16 @@ static void scan_one_cache_dir(const char *dir) {
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Scan the namespaced gcc cache (gcc/<arch-abi>/) AND the legacy flat layout
|
||||
* (<game_id>/*.dll) for back-compat — existing caches keep loading with no
|
||||
* migration. The sljit/<arch-abi>/ namespace is reserved (no on-disk blobs in
|
||||
* v1: sljit re-JITs from the coverage manifest; see SLJIT.md §5.3). */
|
||||
/* Scan the namespaced gcc cache: gcc/<arch-abi>/cg<codegen-ver>/. The codegen
|
||||
* version segment means a build with new emitter output reads a FRESH directory
|
||||
* and never picks up a stale DLL (old versions coexist on disk, no migration).
|
||||
* The sljit/<arch-abi>/ namespace is reserved (no on-disk blobs: sljit re-JITs
|
||||
* from the coverage manifest; see SLJIT.md §5.3). (Pre-1.0: no legacy fallback —
|
||||
* older flat / unversioned caches are simply ignored and regenerated.) */
|
||||
static void scan_cache_dir(void) {
|
||||
char dir[768];
|
||||
snprintf(dir, sizeof(dir), "%s/%s/gcc/%s",
|
||||
s_cache_dir, s_game_id, PSX_OVERLAY_ARCH_ABI);
|
||||
scan_one_cache_dir(dir);
|
||||
/* Legacy flat layout (pre-namespacing). */
|
||||
snprintf(dir, sizeof(dir), "%s/%s", s_cache_dir, s_game_id);
|
||||
snprintf(dir, sizeof(dir), "%s/%s/gcc/%s/cg%d",
|
||||
s_cache_dir, s_game_id, PSX_OVERLAY_ARCH_ABI, PSX_OVERLAY_CODEGEN_VER);
|
||||
scan_one_cache_dir(dir);
|
||||
}
|
||||
|
||||
@@ -717,13 +719,19 @@ void overlay_loader_init(const char *cache_dir, const char *game_id) {
|
||||
* forces on) — a dev escape hatch. NOTE: "live" here is VALIDATED-live, not the
|
||||
* old blind path; see overlay_loader_dispatch's diff gate. */
|
||||
void overlay_loader_apply_live_policy(void) {
|
||||
int live = (overlay_backend_active() == OVERLAY_BACKEND_SLJIT) ? 1 : 0;
|
||||
/* DEFAULT-ON (validated): sljit is live whenever it is AVAILABLE, so it fills
|
||||
* gcc-absent overlay regions even on a gcc dev box (gcc > sljit > interp).
|
||||
* "Live" is VALIDATED-live — shards still route through the same-state diff
|
||||
* gate (see overlay_loader_dispatch) and device-touching / diverging shards
|
||||
* stay on the interpreter, so default-on cannot reintroduce the save-load
|
||||
* wedge. The PSX_OVERLAY_SLJIT_LIVE env var still overrides (0 forces off). */
|
||||
int live = (code_provider_sljit() != NULL) ? 1 : 0;
|
||||
const char *e = getenv("PSX_OVERLAY_SLJIT_LIVE");
|
||||
if (e && *e) live = (*e != '0') ? 1 : 0;
|
||||
s_sljit_live = live;
|
||||
loader_log("sljit live policy: backend=%s env=%s -> live=%d",
|
||||
loader_log("sljit live policy: active_backend=%s sljit_avail=%d env=%s -> live=%d",
|
||||
overlay_backend_name(overlay_backend_active()),
|
||||
e && *e ? e : "(unset)", live);
|
||||
code_provider_sljit() != NULL, e && *e ? e : "(unset)", live);
|
||||
}
|
||||
|
||||
void overlay_loader_check_cache(uint32_t load_addr, uint32_t size,
|
||||
@@ -844,7 +852,12 @@ int overlay_loader_dispatch(CPUState *cpu, uint32_t addr) {
|
||||
* production model — shards created and run live, no per-shard diff).
|
||||
* Never inside a shadow run. */
|
||||
if (head < 0 && (s_diff_mode || s_sljit_live) && !s_in_shadow &&
|
||||
overlay_backend_active() == OVERLAY_BACKEND_SLJIT) {
|
||||
code_provider_sljit() != NULL) {
|
||||
/* head < 0 == no gcc DLL (or any candidate) registered for this region
|
||||
* after the cache-load attempt above => gcc-absent => sljit fills the
|
||||
* gap (priority gcc > sljit > interp). Fires whenever sljit is
|
||||
* AVAILABLE, not only when it is the exclusive backend, so sljit and
|
||||
* gcc run complementarily. */
|
||||
try_sljit_region(addr); /* virtual entry (carries KSEG bits) */
|
||||
head = idx_head(phys);
|
||||
}
|
||||
|
||||
@@ -171,6 +171,12 @@ typedef struct { int reg; int dirty; int pinned; uint32_t lru; } GprSlot;
|
||||
static GprSlot s_gpr[GPR_SLOTS_MAX];
|
||||
static int s_gpr_slots; /* active slots this compile (<= GPR_SLOTS_MAX)*/
|
||||
static uint32_t s_gpr_lru;
|
||||
/* Set per-fragment (PASS 1) when this fragment carries the GTE render-funnel
|
||||
* screen-extent reject signature (sltiu 0x140/0x141 + sltiu 0xE0/0xF1). When
|
||||
* set, SLTIU 0x140/0x141 is routed through psx_ws_cull_sltiu so a sljit-JIT'd
|
||||
* overlay widens identically to the gcc cache + interp (widescreen FOV). */
|
||||
static int s_frag_ws_cull;
|
||||
extern int psx_ws_cull_sltiu(uint32_t sx, uint32_t imm); /* gpu.c — shared widen */
|
||||
|
||||
static inline sljit_s32 slot_reg(int k) { return SLJIT_S(k + 2); } /* S2.. */
|
||||
|
||||
@@ -345,6 +351,23 @@ static void emit_helper2(struct sljit_compiler *C, void *fn, uint32_t insn) {
|
||||
gpr_reset();
|
||||
}
|
||||
|
||||
/* Emit `rt = psx_ws_cull_sltiu(gpr[rs], imm)` for a flagged auto_screen_x cull
|
||||
* site, so a sljit-JIT'd overlay widens the render-funnel screen-X reject the
|
||||
* same way the gcc backend does. The helper reads only its two args (+ the
|
||||
* runtime widescreen margin); it does NOT touch cpu->gpr[], so the GPR cache
|
||||
* survives — same icall discipline as emit_load (materialise the source into a
|
||||
* scratch before the call, write the result into rt after). */
|
||||
static void emit_ws_cull(struct sljit_compiler *C, uint32_t rt, uint32_t rs, uint32_t imm) {
|
||||
sljit_s32 a; sljit_sw aw;
|
||||
gpr_src(C, rs, &a, &aw);
|
||||
sljit_emit_op1(C, SLJIT_MOV32, SLJIT_R0, 0, a, aw); /* sx -> R0 (arg0) */
|
||||
sljit_emit_op1(C, SLJIT_MOV32, SLJIT_R1, 0, SLJIT_IMM, (sljit_sw)imm); /* imm -> R1 (arg1) */
|
||||
sljit_emit_op1(C, SLJIT_MOV, SLJIT_R2, 0, SLJIT_IMM, (sljit_sw)(uintptr_t)psx_ws_cull_sltiu);
|
||||
sljit_emit_icall(C, SLJIT_CALL, SLJIT_ARGS2(32, 32, 32), SLJIT_R2, 0); /* R0 = verdict */
|
||||
sljit_emit_op1(C, SLJIT_MOV32, gpr_dst(C, rt), 0, SLJIT_R0, 0);
|
||||
gpr_unpin();
|
||||
}
|
||||
|
||||
enum { EMIT_OK = 0, EMIT_TERM = 1, EMIT_ABORT = 2 };
|
||||
|
||||
/* Emit ONE non-control instruction. Returns EMIT_OK, or EMIT_TERM iff `insn`
|
||||
@@ -507,7 +530,10 @@ static int emit_one(struct sljit_compiler *C, uint32_t insn) {
|
||||
emit_slt(C, SLJIT_SUB32 | SLJIT_SET_SIG_LESS, SLJIT_SIG_LESS, rt, rs, 0, 0, simm);
|
||||
return EMIT_OK;
|
||||
case 0x0B: /* SLTIU (unsigned, simm sign-extended then compared unsigned) */
|
||||
emit_slt(C, SLJIT_SUB32 | SLJIT_SET_LESS, SLJIT_LESS, rt, rs, 0, 0, simm);
|
||||
if (s_frag_ws_cull && (imm == 0x140 || imm == 0x141))
|
||||
emit_ws_cull(C, rt, rs, imm); /* widescreen render-funnel cull widen (auto_screen_x) */
|
||||
else
|
||||
emit_slt(C, SLJIT_SUB32 | SLJIT_SET_LESS, SLJIT_LESS, rt, rs, 0, 0, simm);
|
||||
return EMIT_OK;
|
||||
case 0x0C: case 0x0D: case 0x0E: { /* ANDI / ORI / XORI (zero-extended imm) */
|
||||
sljit_s32 a, d; sljit_sw aw;
|
||||
@@ -742,8 +768,17 @@ void overlay_sljit_try_compile(uint32_t entry,
|
||||
* see gpr_ref_mask). distinct >= the max regs in any single instruction, so
|
||||
* gpr_alloc always finds a slot. */
|
||||
uint32_t used_mask = 0;
|
||||
for (uint32_t i = 0; i < frag_words; i++)
|
||||
used_mask |= gpr_ref_mask(img_word(bytes, off0 + i * 4u));
|
||||
int ws_hx = 0, ws_hy = 0; /* render-funnel screen-cull signature (auto_screen_x) */
|
||||
for (uint32_t i = 0; i < frag_words; i++) {
|
||||
uint32_t w = img_word(bytes, off0 + i * 4u);
|
||||
used_mask |= gpr_ref_mask(w);
|
||||
if ((w & 0xFC000000u) == 0x2C000000u) { /* sltiu */
|
||||
uint32_t im = w & 0xFFFFu;
|
||||
if (im == 0x140 || im == 0x141) ws_hx = 1;
|
||||
else if (im == 0xE0 || im == 0xF1) ws_hy = 1;
|
||||
}
|
||||
}
|
||||
s_frag_ws_cull = ws_hx && ws_hy;
|
||||
int distinct = 0;
|
||||
for (uint32_t m = used_mask; m; m &= m - 1u) distinct++;
|
||||
s_gpr_slots = (distinct < GPR_SLOTS_MAX) ? distinct : GPR_SLOTS_MAX;
|
||||
|
||||
+21
-12
@@ -42,6 +42,20 @@ except ImportError:
|
||||
|
||||
import json
|
||||
import platform
|
||||
import re
|
||||
|
||||
|
||||
def codegen_ver(runtime_include: str) -> int:
|
||||
"""Parse PSX_OVERLAY_CODEGEN_VER from overlay_api.h so the cache path version
|
||||
is the SAME value the runtime (overlay_loader.c) uses — the two can't drift.
|
||||
The cache is namespaced gcc/<arch-abi>/cg<N>/, so a build with new emitter
|
||||
output reads+writes a FRESH dir and never reuses a stale DLL."""
|
||||
hdr = os.path.join(runtime_include, 'overlay_api.h')
|
||||
with open(hdr) as f:
|
||||
m = re.search(r'#define\s+PSX_OVERLAY_CODEGEN_VER\s+(\d+)', f.read())
|
||||
if not m:
|
||||
raise SystemExit(f'PSX_OVERLAY_CODEGEN_VER not found in {hdr}')
|
||||
return int(m.group(1))
|
||||
|
||||
|
||||
def cache_arch_abi() -> str:
|
||||
@@ -1126,13 +1140,14 @@ def main():
|
||||
return
|
||||
os.makedirs(args.out_dir, exist_ok=True)
|
||||
else:
|
||||
# Namespaced gcc cache (SLJIT.md §4 — no comingling). Legacy flat layout
|
||||
# (<out-dir>/<game_id>/) is still READ by the loader and for prior-ranges
|
||||
# coverage merge below, so existing caches keep working with no migration.
|
||||
legacy_cache_dir = os.path.join(args.out_dir, game_id)
|
||||
cache_dir = os.path.join(legacy_cache_dir, 'gcc', cache_arch_abi())
|
||||
# Namespaced + versioned gcc cache: <game_id>/gcc/<arch-abi>/cg<N>/
|
||||
# (SLJIT.md §4 — no comingling; cg<N> = codegen version so a new emitter
|
||||
# build never reuses a stale DLL, old versions coexist). MUST match
|
||||
# overlay_loader.c scan_cache_dir(). Pre-1.0: no legacy fallback.
|
||||
cg = codegen_ver(args.runtime_include)
|
||||
cache_dir = os.path.join(args.out_dir, game_id, 'gcc', cache_arch_abi(), f'cg{cg}')
|
||||
os.makedirs(cache_dir, exist_ok=True)
|
||||
print(f'Cache dir: {cache_dir}')
|
||||
print(f'Cache dir: {cache_dir} (codegen ver {cg})')
|
||||
|
||||
with open(args.captures) as f:
|
||||
captures = json.load(f)
|
||||
@@ -1160,12 +1175,6 @@ def main():
|
||||
if not args.static:
|
||||
ranges_name = f'{phys_addr:08X}_{crc32:08X}.ranges'
|
||||
prior_ranges = os.path.join(cache_dir, ranges_name)
|
||||
# Fall back to the legacy flat layout so coverage from pre-namespacing
|
||||
# builds still merges forward (don't regress a fresh build).
|
||||
if not os.path.exists(prior_ranges):
|
||||
legacy_prior = os.path.join(legacy_cache_dir, ranges_name)
|
||||
if os.path.exists(legacy_prior):
|
||||
prior_ranges = legacy_prior
|
||||
if os.path.exists(prior_ranges):
|
||||
prior_entries = []
|
||||
with open(prior_ranges) as pf:
|
||||
|
||||
Reference in New Issue
Block a user