Files
psxrecomp/runtime/include/overlay_compile_worker.h
T
Matthew Stanley 71d6550eef overlay: off-thread sljit compile worker + observability; disable sljit tier by default (emitter bug)
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>
2026-06-25 10:13:53 -07:00

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 */