mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
77d036bfb6
Make the repo approachable for outside contributors and an IDE integrator:
- New contributor docs:
- docs/EXECUTION_MODEL.md — semi-technical tour of the static / native-overlay
/ interpreter execution model and how coverage gaps get plugged.
- docs/ARCHITECTURE.md — component-level view (recompiler, runtime, LLE+HLE
BIOS, overlay backend static->gcc->tcc, renderers, oracle model).
- docs/BUILDING.md — accurate dependency table, per-platform prereqs, build
steps, and the submodule + local-junction framework-linking setup.
- CONTRIBUTING.md — distilled core rules, verification expectations, PR flow.
- Declutter root: move ~14 internal dev-logs into docs/internal/ (history
preserved via rename) with an index README; repoint CLAUDE.md references.
- README: correct build recipe, LLE+HLE + overlay-backend summary, links to the
new docs.
- code_provider.c: update the stale backend comment to the real static->gcc->tcc
resolution (dead SLJIT.md reference removed).
- config_schema.md: drop the obsolete "once Phase A2/A3 ships --config" note.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
161 lines
8.6 KiB
Markdown
161 lines
8.6 KiB
Markdown
# The Execution Model — static, native-overlay, and interpreter
|
|
|
|
*A semi-technical tour of how PSXRecomp actually runs a PlayStation game. If you
|
|
only read one design doc, read this one.* For the low-level plumbing, see
|
|
[`ARCHITECTURE.md`](ARCHITECTURE.md).
|
|
|
|
## The one-sentence version
|
|
|
|
PSXRecomp turns a PS1 game into a **native program** by translating its MIPS code
|
|
to C ahead of time; the small amount of code it *can't* see ahead of time is
|
|
either compiled to native code the first time it appears, or run by a tiny
|
|
built-in interpreter until it is — so the game is always **correct**, and gets
|
|
**faster** the more it's played.
|
|
|
|
## Why a PS1 game can't just be "decompiled once"
|
|
|
|
A PlayStation game is not one fixed blob of code sitting in memory. The main
|
|
program (the `PS-X EXE` on the disc) is, but games are far bigger than the PS1's
|
|
2 MB of RAM, so they **stream code off the disc as you play**. A level's logic,
|
|
a boss's AI, a menu — these are loaded from the CD into a fixed region of RAM,
|
|
run, and then *overwritten* by the next chunk when you move on. These streamed
|
|
chunks are called **overlays**.
|
|
|
|
That creates a hard problem for any ahead-of-time recompiler:
|
|
|
|
- The main EXE exists on disc at build time → we can translate all of it.
|
|
- An overlay's code **does not exist anywhere at build time** in a form we can
|
|
find. It only appears in RAM at a specific moment during play, and the same
|
|
RAM addresses get reused by dozens of different overlays over a playthrough.
|
|
|
|
So "recompile the game" can never be a single up-front pass. PSXRecomp handles
|
|
this with **three tiers of execution**, in strict priority order.
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ Guest wants to run code at some address │
|
|
└─────────────────────────────────────────────────────────┘
|
|
│
|
|
┌─────────────────────┼─────────────────────┐
|
|
▼ ▼ ▼
|
|
1. STATIC (AOT) 2. NATIVE OVERLAY 3. INTERPRETER
|
|
Translated at Compiled to native Runs the guest's
|
|
build time from code the first time own instructions
|
|
the disc's EXE the overlay is seen, directly, as a
|
|
and the BIOS. then cached forever. correctness safety
|
|
The bulk of Fills the "streamed net for anything
|
|
execution. from disc" gap. not yet native.
|
|
── fastest ── ── fast ── ── slowest, always
|
|
correct ──
|
|
```
|
|
|
|
## Tier 1 — Static (ahead-of-time)
|
|
|
|
At build time the **recompiler** reads two things and translates their MIPS
|
|
machine code into C, function by function:
|
|
|
|
- the real PS1 **BIOS** (`SCPH1001.BIN`) — this becomes the kernel, and
|
|
- the game's **main executable** extracted from your disc.
|
|
|
|
That C is compiled and linked into the runtime as ordinary native functions, so
|
|
when the game calls one of them the CPU just runs native code — no emulation of
|
|
individual instructions. **This is the overwhelming majority of what executes**:
|
|
boot, the engine, the main loop, most gameplay systems. It is as fast as the C
|
|
compiler can make it.
|
|
|
|
Everything a pure AOT recompiler can see, it recompiles. The only things left
|
|
over are the two categories below.
|
|
|
|
## Tier 2 — Native overlays (compiled the moment they're discovered)
|
|
|
|
Overlays can't be seen at build time — but they *can* be seen the instant the
|
|
game loads one. PSXRecomp watches for that moment and turns each overlay into
|
|
native code so that, from then on, it runs at Tier-1 speed:
|
|
|
|
1. **Capture.** Every time the game DMAs code from the disc into the overlay RAM
|
|
window, the runtime records the exact bytes (before the game can modify them)
|
|
and the addresses that were actually executed. These go into a per-game file,
|
|
`overlay_captures.json`.
|
|
2. **Compile.** Those captured bytes are fed back through the *same recompiler*
|
|
used for the main EXE, producing C for that overlay. The C is compiled to a
|
|
small native library (a DLL) — using `gcc` in development, or a bundled
|
|
`tcc` (TinyCC) toolchain in shipped releases so players need no compiler
|
|
installed. Each library is keyed by the overlay's content hash.
|
|
3. **Cache & dispatch.** The compiled overlay lands in a content-addressed
|
|
cache. On this run and every future run, when that overlay loads, the runtime
|
|
finds the matching native library and dispatches into it **before** falling
|
|
back to the interpreter.
|
|
|
|
The practical effect: **the first time anyone visits an area, it may run in the
|
|
slow tier for a moment; after it's been captured and compiled, that area is
|
|
native for them — and, once the capture is shared into a release, for everyone.**
|
|
Coverage grows toward "100% native" the more the game is played.
|
|
|
|
## Tier 3 — The interpreter (the correctness safety net)
|
|
|
|
There is always a window between "an overlay just appeared in RAM" and "we have
|
|
native code for it." There's also a genuinely hard corner: code that is
|
|
**rewritten to different bytes on every load** (self-modifying / per-load
|
|
relocated), which by definition has no single ahead-of-time translation.
|
|
|
|
For both, PSXRecomp includes a **small MIPS interpreter** that runs the guest's
|
|
*own* instructions directly on the CPU register state. It is deliberately narrow:
|
|
|
|
- It runs **only** code that was installed into RAM at runtime (dirty pages) and
|
|
is not yet covered by a native overlay. It is **never** on the BIOS or
|
|
main-EXE path — those are always static.
|
|
- It is a **safety net and a coverage feeder**, never a substitute for
|
|
recompiling. If the interpreter is running something, that's a signal to
|
|
capture-and-compile it (Tier 2), not to leave it interpreted.
|
|
|
|
Crucially, this is **not** high-level emulation (HLE). The interpreter executes
|
|
the program's real instructions, exactly as the game's authors wrote them — the
|
|
*only* difference from static recompilation is where the instructions came from
|
|
(RAM-at-runtime vs disc-at-build-time). We never synthesize "the answer the code
|
|
would have produced."
|
|
|
|
## The guiding rule: precision over recall
|
|
|
|
The tiers are ordered so the system can only ever fail *toward being slow*, never
|
|
*toward being wrong*:
|
|
|
|
- Code we **haven't** compiled yet safely drops to the interpreter and gets
|
|
captured for next time — under-coverage **self-heals**.
|
|
- Code we might compile **wrong** would corrupt the machine — so native code is
|
|
dispatched *only* when its source RAM is provably unchanged, and a compiled
|
|
overlay is instantly revoked the moment the RAM it came from is overwritten.
|
|
|
|
> **The worst case is always performance, never correctness.** Anything not yet
|
|
> native simply runs interpreted, correctly.
|
|
|
|
## How the gaps actually get plugged
|
|
|
|
Two complementary loops close the gap toward full native coverage:
|
|
|
|
- **While you play (automatic).** The capture step runs continuously in the
|
|
background. New areas you reach are recorded and compiled to native code —
|
|
often within a minute, without interrupting play — and reused on every later
|
|
launch. Your discoveries live in `overlay_captures.json` next to the game.
|
|
- **In releases (shared head start).** A game release ships a `cache/` folder of
|
|
overlays already compiled from everywhere players have collectively visited,
|
|
so common areas are native from the very first moment you arrive.
|
|
|
|
The frontier of the project is pushing Tier-2 coverage up (so the interpreter
|
|
goes idle) and, eventually, handling the self-modifying corner by baking in the
|
|
relocation pattern at build time. Until then, that corner stays correctly
|
|
interpreted — a narrow, accepted cost, not a wall.
|
|
|
|
## Where this lives in the code
|
|
|
|
| Concern | Where |
|
|
|---|---|
|
|
| MIPS→C translation (static + overlay) | `recompiler/src/` (`code_generator.cpp`, `mips_decoder.cpp`, …) |
|
|
| Overlay capture | `runtime/src/overlay_capture.c` |
|
|
| Overlay compile pipeline | `tools/compile_overlays.py`, `runtime/src/overlay_backend.c`, `overlay_compile_worker.c` |
|
|
| Native overlay load/dispatch | `runtime/src/overlay_loader.c` |
|
|
| Runtime interpreter (dirty RAM) | `runtime/src/dirty_ram_interp.c` |
|
|
|
|
See [`ARCHITECTURE.md`](ARCHITECTURE.md) for how these connect to the hardware
|
|
simulation, and [`docs/FEATURES.md`](FEATURES.md) for the overlay-system feature
|
|
reference.
|