mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
71d6550eef
Off-thread sljit compile worker (overlay_compile_worker.c/.h): on a dispatch gap-fill miss the dispatch thread snapshots the region bytes and ENQUEUES; a background worker JITs from the snapshot (never live RAM) and publishes the shard to the on-disk cache via an atomic temp+rename; dispatch picks it up on a later miss via an idempotent rescan, so the candidate table stays single-threaded. This removes the synchronous JIT frame hitch. Validated on Tomba + MMX6: compiles off-thread (one MMX6 compile was 26ms, paid entirely off the dispatch thread with no frame-pacing spike), shards persist + reload, queue never backs up. Observability (ring-buffer doctrine): always-on recent-compile ring + per-compile timing in the worker, the resolved on-disk cache dir, and a tier-enabled flag, all surfaced via a new sljit_async TCP debug command (documented in TCP_COMMANDS.md). DISABLE the sljit tier by default (g_sljit_tier_enabled = 0): the sljit EMITTER mistranslates some MMX6 overlay instruction(s) -- sljit-only save-load softlocks/ loopbacks while PURE INTERP works (discriminator), and it reproduces with the worker OFF in the synchronous path (PSX_SLJIT_SYNC=1), so it is the emitter, not the async worker. Until the emitter bug is found + fixed: no shard READ (scan_sljit_cache_dir skipped), no GENERATE (worker not started + sljit_live forced 0 + dispatch gap-fill gated), every overlay miss falls to the interpreter. gcc (DLL cache + autocompile) and overlay capture are untouched, so priority is gcc > interp and dispatch misses are still recorded for future gcc coverage. gcc shards are bundled in release zips, so shipped builds are unaffected. PSX_SLJIT_ENABLE=1 re-enables the tier for emitter-fix testing. Runtime-only, no regen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
76 lines
3.5 KiB
C
76 lines
3.5 KiB
C
#ifndef PSX_OVERLAY_COMPILE_WORKER_H
|
|
#define PSX_OVERLAY_COMPILE_WORKER_H
|
|
|
|
#include <stdint.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
/*
|
|
* Off-main-thread sljit compile worker.
|
|
*
|
|
* The dispatch (guest-fiber) thread, on an sljit gap-fill miss, snapshots the
|
|
* region bytes and ENQUEUES here instead of JIT'ing inline (which used to spike
|
|
* the frame). A single background worker pops the queue, JITs the fragment FROM
|
|
* THE SNAPSHOT (never live guest RAM), and writes the serialized shard into the
|
|
* on-disk cache via overlay_loader_async_publish(). The dispatch thread picks
|
|
* the shard up on a later miss (cache-dirty -> idempotent rescan -> register),
|
|
* so the candidate table stays single-threaded: the worker NEVER touches it.
|
|
*
|
|
* Safety invariants (see docs/ASYNC_OVERLAY_COMPILE.md §3/§5):
|
|
* - candidate tables / sljit registry: dispatch thread only.
|
|
* - guest RAM: worker reads only the enqueue-time snapshot copy.
|
|
* - the sljit emitter's file-static scratch is safe because the worker is the
|
|
* SOLE runtime caller of the compiler (dispatch enqueues; selftest is init-time).
|
|
*/
|
|
|
|
/* Start/stop the worker. Idempotent. stop() signals + joins. */
|
|
void overlay_compile_worker_start(void);
|
|
void overlay_compile_worker_stop(void);
|
|
|
|
/* Enqueue a region for background JIT. `vaddr` is the VIRTUAL entry (carries
|
|
* KSEG bits the guest uses for return_pc/jal targets). `src` points at the
|
|
* live region bytes [phys .. phys+snap_len); enqueue COPIES them, so the caller
|
|
* keeps ownership of `src`. `crc` is a content hash of the snapshot window,
|
|
* used only for (phys,crc) dedup. Deduped: a QUEUED/COMPILING/DONE (phys,crc)
|
|
* is ignored. Cheap no-op if the worker isn't running. */
|
|
void overlay_compile_worker_enqueue(uint32_t vaddr, uint32_t phys,
|
|
const uint8_t *src, uint32_t snap_len,
|
|
uint32_t crc);
|
|
|
|
/* Telemetry for the debug server (any out-ptr may be NULL). */
|
|
void overlay_compile_worker_stats(uint64_t *enqueued, uint64_t *compiled,
|
|
uint64_t *failed, uint32_t *queued_now);
|
|
|
|
/* 1 if the worker thread is currently running. */
|
|
int overlay_compile_worker_running(void);
|
|
|
|
/* Last / worst single-compile wall time (ms, SDL_GetTicks resolution). The whole
|
|
* point of the worker is that this time is paid OFF the dispatch thread, so a
|
|
* nonzero max here alongside a flat dispatch-thread ce_profile is the proof. */
|
|
void overlay_compile_worker_timing(uint32_t *last_ms, uint32_t *max_ms);
|
|
|
|
/* Always-on ring of the most-recent compile results (one entry PER shard the
|
|
* worker has produced or rejected). Query this for the window of interest —
|
|
* never arm-then-capture. */
|
|
typedef struct {
|
|
uint32_t vaddr; /* virtual entry enqueued */
|
|
uint32_t phys; /* physical entry (dedup key) */
|
|
uint32_t crc; /* content crc of the enqueue-time snapshot */
|
|
uint32_t code_lo; /* compiled range start (phys) */
|
|
uint32_t code_len; /* compiled range length in bytes (0 if fail) */
|
|
uint32_t compile_ms; /* wall time for this compile (off-thread) */
|
|
uint8_t ok; /* 1 = published a shard, 0 = emitter declined */
|
|
} OverlayCompileEvent;
|
|
|
|
/* Copy up to `cap` most-recent events into `out` in chronological order (oldest
|
|
* first), returning the count copied. Thread-safe. */
|
|
int overlay_compile_worker_recent(OverlayCompileEvent *out, int cap);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* PSX_OVERLAY_COMPILE_WORKER_H */
|