mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 10:19:47 -04:00
5ec343ab4f
`bios_hle` (default on) is the player-facing "skip the BIOS and go
straight to the game" flag, and it did nothing under the bundled
OpenBIOS: choosing OpenBIOS sat the player in the boot animation while
the identical build on retail SCPH-1001 went straight to the game.
Root cause was ordering, not policy. The tier has two axes with
different per-image requirements:
* boot-skip needs only `shell_entry_phys`, works under pure LLE
(nothing is synthesized — the shell call just returns
immediately), so any image exporting it can skip
* kernel-call needs `deliver_event_ret`, the kernel's own
DeliverEvent `$ra`, or a delivered callback returns to
the wrong PC
main.cpp correctly refused the call axis on an image without
`deliver_event_ret` by assigning `bios_hle = false` — and then derived
`boot_skip` from that already-mutated flag. OpenBIOS omits
`deliver_event_ret` (B0 semantics unvalidated) but DOES export
`shell_entry_phys`, so an available skip was cancelled by an unrelated
refusal, and one flag meant two things depending on a BIOS detail no
player can see.
Both axes are now decided by one pure, dependency-free function,
`psx_bios_hle_plan()` (runtime/src/bios_hle_plan.c): the boot decision
reads the REQUESTED `bios_hle`, each axis is gated on only the anchor it
needs, and refusals are reported at startup instead of downgrading
silently. `psx_bios_hle_configure()` clamps `boot_skip` to
`shell_entry_phys != 0` as a backstop so the banner and `hle_dump`
cannot overstate what can fire. The main.cpp local is renamed
`bios_hle_requested` and is no longer written back from the plan — the
`session_reboot` path re-enters that block, so folding a refusal into
the request would have made a netplay rematch differ from first launch.
Also in this pass:
* A BIOS profile's `[runtime]` block was parsed into
`BiosConfig::runtime` and read by NOTHING, and bios/OpenBIOS.toml
carried an inert `bios_hle = false` there that read as the reason HLE
was off on OpenBIOS. It was not. `load_bios_config` now REJECTS
`[runtime]` in a BIOS profile: a profile states facts about an image,
never preferences.
* `psx_icache_fastpath_test` had been unbuildable ("redefinition of
`psx_advance_cycles`" — the test's counting stub vs the header's
`static inline`), which blocked every runtime test registered after
it. It now builds with `PSX_OVERLAY_DLL_BUILD=1`, the seam
psx_cycles.h already provides for a TU supplying its own advance.
Tests: new `bios_hle_plan_test` pins the matrix, including a 64-case
sweep asserting the boot decision is independent of
`deliver_event_ret` and the call decision independent of
`shell_entry_phys`. Runtime ctest 15/15, recompiler ctest 30/30.
Validated on Ape Escape (SCUS-94423) built against this framework with
both backends linked, headless, all four cases:
OpenBIOS default bios_boot=skip to game; route=2 fire at vec 0x30000
ra=0xBFC06EA8 (the continuation OpenBIOS.toml
documents); in-game on screen
retail default bios_boot=skip to game; route=2 at vec 0x30000
ra=0xBFC0702C; in-game on screen; differs only in
bios_backend=HLE (LLE fallback)
PSX_BIOS_HLE=0 bios_boot=real intro; ring empty (no hook)
KEEP_INTRO=1 bios_boot=real intro; ring empty
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
123 lines
6.2 KiB
C
123 lines
6.2 KiB
C
/* bios_hle.h — opt-in High-Level Emulation tier for PSX BIOS kernel services.
|
|
*
|
|
* CLAUDE.md §0 AMENDMENT 2026-07-02 (the gbarecomp model, see the sibling
|
|
* gbarecomp project's src/runtime/bios_hle.h): the recompiled real
|
|
* BIOS (LLE) is the DEFAULT, the reference implementation, and the
|
|
* correctness/timing oracle. HLE is an *alternative backend*, selected
|
|
* per-game via `[runtime] bios_hle = true` in game.toml (PSX_BIOS_HLE env
|
|
* override). Every kernel service the HLE layer does not implement
|
|
* transparently falls through to the recompiled BIOS, so HLE is never
|
|
* load-bearing beyond what it covers and never becomes the oracle.
|
|
*
|
|
* Mechanism: psx_bios_hle_configure() installs (or leaves NULL) the dispatch
|
|
* hook g_psx_bios_hle_hook consulted at the top of every psx_dispatch_impl
|
|
* iteration (emitted by full_function_emitter.cpp), BEFORE any backend claims
|
|
* the target. The hook sees the pre-normalize physical address; when the
|
|
* target is a kernel service vector (0xA0/0xB0/0xC0, function number in $t1)
|
|
* and the service is implemented, the handler computes its effect directly on
|
|
* guest state (the REAL kernel structures in guest RAM — EvCB table etc., per
|
|
* docs/psx_bios_disasm.txt + the SCPH1001 kernel disassembly), charges an
|
|
* approximate cycle cost, and returns 1: the guest resumes at $ra with no
|
|
* BIOS dispatch. NULL hook (default) = pure LLE, byte-identical dispatch.
|
|
*
|
|
* Boot HLE: with the tier on (and no keep-intro opt-out) the hook also
|
|
* intercepts the first dispatch of the BIOS SHELL entry (RAM 0x30000 — the
|
|
* boot animation; LoadRunShell's indirect `0x80030000()` call always routes
|
|
* through the dispatcher) and returns immediately, so BIOS Main() proceeds
|
|
* straight to step 8 (SYSTEM.CNF + game EXE load) with kernel state built
|
|
* entirely by the REAL recompiled kernel init. The frontend runs unpaced
|
|
* (turbo) until game handoff. This is THE boot-skip mechanism; it deprecates
|
|
* the old fast_boot snapshot restore.
|
|
*
|
|
* The boot-skip is BIOS-INDEPENDENT and works under pure LLE. It needs only
|
|
* the image's shell_entry_phys anchor — nothing is synthesized, the shell call
|
|
* just returns immediately — so it fires identically on retail SCPH-1001 and
|
|
* on the bundled OpenBIOS, whose kernel-call HLE is separately (and correctly)
|
|
* refused for want of a DeliverEvent anchor. bios_hle_plan.h documents why the
|
|
* two axes must not be collapsed; deriving the skip from the granted call-HLE
|
|
* axis is exactly the bug that made the flag mean different things per BIOS.
|
|
* With the tier fully off, LLE plays the real intro.
|
|
*
|
|
* NO-STUBS STANDS: every handler here is a validated reimplementation of the
|
|
* documented kernel mechanism operating on the real guest structures — never
|
|
* a "return the answer" fake. Timing is approximate (documented limitation;
|
|
* LLE remains the timing oracle).
|
|
*
|
|
* Observability (CLAUDE.md rule 3 + the global ring-buffer rule): every hook
|
|
* decision is recorded in an always-on ring queryable over the TCP debug
|
|
* server ("hle_dump"), never armed, never polled.
|
|
*/
|
|
#ifndef PSXRECOMP_BIOS_HLE_H
|
|
#define PSXRECOMP_BIOS_HLE_H
|
|
|
|
#include <stdint.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
struct CPUState;
|
|
|
|
/* The dispatch hook slot the generated trampoline consults (defined in
|
|
* bios_hle.c, emitted-extern by full_function_emitter.cpp). NULL = pure LLE. */
|
|
extern int (*g_psx_bios_hle_hook)(struct CPUState* cpu, uint32_t phys);
|
|
|
|
/* Select the BIOS backend. Call at bring-up (and again on netplay rematch
|
|
* session_reboot), after config + env resolution, before psx_scheduler_run.
|
|
* Two independent axes — decide them with psx_bios_hle_plan() (bios_hle_plan.h)
|
|
* rather than open-coding the policy at the call site:
|
|
* call_hle — service implemented kernel calls in HLE ([runtime] bios_hle);
|
|
* requires the image's deliver_event_ret anchor
|
|
* boot_skip — one-shot shell intercept ((bios_hle && !keep_intro) OR the
|
|
* deprecated fast_boot alias, which skips boot only); requires
|
|
* only the image's shell_entry_phys anchor, and is clamped to it
|
|
* here so the reported state cannot overstate what can fire
|
|
* Clears the shell-skip latch so rematch can skip again. The hook is installed
|
|
* when either axis is on; both off = NULL = pure LLE. */
|
|
void psx_bios_hle_configure(int call_hle, int boot_skip);
|
|
|
|
int psx_bios_hle_enabled(void);
|
|
int psx_bios_hle_boot_skip_enabled(void);
|
|
const char* psx_bios_hle_backend_name(void); /* startup banner */
|
|
|
|
/* True while the HLE boot-skip is driving the frontend unpaced: boot skip
|
|
* enabled and the game entry PC has not yet been dispatched. main.cpp ORs
|
|
* this into the turbo predicate so kernel init + EXE load run at host speed. */
|
|
int psx_bios_hle_boot_turbo_active(void);
|
|
|
|
/* ── Always-on HLE ring (queried by debug_server "hle_dump") ─────────────── */
|
|
|
|
enum {
|
|
PSX_HLE_ROUTE_LLE = 0, /* vector call seen, fell through to recompiled BIOS */
|
|
PSX_HLE_ROUTE_HLE = 1, /* serviced in HLE */
|
|
PSX_HLE_ROUTE_BOOT = 2, /* boot shell-skip fired */
|
|
};
|
|
|
|
typedef struct PsxHleCallEntry {
|
|
uint64_t seq;
|
|
uint64_t cycle; /* psx_cycle_count at record time (first occurrence) */
|
|
uint64_t cycle_last; /* psx_cycle_count of the most recent collapsed repeat */
|
|
uint32_t vector; /* 0xA0/0xB0/0xC0, or 0x30000 for the boot skip */
|
|
uint32_t fn; /* $t1 function number (0 for boot skip) */
|
|
uint32_t a0, a1, a2, a3;
|
|
uint32_t ra;
|
|
uint32_t v0; /* result for HLE-serviced calls (0 otherwise) */
|
|
uint32_t repeat; /* consecutive identical calls collapsed into this entry */
|
|
uint32_t tcb; /* current guest thread: [[0x108]] at call time (0 if unset) */
|
|
uint8_t route; /* PSX_HLE_ROUTE_* */
|
|
uint8_t in_exc; /* recorded inside guest exception context */
|
|
} PsxHleCallEntry;
|
|
|
|
#define PSX_HLE_RING_CAP 16384 /* power of two */
|
|
|
|
/* Snapshot accessors: seq is the total number of records ever written; entry
|
|
* i (seq-CAP .. seq-1 valid window) returns the ring slot for that sequence. */
|
|
uint64_t psx_hle_ring_seq(void);
|
|
const PsxHleCallEntry* psx_hle_ring_entry(uint64_t seq_index);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* PSXRECOMP_BIOS_HLE_H */
|