Files
psxrecomp/runtime/include/overlay_sljit.h
T
Matthew Stanley c2a3f6ad79 overlay: add tcc tier (toolchain-free fallback) replacing sljit; extract overlay_backend
Compiler-neutral tier-selection policy extracted from the mis-named
overlay_sljit.{c,h} into NEW overlay_backend.{c,h} (OverlayBackend enum +
resolve/name/active). overlay_sljit.{c,h} is now just the deprecated emitter.

New tcc tier. gcc and tcc run the IDENTICAL pipeline (recompiler -> C ->
compiler -> DLL -> loader); only the compiler binary differs, so both map to the
autocompile-spawn provider (code_provider.c) and the COMMAND is chosen by the
resolved backend (main.cpp). compile_overlays.py gains --compiler gcc|tcc: tcc
writes a separate tcc/ cache namespace, BOM-strips the C + a copy of the include
headers (tcc 0.9.27 can't skip a UTF-8 BOM), and uses tcc's built-in linker.

Backends (overlay_backend): auto (gcc if a gcc TOOLCHAIN is present, else tcc --
was sljit), gcc, tcc, auto-no-gcc (dev/test: force tcc even with gcc present, to
exercise the toolchain-less user path), sljit (DEPRECATED, gated off, lowest).
Priority: static > gcc shard > tcc shard > sljit(off) > interp. The loader scans
gcc/ then tcc/ and dedups by region+crc (filename) so a gcc shard always wins a
tie.

Self-contained tcc: with no explicit overlay_autocompile_cmd_tcc, the runtime
CONSTRUCTS the tcc command from a toolchain bundled beside the exe
(<exe>/overlay_toolchain/: embedded python + tcc + recompiler + compile_overlays.py
+ runtime headers) -- no system python or gcc needed. config_loader gains
overlay_autocompile_cmd_tcc (a dev override). autocompile cmd buffers enlarged for
the constructed path-heavy command.

Compilation stays OFF the dispatch/render thread (off-process autocompile
subprocess, same model as gcc); the main thread only spawns it and dlopens the
finished DLL.

Validated: MMX6 with overlay_backend=auto-no-gcc fills the tcc/ namespace live
from the bundled toolchain and plays through save-load + gameplay -- the exact
path the sljit tier softlocked on. The gcc path is untouched.

Runtime-only, no regen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 13:10:57 -07:00

130 lines
6.7 KiB
C

#ifndef OVERLAY_SLJIT_H
#define OVERLAY_SLJIT_H
/* overlay_sljit — Tier-2 self-contained in-process JIT backend (sljit).
*
* Architecture (locked, see SLJIT.md):
* Tier 1 (highest) — statically recompiled native (main EXE + discovered
* overlay DLLs). Fastest. Unchanged.
* Tier 2 (this) — the "shard": a fragment recompiled ON THE FLY the first
* time execution would otherwise fall to the interpreter.
* Built in RAM (sljit, sub-ms, no toolchain), registered as
* a native candidate so the NEXT encounter is Tier 2 not
* Tier 3, then persisted so later sessions reload it instead
* of re-discovering through the slow interpreter. This is
* structurally identical to today's gcc capture->compile->
* cache->load path; sljit is just an in-process producer.
* Tier 3 (lowest) — dirty-RAM interpreter (dirty_ram_interp.c). Correctness
* floor for not-yet-discovered code.
*
* SAFETY CONTRACT (precision over recall): the emitter compiles only what it can
* prove it emits identically to the interpreter. The moment it meets any
* instruction/shape it does not yet support, it ABORTS the whole fragment and
* returns failure — the caller then runs Tier 3 (interpreter). A partial emitter
* is therefore always safe: it can never ship a mis-compiled shard, only decline
* to produce one. gcc remains the default backend; sljit slots in UNDER the
* proven path and is gated behind backend selection (see overlay_backend.h-style
* policy below) until it passes the same-state differential.
*/
#include "cpu_state.h"
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* The native shard ABI — identical to overlay_loader's OverlayFn. */
typedef void (*OverlaySljitFn)(CPUState *);
/* Backend-selection policy (OverlayBackend enum + resolve/name/active) now lives
* in overlay_backend.h — it is compiler-neutral (gcc/tcc/sljit/auto) and not part
* of the sljit emitter. */
/* sljit availability — always 1 once the lib is linked (no external dep). */
int overlay_sljit_available(void);
/* Smoke test: JITs a trivial leaf in-process and runs it, returning 1 iff the
* produced machine code computes the expected value. Proves the sljit codegen +
* executable-allocator path works in THIS build/host before any real fragment is
* trusted. Surfaced via the debug server. */
int overlay_sljit_selftest(void);
/* Result of a fragment compile. fn == NULL means the emitter declined the whole
* fragment to the interpreter (Tier 3) — the always-safe outcome. On success,
* [code_lo, code_lo+code_len) is the PHYS byte-range the shard was compiled from
* (the caller registers a candidate over it for per-call live-byte validation,
* exactly like the gcc .ranges manifest). */
typedef struct {
OverlaySljitFn fn; /* NULL = declined */
uint32_t code_lo; /* phys start of compiled code range */
uint32_t code_len; /* byte length of compiled code range */
uint32_t insns; /* MIPS instructions compiled (diagnostics) */
/* Serialized (position-independent) LIR of this shard for the persisted
* cache (Stage 2). Allocated by sljit_serialize_compiler; the caller writes
* it to disk then frees it via overlay_sljit_free_serialized(). NULL if not
* serialized. */
void *serialized;
unsigned long serialized_size;
} OverlaySljitResult;
/* Attempt to JIT the leaf function at `entry` (guest vram) by decoding from
* `bytes` (an image whose byte[0] maps to vram `image_base_vram`) of `size`
* bytes. Fills *out. On ANY unsupported instruction/shape sets out->fn = NULL
* (caller falls through to Tier 3) — see the SAFETY CONTRACT above. First slice:
* compiles only single-block leaf functions terminating in `jr $ra`; everything
* else (internal branches, jal/jalr, jr to non-$ra, opcodes outside the slice)
* declines the whole fragment. */
void overlay_sljit_try_compile(uint32_t entry,
const uint8_t *bytes, uint32_t size,
uint32_t image_base_vram,
OverlaySljitResult *out);
/* Diagnostics counters for the debug server. */
void overlay_sljit_get_status(int *available, int *selftest_ok,
uint64_t *compiles, uint64_t *declines,
uint64_t *bytes_emitted);
/* Call helper invoked by JIT'd shards at a jal/jalr site. Mirrors the dirty-RAM
* interpreter's call path EXACTLY: dispatch the callee as a unit (compiled /
* native-overlay / interp), then apply the (ra,sp) call contract. Returns 1 if
* the shard must return immediately (a transfer/bail unwind is in progress —
* cpu->pc / g_psx_call_bail carry it up to the dispatch loop), 0 if the callee
* returned normally and the shard should continue at the next instruction.
* check_contract: 1 for jal and jalr whose link reg is $ra (the contract checks
* $ra==return_pc); 0 for jalr to a non-$ra link (the callee won't set $ra to
* return_pc, so the contract doesn't apply) — matches the interpreter. Defined
* in overlay_loader.c (has the dispatch machinery). */
int psx_sljit_call(CPUState *cpu, uint32_t target, uint32_t return_pc,
int check_contract);
/* Per-instruction helpers a shard calls for op-classes that are emitted as a
* call rather than inlined — each reproduces the dirty-RAM interpreter's exact
* behaviour for that instruction (parity by construction). Defined in
* overlay_loader.c.
* psx_sljit_cop2 — COP2/GTE: MFC2/CFC2/MTC2/CTC2/cmd (op 0x12), LWC2 (0x32),
* SWC2 (0x3A).
* psx_sljit_memx — unaligned mem: LWL/LWR/SWL/SWR (op 0x22/0x26/0x2A/0x2E). */
void psx_sljit_cop2(CPUState *cpu, uint32_t insn);
void psx_sljit_memx(CPUState *cpu, uint32_t insn);
/* Populate cpu->sljit_helpers[] with the host-helper addresses the JIT calls
* through (cpu-relative, so shard LIR is position-independent — see Stage 1 in
* SLJIT_PERSIST_CACHE.md). Call once at startup after CPUState is wired. */
void overlay_sljit_init_helpers(CPUState *cpu);
/* Reload a serialized shard blob (persisted cache, Stage 2) into a runnable
* native fn for THIS process. Returns NULL on failure. The blob's LIR is
* position-independent (Stage 1), so the regenerated code reaches host helpers
* via the cpu-relative table — correct at this process's ASLR base. */
OverlaySljitFn overlay_sljit_deserialize(const void *blob, unsigned long blob_size);
/* Free a buffer returned in OverlaySljitResult.serialized (sljit allocator). */
void overlay_sljit_free_serialized(void *p);
#ifdef __cplusplus
}
#endif
#endif /* OVERLAY_SLJIT_H */