diff --git a/recompiler/src/config_loader.cpp b/recompiler/src/config_loader.cpp index 2474b04e..cee12fe2 100644 --- a/recompiler/src/config_loader.cpp +++ b/recompiler/src/config_loader.cpp @@ -140,6 +140,11 @@ static RuntimeConfig parse_runtime_block(const toml::value& cfg, const fs::path& toml::find(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(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(runtime, "overlay_backend"); } diff --git a/recompiler/src/config_loader.h b/recompiler/src/config_loader.h index 0d69a7bc..27c87857 100644 --- a/recompiler/src/config_loader.h +++ b/recompiler/src/config_loader.h @@ -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 ---- diff --git a/runtime/include/overlay_backend.h b/runtime/include/overlay_backend.h new file mode 100644 index 00000000..23f26656 --- /dev/null +++ b/runtime/include/overlay_backend.h @@ -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 */ diff --git a/runtime/include/overlay_sljit.h b/runtime/include/overlay_sljit.h index 4f56be9d..8cbf3188 100644 --- a/runtime/include/overlay_sljit.h +++ b/runtime/include/overlay_sljit.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); diff --git a/runtime/runtime.cmake b/runtime/runtime.cmake index 9f2e273c..38e30d3d 100644 --- a/runtime/runtime.cmake +++ b/runtime/runtime.cmake @@ -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 diff --git a/runtime/src/autocompile.c b/runtime/src/autocompile.c index 9029f755..7e6b3e97 100644 --- a/runtime/src/autocompile.c +++ b/runtime/src/autocompile.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; diff --git a/runtime/src/code_provider.c b/runtime/src/code_provider.c index 7511cf50..ed5a9041 100644 --- a/runtime/src/code_provider.c +++ b/runtime/src/code_provider.c @@ -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; } diff --git a/runtime/src/debug_server.c b/runtime/src/debug_server.c index 424a7b0a..c799282b 100644 --- a/runtime/src/debug_server.c +++ b/runtime/src/debug_server.c @@ -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" diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index 3c59ba4a..ccaac327 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -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 + * (/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 diff --git a/runtime/src/overlay_backend.c b/runtime/src/overlay_backend.c new file mode 100644 index 00000000..31458abe --- /dev/null +++ b/runtime/src/overlay_backend.c @@ -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 +#include +#include +#include + +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; } diff --git a/runtime/src/overlay_loader.c b/runtime/src/overlay_loader.c index 23ca0cc3..2f4d2eb0 100644 --- a/runtime/src/overlay_loader.c +++ b/runtime/src/overlay_loader.c @@ -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 (_.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) -------- */ diff --git a/runtime/src/overlay_sljit.c b/runtime/src/overlay_sljit.c index 020debb0..676da248 100644 --- a/runtime/src/overlay_sljit.c +++ b/runtime/src/overlay_sljit.c @@ -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 diff --git a/tools/compile_overlays.py b/tools/compile_overlays.py index 7799ec80..f57f7351 100644 --- a/tools/compile_overlays.py +++ b/tools/compile_overlays.py @@ -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