Files
psxrecomp/runtime/include/autocompile.h
T
Matthew Stanley ab87cb8ef4 autocompile: report a degraded (interpreter-only) run on a channel that exists
A misconfigured overlay_autocompile_cmd silently degrades an entire session to
the interpreter. The diagnosis machinery for this already existed and was good
— autocompile_report_broken_once() fires after 3 consecutive failures and
explains exactly what to check — but it writes to fprintf(stdout), and the
shipped runtime links -mwindows: a GUI-subsystem binary with no console. The
warning is emitted into nothing on precisely the builds people run.

Observed cost: a fresh worktree ran 100% interpreted for a whole session
(runs=8 fails=8 shard_ok=0, dispatch_native=0) because game.toml named a
recompiler path the documented build recipe does not produce. The only symptom
was "it feels slow", and it invalidated a performance A/B before anyone thought
to query the counters by hand.

Three changes:

- Validate the command's path arguments at CONFIGURE time. The existing check
  resolves only the first token (the interpreter); it never looked at
  --recompiler, which is the argument that actually breaks — it is hardcoded
  per-title and disagrees with where the recipe builds. Paths now resolve
  against the child's working directory and are checked before any compile is
  attempted, rather than the truth emerging after three failed spawns.
  Advisory, not fatal: the runtime still runs exactly as before, just not
  silently.

- Record WHY in one string, exposed as degraded/degraded_reason in
  autocompile_status over the TCP debug server. Rule 3's answer rather than
  more printing. Keeps the FIRST cause, which is the useful one.

- Stamp it into psx_last_run_report.json. This is the part that prevents a
  repeat: a field you have to query is a field nobody queries. A degraded run
  now says so in its own artifact, so "was that measurement valid?" is
  answerable after the fact instead of having to be suspected.

test_autocompile_degraded (new, registered in CTest) builds the real failure
shape and asserts it is caught at configure time, that the reason names the
offending flag, that the first cause is retained, and that the reason is
escaped so it cannot break the JSON for every consumer.

Also fixes a pre-existing %d/LONG format mismatch on last_exit that this
change's argument shuffle surfaced; warnings on autocompile.c go 2 -> 1 (the
remaining array-bounds one in watch_thread is pre-existing and untouched).

NOT done here, deliberately: the underlying cause is that every title's
game.toml hardcodes a build-directory path (Tomba 2 names a worktree-relative
dir the recipe never creates; MMX6 hardcodes an absolute path into one
checkout). Fixing that means having CMake record the real recompiler location
and touching six title repos — its own change. This one makes the failure
impossible to miss.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 19:37:45 -07:00

75 lines
3.5 KiB
C

/* autocompile.h — background overlay compile for variant-capture automation
* (step 2.8). Spawns the configured compile command (compile_overlays.py
* invocation from game.toml [runtime] overlay_autocompile_cmd) after an
* automatic capture, collects its output into an in-memory ring (no log
* files — CLAUDE.md §3; read it via the autocompile_status TCP command),
* and on success has the emu thread rescan the overlay-DLL cache so the new
* variant goes native in-session, without a restart. */
#ifndef PSXRECOMP_AUTOCOMPILE_H
#define PSXRECOMP_AUTOCOMPILE_H
#ifdef __cplusplus
extern "C" {
#endif
/* Store the command line + working directory. Empty cmd disables. */
void autocompile_configure(const char *cmd, const char *cwd);
int autocompile_configured(void);
/* Pin the canonical overlay cache dir + captures file the LOADER actually uses
* (<exe>/cache and <exe>/overlay_captures.json — computed once in main.cpp).
* autocompile injects these into every spawned compile via the environment
* (PSX_OVERLAY_CACHE_DIR / PSX_OVERLAY_CAPTURES), so the WRITE cache and the
* READ captures can never diverge from where the loader reads — for every game,
* dev or prod, gcc or tcc. compile_overlays.py / coverage_vault.py honor these
* over any CLI --out-dir/--captures. This is the single source of truth for the
* cache location: no per-game config, no drift. */
void autocompile_set_cache_paths(const char *cache_dir, const char *captures);
/* Probe whether a C compiler is actually reachable on PATH (gcc/cc/clang) — the
* REAL "developer machine" signal, distinct from autocompile_configured() which
* only reports that a command STRING is set (the shipped game.toml always sets
* one). overlay_backend_resolve uses this so `auto` picks gcc only when the
* toolchain can really build a shard, else tcc (toolchain-less production).
* Memoized; safe to call repeatedly. */
int autocompile_toolchain_available(void);
/* 1 while a compile process is running. */
int autocompile_busy(void);
/* Kick a compile if configured and idle. Returns 1 if started. */
int autocompile_request(void);
/* Emu-thread tick: applies a finished compile (cache rescan on success).
* Must be called from the same thread that owns the overlay loader. */
void autocompile_poll_main(void);
/* Process-shutdown teardown, emu thread only: stops the publication pipeline,
* kills the compiler process tree (job object), and JOINS the watcher and
* preparer threads — waiting out any in-flight LoadLibrary rather than
* abandoning a thread inside the Windows loader lock (which can deadlock
* ExitProcess). Safe to call in any state, including mid-run; idempotent. */
void autocompile_shutdown(void);
/* JSON status blob for the debug server: state, run/fail counters, last
* exit code, and the output tail. Returns bytes written. */
int autocompile_status_json(char *out, int cap);
/* Why overlay autocompile cannot work, or NULL if nothing is known to be wrong.
*
* When this is non-NULL, no shard will ever be compiled and overlay execution
* stays in the interpreter — the run is valid but its performance is
* meaningless. Callers that report timings should say so rather than publish a
* number measured in that state.
*
* This exists because the equivalent warnings are written to stdout and the
* shipped runtime links -mwindows, so they reach nobody. `autocompile_status`
* carries this out over the TCP debug server as "degraded"/"degraded_reason". */
const char *autocompile_degraded_reason(void);
#ifdef __cplusplus
}
#endif
#endif /* PSXRECOMP_AUTOCOMPILE_H */