From c10bad2ebbdc669b8e315cf41b38dbc5bb56fcad Mon Sep 17 00:00:00 2001 From: Alexandros Mandravillis Date: Wed, 16 Sep 2026 17:44:36 +0300 Subject: [PATCH] cli: stage the overlay toolchain beside every built product No setup package ever staged one, so overlay_cache = true alone left every streamed overlay on the interpreter. rebuild now stages the pinned Python, tcc, this pin's recompiler, headers and the build's codegen hash beside the normal and diagnostic products, and writes compiler.txt naming the compiler the wizard already has; the runtime treats that as a gcc-tier compiler, so a player with nothing on PATH gets -O2 shards. Verified on the NCII kit: gcc tier reported and four shards built with no compiler on PATH. (cherry picked from commit 03865e10f6ee198d58c47c74526df5db46bb35a9) --- psxrecomp_cli.py | 53 ++++++++++++++++++++++++++++++++++++++++++ runtime/src/main.cpp | 23 +++++++++++++++--- tools/release_stage.py | 14 ++++++++--- 3 files changed, 84 insertions(+), 6 deletions(-) diff --git a/psxrecomp_cli.py b/psxrecomp_cli.py index cc593f83..7f3d269b 100755 --- a/psxrecomp_cli.py +++ b/psxrecomp_cli.py @@ -1778,6 +1778,8 @@ def cmd_rebuild(args: argparse.Namespace, progress: ProgressReporter) -> int: if exe is None: progress.error(exe_err, code=EXIT_ERROR) return EXIT_ERROR + progress.phase("overlays", pct=0.93, message="Staging overlay toolchain beside the product...") + stage_overlay_toolchain_for_product(project_root, exe.parent, progress) prune_raw = (getattr(args, "prune_after", None) or "").strip() if prune_raw: @@ -1790,6 +1792,57 @@ def cmd_rebuild(args: argparse.Namespace, progress: ProgressReporter) -> int: return EXIT_OK +def stage_overlay_toolchain_for_product(project_root: Path, exe_dir: Path, progress) -> Optional[Path]: + """Stage overlay_toolchain/ beside a built product so the runtime's autocompile gate is + true for every player build (wave-5 F1), and name the compiler the wizard already has so + shards get an optimising compiler instead of tcc (F2). Best effort: a failure leaves the + product playable with overlays interpreted, and says so.""" + try: + fw = framework_root(project_root) + tools_dir = fw / "tools" + inc_dir = fw / "runtime" / "include" + game_emitter = find_psxrecomp_game(project_root) + if str(tools_dir) not in sys.path: + sys.path.insert(0, str(tools_dir)) + import release_stage # noqa: E402 + + dl_cache = project_root / ".cache" / "overlay-toolchain" + # Runtime DLLs only from the emitter's own directory, never from PATH: Git's + # mingw64 on PATH carries the MSVCRT runtime, which is the 0xC0000139 trap. + mingw_bin = game_emitter.parent if (game_emitter.parent / "libgcc_s_seh-1.dll").is_file() else None + tk = Path(release_stage.stage_toolchain( + str(exe_dir), str(game_emitter.parent), str(tools_dir), str(inc_dir), str(dl_cache), + mingw_bin=str(mingw_bin) if mingw_bin else None, log=progress.log)) + # The stale-recompiler check compares the emitter's codegen hash with the runtime's tag. + for cand in (exe_dir / "psxrecomp_codegen_include" / "overlay_codegen_hash.h", + inc_dir / "overlay_codegen_hash.h"): + if cand.is_file(): + shutil.copy2(cand, tk / "include" / "overlay_codegen_hash.h") + break + compiler = None + if sys.platform == "win32": + bin_dir = resolve_toolchain_bin(project_root) + if bin_dir and (bin_dir / "clang.exe").is_file(): + compiler = bin_dir / "clang.exe" + else: + for name in ("gcc", "cc", "clang"): + w = shutil.which(name) + if w: + compiler = Path(w) + break + if compiler: + (tk / "compiler.txt").write_text(str(compiler) + "\n", encoding="utf-8") + progress.log(f"overlay toolchain staged at {tk}; shard compiler: {compiler}") + else: + progress.log(f"overlay toolchain staged at {tk}; no optimising compiler found (tcc tier)") + return tk + except Exception as exc: # noqa: BLE001 + progress.log(f"WARNING: overlay toolchain staging failed; overlays will run interpreted: {exc}") + return None + + + + def cmd_pgo_train(args: argparse.Namespace, progress: ProgressReporter) -> int: args.force_pgo = True args.no_pgo = False diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index ca569218..b819287c 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -13530,6 +13530,20 @@ int main(int argc, char** argv) { const char *tk_tcc = "tcc"; #endif const bool tk_present = std::filesystem::exists(tk_py); + /* Wave-5 F2: the CLI names the compiler it already has (the wizard's pack on + * Windows, the native cc on POSIX) in overlay_toolchain/compiler.txt, so a player + * with nothing on PATH still gets optimised shards instead of tcc's. */ + std::string tk_compiler; + { + std::ifstream cf(tk_dir / "compiler.txt"); + std::string line; + if (cf.is_open() && std::getline(cf, line)) { + while (!line.empty() && (line.back() == '\r' || line.back() == '\n' || line.back() == ' ')) + line.pop_back(); + std::error_code cec; + if (!line.empty() && std::filesystem::exists(line, cec)) tk_compiler = line; + } + } auto build_toolchain_cmd = [&](const char *compiler) { auto cmd_quote = [](const std::string& s) { return std::string("\"") + s + "\""; @@ -13546,12 +13560,14 @@ int main(int argc, char** argv) { " --out-dir " + cmd_quote((tk_xd / "cache").string()) + (g_psx_cps_mode ? " --cps" : "") + " --compiler " + compiler; + if (std::string(compiler) == "gcc" && !tk_compiler.empty()) + c += " --gcc " + cmd_quote(tk_compiler); if (std::string(compiler) == "tcc") c += " --tcc " + cmd_quote((tk_dir / "tcc" / tk_tcc).string()); return c; }; int gcc_avail = (deferred_has_overlay_ac || tk_present) - && autocompile_toolchain_available(); + && (autocompile_toolchain_available() || !tk_compiler.empty()); OverlayBackend eff = overlay_backend_resolve(cfg_backend, gcc_avail); std::string built_tcc_cmd; std::string built_gcc_cmd; @@ -13579,8 +13595,9 @@ int main(int argc, char** argv) { built_gcc_cmd = build_toolchain_cmd("gcc"); ac_cmd = &built_gcc_cmd; std::fprintf(stdout, - "psxrecomp: gcc tier using bundled toolchain (%s) with gcc from PATH\n", - tk_dir.string().c_str()); + "psxrecomp: gcc tier using bundled toolchain (%s) with %s\n", + tk_dir.string().c_str(), + tk_compiler.empty() ? "gcc from PATH" : tk_compiler.c_str()); } } if (const char *e = std::getenv("PSX_OVERLAY_AUTOCOMPILE_CMD")) { diff --git a/tools/release_stage.py b/tools/release_stage.py index cb87a8f2..daac84e6 100644 --- a/tools/release_stage.py +++ b/tools/release_stage.py @@ -636,10 +636,18 @@ def stage_toolchain(stage, recomp_dir, recomp_tools, recomp_include, dl_cache, # Windows needs the mingw runtime beside the recompiler; a Linux build links # against the system libstdc++ that is already present. if platform_tag == 'win': - if not mingw_bin: - _die('--mingw-bin is required when staging a Windows toolchain') + # A recompiler built against a static CRT (the cmake-clang-v1 emitters) needs no + # runtime DLLs; a gcc-built one does. Copy whatever the named bin dir (or the + # emitter's own directory) holds, and say which case this is. + dll_src = mingw_bin or recomp_dir + copied = 0 for d in ('libgcc_s_seh-1.dll', 'libstdc++-6.dll', 'libwinpthread-1.dll'): - shutil.copy2(os.path.join(mingw_bin, d), os.path.join(toolchain, d)) + p = os.path.join(dll_src, d) + if os.path.isfile(p): + shutil.copy2(p, os.path.join(toolchain, d)) + copied += 1 + log('mingw runtime DLLs staged beside the recompiler: %d (from %s)%s' + % (copied, dll_src, '' if copied else ' -- assuming a static recompiler')) shutil.copy2(os.path.join(recomp_tools, 'compile_overlays.py'), toolchain) tool_inc = _mkdirs(os.path.join(toolchain, 'include'))