Files
Alex Vanderveen fe403e6ce1 rollback patches
2026-07-31 17:14:35 -04:00

128 lines
6.5 KiB
C

/* psx_scheduler.h — deterministic TCB cooperative-thread scheduler (carve-out).
*
* Replaces the non-deterministic host-fiber bridge (traps.c
* psx_change_thread_fiber / psx_thread_fiber_entry, which maps each guest
* thread to a Win32/ucontext fiber and overloads cpu->pc=0 as a host-control
* signal) with a structured, deterministic scheduler that operates on the REAL
* guest TCB structures.
*
* Rationale (see HLE_SCHEDULER_CARVEOUT_PLAN.md + CLAUDE.md §0 AMENDMENT): the
* PS1 BIOS scheduler is pure-TCB — a thread's entire context (GPRs + guest SP +
* EPC) lives in its TCB; a "thread switch" is just changing which TCB
* dword_108->entry points to (ExceptionHandler 0xC80 / ReturnFromException
* 0xF40, docs/psx_bios_disasm.txt). There is NO stack switch on real hardware,
* so host fibers were never semantically required — they exist only because a
* recompiled infinite-loop function never returns to its native caller. Because
* guest control flow here is dispatch-based (tail-call trampoline + every-
* block-leader re-entry), a thread switch can instead be a deterministic
* unwind-to-scheduler + re-dispatch-the-target's-EPC.
*
* INVARIANT this design lives or dies on: guest thread context lives ONLY in
* CPUState / RAM / the TCB reg array — NEVER in a host fiber stack, generated
* C locals, a pc=0 sentinel, or a global resume PC. A yield/switch/RFE may
* unwind the host stack ONLY after all guest-visible state is committed and the
* resume PC is dispatchable, and only at a block-leader boundary.
*
* ── SCAFFOLDING (plan steps 1-2) ──────────────────────────────────────────
* This header + the matching definitions in traps.c are INERT: the host-fiber
* bridge (psx_change_thread_fiber) is still authoritative. Later steps wire the
* outer scheduler loop and structured escapes in to replace it.
*/
#ifndef PSX_SCHEDULER_H
#define PSX_SCHEDULER_H
#include <stdint.h>
#include <setjmp.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Structured scheduler-escape reasons — replaces the overloaded cpu->pc=0
* host-control sentinel. The outer scheduler loop reads the reason after each
* psx_dispatch returns to decide what to run next. */
typedef enum {
PSX_RUN_CONTINUE = 0, /* dispatch returned normally; keep running the current thread */
PSX_RUN_YIELD_TO_TCB, /* ChangeThread / cross-thread RFE: switch to g_sched_escape.target_tcb */
PSX_RUN_RESUME_CURRENT, /* same-thread RFE: re-dispatch the current thread at resume_pc */
PSX_RUN_GUEST_EXIT, /* a guest thread legitimately ended */
PSX_RUN_RETURN_TO_LOBBY, /* netplay soft-exit: tear down match, keep lobby WS */
PSX_RUN_FATAL, /* invariant violation — fail closed, never silently continue */
} psx_run_reason_t;
typedef struct {
psx_run_reason_t reason;
uint32_t target_tcb; /* for PSX_RUN_YIELD_TO_TCB */
uint32_t resume_pc; /* the dispatchable resume PC the escape commits */
} psx_sched_escape_t;
/* The single outer-scheduler escape target + its escape descriptor. A
* yield/switch/RFE commits all guest-visible state to CPUState/RAM/TCB, fills
* g_sched_escape, and longjmp(g_scheduler_jmpbuf, 1)s here. (Wired in a later
* step; defined inert in traps.c for now.) */
extern jmp_buf g_scheduler_jmpbuf;
extern psx_sched_escape_t g_sched_escape;
/* Deferred cooperative thread switch (Ape Escape memcard fix #2). Thin exports
* over the scheduler's private TCB save/restore + PCB[0] accessors, so the
* interrupt path can re-save a deferred thread cleanly and re-point the kernel
* current-thread pointer at the outermost dispatch boundary. See traps.c. */
struct CPUState;
void psx_sched_save_context(struct CPUState* cpu, uint32_t tcb, uint32_t resume_pc);
void psx_sched_set_current_tcb(struct CPUState* cpu, uint32_t tcb);
uint32_t psx_sched_current_tcb(struct CPUState* cpu);
/* Fail-closed guard: is `pc` a valid, dispatchable guest resume point? A TCB
* EPC / resume PC that is NOT dispatchable (0, the exception sentinel, or not a
* known function/re-enterable block-leader entry) is an invariant violation —
* the scheduler must fail loudly rather than dispatch into nothing (the old
* pc=0 silent fiber-recreate pathology). */
int psx_is_dispatchable(uint32_t pc);
/* The outer scheduler trampoline. Replaces the bare top-level psx_dispatch()
* call in main.cpp: dispatch the current thread until it yields via a
* structured escape (longjmp to g_scheduler_jmpbuf), then run whatever the
* escape selected. Returns ONLY on an abnormal top-level exit (the outermost
* dispatch saw cpu->pc==0 with no escape pending) — main.cpp's existing
* exit-diagnostic dump then runs. Forward-declared for main.cpp (C++). */
struct CPUState;
void psx_scheduler_run(struct CPUState* cpu);
/*
* Soft-exit netplay back to the lobby UI. Sets PSX_RUN_RETURN_TO_LOBBY and
* longjmps out of the scheduler when inside psx_scheduler_run; otherwise just
* latches the flag for the caller to observe. Safe from the netplay barrier.
*/
void psx_request_return_to_lobby(void);
int psx_return_to_lobby_requested(void);
void psx_clear_return_to_lobby(void);
/* Save-state restore: unwind to psx_scheduler_run and re-dispatch resume_pc
* (the restored CPUState's PC). Call on the scheduler fiber at a block-leader
* boundary, in_exception == 0. Never returns (longjmp). Arms top-level
* resume (see below) for the first post-resume frame. */
void psx_scheduler_resume_at(uint32_t resume_pc);
/* After resume_at, dispatch is top-level — there is no abandoned mid-block
* native chain under the resume PC. Sentinel same-thread RFE must not publish
* pc=0 / "continue live chain" (that is GUEST_EXIT). Active until the first
* finish_frame / explicit clear. Shared by savestate, selfcheck, and RB. */
int psx_scheduler_top_level_resume_active(void);
void psx_scheduler_top_level_resume_clear(void);
/* HLE-tier standing subsystem replacement (CLAUDE.md §0 amendments
* 2026-06-29 + 2026-07-02). 1 = deterministic TCB scheduler (default, both
* BIOS backends — the LLE host-fiber bridge it replaces is non-deterministic);
* 0 = legacy host-fiber bridge. Default from [runtime] hle_scheduler via
* psx_hle_scheduler_set_default (call before any dispatch); env
* PSX_HLE_SCHEDULER wins over config. Latched at first query; the mode cannot
* change mid-run. */
void psx_hle_scheduler_set_default(int on);
int psx_hle_scheduler_enabled(void);
#ifdef __cplusplus
}
#endif
#endif /* PSX_SCHEDULER_H */