mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 18:25:35 -04:00
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>
This commit is contained in:
@@ -140,6 +140,11 @@ static RuntimeConfig parse_runtime_block(const toml::value& cfg, const fs::path&
|
||||
toml::find<std::string>(runtime, "overlay_autocompile_cmd");
|
||||
rt.has_overlay_autocompile_cmd = !rt.overlay_autocompile_cmd.empty();
|
||||
}
|
||||
if (runtime.contains("overlay_autocompile_cmd_tcc")) {
|
||||
rt.overlay_autocompile_cmd_tcc =
|
||||
toml::find<std::string>(runtime, "overlay_autocompile_cmd_tcc");
|
||||
rt.has_overlay_autocompile_cmd_tcc = !rt.overlay_autocompile_cmd_tcc.empty();
|
||||
}
|
||||
if (runtime.contains("overlay_backend")) {
|
||||
rt.overlay_backend = toml::find<std::string>(runtime, "overlay_backend");
|
||||
}
|
||||
|
||||
@@ -98,12 +98,22 @@ struct RuntimeConfig {
|
||||
bool has_overlay_autocompile_cmd = false;
|
||||
std::string overlay_autocompile_cmd;
|
||||
|
||||
// overlay_backend: Tier-2 codegen backend selection (SLJIT.md §1).
|
||||
// "auto" (default, empty == auto) | "gcc" | "sljit". auto resolves to gcc
|
||||
// when overlay_autocompile_cmd is configured (a dev machine with a
|
||||
// toolchain), else sljit (self-contained / toolchain-less). The env var
|
||||
// PSX_OVERLAY_BACKEND overrides this at runtime. gcc stays the default
|
||||
// until the sljit emitter passes the same-state differential gate.
|
||||
// overlay_autocompile_cmd_tcc: the SAME pipeline as overlay_autocompile_cmd
|
||||
// but invoking the bundled, toolchain-free TinyCC compiler instead of gcc.
|
||||
// The runtime spawns THIS command instead of the gcc one when the resolved
|
||||
// backend is tcc (a machine without a gcc toolchain, or overlay_backend =
|
||||
// "tcc"/"auto-no-gcc"). Lets gcc stay the dev/production path while tcc is
|
||||
// the user fallback. When unset, a tcc-resolved backend has no compiler and
|
||||
// overlay gaps fall to the interpreter.
|
||||
bool has_overlay_autocompile_cmd_tcc = false;
|
||||
std::string overlay_autocompile_cmd_tcc;
|
||||
|
||||
// overlay_backend: overlay tier-selection (overlay_backend.h).
|
||||
// "auto" (default, empty == auto) | "gcc" | "tcc" | "auto-no-gcc" | "sljit"
|
||||
// (deprecated). auto resolves to gcc when a gcc TOOLCHAIN is present (a dev /
|
||||
// production box), else tcc (bundled, toolchain-free). "auto-no-gcc" forces
|
||||
// the tcc branch even with gcc present (simulate a toolchain-less user box;
|
||||
// gcc shards still load). The env var PSX_OVERLAY_BACKEND overrides at runtime.
|
||||
std::string overlay_backend;
|
||||
|
||||
// ---- [video] block — visual enhancement options ----
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
#ifndef OVERLAY_BACKEND_H
|
||||
#define OVERLAY_BACKEND_H
|
||||
|
||||
/* overlay_backend — compiler-NEUTRAL overlay tier-selection policy.
|
||||
*
|
||||
* This is NOT specific to any one producer. It decides which native overlay
|
||||
* tier fills a gap, in priority order:
|
||||
*
|
||||
* static > gcc shard > tcc shard > sljit shard (deprecated) > interp
|
||||
*
|
||||
* - gcc: spawn-gcc -> overlay DLL. The developer / production "holy grail"
|
||||
* (best-optimized; the shards shipped in releases).
|
||||
* - tcc: spawn-tcc -> overlay DLL. Bundled, toolchain-free user fallback that
|
||||
* fills regions the shipped gcc cache misses on a machine without gcc.
|
||||
* - sljit: in-process JIT. DEPRECATED (it mis-compiled; gated off) — kept only
|
||||
* until it is removed, and always the lowest native priority.
|
||||
*
|
||||
* gcc and tcc go through the IDENTICAL pipeline (recompiler -> C -> compiler ->
|
||||
* DLL -> loader); they differ only in the compiler binary the autocompile
|
||||
* command runs. So both map to the same in-runtime "code provider" (the
|
||||
* autocompile spawn) — see code_provider.c — and which compiler runs is chosen
|
||||
* here by the resolved backend.
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
OVERLAY_BACKEND_AUTO = 0, /* gcc if a gcc toolchain is present, else tcc */
|
||||
OVERLAY_BACKEND_GCC = 1, /* force spawn-gcc->DLL (dev / production shards) */
|
||||
OVERLAY_BACKEND_SLJIT = 2, /* DEPRECATED in-process sljit producer (gated off) */
|
||||
OVERLAY_BACKEND_TCC = 3, /* spawn-tcc->DLL (bundled, toolchain-free fallback) */
|
||||
OVERLAY_BACKEND_AUTO_NO_GCC = 4 /* dev/test: resolve like AUTO but pretend no gcc toolchain
|
||||
* (gcc shards still LOAD; gaps fill via tcc) — simulate a
|
||||
* toolchain-less user box on a dev machine. */
|
||||
} OverlayBackend;
|
||||
|
||||
/* Resolve the effective backend once at startup. `gcc_toolchain_available` is 1
|
||||
* when a gcc toolchain is actually reachable AND a compile command is wired (a
|
||||
* real dev/production box). Precedence: env PSX_OVERLAY_BACKEND > game.toml
|
||||
* [runtime] overlay_backend (`cfg`) > AUTO. AUTO -> gcc if the toolchain is
|
||||
* present, else tcc. AUTO_NO_GCC -> tcc regardless (the simulate-user mode).
|
||||
* The result is cached + logged. */
|
||||
OverlayBackend overlay_backend_resolve(const char *cfg, int gcc_toolchain_available);
|
||||
|
||||
/* The cached resolution (OVERLAY_BACKEND_AUTO until resolve() runs). */
|
||||
OverlayBackend overlay_backend_active(void);
|
||||
|
||||
/* "auto" | "gcc" | "sljit" | "tcc" | "auto-no-gcc". */
|
||||
const char *overlay_backend_name(OverlayBackend b);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OVERLAY_BACKEND_H */
|
||||
@@ -37,22 +37,9 @@ extern "C" {
|
||||
/* The native shard ABI — identical to overlay_loader's OverlayFn. */
|
||||
typedef void (*OverlaySljitFn)(CPUState *);
|
||||
|
||||
/* Backend selection policy (config + PSX_OVERLAY_BACKEND env override). */
|
||||
typedef enum {
|
||||
OVERLAY_BACKEND_AUTO = 0, /* gcc if a toolchain is configured, else sljit */
|
||||
OVERLAY_BACKEND_GCC = 1, /* force the spawn-gcc->DLL producer (dev) */
|
||||
OVERLAY_BACKEND_SLJIT = 2 /* force the in-process sljit producer */
|
||||
} OverlayBackend;
|
||||
|
||||
/* Resolve the active backend once at startup. autocompile_configured indicates
|
||||
* whether a gcc compile command is wired (game.toml overlay_autocompile_cmd).
|
||||
* Reads PSX_OVERLAY_BACKEND (auto|gcc|sljit) env; falls back to `cfg` (from
|
||||
* [runtime] overlay_backend) then AUTO. Result is logged and cached. */
|
||||
OverlayBackend overlay_backend_resolve(const char *cfg, int autocompile_configured);
|
||||
|
||||
/* The cached resolution (OVERLAY_BACKEND_AUTO until resolve() runs). */
|
||||
OverlayBackend overlay_backend_active(void);
|
||||
const char *overlay_backend_name(OverlayBackend b);
|
||||
/* 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);
|
||||
|
||||
@@ -155,6 +155,7 @@ set(PSXRECOMP_RUNTIME_SOURCES
|
||||
${PSXRECOMP_ROOT}/runtime/src/overlay_loader.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/overlay_compile_worker.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/overlay_sljit.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/overlay_backend.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/autocompile.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/code_provider.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/event_ring.c
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
|
||||
extern void overlay_loader_rescan(void);
|
||||
|
||||
static char s_cmd[1024];
|
||||
static char s_cmd[4096]; /* large: the runtime-constructed bundled tcc cmd has
|
||||
* many absolute paths (python+script+recompiler+tcc+...) */
|
||||
static char s_cwd[512];
|
||||
|
||||
enum { AC_IDLE = 0, AC_RUNNING = 1, AC_DONE = 2 };
|
||||
@@ -134,7 +135,7 @@ int autocompile_request(void) {
|
||||
|
||||
/* cmd.exe /C resolves the command via PATH and supports the relative
|
||||
* paths in the configured line (cwd = project root). */
|
||||
char full[1200];
|
||||
char full[4200];
|
||||
snprintf(full, sizeof(full), "cmd.exe /C %s", s_cmd);
|
||||
|
||||
STARTUPINFOA si;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "code_provider.h"
|
||||
#include "autocompile.h"
|
||||
#include "overlay_sljit.h"
|
||||
#include "overlay_backend.h"
|
||||
|
||||
/* ---- gcc provider: pass-through to the validated spawn->DLL->rescan path --- */
|
||||
/* The hook signatures match autocompile_* exactly, so we point straight at
|
||||
@@ -68,6 +69,11 @@ void code_provider_init(const char *cfg_backend, int gcc_configured) {
|
||||
* AUTO, so this maps cleanly to one of the two providers. It also logs the
|
||||
* decision (surfaced via the sljit_status debug command). */
|
||||
OverlayBackend b = overlay_backend_resolve(cfg_backend, gcc_configured);
|
||||
/* The "gcc" provider is really the spawn-a-compiler->DLL->rescan path; it is
|
||||
* compiler-agnostic (the actual compiler is whatever overlay_autocompile_cmd
|
||||
* runs — gcc OR tcc, selected in main.cpp by the resolved backend). So BOTH
|
||||
* gcc and tcc map to it. Only the deprecated in-process sljit producer is
|
||||
* separate. */
|
||||
s_active = (b == OVERLAY_BACKEND_SLJIT) ? &s_sljit : &s_gcc;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "overlay_capture.h"
|
||||
#include "code_provider.h"
|
||||
#include "overlay_sljit.h"
|
||||
#include "overlay_backend.h"
|
||||
#include "overlay_compile_worker.h"
|
||||
#include "cpu_state.h"
|
||||
#include "dma.h"
|
||||
|
||||
+65
-17
@@ -15,6 +15,7 @@
|
||||
#include "autocompile.h"
|
||||
#include "code_provider.h"
|
||||
#include "overlay_sljit.h"
|
||||
#include "overlay_backend.h"
|
||||
#include "gpu.h"
|
||||
#include "gpu_sw_renderer.h"
|
||||
#include "gpu_render.h"
|
||||
@@ -1980,24 +1981,71 @@ int main(int argc, char** argv) {
|
||||
* The background compile additionally needs a configured
|
||||
* command; autocompile_request() no-ops without one. */
|
||||
overlay_autocapture_set_enabled(1);
|
||||
if (gc.runtime.has_overlay_autocompile_cmd) {
|
||||
autocompile_configure(
|
||||
gc.runtime.overlay_autocompile_cmd.c_str(),
|
||||
gc.project_root.string().c_str());
|
||||
std::fprintf(stdout,
|
||||
"psxrecomp: overlay autocompile enabled\n");
|
||||
/* Resolve the overlay tier first so we wire the RIGHT compiler's
|
||||
* autocompile command. gcc is "available" only when a gcc cmd is
|
||||
* configured AND a gcc toolchain is actually reachable (a real
|
||||
* dev/production box). auto => gcc if so, else tcc; auto-no-gcc =>
|
||||
* tcc even with gcc present (simulate a toolchain-less user box).
|
||||
* env PSX_OVERLAY_BACKEND overrides. Tiers: static > gcc > tcc >
|
||||
* (sljit, deprecated/off) > interp. */
|
||||
const char *cfg_backend = gc.runtime.overlay_backend.empty()
|
||||
? nullptr : gc.runtime.overlay_backend.c_str();
|
||||
int gcc_avail = gc.runtime.has_overlay_autocompile_cmd
|
||||
&& autocompile_toolchain_available();
|
||||
OverlayBackend eff = overlay_backend_resolve(cfg_backend, gcc_avail);
|
||||
/* gcc and tcc run the IDENTICAL recompiler->C->DLL->load pipeline;
|
||||
* only the compiler binary differs. Wire the autocompile spawn with
|
||||
* the command for the resolved tier (tcc cmd for the tcc tier, gcc
|
||||
* cmd otherwise). gcc shards already on disk still LOAD either way
|
||||
* (the loader is compiler-blind), so a tcc box uses shipped gcc
|
||||
* shards first and fills the rest with tcc. */
|
||||
std::string built_tcc_cmd; /* runtime-constructed bundled tcc cmd */
|
||||
const std::string *ac_cmd = nullptr;
|
||||
if (eff == OVERLAY_BACKEND_TCC) {
|
||||
if (gc.runtime.has_overlay_autocompile_cmd_tcc) {
|
||||
ac_cmd = &gc.runtime.overlay_autocompile_cmd_tcc; /* explicit override (dev) */
|
||||
} else {
|
||||
/* PRODUCTION: construct the tcc autocompile cmd from the
|
||||
* self-contained toolchain bundled beside the exe
|
||||
* (<exe>/overlay_toolchain/ = embedded python + tcc +
|
||||
* recompiler + compile_overlays.py + runtime headers). No
|
||||
* system python or gcc required. */
|
||||
extern int g_psx_cps_mode;
|
||||
std::filesystem::path xd = exe_dir_from_argv(argv[0]);
|
||||
std::filesystem::path tk = xd / "overlay_toolchain";
|
||||
std::filesystem::path py = tk / "python" / "python.exe";
|
||||
if (std::filesystem::exists(py)) {
|
||||
built_tcc_cmd =
|
||||
py.string() + " " + (tk / "compile_overlays.py").string() +
|
||||
" --captures " + (xd / "overlay_captures.json").string() +
|
||||
" --game-toml " + std::string(game_config_path ? game_config_path : "game.toml") +
|
||||
" --recompiler " + (tk / "psxrecomp-game.exe").string() +
|
||||
" --runtime-include " + (tk / "include").string() +
|
||||
" --out-dir " + (xd / "cache").string() +
|
||||
(g_psx_cps_mode ? " --cps" : "") +
|
||||
" --compiler tcc --tcc " + (tk / "tcc" / "tcc.exe").string();
|
||||
ac_cmd = &built_tcc_cmd;
|
||||
std::fprintf(stdout,
|
||||
"psxrecomp: tcc tier using bundled toolchain (%s)\n",
|
||||
tk.string().c_str());
|
||||
} else {
|
||||
std::fprintf(stdout,
|
||||
"psxrecomp: tcc tier active but no bundled toolchain at %s "
|
||||
"(overlay gaps -> interpreter)\n", tk.string().c_str());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (gc.runtime.has_overlay_autocompile_cmd)
|
||||
ac_cmd = &gc.runtime.overlay_autocompile_cmd;
|
||||
}
|
||||
/* Resolve the Tier-2 codegen backend now that we know whether a
|
||||
* compile command is wired. auto => gcc only when a C compiler is
|
||||
* ACTUALLY reachable (a real dev box — not merely a configured
|
||||
* command string, which the shipped game.toml always carries),
|
||||
* else sljit (toolchain-less production); env PSX_OVERLAY_BACKEND
|
||||
* overrides. The active provider is what the capture/dispatch
|
||||
* spine produces code through. */
|
||||
code_provider_init(
|
||||
gc.runtime.overlay_backend.empty()
|
||||
? nullptr : gc.runtime.overlay_backend.c_str(),
|
||||
autocompile_configured() && autocompile_toolchain_available());
|
||||
if (ac_cmd) {
|
||||
autocompile_configure(ac_cmd->c_str(),
|
||||
gc.project_root.string().c_str());
|
||||
std::fprintf(stdout,
|
||||
"psxrecomp: overlay autocompile enabled (%s)\n",
|
||||
overlay_backend_name(eff));
|
||||
}
|
||||
code_provider_init(cfg_backend, gcc_avail);
|
||||
/* Now that the backend is resolved, apply the sljit live policy:
|
||||
* a toolchain-less (sljit) machine runs validated shards live so
|
||||
* it self-improves on the normal play path; PSX_OVERLAY_SLJIT_LIVE
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/* overlay_backend.c — compiler-neutral overlay tier-selection policy.
|
||||
* See overlay_backend.h. Moved out of overlay_sljit.c (which is now just the
|
||||
* deprecated sljit emitter): deciding gcc vs tcc vs sljit is not sljit's job. */
|
||||
|
||||
#include "overlay_backend.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static OverlayBackend s_active = OVERLAY_BACKEND_AUTO;
|
||||
static int s_resolved = 0;
|
||||
static char s_last_msg[256];
|
||||
|
||||
static void backend_log(const char *fmt, ...) {
|
||||
va_list ap; va_start(ap, fmt);
|
||||
vsnprintf(s_last_msg, sizeof(s_last_msg), fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
const char *overlay_backend_name(OverlayBackend b) {
|
||||
switch (b) {
|
||||
case OVERLAY_BACKEND_GCC: return "gcc";
|
||||
case OVERLAY_BACKEND_SLJIT: return "sljit";
|
||||
case OVERLAY_BACKEND_TCC: return "tcc";
|
||||
case OVERLAY_BACKEND_AUTO_NO_GCC: return "auto-no-gcc";
|
||||
default: return "auto";
|
||||
}
|
||||
}
|
||||
|
||||
static OverlayBackend parse_backend(const char *s, OverlayBackend dflt) {
|
||||
if (!s || !*s) return dflt;
|
||||
if (!strcmp(s, "gcc")) return OVERLAY_BACKEND_GCC;
|
||||
if (!strcmp(s, "sljit")) return OVERLAY_BACKEND_SLJIT;
|
||||
if (!strcmp(s, "tcc")) return OVERLAY_BACKEND_TCC;
|
||||
if (!strcmp(s, "auto-no-gcc")) return OVERLAY_BACKEND_AUTO_NO_GCC;
|
||||
if (!strcmp(s, "auto")) return OVERLAY_BACKEND_AUTO;
|
||||
return dflt;
|
||||
}
|
||||
|
||||
OverlayBackend overlay_backend_resolve(const char *cfg, int gcc_toolchain_available) {
|
||||
/* Precedence: env PSX_OVERLAY_BACKEND > game.toml [runtime] overlay_backend
|
||||
* (cfg) > AUTO. Tier order is static > gcc > tcc > (sljit, deprecated/off) >
|
||||
* interp. AUTO prefers gcc when a gcc TOOLCHAIN is actually present (a dev /
|
||||
* production box), else tcc (the bundled toolchain-free user fallback).
|
||||
* AUTO_NO_GCC forces the tcc branch even when gcc IS present — it simulates a
|
||||
* toolchain-less user box on a dev machine (gcc shards still LOAD via the
|
||||
* loader; only the gap-fill compiler is forced to tcc). */
|
||||
OverlayBackend want = parse_backend(getenv("PSX_OVERLAY_BACKEND"),
|
||||
parse_backend(cfg, OVERLAY_BACKEND_AUTO));
|
||||
OverlayBackend eff = want;
|
||||
if (want == OVERLAY_BACKEND_AUTO)
|
||||
eff = gcc_toolchain_available ? OVERLAY_BACKEND_GCC : OVERLAY_BACKEND_TCC;
|
||||
else if (want == OVERLAY_BACKEND_AUTO_NO_GCC)
|
||||
eff = OVERLAY_BACKEND_TCC;
|
||||
|
||||
s_active = eff;
|
||||
s_resolved = 1;
|
||||
backend_log("backend resolved: want=%s effective=%s (gcc_toolchain=%d)",
|
||||
overlay_backend_name(want), overlay_backend_name(eff),
|
||||
gcc_toolchain_available);
|
||||
return eff;
|
||||
}
|
||||
|
||||
OverlayBackend overlay_backend_active(void) { return s_active; }
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "overlay_api.h"
|
||||
#include "code_provider.h"
|
||||
#include "overlay_sljit.h"
|
||||
#include "overlay_backend.h"
|
||||
#include "crc32.h"
|
||||
#include "dirty_ram_interp.h"
|
||||
#include "interrupts.h"
|
||||
@@ -475,6 +476,19 @@ static int cache_idx_has_path(const char *path) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Dedup by FILENAME (<addr8>_<crc8>.dll = region+crc). Used so a tcc/ shard for a
|
||||
* region already covered by a gcc/ shard is skipped: scan_cache_dir scans gcc/
|
||||
* FIRST, so gcc always wins the tie (tier order gcc > tcc). Also subsumes the
|
||||
* per-path rescan-idempotence check (same dir => same filename). */
|
||||
static int cache_idx_has_basename(const char *fname) {
|
||||
for (int i = 0; i < s_cache_idx_count; i++) {
|
||||
const char *b = strrchr(s_cache_idx[i].path, '/');
|
||||
b = b ? b + 1 : s_cache_idx[i].path;
|
||||
if (strcmp(b, fname) == 0) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Canonical cache arch-abi tag (see SLJIT.md §4 — caches are namespaced per
|
||||
* backend AND per target so a Windows-x64 gcc DLL and, later, a same-OS arm64
|
||||
* or an sljit blob for the same fragment never comingle). compile_overlays.py
|
||||
@@ -525,7 +539,9 @@ static void scan_one_cache_dir(const char *dir) {
|
||||
if (s_cache_idx_count >= CACHE_IDX_CAP) break;
|
||||
char full[768];
|
||||
snprintf(full, sizeof(full), "%s/%s", dir, fd.cFileName);
|
||||
if (cache_idx_has_path(full)) continue; /* rescan idempotence */
|
||||
/* skip if this region+crc is already indexed (rescan idempotence, AND a
|
||||
* higher-priority namespace already covers it — gcc/ scanned before tcc/). */
|
||||
if (cache_idx_has_basename(fd.cFileName)) continue;
|
||||
CacheEntry *e = &s_cache_idx[s_cache_idx_count++];
|
||||
e->region_start = addr;
|
||||
snprintf(e->path, sizeof(e->path), "%s", full);
|
||||
@@ -544,10 +560,18 @@ static void scan_one_cache_dir(const char *dir) {
|
||||
* older flat / unversioned caches are simply ignored and regenerated.) */
|
||||
static void scan_cache_dir(void) {
|
||||
char dir[768];
|
||||
/* Tier order: scan gcc/ FIRST (highest native priority — the dev/production
|
||||
* shards), THEN tcc/ (the toolchain-free fallback). scan_one_cache_dir dedups
|
||||
* by filename (region+crc), so a tcc shard for a region a gcc shard already
|
||||
* covers is skipped — gcc always wins the tie. */
|
||||
snprintf(dir, sizeof(dir), "%s/%s/gcc/%s/cg%d_%08x",
|
||||
s_cache_dir, s_game_id, PSX_OVERLAY_ARCH_ABI, PSX_OVERLAY_CODEGEN_VER,
|
||||
(unsigned)PSX_OVERLAY_CODEGEN_HASH);
|
||||
scan_one_cache_dir(dir);
|
||||
snprintf(dir, sizeof(dir), "%s/%s/tcc/%s/cg%d_%08x",
|
||||
s_cache_dir, s_game_id, PSX_OVERLAY_ARCH_ABI, PSX_OVERLAY_CODEGEN_VER,
|
||||
(unsigned)PSX_OVERLAY_CODEGEN_HASH);
|
||||
scan_one_cache_dir(dir);
|
||||
}
|
||||
|
||||
/* ---- Persisted sljit shard cache (Stage 2, SLJIT_PERSIST_CACHE.md) -------- */
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#endif
|
||||
|
||||
/* ---- counters ---------------------------------------------------------- */
|
||||
static OverlayBackend s_active = OVERLAY_BACKEND_AUTO;
|
||||
/* RECURSION_BUG.md §25 — 1 when the build is continuation-passing (set by a
|
||||
* constructor in the generated CPS dispatch). Under CPS this JIT must emit the
|
||||
* tail-transfer contract: jr $ra publishes cpu->pc, and call-containing
|
||||
@@ -34,7 +33,6 @@ static OverlayBackend s_active = OVERLAY_BACKEND_AUTO;
|
||||
* nested psx_sljit_call. Defined in overlay_loader.c. */
|
||||
extern int g_psx_cps_mode;
|
||||
|
||||
static int s_resolved = 0;
|
||||
static int s_selftest_ok = -1; /* -1 = not run */
|
||||
static uint64_t s_compiles = 0;
|
||||
static uint64_t s_declines = 0;
|
||||
@@ -47,42 +45,8 @@ static void sljit_log(const char *fmt, ...) {
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/* ---- backend selection policy ----------------------------------------- */
|
||||
const char *overlay_backend_name(OverlayBackend b) {
|
||||
switch (b) {
|
||||
case OVERLAY_BACKEND_GCC: return "gcc";
|
||||
case OVERLAY_BACKEND_SLJIT: return "sljit";
|
||||
default: return "auto";
|
||||
}
|
||||
}
|
||||
|
||||
static OverlayBackend parse_backend(const char *s, OverlayBackend dflt) {
|
||||
if (!s || !*s) return dflt;
|
||||
if (!strcmp(s, "gcc")) return OVERLAY_BACKEND_GCC;
|
||||
if (!strcmp(s, "sljit")) return OVERLAY_BACKEND_SLJIT;
|
||||
if (!strcmp(s, "auto")) return OVERLAY_BACKEND_AUTO;
|
||||
return dflt;
|
||||
}
|
||||
|
||||
OverlayBackend overlay_backend_resolve(const char *cfg, int autocompile_configured) {
|
||||
/* Precedence: env PSX_OVERLAY_BACKEND > game.toml [runtime] overlay_backend
|
||||
* (cfg) > AUTO. AUTO prefers gcc when a compile command is wired (a dev
|
||||
* machine), else sljit (self-contained production / toolchain-less dev). */
|
||||
OverlayBackend want = parse_backend(getenv("PSX_OVERLAY_BACKEND"),
|
||||
parse_backend(cfg, OVERLAY_BACKEND_AUTO));
|
||||
OverlayBackend eff = want;
|
||||
if (want == OVERLAY_BACKEND_AUTO)
|
||||
eff = autocompile_configured ? OVERLAY_BACKEND_GCC : OVERLAY_BACKEND_SLJIT;
|
||||
|
||||
s_active = eff;
|
||||
s_resolved = 1;
|
||||
sljit_log("backend resolved: want=%s effective=%s (autocompile=%d)",
|
||||
overlay_backend_name(want), overlay_backend_name(eff),
|
||||
autocompile_configured);
|
||||
return eff;
|
||||
}
|
||||
|
||||
OverlayBackend overlay_backend_active(void) { return s_active; }
|
||||
/* Backend-selection policy moved to overlay_backend.{c,h} (it is compiler-
|
||||
* neutral — gcc/tcc/sljit/auto — and does not belong in the sljit emitter). */
|
||||
|
||||
int overlay_sljit_available(void) {
|
||||
/* Debug A/B knob: PSX_NO_SLJIT=1 makes sljit report unavailable, so the
|
||||
|
||||
@@ -1116,14 +1116,64 @@ def _toolchain_env(gcc: str):
|
||||
return env, None
|
||||
|
||||
|
||||
# ---- TinyCC (tcc) overlay compile — toolchain-free user fallback -----------
|
||||
# tcc has its own built-in linker (no ld/collect2) and bundles its own headers,
|
||||
# so it is self-contained. The only friction is that tcc 0.9.27 does not skip a
|
||||
# UTF-8 BOM, and the recompiler's runtime headers carry one — so we feed tcc a
|
||||
# BOM-stripped copy of the include dirs (used ONLY for tcc's -I; the codegen hash
|
||||
# still derives from the real headers, so the cache dir matches the runtime).
|
||||
_TCC_BOMFREE_INC = {} # orig include dir -> bom-stripped temp dir (memoized)
|
||||
|
||||
def _bom_free_incdir(d: str) -> str:
|
||||
d = os.path.abspath(d)
|
||||
if d in _TCC_BOMFREE_INC:
|
||||
return _TCC_BOMFREE_INC[d]
|
||||
out = tempfile.mkdtemp(prefix='tcc_inc_')
|
||||
for name in os.listdir(d):
|
||||
if not name.endswith('.h'):
|
||||
continue
|
||||
with open(os.path.join(d, name), 'rb') as f:
|
||||
data = f.read()
|
||||
if data[:3] == b'\xef\xbb\xbf':
|
||||
data = data[3:]
|
||||
with open(os.path.join(out, name), 'wb') as f:
|
||||
f.write(data)
|
||||
_TCC_BOMFREE_INC[d] = out
|
||||
return out
|
||||
|
||||
def _compile_dll_tcc(c_path: str, out_dll: str, include_dirs, flavor: int,
|
||||
tcc: str) -> bool:
|
||||
# strip a UTF-8 BOM off the overlay C itself (tcc 0.9.27 chokes on it)
|
||||
with open(c_path, 'rb') as f:
|
||||
data = f.read()
|
||||
if data[:3] == b'\xef\xbb\xbf':
|
||||
with open(c_path, 'wb') as f:
|
||||
f.write(data[3:])
|
||||
cmd = [tcc, '-shared',
|
||||
'-DPSX_OVERLAY_DLL_BUILD',
|
||||
f'-DPSX_OVERLAY_FLAVOR={int(flavor)}',
|
||||
c_path, '-o', out_dll]
|
||||
for d in include_dirs:
|
||||
cmd.append('-I' + _bom_free_incdir(d))
|
||||
print(f' compile (tcc): {" ".join(cmd)}')
|
||||
r = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
print(f' TCC COMPILE ERROR (exit {r.returncode}):\n{r.stderr or r.stdout}')
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def compile_dll(c_path: str, out_dll: str, include_dirs: list[str],
|
||||
gcc: str = 'gcc', flavor: int = 0) -> bool:
|
||||
gcc: str = 'gcc', flavor: int = 0,
|
||||
compiler: str = 'gcc', tcc: str = 'tcc') -> bool:
|
||||
import platform
|
||||
# Absolute OS-native paths: gcc invoked from cmd.exe (the runtime's
|
||||
# autocompile spawn) silently fails (exit 1, no stderr) on relative
|
||||
# forward-slash paths; absolute backslash paths work in every context.
|
||||
c_path = os.path.abspath(c_path)
|
||||
out_dll = os.path.abspath(out_dll)
|
||||
if compiler == 'tcc':
|
||||
return _compile_dll_tcc(c_path, out_dll, include_dirs, flavor, tcc)
|
||||
env, tc_dir = _toolchain_env(gcc)
|
||||
includes = [f'-I{os.path.abspath(d)}' for d in include_dirs]
|
||||
# On Windows, DLLs use PE relocations — -fPIC triggers GCC CRT init
|
||||
@@ -1172,6 +1222,12 @@ def main():
|
||||
help='cache root dir (default: build-dev/cache)')
|
||||
ap.add_argument('--gcc', default='gcc',
|
||||
help='GCC binary (default: gcc)')
|
||||
ap.add_argument('--compiler', choices=['gcc', 'tcc'], default='gcc',
|
||||
help='overlay shard compiler: gcc (default; dev/production, '
|
||||
'best-optimized) or tcc (bundled, toolchain-free user '
|
||||
'fallback). tcc shards land in the tcc/ cache namespace.')
|
||||
ap.add_argument('--tcc', default='tcc',
|
||||
help='TinyCC binary (used when --compiler tcc)')
|
||||
ap.add_argument('--force', action='store_true',
|
||||
help='recompile even if output already exists')
|
||||
ap.add_argument('--static', action='store_true',
|
||||
@@ -1221,7 +1277,7 @@ def main():
|
||||
# overlay_loader.c scan_cache_dir(). Pre-1.0: no legacy fallback.
|
||||
cg = codegen_ver(args.runtime_include)
|
||||
ch = codegen_hash(args.runtime_include)
|
||||
cache_dir = os.path.join(args.out_dir, game_id, 'gcc', cache_arch_abi(),
|
||||
cache_dir = os.path.join(args.out_dir, game_id, args.compiler, cache_arch_abi(),
|
||||
f'cg{cg}_{ch:08x}')
|
||||
os.makedirs(cache_dir, exist_ok=True)
|
||||
print(f'Cache dir: {cache_dir} (codegen ver {cg}, hash {ch:08x})')
|
||||
@@ -1393,7 +1449,8 @@ def main():
|
||||
include_dirs.append(p)
|
||||
|
||||
success = compile_dll(patched_c, dll_path, include_dirs,
|
||||
gcc=args.gcc, flavor=args.flavor)
|
||||
gcc=args.gcc, flavor=args.flavor,
|
||||
compiler=args.compiler, tcc=args.tcc)
|
||||
if success:
|
||||
# Emit the per-entry code-range manifest beside the DLL.
|
||||
# The loader keys it by the same filename stem with .ranges
|
||||
|
||||
Reference in New Issue
Block a user