mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-27 10:38:46 -04:00
c2a3f6ad79
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>
130 lines
6.7 KiB
C
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 */
|