Add private dispatch metadata and bounded worker command capture

This commit is contained in:
Shokunin
2026-10-21 17:33:39 -07:00
parent 2bedbe9a97
commit efe800c9a4
98 changed files with 867 additions and 420 deletions
+12
View File
@@ -2080,3 +2080,15 @@ Guest-frame lookup merged multiple extra presentations and could not verify
HUD stability. Added duplicate-frame/freeze/eviction regression coverage and
retained legacy frame lookup. Six focused framework checks and all 21 title
checks pass on Windows and Linux, and both runtime builds link successfully.
2026-10-21 BRIEF-5 continuation: private execution now owns IRQ/DMA/timer,
I-cache and callback nesting state, with code-validation/MMIO metadata captured
beside RAM. Generated game and built-in BIOS expose native code lookup for a
sealed worker dispatcher; loadable BIOS ABI advances to 3. Added bounded,
thread-local immutable render command capture, independent of the live SPSC
producer. Concurrent memory/IRQ and capture/consumer fixtures pass on Windows
and Mac; Mac syntax checks pass for the changed device modules, mod runtime,
and regenerated OpenBIOS. Worker scheduling/plugin host-state/GL handoff remain
in progress. No Stage 3 acceptance or push; ordered route/performance gates have
not started for this implementation.
+1 -1
View File
@@ -1 +1 @@
74e1a7bb76ab90d30f3cf4d5627204d0184d88b9abc605b742e0f84934c4c464
7b08de0636dd809d712346f2a0169885543dce64379d7d977320bfd2dfa59857
+20 -2
View File
@@ -21,7 +21,7 @@ extern void psx_restore_state_escape(void);
extern void gte_execute(CPUState* cpu, uint32_t cmd);
extern void gte_write_data(CPUState* cpu, uint8_t reg, uint32_t val);
extern uint32_t gte_read_data(CPUState* cpu, uint8_t reg);
extern uint32_t g_debug_current_func_addr;
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
extern void debug_server_trace_dispatch(uint32_t func_addr);
/* BIOS HLE tier (CLAUDE.md §0 amendment 2026-07-02): null-by-default
* hook consulted at the top of every dispatch iteration, BEFORE any
@@ -9057,13 +9057,30 @@ static PsxRecompFunc psx_bios_hit_body(CPUState* cpu, uint32_t addr, uint32_t ke
return home;
}
extern int psx_kernel_bless_dispatchable(uint32_t phys);
static int psx_bios_execution_lookup(CPUState* cpu, uint32_t addr, PsxExecutionCode* code) {
if (psx_bios_try_native_call_stub(cpu, addr)) { code->function = 0; code->resume_pc = cpu->pc; return 2; }
const uint32_t key = normalize(addr);
int lo = 0, hi = (int)(sizeof(dispatch_table) / sizeof(dispatch_table[0])) - 1;
while (lo <= hi) {
int mid = (lo + hi) / 2;
if (dispatch_table[mid].addr == key) {
if (key - 0x500u < 0x46A0u && !psx_kernel_bless_dispatchable(key)) return 0;
code->function = psx_bios_hit_body(cpu, addr, key, dispatch_table[mid].func);
code->resume_pc = addr; return 1;
}
if (dispatch_table[mid].addr < key) lo = mid + 1; else hi = mid - 1;
}
return 0;
}
extern int dirty_ram_dispatch(CPUState* cpu, uint32_t addr, uint32_t stop_addr);
extern int dirty_ram_is_dirty(uint32_t phys);
extern int psx_kernel_bless_dispatchable(uint32_t phys);
extern void fntrace_record(CPUState* cpu, uint32_t target);
extern uint64_t g_dispatch_static_hits;
extern int g_psx_dispatch_depth; /* runtime-owned: shared dispatch state, not per-image */
extern PSX_THREAD_LOCAL int g_psx_dispatch_depth; /* execution-thread dispatch state */
static void psx_dispatch_check_return_boundary(CPUState* cpu, uint32_t stop_addr) {
if (stop_addr != 0u) {
@@ -9328,4 +9345,5 @@ const PsxBiosBackend OpenBIOS_psx_bios_backend = {
OpenBIOS_psx_bios_kernel_body_count,
OpenBIOS_psx_bios_kernel_patch_ranges,
OpenBIOS_psx_bios_kernel_patch_range_count,
psx_bios_execution_lookup,
};
+3 -1
View File
@@ -26,7 +26,9 @@ extern void cosim_instr(uint32_t pc);
extern void debug_server_log_call_entry(uint32_t func_addr);
extern void debug_server_cyc_observe(uint32_t block_leader_phys);
#endif
extern uint32_t g_debug_last_store_pc;
#ifndef PSX_OVERLAY_DLL_BUILD
extern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
#endif
/* Phase 1.0e-d: per-block guest cycle accounting.
* Compile generated code with -DPSX_ENABLE_BLOCK_CYCLES=1 to
* activate cycle advancement at every block leader. */
+1 -1
View File
@@ -3724,7 +3724,7 @@ void CodeGenerator::emit_runtime_externs(std::ostream& ss) const {
ss << "extern void gte_ws_set_suppress(int on); /* widescreen far-backdrop un-squash (gte.cpp) */\n";
if (!config_.overlay_mode && !config_.draw_distance_clamp_sites.empty())
ss << "extern uint32_t g_psx_draw_distance_clamp; /* [[draw_distance.clamp]] switch (draw_distance.c) */\n";
ss << "extern uint32_t g_debug_last_store_pc; /* exact PC of the executing SW/SH/SB — wtrace/readtrace producer attribution (debug_server.c) */\n\n";
ss << "#ifndef PSX_OVERLAY_DLL_BUILD\nextern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;\n#endif\n\n";
}
void CodeGenerator::emit_unaligned_helpers(std::ostream& ss, bool as_inline) const {
+21 -3
View File
@@ -1826,7 +1826,7 @@ void FullFunctionEmitter::emit_dispatch(
out += "extern void gte_execute(CPUState* cpu, uint32_t cmd);\n";
out += "extern void gte_write_data(CPUState* cpu, uint8_t reg, uint32_t val);\n";
out += "extern uint32_t gte_read_data(CPUState* cpu, uint8_t reg);\n";
out += "extern uint32_t g_debug_current_func_addr;\n";
out += "extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;\n";
out += "extern void debug_server_trace_dispatch(uint32_t func_addr);\n";
out += "/* BIOS HLE tier (CLAUDE.md \xC2\xA7""0 amendment 2026-07-02): null-by-default\n";
out += " * hook consulted at the top of every dispatch iteration, BEFORE any\n";
@@ -2229,6 +2229,23 @@ void FullFunctionEmitter::emit_dispatch(
out += " }\n";
out += " return home;\n";
out += "}\n\n";
out += "extern int psx_kernel_bless_dispatchable(uint32_t phys);\n";
out += "static int psx_bios_execution_lookup(CPUState* cpu, uint32_t addr, PsxExecutionCode* code) {\n";
out += " if (psx_bios_try_native_call_stub(cpu, addr)) { code->function = 0; code->resume_pc = cpu->pc; return 2; }\n";
out += " const uint32_t key = normalize(addr);\n";
out += " int lo = 0, hi = (int)(sizeof(dispatch_table) / sizeof(dispatch_table[0])) - 1;\n";
out += " while (lo <= hi) {\n";
out += " int mid = (lo + hi) / 2;\n";
out += " if (dispatch_table[mid].addr == key) {\n";
if (addr_model().has_kbless()) {
out += fmt::format(" if (key - 0x{:X}u < 0x{:X}u && !psx_kernel_bless_dispatchable(key)) return 0;\n",
addr_model().kbless_ram_lo(), addr_model().kbless_ram_hi() - addr_model().kbless_ram_lo());
}
out += " code->function = psx_bios_hit_body(cpu, addr, key, dispatch_table[mid].func);\n";
out += " code->resume_pc = addr; return 1;\n";
out += " }\n";
out += " if (dispatch_table[mid].addr < key) lo = mid + 1; else hi = mid - 1;\n";
out += " }\n return 0;\n}\n\n";
out += "extern int dirty_ram_dispatch(CPUState* cpu, uint32_t addr, uint32_t stop_addr);\n";
out += "extern int dirty_ram_is_dirty(uint32_t phys);\n";
@@ -2236,7 +2253,7 @@ void FullFunctionEmitter::emit_dispatch(
out += "extern void fntrace_record(CPUState* cpu, uint32_t target);\n";
out += "extern uint64_t g_dispatch_static_hits;\n";
out += "\n";
out += "extern int g_psx_dispatch_depth; /* runtime-owned: shared dispatch state, not per-image */\n\n";
out += "extern PSX_THREAD_LOCAL int g_psx_dispatch_depth; /* execution-thread dispatch state */\n\n";
// Return PCs are compared in full, segment included, as psx_call_contract
// does (docs/SEGMENT_AWARE_CODE.md §5.5): stop_addr is the link the call
// wrote, in its body's segment.
@@ -2536,6 +2553,7 @@ void FullFunctionEmitter::emit_dispatch(
" {0}psx_bios_kernel_body_count," "\n"
" {0}psx_bios_kernel_patch_ranges," "\n"
" {0}psx_bios_kernel_patch_range_count," "\n"
" psx_bios_execution_lookup," "\n"
"}};" "\n",
g_sym_prefix);
}
@@ -2639,7 +2657,7 @@ EmitStats FullFunctionEmitter::emit(
full_c += "extern void debug_server_log_call_entry(uint32_t func_addr);\n";
full_c += "extern void debug_server_cyc_observe(uint32_t block_leader_phys);\n";
full_c += "#endif\n";
full_c += "extern uint32_t g_debug_last_store_pc;\n";
full_c += "#ifndef PSX_OVERLAY_DLL_BUILD\nextern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;\n#endif\n";
full_c += "/* Phase 1.0e-d: per-block guest cycle accounting.\n";
full_c += " * Compile generated code with -DPSX_ENABLE_BLOCK_CYCLES=1 to\n";
full_c += " * activate cycle advancement at every block leader. */\n";
+5 -1
View File
@@ -32,7 +32,7 @@ bool emit_game_dispatch(const std::vector<GameDispatchUnit>& units,
const bool cps = units.front().codegen->cps_enabled();
std::ostringstream ds;
ds << "/* Generated by PSXRecomp - dynamic dispatch table */\n";
ds << "#include \"psx_runtime.h\"\n\n";
ds << "#include \"psx_runtime.h\"\n#include \"psx_execution.h\"\n\n";
ds << "extern void psx_check_interrupts_dispatch_entry(CPUState* cpu, uint32_t resume_pc);\n\n";
ds << "extern int dirty_ram_text_native_ok_ranges_from(const uint32_t* lo_len_pairs, uint32_t count, uint32_t exec_pc);\n\n";
ds << "extern int dirty_ram_text_native_ok_ranges(const uint32_t* lo_len_pairs, uint32_t count);\n\n";
@@ -289,6 +289,10 @@ bool emit_game_dispatch(const std::vector<GameDispatchUnit>& units,
ds << "}\n\n";
ds << "/* Maps PS1 address to compiled game code. Returns 1 if dispatched, 0 if unknown. */\n";
ds << "int psx_game_execution_lookup(uint32_t addr, PsxExecutionCode* code) {\n";
ds << " const PsxGameDispatchEntry* entry = psx_game_find_entry(addr);\n";
ds << " if (!entry || !psx_game_text_native_ok(addr)) return 0;\n";
ds << " code->function = entry->fn; code->resume_pc = entry->resume_pc; return 1;\n}\n\n";
ds << "int psx_dispatch_game_compiled(CPUState* cpu, uint32_t addr) {\n";
ds << " const PsxGameDispatchEntry* entry = psx_game_find_entry(addr);\n";
ds << " if (!entry) return 0;\n";
@@ -149,8 +149,8 @@ CONTRACT_HARNESS = r"""
#include "cpu_state.h"
#include <stdio.h>
#include <string.h>
int g_psx_call_bail;
uint64_t g_psx_bail_first, g_psx_bail_resolved, g_psx_bail_flattened, g_psx_bail_anomaly;
PSX_THREAD_LOCAL int g_psx_call_bail;
PSX_THREAD_LOCAL uint64_t g_psx_bail_first, g_psx_bail_resolved, g_psx_bail_flattened, g_psx_bail_anomaly;
void psx_bail_record(uint32_t a, uint32_t b, uint32_t c, uint32_t d) {
(void)a; (void)b; (void)c; (void)d;
}
+5
View File
@@ -1654,6 +1654,11 @@ if(BUILD_TESTING)
find_package(Threads REQUIRED)
target_link_libraries(render_thread_test PRIVATE Threads::Threads)
add_test(NAME render_thread_test COMMAND render_thread_test)
add_executable(render_capture_test tests/test_render_capture.c src/render_thread.c)
target_include_directories(render_capture_test PRIVATE include)
target_link_libraries(render_capture_test PRIVATE Threads::Threads)
add_test(NAME render_capture_test COMMAND render_capture_test)
# Present thread core ([video] present_thread): submission order is
# presentation order, slots are never composed while queued or on
# screen, done tokens reach the next composer, stop drains.
+1
View File
@@ -18,6 +18,7 @@
# overlay-cache hazard (same class as the "ws emission not in cg hash" gap).
set(PSXRECOMP_CODEGEN_HASH_SRCS
${PSXRECOMP_CODEGEN_HASH_ROOT}/runtime/include/psx_thread_local.h
${PSXRECOMP_CODEGEN_HASH_ROOT}/runtime/include/psx_execution.h
${PSXRECOMP_CODEGEN_HASH_ROOT}/recompiler/src/code_generator.cpp
${PSXRECOMP_CODEGEN_HASH_ROOT}/recompiler/src/mips_decoder.cpp
${PSXRECOMP_CODEGEN_HASH_ROOT}/recompiler/src/control_flow.cpp
+1 -2
View File
@@ -1,4 +1,3 @@
#include "psx_thread_local.h"
/*
* card_data_writes.h — always-on capture of where the BIOS stores
* incoming SIO data bytes during memory-card READ_DATA.
@@ -33,7 +32,7 @@ extern "C" {
typedef struct {
uint64_t seq; /* monotonic across all entries */
PSX_THREAD_LOCAL uint64_t psx_cycle_count;
uint64_t psx_cycle_count;
uint32_t addr; /* dest physical RAM address */
uint32_t store_pc; /* g_debug_last_store_pc when stored */
uint32_t func_addr; /* g_debug_current_func_addr */
+1 -2
View File
@@ -1,4 +1,3 @@
#include "psx_thread_local.h"
/*
* card_read_summary.h — non-evicting evidence ring for the first 32
* completed memory-card reads.
@@ -27,7 +26,7 @@ extern "C" {
typedef struct {
uint64_t seq; /* monotonic; first record = 0 */
PSX_THREAD_LOCAL uint64_t psx_cycle_count; /* guest cycle clock at completion */
uint64_t psx_cycle_count; /* guest cycle clock at completion */
uint32_t current_func; /* g_debug_current_func_addr at MC_READ_END */
uint32_t last_store_pc; /* g_debug_last_store_pc at MC_READ_END */
uint16_t sector;
+7 -6
View File
@@ -1,4 +1,5 @@
/* ============================================================================
#include "psx_thread_local.h"
/* ============================================================================
* cpu_state.h — Phase 2 runtime version
* ----------------------------------------------------------------------------
* This is the RUNTIME version of the CPUState header. It has the same struct
@@ -315,11 +316,11 @@ static inline uint32_t *psx_overlay_last_store_pc(void) {
}
#define g_debug_last_store_pc (*psx_overlay_last_store_pc())
#else
extern int g_psx_call_bail;
extern uint64_t g_psx_bail_first; /* contract violations detected */
extern uint64_t g_psx_bail_resolved; /* unwinds resolved at a call site */
extern uint64_t g_psx_bail_flattened; /* unwinds flattened at outermost */
extern uint64_t g_psx_bail_anomaly; /* bail flag seen where impossible */
extern PSX_THREAD_LOCAL int g_psx_call_bail;
extern PSX_THREAD_LOCAL uint64_t g_psx_bail_first; /* contract violations detected */
extern PSX_THREAD_LOCAL uint64_t g_psx_bail_resolved; /* unwinds resolved at a call site */
extern PSX_THREAD_LOCAL uint64_t g_psx_bail_flattened; /* unwinds flattened at outermost */
extern PSX_THREAD_LOCAL uint64_t g_psx_bail_anomaly; /* bail flag seen where impossible */
#endif
/* Deduped wild-return source ledger (traps.c). Not present in overlay DLLs
+4 -3
View File
@@ -1,4 +1,5 @@
/*
#include "psx_thread_local.h"
/*
* debug_server.h -- TCP debug server for PSX recomp v4
*
* Non-blocking TCP server on localhost (default port 4370).
@@ -127,7 +128,7 @@ int debug_server_is_connected(void);
/* Current dispatch target — set by psx_dispatch() before each call.
* Captured by the write-trace hook for function attribution. */
extern uint32_t g_debug_current_func_addr;
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
/* Multi-range write-trace check. Called from memory.c write paths.
* Iterates up to 8 configurable address ranges; records matching
@@ -157,7 +158,7 @@ void debug_server_log_call_entry_fn(uint32_t func_addr);
* store (sb/sh/sw/swl/swr/swc2). Read by SIO/MMIO write handlers to
* attribute the writing instruction without needing a full call-stack
* walk. Stale across non-store instructions but every store updates it. */
extern uint32_t g_debug_last_store_pc;
extern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
/* SIO write PC tracer ring — records (pc, addr, value, byte_seq) for every
* write to SIO_DATA / SIO_CTRL / SIO_STAT. Always-on; pulled via TCP cmd
+6 -1
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* psx_bios_backend.h — the routing seam between the runtime and whichever
* recompiled BIOS is active.
*
@@ -25,6 +26,7 @@
#include <stdint.h>
#include "psx_bios_image.h"
#include "psx_execution.h"
#ifdef __cplusplus
extern "C" {
@@ -56,10 +58,13 @@ typedef struct PsxBiosBackend {
* each range's hi. Null/0 for an image that declares no slots. */
const PsxKernelPatchRange *kernel_patch_ranges;
uint32_t kernel_patch_range_count;
/* Side-effect-free code selection for an isolated execution context.
* 1: function/resume_pc; 2: native vector instructions ran and set pc. */
int (*execution_lookup)(struct CPUState *, uint32_t, PsxExecutionCode *);
} PsxBiosBackend;
/* Dispatch nesting depth, shared by every backend (psx_bios_backend.c). */
extern int g_psx_dispatch_depth;
extern PSX_THREAD_LOCAL int g_psx_dispatch_depth;
/* The backend in use. Null before psx_bios_select() runs; every forwarder and
* every consumer of psx_bios_image depends on it being set first. */
+1 -1
View File
@@ -84,7 +84,7 @@ struct CPUState;
struct OverlayCallbacks;
struct PsxBiosBackend;
#define PSX_BIOS_MODULE_ABI_VERSION 2
#define PSX_BIOS_MODULE_ABI_VERSION 3
/* Combined tag: ABI version (low 16) | codegen flavor (high 16), like
* PSX_OVERLAY_ABI_TAG. The flavor comes from the same -DPSX_OVERLAY_FLAVOR
+2 -2
View File
@@ -40,9 +40,9 @@ static uint32_t s_mod_guest_count_dummy;
* mappings): `extern int g_psx_dispatch_depth;` became
* `extern int (*g_psx_bm_dispatch_depth_p);`, and so on. Defined here. */
int (**g_psx_bm_hle_hook_p)(CPUState *cpu, uint32_t phys) = &s_hle_hook_dummy;
int *g_psx_bm_dispatch_depth_p = &s_depth_dummy;
PSX_THREAD_LOCAL int *g_psx_bm_dispatch_depth_p = &s_depth_dummy;
uint64_t *g_psx_bm_static_hits_p = &s_hits_dummy;
uint32_t *g_psx_bm_current_func_addr_p = &s_func_addr_dummy;
PSX_THREAD_LOCAL uint32_t *g_psx_bm_current_func_addr_p = &s_func_addr_dummy;
uint64_t *g_psx_bm_bail_flattened_p = &s_flattened_dummy;
uint32_t *g_psx_bm_ram_mask_p = &s_ram_mask_dummy;
uint64_t *g_psx_bm_frame_count_p = &s_frame_count_dummy;
+22
View File
@@ -0,0 +1,22 @@
#ifndef PSX_EXECUTION_H
#define PSX_EXECUTION_H
#include <stdint.h>
#include "psx_thread_local.h"
#ifdef __cplusplus
extern "C" {
#endif
struct CPUState;
typedef struct PsxExecutionCode {
void (*function)(struct CPUState *);
uint32_t resume_pc;
} PsxExecutionCode;
typedef void (*PsxExecutionDispatch)(struct CPUState *, uint32_t, uint32_t);
typedef void (*PsxExecutionAbort)(const char *, uint32_t);
/* A private execution supplies its sealed dispatch table and abort landing.
* NULL selects the ordinary runtime. Never replace these on another thread. */
extern PSX_THREAD_LOCAL PsxExecutionDispatch g_psx_execution_dispatch;
extern PSX_THREAD_LOCAL PsxExecutionAbort g_psx_execution_abort;
#ifdef __cplusplus
}
#endif
#endif
+3 -2
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
#ifndef PSXRECOMP_PSX_ICACHE_H
#define PSXRECOMP_PSX_ICACHE_H
@@ -7,8 +8,8 @@
extern "C" {
#endif
extern uint32_t g_psx_icache_tv[1024];
extern int g_psx_icache_active;
extern PSX_THREAD_LOCAL uint32_t g_psx_icache_tv[1024];
extern PSX_THREAD_LOCAL int g_psx_icache_active;
extern int g_ls_replay_active;
void psx_icache_reset(void);
void psx_icache_fetch(CPUState *cpu, uint32_t addr);
+9
View File
@@ -57,6 +57,15 @@ void memory_backing_destroy(PsxMemoryBacking *backing);
void memory_backing_capture(PsxMemoryBacking *destination);
PsxMemoryBacking *memory_backing_bind(PsxMemoryBacking *backing);
extern PSX_THREAD_LOCAL uint8_t *g_psx_ram;
/* Code-validity and MMIO metadata accompanies the backing snapshot. Capture
* on the source owner; bind only on a dedicated worker after backing_bind.
* The active code image remains immutable until that worker has stopped. */
typedef struct PsxMemoryExecutionState PsxMemoryExecutionState;
PsxMemoryExecutionState *memory_execution_create(void);
void memory_execution_destroy(PsxMemoryExecutionState *state);
void memory_execution_capture(PsxMemoryExecutionState *state);
void memory_execution_bind(const PsxMemoryExecutionState *state, const uint32_t *status);
int psx_ram_8mb_active(void);
void psx_ram_reset_size_request(void);
/* memory_init(): latch the requested geometry for this boot. */
+15
View File
@@ -89,6 +89,21 @@ int rt_on_render_thread(void);
void *rt_cmd_begin(uint16_t op, uint16_t flags, uint32_t payload_bytes);
void rt_cmd_commit(void);
/* A dedicated guest worker records into its own bounded list. It never writes
* the SPSC ring. Ownership transfers only after end; the consumer executes a
* sealed list on the render thread, or drops it when its generation is late.
* Allocation happens before publication, never while guest code is running. */
typedef struct RtCommandList RtCommandList;
typedef void (*RtCaptureAbort)(const char *reason);
RtCommandList *rt_list_create(size_t capacity);
void rt_list_destroy(RtCommandList *list);
void rt_capture_begin(RtCommandList *list, RtCaptureAbort failure);
void rt_capture_end(void);
int rt_capture_active(void);
size_t rt_list_bytes(const RtCommandList *list);
void rt_list_execute(const RtCommandList *list, RtExecFn exec, void *user);
/* Close a guest frame: enqueue a frame marker, then block while more than
* max_frames closed frames are unconsumed. */
void rt_frame_end(void);
+2 -2
View File
@@ -58,8 +58,8 @@ static double boot_state_mono_ms(void) {
extern uint8_t* memory_get_ram_ptr(void);
extern uint32_t memory_get_ram_bytes(void);
extern uint8_t* memory_get_scratchpad_ptr(void);
extern uint32_t i_stat;
extern uint32_t i_mask;
extern PSX_THREAD_LOCAL uint32_t i_stat;
extern PSX_THREAD_LOCAL uint32_t i_mask;
extern PSX_THREAD_LOCAL uint64_t psx_cycle_count;
extern void timers_get_snapshot(uint16_t counter[3], uint32_t mode[3],
uint16_t target[3], int32_t irq_line[3],
+3 -2
View File
@@ -1,11 +1,12 @@
#include "psx_thread_local.h"
/* card_data_writes.c — capture BIOS RAM destination of card data bytes. */
#include "card_data_writes.h"
#include "psx_cycles.h"
#include <string.h>
extern uint32_t g_debug_current_func_addr;
extern uint32_t g_debug_last_store_pc;
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
extern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
/* "Armed" byte from the most recent SIO_RX_DATA read inside a card
* data phase. The next matching RAM write captures it. */
+3 -2
View File
@@ -1,11 +1,12 @@
#include "psx_thread_local.h"
/* card_read_summary.c — non-evicting first-32 completed card reads. */
#include "card_read_summary.h"
#include "psx_cycles.h"
#include <string.h>
extern uint32_t g_debug_current_func_addr;
extern uint32_t g_debug_last_store_pc;
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
extern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
static CardReadSummary s_ring[CARD_READ_SUMMARY_CAP];
static uint32_t s_count = 0;
+4 -3
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/*
* cdrom.c — PS1 CD-ROM controller (adapted from psxrecomp-v3)
*
@@ -53,11 +54,11 @@ extern uint32_t iso_cdda_track_pregap_lba(void* handle, int track);
extern int iso_cdda_track_is_audio(void* handle, int track);
/* I_STAT owned by memory.c — set bit 2 for CDROM IRQ */
extern uint32_t i_stat;
extern PSX_THREAD_LOCAL uint32_t i_stat;
/* Central IRQ-raise choke point (interrupts.c) — also records the device ring. */
extern void psx_irq_raise(uint32_t bit, uint32_t detail);
extern uint32_t g_debug_current_func_addr;
extern uint32_t g_debug_last_store_pc;
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
extern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
extern uint64_t s_frame_count;
/* CD-ROM state */
+1 -1
View File
@@ -36,7 +36,7 @@ typedef int sock_t;
#define COSIM_SLEEP(ms) usleep((ms)*1000)
#endif
extern uint32_t i_stat, i_mask;
extern PSX_THREAD_LOCAL uint32_t i_stat, i_mask;
extern PSX_THREAD_LOCAL uint64_t psx_cycle_count;
/* EXPERIMENTAL input-driven cosim: held pad state applied every frame by the headless
+2 -2
View File
@@ -33,8 +33,8 @@ static inline uint64_t fnv_u64(uint64_t h, uint64_t v) { return fnv(h, &v, 8); }
extern CPUState *debug_cpu_ptr;
extern uint8_t *memory_get_ram_ptr(void);
extern uint8_t *memory_get_scratchpad_ptr(void);
extern uint32_t i_stat;
extern uint32_t i_mask;
extern PSX_THREAD_LOCAL uint32_t i_stat;
extern PSX_THREAD_LOCAL uint32_t i_mask;
extern PSX_THREAD_LOCAL uint64_t psx_cycle_count;
extern uint32_t dirty_ram_get_bitmap_word(uint32_t word_index);
extern uint32_t dirty_ram_get_bitmap_word_count(void);
+4 -3
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* crash_trace.c — unified crash diagnostic dump.
*
* Writes a single JSON report file (psx_last_run_report.json) on:
@@ -94,15 +95,15 @@ extern int psx_netplay_is_resimulating(void);
extern uint64_t s_frame_count;
/* Globals from debug_server.c we want to capture. */
extern uint32_t g_debug_current_func_addr;
extern uint32_t g_debug_last_store_pc;
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
extern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
/* Native dispatch nesting depth (generated/SCPH1001_dispatch.c). Incremented per
* nested psx_dispatch_call, decremented on return. A huge value at crash time is
* the direct fingerprint of runaway recursion (the host C stack mirrors the guest
* call graph, so an unbounded call chain overflows it -> STATUS_STACK_OVERFLOW
* 0xC00000FD). Pairs with the dirty_block tail, which names the recursing PCs. */
extern int g_psx_dispatch_depth;
extern PSX_THREAD_LOCAL int g_psx_dispatch_depth;
/* Dispatch ring — accessor wrappers exported by debug_server.c. */
#define DISPATCH_TRACE_CAP (1 << 16)
+8 -7
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/*
* debug_server.c -- TCP debug server for PSX recomp v4
*
@@ -119,8 +120,8 @@
#include "debug_socket_io.h"
/* ---- Externs from runtime ---- */
extern uint32_t i_stat;
extern uint32_t i_mask;
extern PSX_THREAD_LOCAL uint32_t i_stat;
extern PSX_THREAD_LOCAL uint32_t i_mask;
/* Memory access (from memory.c) */
extern uint32_t psx_read_word(uint32_t addr);
@@ -491,10 +492,10 @@ static struct { uint32_t lo, hi; } s_wtrace_trans_ranges[WTRACE_TRANS_MAX_RANGES
static int s_wtrace_trans_range_count = 0;
/* Function attribution global — set by psx_dispatch() before each call. */
uint32_t g_debug_current_func_addr = 0;
PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr = 0;
/* Last store PC — set by recompiler emitter before every store instruction. */
uint32_t g_debug_last_store_pc = 0;
PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc = 0;
/* Static dispatch hit counter — incremented by generated dispatch code on
* every binary-search hit (i.e. successfully dispatched to static C). */
@@ -11391,9 +11392,9 @@ static void wtrace_fill_entry(WriteTraceEntry *e, uint64_t seq,
uint32_t phys, uint32_t old_val,
uint32_t new_val, uint8_t width)
{
extern int g_dma_exec_depth; /* >0 while a DMA moves data (dma.c) */
extern int g_dma_cur_ch; /* in-flight DMA channel, or -1 */
extern uint32_t g_dma_initiator_pc; /* guest PC that kicked the DMA */
extern PSX_THREAD_LOCAL int g_dma_exec_depth; /* >0 while a DMA moves data (dma.c) */
extern PSX_THREAD_LOCAL int g_dma_cur_ch; /* in-flight DMA channel, or -1 */
extern PSX_THREAD_LOCAL uint32_t g_dma_initiator_pc; /* guest PC that kicked the DMA */
uint32_t ra = debug_cpu_ptr ? debug_cpu_ptr->gpr[31] : 0;
e->seq = seq;
e->addr = phys;
+9 -9
View File
@@ -283,8 +283,8 @@ static inline void exec_pc_table_record(uint32_t pc) {
}
/* From debug_server.c — keep our outer-frame attribution coherent. */
extern uint32_t g_debug_current_func_addr;
extern uint32_t g_debug_last_store_pc;
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
extern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
/* Execution-mode tag for the event-timeline ring: 1 while a dirty_ram_dispatch
* call is on the stack. Cleared defensively at the psx_check_interrupts longjmp
@@ -1128,8 +1128,8 @@ int dirty_ram_re954_json(char *out, int cap) {
* so the real driver address is not pre-assumed. NB: there is no due-cycle event
* scheduler in this build (psx_cycles.c), so "next scheduled event cycle" is N/A;
* cycles-advanced-per-frame + I_STAT/I_MASK cover event progress instead. */
extern uint32_t i_stat; /* owned by memory.c */
extern uint32_t i_mask;
extern PSX_THREAD_LOCAL uint32_t i_stat; /* owned by memory.c */
extern PSX_THREAD_LOCAL uint32_t i_mask;
enum { XOP_JAL = 0, XOP_JALR = 1, XOP_JR = 2, XOP_J = 3, XOP_DD = 4, XOP_BR = 5,
XOP_RES = 6 /* watched-target call RESOLUTION: site = path code (XRES_*),
@@ -2570,7 +2570,7 @@ static int dirty_ram_dispatch_inner(CPUState* cpu, uint32_t addr, uint32_t stop_
* still needs the oracle; this converts a hard crash into a catchable, diagnosed
* bail (Rule 15: make the failure observable). */
int dirty_ram_dispatch(CPUState* cpu, uint32_t addr, uint32_t stop_addr) {
extern int g_psx_dispatch_depth;
extern PSX_THREAD_LOCAL int g_psx_dispatch_depth;
extern void psx_fatal_halt(const char *reason);
/* `addr` is architectural: the interpreter runs at exactly this PC, so a
* retail RAM-mirror PC (0x807xxxxx) keeps its own $ra/EPC while fetch_word
@@ -2759,7 +2759,7 @@ int dirty_ram_run_span(CPUState* cpu, uint32_t start_pc, uint32_t stop_pc,
* boundary? Mirrors the gate in psx_check_interrupts: a pending+unmasked I_STAT
* bit, COP0 IEc + IM2 set, and not already inside the exception handler. */
static int precise_irq_deliverable(CPUState *cpu) {
extern uint32_t i_stat;
extern PSX_THREAD_LOCAL uint32_t i_stat;
uint32_t sr = cpu->cop0[12];
/* COP0 software interrupts (CAUSE.IP0/IP1 & SR.IM0/IM1): guest-raised via
* mtc0 to CAUSE, independent of the INTC line (see psx_check_interrupts). */
@@ -2832,7 +2832,7 @@ static void psx_run_precise(CPUState *cpu, uint32_t bcyc, int deadline_entry) {
for (int i = 0; i < MAX_PRECISE_INSNS; i++) {
if (!irq_taken && precise_irq_deliverable(cpu)) {
uint32_t committed = pc;
extern uint32_t i_stat;
extern PSX_THREAD_LOCAL uint32_t i_stat;
g_slice_last_committed = committed;
g_slice_last_istat = i_stat;
g_slice_last_imask = i_mask;
@@ -2896,7 +2896,7 @@ static void psx_run_precise(CPUState *cpu, uint32_t bcyc, int deadline_entry) {
* after a take re-fires an IRQ the handler has not acked yet (an
* 8-takes-in-11-insns storm), so gate on !irq_taken. */
if (!irq_taken && precise_irq_deliverable(cpu)) {
extern uint32_t i_stat;
extern PSX_THREAD_LOCAL uint32_t i_stat;
g_slice_last_committed = committed;
g_slice_last_istat = i_stat;
g_slice_last_imask = i_mask;
@@ -3281,7 +3281,7 @@ static int dirty_ram_dispatch_inner(CPUState* cpu, uint32_t addr, uint32_t stop_
s_entry_poll_enabled = (e && e[0] == '0') ? 0 : 1;
}
if (s_entry_poll_enabled) {
extern uint32_t i_stat;
extern PSX_THREAD_LOCAL uint32_t i_stat;
uint32_t sr = cpu->cop0[12];
int deliverable =
((i_stat & i_mask) != 0u) &&
+31 -30
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* dma.c — PS1 DMA controller simulation (Phase 3).
*
* Implements all 7 channel register reads/writes, DPCR, DICR,
@@ -52,11 +53,11 @@ extern uint32_t psx_read_word(uint32_t addr);
extern void psx_write_word(uint32_t addr, uint32_t val);
/* Interrupt status — defined in memory.c */
extern uint32_t i_stat;
extern PSX_THREAD_LOCAL uint32_t i_stat;
/* Central IRQ-raise choke point (interrupts.c) — also records the device ring. */
extern void psx_irq_raise(uint32_t bit, uint32_t detail);
extern uint32_t g_debug_current_func_addr;
extern uint32_t g_debug_last_store_pc;
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
extern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
extern uint64_t s_frame_count;
/* ---- Per-channel registers ---- */
@@ -67,7 +68,7 @@ typedef struct {
uint32_t chcr; /* +0x08: Channel control */
} DMAChannel;
static DMAChannel channels[7];
static PSX_THREAD_LOCAL DMAChannel channels[7];
typedef struct {
uint8_t active;
@@ -78,12 +79,12 @@ typedef struct {
uint32_t start_addr; /* madr at transfer start (CD overlay capture) */
} DMAAsyncChannel;
static DMAAsyncChannel mdec_async[2];
static DMAAsyncChannel cdrom_async;
static DMAGPULinkedList gpu_linked_list;
static DMAGpuOtStats gpu_ot_stats;
static uint64_t gpu_ot_start_cycle;
static uint32_t gpu_ot_polls_this_walk;
static PSX_THREAD_LOCAL DMAAsyncChannel mdec_async[2];
static PSX_THREAD_LOCAL DMAAsyncChannel cdrom_async;
static PSX_THREAD_LOCAL DMAGPULinkedList gpu_linked_list;
static PSX_THREAD_LOCAL DMAGpuOtStats gpu_ot_stats;
static PSX_THREAD_LOCAL uint64_t gpu_ot_start_cycle;
static PSX_THREAD_LOCAL uint32_t gpu_ot_polls_this_walk;
/* ---- CD DMA transfer log ---- */
/* Every forward CH3 DMA that lands below 0x1C0000 (game data region) records
@@ -93,9 +94,9 @@ static uint32_t gpu_ot_polls_this_walk;
* overlay regions back to disc positions for extract_overlays.py. */
#define CD_DMA_LOG_CAP 65536
typedef struct { int lba; uint32_t dest; uint32_t size; } CdDmaEntry;
static CdDmaEntry cd_dma_log[CD_DMA_LOG_CAP];
static uint32_t cd_dma_log_head = 0;
static uint32_t cd_dma_log_total = 0;
static PSX_THREAD_LOCAL CdDmaEntry cd_dma_log[CD_DMA_LOG_CAP];
static PSX_THREAD_LOCAL uint32_t cd_dma_log_head = 0;
static PSX_THREAD_LOCAL uint32_t cd_dma_log_total = 0;
static void cd_dma_log_push(int lba, uint32_t dest, uint32_t size) {
cd_dma_log[cd_dma_log_head % CD_DMA_LOG_CAP].lba = lba;
@@ -138,18 +139,18 @@ void cd_dma_log_get_entry(uint32_t idx, int *lba, uint32_t *dest, uint32_t *
* DMA-sourced (not a CPU/dirty-interp store, whose g_debug_last_store_pc is accurate).
* cur_ch/cur_madr/cur_bcr name the in-flight channel + its destination, so a wrong
* DMA destination clobbering kernel data becomes directly visible. */
int g_dma_exec_depth = 0;
int g_dma_cur_ch = -1;
uint32_t g_dma_cur_madr = 0;
uint32_t g_dma_cur_bcr = 0;
PSX_THREAD_LOCAL int g_dma_exec_depth = 0;
PSX_THREAD_LOCAL int g_dma_cur_ch = -1;
PSX_THREAD_LOCAL uint32_t g_dma_cur_madr = 0;
PSX_THREAD_LOCAL uint32_t g_dma_cur_bcr = 0;
/* Guest PC that kicked the in-flight DMA (the CHCR store that set start/busy),
* so write-provenance (wtrace / parity note_write) attributes DMA-sourced RAM
* writes to the code that INITIATED the transfer, not to g_debug_last_store_pc
* (which for an async transfer is a stale, unrelated CPU store). Captured at the
* kick in trigger_dma_transfer and re-published for async channels whose RAM
* writes run in a later advance_*() step. 0 = unknown. */
uint32_t g_dma_initiator_pc = 0;
static uint32_t s_dma_ch_initiator_pc[7] = {0};
PSX_THREAD_LOCAL uint32_t g_dma_initiator_pc = 0;
static PSX_THREAD_LOCAL uint32_t s_dma_ch_initiator_pc[7] = {0};
typedef struct {
uint8_t active;
@@ -157,22 +158,22 @@ typedef struct {
uint32_t cycles_remaining;
} DMADelayedComplete;
static DMADelayedComplete delayed_complete[7];
static PSX_THREAD_LOCAL DMADelayedComplete delayed_complete[7];
/* ---- Global registers ---- */
static uint32_t dpcr; /* 0x1F8010F0: DMA control (enable bits) */
static uint32_t dicr; /* 0x1F8010F4: DMA interrupt control */
static PSX_THREAD_LOCAL uint32_t dpcr; /* 0x1F8010F0: DMA control (enable bits) */
static PSX_THREAD_LOCAL uint32_t dicr; /* 0x1F8010F4: DMA interrupt control */
#define DICR_WRITE_MASK 0x00FF807Fu
#define DICR_RESET_MASK 0x7F000000u
static DMATraceEntry dma_trace[DMA_TRACE_CAP];
static uint64_t dma_trace_seq;
static DMACDROMHistoryEntry cdrom_dma_history[DMA_CDROM_HISTORY_CAP];
static uint64_t cdrom_dma_history_seq;
static DMACDROMHistoryEntry cdrom_dma_active_entry;
static uint8_t cdrom_dma_history_active;
static PSX_THREAD_LOCAL DMATraceEntry dma_trace[DMA_TRACE_CAP];
static PSX_THREAD_LOCAL uint64_t dma_trace_seq;
static PSX_THREAD_LOCAL DMACDROMHistoryEntry cdrom_dma_history[DMA_CDROM_HISTORY_CAP];
static PSX_THREAD_LOCAL uint64_t cdrom_dma_history_seq;
static PSX_THREAD_LOCAL DMACDROMHistoryEntry cdrom_dma_active_entry;
static PSX_THREAD_LOCAL uint8_t cdrom_dma_history_active;
static uint32_t dicr_read_value(uint32_t v);
@@ -823,8 +824,8 @@ static void start_async_gpu_linked_list(void) {
* whole walk (and the GPU work it feeds) with in_exception set, which is what
* the phase profiler's exc_share then measures. */
extern int psx_get_in_exception(void);
static DMAGpuHoldEntry s_hold_ring[DMA_GPU_HOLD_RING_CAP];
static DMAGpuHoldStats s_hold_stats;
static PSX_THREAD_LOCAL DMAGpuHoldEntry s_hold_ring[DMA_GPU_HOLD_RING_CAP];
static PSX_THREAD_LOCAL DMAGpuHoldStats s_hold_stats;
static uint64_t hold_host_ticks(void) {
#ifdef _WIN32
+5 -4
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/*
* event_ring.c — always-on unified event-timeline ring. See event_ring.h.
*
@@ -13,10 +14,10 @@
#include <string.h>
/* ---- runtime globals captured at record time ---- */
extern uint32_t i_stat; /* memory.c */
extern uint32_t i_mask; /* memory.c */
extern uint32_t g_debug_current_func_addr; /* debug_server */
extern uint32_t g_debug_last_store_pc; /* debug_server */
extern PSX_THREAD_LOCAL uint32_t i_stat; /* memory.c */
extern PSX_THREAD_LOCAL uint32_t i_mask; /* memory.c */
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr; /* debug_server */
extern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc; /* debug_server */
/* Execution-mode sources (see overlay_loader.c / dirty_ram_interp.c). */
extern uint32_t overlay_loader_get_inprogress(void); /* in-progress native ovl fn, 0 if none */
+3 -2
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* fntrace.c — runtime side of psx_dispatch call ring. See fntrace.h. */
#include "fntrace.h"
@@ -192,8 +193,8 @@ void fntrace_record(CPUState* cpu, uint32_t target) {
* bail_top_wild_pc) so the dominant wild-returning function is readable even
* when the bail storm makes the window "Not Responding". */
{
extern uint64_t g_psx_bail_flattened;
extern uint32_t g_debug_current_func_addr;
extern PSX_THREAD_LOCAL uint64_t g_psx_bail_flattened;
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
extern void psx_bail_record(uint32_t site_ra, uint32_t site_sp,
uint32_t wild_pc, uint32_t guest_sp);
static uint64_t s_last_flat = 0;
+5 -5
View File
@@ -24,10 +24,10 @@
/* State accessors. All defined in other compilation units; declared here
* to avoid pulling in heavy headers. */
extern uint64_t s_frame_count; /* debug_server.c */
extern uint32_t g_debug_current_func_addr; /* debug_server.c */
extern uint32_t g_debug_last_store_pc; /* debug_server.c */
extern uint32_t i_stat; /* memory.c */
extern uint32_t i_mask; /* memory.c */
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr; /* debug_server.c */
extern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc; /* debug_server.c */
extern PSX_THREAD_LOCAL uint32_t i_stat; /* memory.c */
extern PSX_THREAD_LOCAL uint32_t i_mask; /* memory.c */
extern uint64_t psx_get_cycle_count(void);
extern void psx_get_freeze_diag(uint64_t *total_checks,
@@ -150,7 +150,7 @@ static int s_sym_initialized = 0;
#define RING_CAP 64
typedef struct {
uint64_t frame_count;
PSX_THREAD_LOCAL uint64_t psx_cycle_count;
uint64_t psx_cycle_count;
uint64_t exc_reentry;
uint64_t dirty_ram_insns;
uint32_t current_func;
+3 -3
View File
@@ -4912,7 +4912,7 @@ static inline GpuContextType_lcf *gpu_context_lcf(void) {
* raise the VBLANK bit when a synthetic vblank fires from a tight
* BIOS-shell GPUSTAT poll loop, without going through the
* vblank-callback path (which would call SDL_Delay/Sleep). */
extern uint32_t i_stat;
extern PSX_THREAD_LOCAL uint32_t i_stat;
/* Central IRQ-raise choke point (interrupts.c) — also records the device ring. */
extern void psx_irq_raise(uint32_t bit, uint32_t detail);
@@ -8468,8 +8468,8 @@ uint32_t gpu_get_opcode_count(uint8_t op) { return (*gpu_context_gp0_opcode_coun
extern uint64_t s_frame_count; /* defined in debug_server.c */
#ifndef PSX_NO_DEBUG_TOOLS
extern uint32_t g_debug_last_store_pc; /* defined in debug_server.c */
extern uint32_t g_debug_current_func_addr; /* defined in debug_server.c */
extern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc; /* defined in debug_server.c */
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr; /* defined in debug_server.c */
uint32_t debug_guest_ra(void); /* accessor in debug_server.c (guest $ra) */
uint32_t debug_guest_sp(void); /* accessor in debug_server.c (guest $sp) */
extern uint8_t *memory_get_ram_ptr(void); /* raw 2MB main-RAM base (no lockstep) */
+2 -2
View File
@@ -21,7 +21,7 @@ extern "C" int gpu_ws_precise_nclip_enabled(void);
* plus native replay of one candidate) is running. */
extern "C" int psx_overlay_shadow_diff_active(void);
extern "C" uint64_t s_frame_count; /* debug_server.c */
extern "C" uint32_t g_debug_last_store_pc; /* debug_server.c */
extern "C" PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc; /* debug_server.c */
extern "C" { PSX_THREAD_LOCAL int (*g_psx_projection_command)(CPUState*, uint32_t) = nullptr; }
namespace PSXRecomp {
@@ -686,7 +686,7 @@ extern "C" void gte_ws_set_dome_expand(int on, int aspect_num, int aspect_den) {
// projects FAR (SZ >= threshold) vertices, so the sky-dome draw function can be
// identified as the top far-vertex emitter — the target for the per-function
// dome-expand bracket (the clean alternative to the scene-dependent depth gate).
extern "C" { extern uint32_t g_debug_current_func_addr; }
extern "C" { extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr; }
static PSX_THREAD_LOCAL uint32_t s_gte_caller_ra = 0; /* guest ra at gte_execute = return into the GAME fn that issued the projection (not the libgte leaf) */
#define WS_DOME_CALL_SITES_MAX 32
static PSX_THREAD_LOCAL uint32_t s_ws_dome_call_sites[WS_DOME_CALL_SITES_MAX];
+2 -1
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* guest_cycle_profile.c - always-on guest-cycle profile ring.
*
* Answers "where did the guest CPU spend its cycles between frame A and
@@ -35,7 +36,7 @@
extern uint64_t s_frame_count;
extern volatile uint32_t g_psx_last_fn_entry;
extern volatile uint32_t g_psx_last_block;
extern uint32_t g_debug_current_func_addr;
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
extern uint32_t debug_guest_ra(void);
extern uint32_t overlay_loader_native_inprogress(void);
extern int psx_exec_phase(void);
+10 -9
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/*
* interrupts.c — v4 interrupt delivery for recompiled BIOS.
*
@@ -124,8 +125,8 @@ static void irq_record_outcome(uint8_t kind, uint8_t detail, uint32_t aux) {
#define COP0_EPC 14
/* I_STAT and I_MASK are owned by memory.c */
extern uint32_t i_stat;
extern uint32_t i_mask;
extern PSX_THREAD_LOCAL uint32_t i_stat;
extern PSX_THREAD_LOCAL uint32_t i_mask;
/* Device-event cycle ring (device_trace.c): every hardware IRQ-raise edge is
* recorded here with its guest cycle. */
@@ -159,7 +160,7 @@ extern uint32_t i_mask;
*
* Derived from PR #102 by Alexandros Mandravillis; the mirror call sites and
* the single-owner refactor are ours. */
static uint32_t *s_cause_ptr;
static PSX_THREAD_LOCAL uint32_t *s_cause_ptr;
void psx_irq_refresh_cause_ip2(void)
{
@@ -186,7 +187,7 @@ void psx_irq_raise(uint32_t bit, uint32_t detail)
{
i_stat |= (1u << bit);
psx_irq_refresh_cause_ip2();
device_trace_note(bit, detail);
if (!g_psx_render_pass_active) device_trace_note(bit, detail);
}
/* Dispatch counter for vblank scheduling. */
@@ -235,7 +236,7 @@ typedef struct {
static CosimIrqEntry s_cosim_irq_ring[COSIM_IRQ_RING_CAP];
static uint64_t s_cosim_irq_seq;
extern int g_cosim_dirty_pump_site;
extern uint32_t g_debug_current_func_addr;
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
extern uint32_t cosim_last_block(void);
extern uint32_t overlay_loader_get_inprogress(void);
@@ -625,7 +626,7 @@ uint64_t psx_exception_setjmp_epoch(void) { return g_exc_setjmp_epoch; }
* the thread scheduler is fiber-based everywhere. */
void* g_exception_owner_fiber = NULL;
int g_pending_exception_longjmp = 0;
extern int g_psx_dispatch_depth;
extern PSX_THREAD_LOCAL int g_psx_dispatch_depth;
/* Set by psx_check_interrupts_at while a compiled block-leader interrupt check
* is in progress. If the delivered handler later RFEs to the sentinel outside
@@ -676,7 +677,7 @@ int psx_irq_resume_context_snapshot_safe_at(uint32_t resume_pc)
* dirty resume PC is the same PC the caller intends to serialize. Keep
* call-return and precise-IRQ pump sites rejected; those are the contexts
* that can pair a plausible resume PC with stale nested-call registers. */
extern int g_call_unit_depth;
extern PSX_THREAD_LOCAL int g_call_unit_depth;
if (in_exception || g_call_unit_depth != 0 || g_psx_dispatch_depth > 1)
return 0;
if (resume_pc == 0u || (resume_pc & 3u) != 0u)
@@ -1553,7 +1554,7 @@ irq_deliver_eval:
* clobber window because the CPU isn't stalled / the event wasn't torn down. */
{
extern int dma_cdrom_transfer_active(void);
extern int g_dma_exec_depth;
extern PSX_THREAD_LOCAL int g_dma_exec_depth;
extern uint64_t s_frame_count;
g_irqctx_ring[g_irqctx_seq & (IRQCTX_RING_CAP - 1u)] = (IrqCtxEntry){
.seq = g_irqctx_seq, .cycle = psx_get_cycle_count(),
@@ -2146,7 +2147,7 @@ irq_deliver_eval:
* live GPRs when the IRQ was taken deep inside a nested call (the
* async-poll callback) — switching there commits a poisoned TCB context
* and the resumed thread faults on a smeared jumptable index. */
extern int g_call_unit_depth;
extern PSX_THREAD_LOCAL int g_call_unit_depth;
int at_outermost = (g_psx_dispatch_depth == 0 && g_call_unit_depth == 0);
/* Defer only when nested AND the outgoing thread has a valid resume PC
* of its OWN (g_exception_real_epc — the architectural EPC latched for
+2 -1
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* load_accel.c -- verified enhancement-phase load-time acceleration.
*
* PsyQ's VSync(-1) is used as a timeout clock by CD_sync/CD_ready loops. The
@@ -23,7 +24,7 @@
#include <stdio.h>
extern uint32_t g_debug_last_store_pc;
extern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
extern int g_precise_mode;
static int s_enabled = 1;
+12 -12
View File
@@ -267,7 +267,7 @@ extern "C" {
extern uint32_t g_slice_exit_pc, g_slice_exit_reason, g_slice_exit_iter;
extern uint32_t g_slice_exit_dispatchable, g_slice_exit_dirty, g_slice_exit_in_text, g_slice_exit_want;
/* memory.c */
extern uint32_t i_stat, i_mask;
extern PSX_THREAD_LOCAL uint32_t i_stat, i_mask;
extern uint64_t g_guest_store_count;
extern uint64_t g_vblank_ack_count;
/* interrupts.c */
@@ -276,9 +276,9 @@ extern "C" {
extern uint64_t g_dirty_ram_blocks_run;
extern uint64_t g_dirty_pump_count;
/* overlay_loader.c */
extern int g_call_unit_depth;
extern PSX_THREAD_LOCAL int g_call_unit_depth;
/* psx_bios_backend.c */
extern int g_psx_dispatch_depth;
extern PSX_THREAD_LOCAL int g_psx_dispatch_depth;
}
/* memory.c */
@@ -844,7 +844,7 @@ static void post_load_probe_on_vblank(int turbo_active, int present_reached) {
extern uint64_t g_vblank_raise_count, g_vblank_deliver_count, g_vblank_ack_count;
extern uint64_t g_dirty_ram_blocks_run;
extern uint32_t i_stat, i_mask;
extern PSX_THREAD_LOCAL uint32_t i_stat, i_mask;
extern CPUState *debug_cpu_ptr;
const uint64_t vb_r = g_vblank_raise_count - s_post_load_probe_vb_raise0;
const uint64_t vb_d = g_vblank_deliver_count - s_post_load_probe_vb_deliv0;
@@ -963,9 +963,9 @@ static void post_load_probe_on_vblank(int turbo_active, int present_reached) {
const uint32_t irq_pc = psx_last_irq_check_pc();
const uint32_t resume_pc = psx_compiled_irq_resume_pc();
extern uint32_t g_debug_current_func_addr;
extern uint32_t g_debug_last_store_pc;
extern int g_psx_dispatch_depth;
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
extern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
extern PSX_THREAD_LOCAL int g_psx_dispatch_depth;
const uint32_t func = g_debug_current_func_addr;
const uint32_t store_pc = g_debug_last_store_pc;
const uint32_t live_pc = debug_cpu_ptr ? debug_cpu_ptr->pc : 0u;
@@ -20412,10 +20412,10 @@ session_reboot:
psx_netplay_cold_reset();
{
extern uint64_t s_frame_count;
extern uint32_t g_debug_current_func_addr;
extern uint32_t g_debug_last_store_pc;
extern int g_call_unit_depth;
extern int g_psx_dispatch_depth;
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
extern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
extern PSX_THREAD_LOCAL int g_call_unit_depth;
extern PSX_THREAD_LOCAL int g_psx_dispatch_depth;
s_frame_count = 0;
g_debug_current_func_addr = 0;
g_debug_last_store_pc = 0;
@@ -21890,7 +21890,7 @@ session_reboot:
* longjmp fixup). */
if (rematch_session) {
extern PSX_THREAD_LOCAL int psx_in_device_service;
extern int g_call_unit_depth;
extern PSX_THREAD_LOCAL int g_call_unit_depth;
char stall[96];
uint32_t sim = 0;
int lead = 0;
+155 -53
View File
@@ -28,6 +28,7 @@
#include "psx_cycles.h"
#include "psx_icache.h"
#include "psx_memory.h"
#include "psx_execution.h"
#include "pgxp.h"
#include "render_pass.h"
#include "render_pass_plan.h"
@@ -279,7 +280,7 @@ void memory_clear_low_boot_scratch(void) {
#define RAM_LIVE (g_psx_ram_size)
#define DIRTY_RAM_LIVE_BITMAP_WORDS \
(((RAM_LIVE >> DIRTY_RAM_PAGE_SHIFT) + 31u) / 32u)
static uint32_t dirty_ram_bitmap[DIRTY_RAM_BITMAP_WORDS];
static PSX_THREAD_LOCAL uint32_t dirty_ram_bitmap[DIRTY_RAM_BITMAP_WORDS];
/* Monotonic generation for RAM-resident CODE changes (kernel install-stub
* writes + DMA/EXE loads that mark executable ranges). Consumers that cache
@@ -330,24 +331,24 @@ static inline void dirty_ram_mark_page(uint32_t phys) {
#include "psx_bios_image.h"
#include "kernel_patch_ranges.h"
static uint32_t s_kb_lo = 0, s_kb_span = 0, s_kb_rom_off = 0;
static PSX_THREAD_LOCAL uint32_t s_kb_lo = 0, s_kb_span = 0, s_kb_rom_off = 0;
static const PsxKernelPatchRange* s_kb_pr = 0;
static uint32_t s_kb_pr_n = 0;
static uint64_t kbless_patch_skips = 0; /* segments skipped, TCP counter */
static PSX_THREAD_LOCAL const PsxKernelPatchRange* s_kb_pr = 0;
static PSX_THREAD_LOCAL uint32_t s_kb_pr_n = 0;
static PSX_THREAD_LOCAL uint64_t kbless_patch_skips = 0; /* segments skipped, TCP counter */
#define KBLESS_UNKNOWN 0u
#define KBLESS_CLEAN 1u
#define KBLESS_MISMATCH 2u
#define KBLESS_MAX_ENTRIES 4096u
static uint8_t kbless_state[KBLESS_MAX_ENTRIES]; /* parallel to bodies[] */
static int kbless_enabled = -1; /* env PSX_KERNEL_BLESS=0 disables */
static PSX_THREAD_LOCAL uint8_t kbless_state[KBLESS_MAX_ENTRIES]; /* parallel to bodies[] */
static PSX_THREAD_LOCAL int kbless_enabled = -1; /* env PSX_KERNEL_BLESS=0 disables */
/* Always-on counters (TCP kernel_bless). */
static uint64_t kbless_native_hits = 0;
static uint64_t kbless_verifies = 0;
static uint64_t kbless_mismatches = 0;
static uint64_t kbless_invalidations = 0;
static PSX_THREAD_LOCAL uint64_t kbless_native_hits = 0;
static PSX_THREAD_LOCAL uint64_t kbless_verifies = 0;
static PSX_THREAD_LOCAL uint64_t kbless_mismatches = 0;
static PSX_THREAD_LOCAL uint64_t kbless_invalidations = 0;
static int kbless_on(void) {
if (kbless_enabled < 0) {
@@ -582,18 +583,18 @@ void dirty_ram_clear_image_baseline(void) {
* never frees it). Intentional data patches by the translation layer are blessed
* into it via dirty_ram_text_bless so they are not mistaken for self-modifying
* code — see dirty_ram_text_native_ok / dirty_ram_text_bless. */
static uint8_t *text_ref_image = NULL;
static uint32_t text_ref_lo = 0, text_ref_hi = 0;
static uint32_t text_modified_bitmap[DIRTY_RAM_BITMAP_WORDS];
static uint32_t text_diverged_bitmap[DIRTY_RAM_BITMAP_WORDS];
static uint64_t g_text_native_blocked = 0;
static uint32_t g_text_diverged_pages = 0;
static uint64_t g_text_exact_mismatches = 0;
static uint32_t g_text_exact_last_range_lo = 0;
static uint32_t g_text_exact_last_range_len = 0;
static uint32_t g_text_exact_last_mismatch = 0;
static uint32_t g_text_exact_last_live = 0;
static uint32_t g_text_exact_last_ref = 0;
static PSX_THREAD_LOCAL uint8_t *text_ref_image = NULL;
static PSX_THREAD_LOCAL uint32_t text_ref_lo = 0, text_ref_hi = 0;
static PSX_THREAD_LOCAL uint32_t text_modified_bitmap[DIRTY_RAM_BITMAP_WORDS];
static PSX_THREAD_LOCAL uint32_t text_diverged_bitmap[DIRTY_RAM_BITMAP_WORDS];
static PSX_THREAD_LOCAL uint64_t g_text_native_blocked = 0;
static PSX_THREAD_LOCAL uint32_t g_text_diverged_pages = 0;
static PSX_THREAD_LOCAL uint64_t g_text_exact_mismatches = 0;
static PSX_THREAD_LOCAL uint32_t g_text_exact_last_range_lo = 0;
static PSX_THREAD_LOCAL uint32_t g_text_exact_last_range_len = 0;
static PSX_THREAD_LOCAL uint32_t g_text_exact_last_mismatch = 0;
static PSX_THREAD_LOCAL uint32_t g_text_exact_last_live = 0;
static PSX_THREAD_LOCAL uint32_t g_text_exact_last_ref = 0;
void dirty_ram_register_text_image(uint32_t phys_lo, const uint8_t *bytes,
uint32_t len) {
@@ -902,8 +903,8 @@ void dirty_ram_set_bitmap_words(const uint32_t* words, uint32_t count) {
* The bitmap is almost always empty, so the per-store cost on the common path
* is a single bitmap lookup.
*/
static uint32_t overlay_watch_bitmap[DIRTY_RAM_BITMAP_WORDS];
static uint32_t overlay_page_gen[DIRTY_RAM_PAGE_COUNT];
static PSX_THREAD_LOCAL uint32_t overlay_watch_bitmap[DIRTY_RAM_BITMAP_WORDS];
static PSX_THREAD_LOCAL uint32_t overlay_page_gen[DIRTY_RAM_PAGE_COUNT];
void dirty_ram_reset_for_boot(void) {
memset(dirty_ram_bitmap, 0, sizeof(dirty_ram_bitmap));
@@ -1039,24 +1040,24 @@ static inline void overlay_watch_note_write(uint32_t phys, uint32_t size) {
/* Memory control registers: 0x1F801000..0x1F80103F (16 words) + 0x1F801060 (RAM size).
* Includes expansion base/size, COM_DELAY, SPU_DELAY, CDROM_DELAY etc. */
static uint32_t mem_ctrl[16]; /* indices 0..15 → addresses 0x1F801000..0x1F80103C */
static uint32_t ram_size_reg; /* 0x1F801060 */
static PSX_THREAD_LOCAL uint32_t mem_ctrl[16]; /* indices 0..15 → addresses 0x1F801000..0x1F80103C */
static PSX_THREAD_LOCAL uint32_t ram_size_reg; /* 0x1F801060 */
/* KSEG2 no-op access telemetry (see the guards in the accessors). */
uint64_t g_kseg2_ignored_reads;
uint64_t g_kseg2_ignored_writes;
/* Cache control register (KSEG2: 0xFFFE0130). */
static uint32_t cache_ctrl;
static PSX_THREAD_LOCAL uint32_t cache_ctrl;
/* Pointer to cpu->cop0[12] (SR). Set once at init.
* Used by write functions to check the IsC (Isolate Cache) bit: while it is
* set, a CPU store goes to the caches and never reaches the bus (isc_store). */
static const uint32_t *sr_ptr;
static PSX_THREAD_LOCAL const uint32_t *sr_ptr;
/* Interrupt controller — non-static so hardware subsystems can set I_STAT bits. */
uint32_t i_stat; /* 0x1F801070 — interrupt status (AND-acknowledge semantics) */
uint32_t i_mask; /* 0x1F801074 — interrupt enable mask */
PSX_THREAD_LOCAL uint32_t i_stat; /* 0x1F801070 — interrupt status (AND-acknowledge semantics) */
PSX_THREAD_LOCAL uint32_t i_mask; /* 0x1F801074 — interrupt enable mask */
/* Shadow-diff device-access detector. run_shadow_diff arms g_shadow_mmio_watch
* around its single authoritative (interpreter) probe pass; ANY MMIO touch bumps
@@ -1072,8 +1073,8 @@ uint64_t g_shadow_mmio_hits = 0;
* time-varying or side-effecting values (timer counters, CD response FIFO),
* so any touch disqualifies the window. One increment per access. */
uint64_t g_mmio_access_count = 0;
#define SHADOW_NOTE_MMIO() do { g_mmio_access_count++; \
g_shadow_mmio_hits += (uint64_t)g_shadow_mmio_watch; } while (0)
#define SHADOW_NOTE_MMIO() do { if (s_backing == &s_live_backing) { \
g_mmio_access_count++; g_shadow_mmio_hits += (uint64_t)g_shadow_mmio_watch; } } while (0)
/* Always-on guest store counter, bumped at the top of every raw store
* (RAM, scratchpad, MMIO, even discarded KSEG2/IsC stores — over-counting
@@ -1108,8 +1109,8 @@ static int imask_bit7_set_count = 0;
static int imask_bit7_clear_count = 0;
static void imask_trace_record(uint32_t old_val, uint32_t new_val, uint8_t width) {
extern uint32_t g_debug_current_func_addr;
extern uint32_t g_debug_last_store_pc;
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
extern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
extern int psx_get_in_exception(void);
ImaskTraceEntry *e = &imask_trace[imask_trace_idx];
e->old_mask = old_val;
@@ -1177,7 +1178,7 @@ extern void debug_server_trace_ram_read_watch(uint32_t phys, uint32_t val);
extern void debug_server_trace_entryint_write(uint32_t phys, uint32_t old_val,
uint32_t new_val, uint8_t width);
extern CPUState *debug_cpu_ptr;
extern uint32_t g_debug_last_store_pc;
extern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
/* Parity last-writer provenance (parity_trace.c): note every main-RAM write so
* the watch-word last-writer table tracks the exact producing store. No-op
@@ -1187,8 +1188,8 @@ extern void parity_trace_note_write(uint32_t addr, uint32_t width, uint32_t writ
/* Effective writer PC for provenance: during a DMA transfer the last CPU-store
* PC is stale/unrelated, so attribute DMA-sourced RAM writes to the PC that
* kicked the DMA (dma.c). Matches the wtrace recorder's DMA attribution. */
extern int g_dma_exec_depth;
extern uint32_t g_dma_initiator_pc;
extern PSX_THREAD_LOCAL int g_dma_exec_depth;
extern PSX_THREAD_LOCAL uint32_t g_dma_initiator_pc;
static inline uint32_t effective_store_pc(void) {
return (g_dma_exec_depth > 0 && g_dma_initiator_pc) ? g_dma_initiator_pc
: g_debug_last_store_pc;
@@ -1317,6 +1318,18 @@ static void unmapped_fatal(uint32_t vaddr, uint32_t phys, const char* op) {
/* --- MMIO read/write helpers --- */
static int private_mmio_read_allowed(uint32_t addr) {
if (s_backing == &s_live_backing) return 1;
if ((addr >= 0x1F801040u && addr <= 0x1F80105Fu) ||
(addr >= 0x1F801800u && addr <= 0x1F801803u) ||
(addr >= 0x1F801820u && addr <= 0x1F801827u) ||
(addr >= 0x1F801C00u && addr <= 0x1F801FFFu)) {
if (g_psx_execution_abort) g_psx_execution_abort("device_read", addr);
abort(); /* A private execution must install a non-returning landing. */
}
return 1;
}
static uint32_t mmio_read32_impl(uint32_t addr) {
SHADOW_NOTE_MMIO();
/* Memory control: 0x1F801000..0x1F801020 */
@@ -1370,6 +1383,10 @@ static uint32_t mmio_read32_impl(uint32_t addr) {
* single (side-effecting) read. Callers use mmio_read32; the body is _impl, so
* the device read executes exactly once. */
static uint32_t mmio_read32(uint32_t addr) {
if (s_backing != &s_live_backing) {
private_mmio_read_allowed(addr);
return mmio_read32_impl(addr);
}
if (g_psx_render_pass_active && !render_pass_mmio_read_allowed(addr, 4)) return 0;
psx_devices_mmio_sync();
uint32_t v = mmio_read32_impl(addr);
@@ -1380,7 +1397,7 @@ static uint32_t mmio_read32(uint32_t addr) {
static void mmio_write32(uint32_t addr, uint32_t val) {
psx_devices_mmio_sync();
SHADOW_NOTE_MMIO();
debug_server_trace_mmio_write(addr, val, 4);
if (s_backing == &s_live_backing) debug_server_trace_mmio_write(addr, val, 4);
/* Memory control: 0x1F801000..0x1F801020 */
if (addr >= 0x1F801000u && addr <= 0x1F80103Cu) {
mem_ctrl[(addr - 0x1F801000u) >> 2] = val;
@@ -1493,6 +1510,10 @@ static uint16_t mmio_read16_impl(uint32_t addr) {
}
static uint16_t mmio_read16(uint32_t addr) {
if (s_backing != &s_live_backing) {
private_mmio_read_allowed(addr);
return mmio_read16_impl(addr);
}
if (g_psx_render_pass_active && !render_pass_mmio_read_allowed(addr, 2)) return 0;
psx_devices_mmio_sync();
uint16_t v = mmio_read16_impl(addr);
@@ -1503,7 +1524,7 @@ static uint16_t mmio_read16(uint32_t addr) {
static void mmio_write16(uint32_t addr, uint16_t val) {
psx_devices_mmio_sync();
SHADOW_NOTE_MMIO();
debug_server_trace_mmio_write(addr, (uint32_t)val, 2);
if (s_backing == &s_live_backing) debug_server_trace_mmio_write(addr, (uint32_t)val, 2);
/* Memory control: 0x1F801000..0x1F80103C — halfword lane RMW. */
if (addr >= 0x1F801000u && addr <= 0x1F80103Fu) {
uint32_t idx = (addr - 0x1F801000u) >> 2;
@@ -1613,6 +1634,10 @@ static uint8_t mmio_read8_impl(uint32_t addr) {
}
static uint8_t mmio_read8(uint32_t addr) {
if (s_backing != &s_live_backing) {
private_mmio_read_allowed(addr);
return mmio_read8_impl(addr);
}
if (g_psx_render_pass_active && !render_pass_mmio_read_allowed(addr, 1)) return 0;
psx_devices_mmio_sync();
uint8_t v = mmio_read8_impl(addr);
@@ -1623,7 +1648,7 @@ static uint8_t mmio_read8(uint32_t addr) {
static void mmio_write8(uint32_t addr, uint8_t val) {
psx_devices_mmio_sync();
SHADOW_NOTE_MMIO();
debug_server_trace_mmio_write(addr, (uint32_t)val, 1);
if (s_backing == &s_live_backing) debug_server_trace_mmio_write(addr, (uint32_t)val, 1);
/* Memory control: 0x1F801000..0x1F80103C — byte lane RMW. Tomba2's late
* attract byte-writes the EXP1 base register at 0x1F801000; only the
* 32-bit path covered the block and the byte path fatal'd. */
@@ -1708,7 +1733,7 @@ static void mmio_write8(uint32_t addr, uint8_t val) {
* (psx_get_in_exception): the replay runs only the block, never the handler. */
static int s_ls_op_active = 0;
extern int g_ls_suppress_record;
extern int g_dma_exec_depth;
extern PSX_THREAD_LOCAL int g_dma_exec_depth;
extern int psx_get_in_exception(void);
static uint32_t psx_read_word_raw(uint32_t addr);
/* data-shard capture feed (data_shards.c): record guest reads/writes while a
@@ -1716,6 +1741,7 @@ static uint32_t psx_read_word_raw(uint32_t addr);
* exception context (ISR work replays live), never under DMA (DMA writes
* poison the window via ds_note_dma_write instead). */
uint32_t psx_read_word(uint32_t addr) {
if (s_backing != &s_live_backing) return psx_read_word_raw(addr);
if (g_ls_mode == 2) return ls_read_hook(addr, 4, 0u);
if (g_ds_recording && g_dma_exec_depth == 0 && !psx_get_in_exception())
ds_note_read(addr, 4);
@@ -1758,7 +1784,7 @@ static uint32_t psx_read_word_raw(uint32_t addr) {
memcpy(&v, &ram[phys], sizeof(v));
/* Targeted main-RAM read watch (debug). Flag is 0 in normal runs, so the
* hot read path pays only a predictable branch. */
if (g_ram_read_watch_active) debug_server_trace_ram_read_watch(phys, v);
if (s_backing == &s_live_backing && g_ram_read_watch_active) debug_server_trace_ram_read_watch(phys, v);
return v;
}
{
@@ -1820,11 +1846,11 @@ typedef struct {
} D44Entry;
D44Entry g_d44_ring[D44_RING_CAP];
uint64_t g_d44_seq = 0;
extern uint32_t g_debug_last_store_pc;
extern int g_dma_exec_depth; /* >0 while a DMA is moving data (dma.c) */
extern int g_dma_cur_ch; /* channel of the in-flight DMA, else -1 */
extern uint32_t g_dma_cur_madr; /* current MADR of the in-flight DMA */
extern uint32_t g_dma_cur_bcr; /* BCR of the in-flight DMA */
extern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
extern PSX_THREAD_LOCAL int g_dma_exec_depth; /* >0 while a DMA is moving data (dma.c) */
extern PSX_THREAD_LOCAL int g_dma_cur_ch; /* channel of the in-flight DMA, else -1 */
extern PSX_THREAD_LOCAL uint32_t g_dma_cur_madr; /* current MADR of the in-flight DMA */
extern PSX_THREAD_LOCAL uint32_t g_dma_cur_bcr; /* BCR of the in-flight DMA */
extern uint64_t s_frame_count;
static inline void d44_note(uint32_t phys, uint32_t old, uint32_t val) {
if (phys != D44_PHYS) return;
@@ -1866,7 +1892,7 @@ uint64_t g_render_pass_dropped_writes[RENDER_PASS_DROP_CLASSES];
* writes RAM directly, so IsC does not apply while a transfer moves data.
* Neither is a host store (psx_host_write_*: mods, FMV skip, debug pokes,
* enhancement fills); like DMA it reaches memory whatever SR says. */
int g_host_store_depth; /* >0 inside psx_host_write_* */
PSX_THREAD_LOCAL int g_host_store_depth; /* >0 inside psx_host_write_* */
/* DMA and host stores have no instruction hook to carry or reset a PGXP
* shadow. Validation on read cannot catch one that rewrites the identical
@@ -1925,6 +1951,7 @@ uint64_t g_render_pass_mod_pages = 0; /* pages journaled, total */
static int rp_mod_journal(uint8_t *base, uint32_t page_base, uint32_t off,
uint32_t width) {
if (s_backing != &s_live_backing) return 1;
for (uint32_t pg = off / RP_MOD_PAGE; pg <= (off + width - 1u) / RP_MOD_PAGE; pg++) {
const uint32_t index = page_base + pg;
const uint8_t bit = (uint8_t)(1u << (index & 7u));
@@ -2001,12 +2028,12 @@ static void render_pass_store(uint32_t addr, uint32_t val, uint32_t width) {
if (t.isolate_cache) isc_store(addr, val, width);
t.mmio_write = render_pass_mmio_write;
cls = render_pass_store_to(&t, addr, val, width);
if (cls >= 0) g_render_pass_dropped_writes[cls]++;
if (cls >= 0 && s_backing == &s_live_backing) g_render_pass_dropped_writes[cls]++;
}
static void psx_write_word_raw(uint32_t addr, uint32_t val);
void psx_write_word(uint32_t addr, uint32_t val) {
extern void (*g_overlay_flush_pending_cycles)(void);
extern PSX_THREAD_LOCAL void (*g_overlay_flush_pending_cycles)(void);
if (g_overlay_flush_pending_cycles) g_overlay_flush_pending_cycles();
if (g_psx_render_pass_active) { render_pass_store(addr, val, 4); return; }
if (g_ls_mode == 2) { ls_write_hook(addr, 4, val); return; }
@@ -2173,6 +2200,7 @@ static void psx_write_word_raw(uint32_t addr, uint32_t val) {
static uint16_t psx_read_half_raw(uint32_t addr);
uint16_t psx_read_half(uint32_t addr) {
if (s_backing != &s_live_backing) return psx_read_half_raw(addr);
if (g_ls_mode == 2) return (uint16_t)ls_read_hook(addr, 2, 0u);
if (g_ds_recording && g_dma_exec_depth == 0 && !psx_get_in_exception())
ds_note_read(addr, 2);
@@ -2221,7 +2249,7 @@ static uint16_t psx_read_half_raw(uint32_t addr) {
static void psx_write_half_raw(uint32_t addr, uint16_t val);
void psx_write_half(uint32_t addr, uint16_t val) {
extern void (*g_overlay_flush_pending_cycles)(void);
extern PSX_THREAD_LOCAL void (*g_overlay_flush_pending_cycles)(void);
if (g_overlay_flush_pending_cycles) g_overlay_flush_pending_cycles();
if (g_psx_render_pass_active) { render_pass_store(addr, val, 2); return; }
if (g_ls_mode == 2) { ls_write_hook(addr, 2, val); return; }
@@ -2302,6 +2330,7 @@ static void psx_write_half_raw(uint32_t addr, uint16_t val) {
static uint8_t psx_read_byte_raw(uint32_t addr);
uint8_t psx_read_byte(uint32_t addr) {
if (s_backing != &s_live_backing) return psx_read_byte_raw(addr);
if (g_ls_mode == 2) return (uint8_t)ls_read_hook(addr, 1, 0u);
if (g_ls_mode != 1 || s_ls_op_active || g_ls_suppress_record || g_dma_exec_depth > 0) return psx_read_byte_raw(addr);
s_ls_op_active = 1;
@@ -2576,7 +2605,7 @@ uint8_t psx_guest_read_byte(uint32_t addr) { return psx_read_byte(addr); }
static void psx_write_byte_raw(uint32_t addr, uint8_t val);
void psx_write_byte(uint32_t addr, uint8_t val) {
extern void (*g_overlay_flush_pending_cycles)(void);
extern PSX_THREAD_LOCAL void (*g_overlay_flush_pending_cycles)(void);
if (g_overlay_flush_pending_cycles) g_overlay_flush_pending_cycles();
if (g_psx_render_pass_active) { render_pass_store(addr, val, 1); return; }
if (g_ls_mode == 2) { ls_write_hook(addr, 1, val); return; }
@@ -2663,3 +2692,76 @@ static void psx_write_byte_raw(uint32_t addr, uint8_t val) {
}
unmapped_fatal(addr, phys, "WRITE");
}
struct PsxMemoryExecutionState { size_t bytes; unsigned char data[]; };
typedef struct { void *address; size_t bytes; } MemoryExecutionRegion;
static size_t memory_execution_regions(MemoryExecutionRegion *regions) {
size_t n = 0;
#define MEMORY_REGION(name) do { regions[n].address = &(name); regions[n++].bytes = sizeof(name); } while (0)
MEMORY_REGION(dirty_ram_bitmap);
MEMORY_REGION(s_kb_lo);
MEMORY_REGION(s_kb_span);
MEMORY_REGION(s_kb_rom_off);
MEMORY_REGION(s_kb_pr);
MEMORY_REGION(s_kb_pr_n);
MEMORY_REGION(kbless_patch_skips);
MEMORY_REGION(kbless_state);
MEMORY_REGION(kbless_enabled);
MEMORY_REGION(kbless_native_hits);
MEMORY_REGION(kbless_verifies);
MEMORY_REGION(kbless_mismatches);
MEMORY_REGION(kbless_invalidations);
MEMORY_REGION(text_ref_image);
MEMORY_REGION(text_ref_lo);
MEMORY_REGION(text_ref_hi);
MEMORY_REGION(text_modified_bitmap);
MEMORY_REGION(text_diverged_bitmap);
MEMORY_REGION(g_text_native_blocked);
MEMORY_REGION(g_text_diverged_pages);
MEMORY_REGION(g_text_exact_mismatches);
MEMORY_REGION(g_text_exact_last_range_lo);
MEMORY_REGION(g_text_exact_last_range_len);
MEMORY_REGION(g_text_exact_last_mismatch);
MEMORY_REGION(g_text_exact_last_live);
MEMORY_REGION(g_text_exact_last_ref);
MEMORY_REGION(overlay_watch_bitmap);
MEMORY_REGION(overlay_page_gen);
MEMORY_REGION(mem_ctrl);
MEMORY_REGION(ram_size_reg);
MEMORY_REGION(cache_ctrl);
MEMORY_REGION(sr_ptr);
MEMORY_REGION(i_stat);
MEMORY_REGION(i_mask);
#undef MEMORY_REGION
return n;
}
PsxMemoryExecutionState *memory_execution_create(void) {
MemoryExecutionRegion regions[64];
size_t n = memory_execution_regions(regions), bytes = 0;
for (size_t i = 0; i < n; ++i) bytes += regions[i].bytes;
PsxMemoryExecutionState *state = (PsxMemoryExecutionState *)malloc(sizeof(*state) + bytes);
if (state) state->bytes = bytes;
return state;
}
void memory_execution_destroy(PsxMemoryExecutionState *state) { free(state); }
void memory_execution_capture(PsxMemoryExecutionState *state) {
MemoryExecutionRegion regions[64];
size_t n = memory_execution_regions(regions), offset = 0;
if (!state || s_backing != &s_live_backing) abort();
for (size_t i = 0; i < n; ++i) {
if (offset + regions[i].bytes > state->bytes) abort();
memcpy(state->data + offset, regions[i].address, regions[i].bytes);
offset += regions[i].bytes;
}
}
void memory_execution_bind(const PsxMemoryExecutionState *state, const uint32_t *status) {
MemoryExecutionRegion regions[64];
size_t n = memory_execution_regions(regions), offset = 0;
if (!state || s_backing == &s_live_backing) abort();
for (size_t i = 0; i < n; ++i) {
if (offset + regions[i].bytes > state->bytes) abort();
memcpy(regions[i].address, state->data + offset, regions[i].bytes);
offset += regions[i].bytes;
}
sr_ptr = status; /* Never retain the source CPU's status pointer. */
}
+41 -33
View File
@@ -142,10 +142,19 @@ struct RuntimeMods {
/* The plan is a netplay match's own-view set: its hooks run only inside
* psx_mod_render_local_view, its vblank/savestate callbacks not at all. */
bool netplay_view_plan = false;
};
/* Callback ownership belongs to the guest execution thread. The active
* plan and its hook tables remain immutable until all executions stop. */
struct ModExecutionContext {
const ModResolution::Plugin* current_plugin = nullptr;
CPUState* current_function_cpu = nullptr;
bool current_function_finished = false;
};
ModExecutionContext& execution() {
static thread_local ModExecutionContext value;
return value;
}
RuntimeMods& state() {
static RuntimeMods value;
@@ -253,23 +262,22 @@ public:
* only entry hooks would let VBlank erase the interrupted plugin or complete
* a function that belongs to another callback. */
class PluginCallbackScope {
RuntimeMods& runtime;
const ModResolution::Plugin* previous_plugin;
CPUState* previous_cpu;
bool previous_finished;
public:
PluginCallbackScope(RuntimeMods& s, const ModResolution::Plugin* plugin,
CPUState* cpu = nullptr)
: runtime(s), previous_plugin(s.current_plugin),
previous_cpu(s.current_function_cpu), previous_finished(s.current_function_finished) {
s.current_plugin = plugin;
s.current_function_cpu = cpu;
s.current_function_finished = false;
: previous_plugin(execution().current_plugin),
previous_cpu(execution().current_function_cpu), previous_finished(execution().current_function_finished) {
execution().current_plugin = plugin;
execution().current_function_cpu = cpu;
execution().current_function_finished = false;
}
~PluginCallbackScope() {
runtime.current_plugin = previous_plugin;
runtime.current_function_cpu = previous_cpu;
runtime.current_function_finished = previous_finished;
execution().current_plugin = previous_plugin;
execution().current_function_cpu = previous_cpu;
execution().current_function_finished = previous_finished;
}
PluginCallbackScope(const PluginCallbackScope&) = delete;
PluginCallbackScope& operator=(const PluginCallbackScope&) = delete;
@@ -293,7 +301,7 @@ std::vector<ActiveFunctionEntryHook>& active_function_entry_hooks() {
static std::vector<ActiveFunctionEntryHook> value;
return value;
}
unsigned function_entry_depth;
thread_local unsigned function_entry_depth;
std::vector<ActiveFunctionEntryHook>& active_guest_functions() {
static std::vector<ActiveFunctionEntryHook> value;
@@ -2603,9 +2611,9 @@ extern "C" void mod_runtime_on_savestate_loaded(void) {
}
s.main_applied = true;
for (const ModResolution::Plugin& plugin : s.plan.plugins) {
s.current_plugin = &plugin;
execution().current_plugin = &plugin;
mod_invoke_savestate_plugin(plugin.id);
s.current_plugin = nullptr;
execution().current_plugin = nullptr;
}
if (applied)
std::fprintf(stdout,
@@ -2998,10 +3006,10 @@ extern "C" void psx_mod_set_disc_request_provider(PSXModDiscRequestProvider prov
return;
}
RuntimeMods& s = state();
if (!s.initialized || !s.plan.ok || !s.activating || !s.current_plugin ||
if (!s.initialized || !s.plan.ok || !s.activating || !execution().current_plugin ||
s.netplay_view_plan || psx_netplay_active()) return;
g_disc_request_provider = provider;
g_disc_request_owner = s.current_plugin;
g_disc_request_owner = execution().current_plugin;
}
extern "C" int mod_runtime_disc_request(int *ready) {
@@ -3150,10 +3158,10 @@ extern "C" int psx_mod_current_resource_path(const char* resource_id,
if (!resource_id || !resource_id[0] || !out || out_size == 0)
return 0;
RuntimeMods& s = state();
if (!s.initialized || !s.plan.ok || !s.current_plugin) return 0;
if (!s.initialized || !s.plan.ok || !execution().current_plugin) return 0;
for (const ModResolution::Resource& resource : s.plan.resources) {
if (resource.package_id != s.current_plugin->package_id ||
resource.feature_id != s.current_plugin->feature_id ||
if (resource.package_id != execution().current_plugin->package_id ||
resource.feature_id != execution().current_plugin->feature_id ||
resource.id != resource_id)
continue;
const std::string text = resource.path.u8string();
@@ -3168,7 +3176,7 @@ extern "C" int psx_mod_current_option_value(const char* option_id,
char* out, uint32_t out_size) {
using namespace PSXRecompV4;
if (out && out_size) out[0] = '\0';
const auto* plugin = state().current_plugin;
const auto* plugin = execution().current_plugin;
if (!plugin) return 0;
return psx_mod_option_value(plugin->package_id.c_str(), plugin->feature_id.c_str(),
option_id, out, out_size);
@@ -3239,10 +3247,10 @@ extern "C" int psx_mod_current_resource_bytes(const char* resource_id,
if (size) *size = 0;
if (!resource_id || !*resource_id || !bytes || !size) return 0;
const auto& s = state();
if (!s.initialized || !s.plan.ok || !s.current_plugin) return 0;
if (!s.initialized || !s.plan.ok || !execution().current_plugin) return 0;
for (const auto& resource : s.plan.resources) {
if (resource.package_id == s.current_plugin->package_id &&
resource.feature_id == s.current_plugin->feature_id &&
if (resource.package_id == execution().current_plugin->package_id &&
resource.feature_id == execution().current_plugin->feature_id &&
resource.id == resource_id && resource.bytes) {
*bytes = resource.bytes->data();
*size = resource.bytes->size();
@@ -3416,8 +3424,8 @@ extern "C" int psx_mod_finish_function(CPUState* cpu) {
using namespace PSXRecompV4;
RuntimeMods& s = state();
if (psx_mod_host_output_scope()) return 0;
if (!cpu || s.current_function_cpu != cpu || !s.current_plugin) return 0;
s.current_function_finished = true;
if (!cpu || execution().current_function_cpu != cpu || !execution().current_plugin) return 0;
execution().current_function_finished = true;
return 1;
}
@@ -3535,7 +3543,7 @@ static void run_host_output_hooks(CPUState* cpu, uint32_t address) {
++s_host_output_depth;
it->callback(&view, address);
--s_host_output_depth;
s.current_function_finished = false;
execution().current_function_finished = false;
}
}
@@ -3544,8 +3552,8 @@ static void run_host_output_hooks(CPUState* cpu, uint32_t address) {
static bool mod_host_output_write_blocked(void) {
const auto& s = PSXRecompV4::state();
return psx_mod_host_output_scope() ||
(s.netplay_view_plan && s.current_plugin &&
s.current_plugin->netplay_host_output);
(s.netplay_view_plan && PSXRecompV4::execution().current_plugin &&
PSXRecompV4::execution().current_plugin->netplay_host_output);
}
extern "C" int psx_mod_function_entry(CPUState* cpu, uint32_t address) {
@@ -3567,7 +3575,7 @@ extern "C" int psx_mod_function_entry(CPUState* cpu, uint32_t address) {
if (it->callback) it->callback(cpu, address);
/* Either completion form skips the body: an entry callback that
* called psx_mod_finish_function(), or a filter returning nonzero. */
const bool handled = s.current_function_finished ||
const bool handled = execution().current_function_finished ||
(it->filter && it->filter(cpu, address));
--function_entry_depth;
if (handled) {
@@ -3640,17 +3648,17 @@ extern "C" int psx_mod_function_entry_active(void) {
extern "C" void mod_runtime_function_entry_context_save(ModFunctionEntryContext *out) {
out->depth = PSXRecompV4::function_entry_depth;
out->plugin = PSXRecompV4::state().current_plugin;
out->cpu = PSXRecompV4::state().current_function_cpu;
out->finished = PSXRecompV4::state().current_function_finished;
out->plugin = PSXRecompV4::execution().current_plugin;
out->cpu = PSXRecompV4::execution().current_function_cpu;
out->finished = PSXRecompV4::execution().current_function_finished;
}
extern "C" void mod_runtime_function_entry_context_restore(const ModFunctionEntryContext *in) {
PSXRecompV4::function_entry_depth = in->depth;
PSXRecompV4::state().current_plugin =
PSXRecompV4::execution().current_plugin =
static_cast<const PSXRecompV4::ModResolution::Plugin *>(in->plugin);
PSXRecompV4::state().current_function_cpu = in->cpu;
PSXRecompV4::state().current_function_finished = in->finished != 0;
PSXRecompV4::execution().current_function_cpu = in->cpu;
PSXRecompV4::execution().current_function_finished = in->finished != 0;
}
/* Apply the committed plan's disc writes and overlays to one sector. The
+3 -2
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
#include "netplay_state_digest.h"
#include "mod_netplay.h"
#include "cdrom.h"
@@ -14,8 +15,8 @@
extern uint8_t* memory_get_ram_ptr(void);
extern uint32_t memory_get_ram_bytes(void);
extern uint32_t i_stat;
extern uint32_t i_mask;
extern PSX_THREAD_LOCAL uint32_t i_stat;
extern PSX_THREAD_LOCAL uint32_t i_mask;
extern void timers_get_snapshot(uint16_t counter[3], uint32_t mode[3],
uint16_t target[3], int32_t irq_line[3],
uint32_t frac[3]);
+9 -8
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
#include "overlay_loader.h"
#include "overlay_api.h"
#include "overlay_path_canon.h"
@@ -543,7 +544,7 @@ static uint32_t s_last_write_size = 0;
* before the guest-visible write; block/device boundaries flush through the
* DLL glue directly. */
static OverlayFlushFn s_dll_flush[4096];
OverlayFlushFn g_overlay_flush_pending_cycles = NULL;
PSX_THREAD_LOCAL OverlayFlushFn g_overlay_flush_pending_cycles = NULL;
static OverlayFlushFn overlay_flush_enter(const Candidate *c) {
OverlayFlushFn prev = g_overlay_flush_pending_cycles;
@@ -2268,8 +2269,8 @@ static int s_irq_suppress_cdrom_only = 0;
static int s_irq_post_dispatch_pump = 0;
static int s_irq_defer_cdrom = 0;
extern uint32_t i_stat;
extern uint32_t i_mask;
extern PSX_THREAD_LOCAL uint32_t i_stat;
extern PSX_THREAD_LOCAL uint32_t i_mask;
/* Nested call-unit depth (Ape memcard native<->interp resume-desync fix).
*
@@ -2296,7 +2297,7 @@ extern uint32_t i_mask;
* inside units; the blanket skip survives only behind
* PSX_OVERLAY_UNIT_IRQ_DEFER=1 for A/B bisection (overlay_unit_defer.h).
* Incremented only when the A/B toggle is on (below). */
int g_call_unit_depth = 0;
PSX_THREAD_LOCAL int g_call_unit_depth = 0;
/* A/B toggle for the nested-unit IRQ deferral (PSX_OVERLAY_UNIT_DEFER, default
* ON). Gates the depth INCREMENTS (so =0 leaves g_call_unit_depth at 0 and
@@ -2553,7 +2554,7 @@ static void init_callbacks(void) {
* static and interpreted stores do, in every build, because memory.c's
* store filters read it as well as the debug tooling. */
{
extern uint32_t g_debug_last_store_pc;
extern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
s_callbacks.last_store_pc = &g_debug_last_store_pc;
}
/* Widescreen hooks (ABI v3): overlay-emitted psx_ws_* calls forward to
@@ -4271,7 +4272,7 @@ retry_candidates:
* the current activation, so we permanently demote that entry to interp. Called
* from memory.c only when the written page is watched. */
void overlay_loader_active_write_check(uint32_t phys, uint32_t size) {
extern uint32_t g_debug_last_store_pc;
extern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
uint32_t p = phys & 0x1FFFFFFFu;
for (int d = 0; d < s_active_depth; d++) {
Candidate *c = &s_cand[s_active_stack[d]];
@@ -4713,7 +4714,7 @@ static void run_shadow_diff(CPUState *cpu, Candidate *c, uint32_t addr) {
extern void psx_dispatch_call(CPUState *cpu, uint32_t addr, uint32_t return_addr);
extern int gte_replay_side_effects_begin(void);
extern void gte_replay_side_effects_end(void);
extern uint32_t g_debug_current_func_addr;
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
extern uint64_t psx_exception_setjmp_epoch(void);
extern int g_shadow_mmio_watch;
@@ -4912,7 +4913,7 @@ static void shadow_escape_cleanup(void) {
s_suppress_irq = s_shadow_saved_supp;
g_exec_phase = s_shadow_saved_exec_phase;
g_psx_call_bail = s_shadow_saved_call_bail;
{ extern uint32_t g_debug_current_func_addr;
{ extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
g_debug_current_func_addr = s_shadow_saved_debug_func; }
s_irq_callcount = s_shadow_saved_irq_calls;
s_irq_suppressed = s_shadow_saved_irq_supp;
+8 -1
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* psx_bios_backend.c — routing between the runtime and the active BIOS.
*
* A build links every recompiled BIOS it ships (bundled OpenBIOS plus a retail
@@ -19,6 +20,7 @@
#include "bios_hle.h"
#include "cpu_state.h"
#include "psx_bios_backend.h"
#include "psx_execution.h"
/* ── State published to the rest of the runtime ─────────────────────────── */
@@ -37,7 +39,7 @@ uint32_t psx_bios_kernel_patch_range_count = 0;
/* Dispatch nesting depth. Shared dispatch state, not per-image: each generated
* dispatch used to define its own copy, which is precisely why two of them
* could not be linked together. */
int g_psx_dispatch_depth = 0;
PSX_THREAD_LOCAL int g_psx_dispatch_depth = 0;
/* ── Registry ───────────────────────────────────────────────────────────────
*
@@ -99,14 +101,19 @@ __attribute__((constructor)) static void psx_bios_registry_ctor(void) { registry
* The generated BIOS C defines <STEM>_psx_dispatch; these keep the unprefixed
* names that ~66 runtime call sites and the game's generated C already use. */
PSX_THREAD_LOCAL PsxExecutionDispatch g_psx_execution_dispatch;
PSX_THREAD_LOCAL PsxExecutionAbort g_psx_execution_abort;
void psx_dispatch(CPUState *cpu, uint32_t addr)
{
if (g_psx_execution_dispatch) { g_psx_execution_dispatch(cpu, addr, 0); return; }
if (!psx_bios_active || !psx_bios_active->dispatch) return;
psx_bios_active->dispatch(cpu, addr);
}
void psx_dispatch_call(CPUState *cpu, uint32_t addr, uint32_t return_addr)
{
if (g_psx_execution_dispatch) { g_psx_execution_dispatch(cpu, addr, return_addr); return; }
if (!psx_bios_active || !psx_bios_active->dispatch_call) return;
psx_bios_active->dispatch_call(cpu, addr, return_addr);
}
+2 -1
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* psx_bios_module.c — host side of loadable BIOS backends (psx_bios_module.h).
*
* A bundled release links OpenBIOS only. When the player picks a retail dump
@@ -203,7 +204,7 @@ int psx_bios_module_cache_path(const char *exe_dir, const char *stem,
void psx_bios_module_fill_callbacks(PsxBiosModuleCallbacks *out) {
extern int (*g_psx_bios_hle_hook)(struct CPUState *cpu, uint32_t phys);
extern uint64_t g_dispatch_static_hits;
extern uint64_t g_psx_bail_flattened;
extern PSX_THREAD_LOCAL uint64_t g_psx_bail_flattened;
extern int psx_game_address_in_text(uint32_t addr);
extern uint64_t s_frame_count;
extern uint32_t g_psx_mod_guest_functions;
+3 -2
View File
@@ -320,7 +320,7 @@ static uint32_t devices_cycles_to_next_internal_event(void) {
* tens of thousands of host-side re-detections per second. VBlank remains an
* unconditional ceiling so frame pacing is never crossed in one jump. */
static uint32_t devices_cycles_to_next_idle_event(void) {
extern uint32_t i_mask;
extern PSX_THREAD_LOCAL uint32_t i_mask;
uint32_t best = interrupts_cycles_to_vblank();
uint32_t t = timers_cycles_to_irq(i_mask); if (t < best) best = t;
uint32_t c = cdrom_cycles_to_irq(i_mask); if (c < best) best = c;
@@ -723,7 +723,8 @@ void psx_idle_note_check(CPUState *cpu, uint32_t check_pc) {
(void)cpu; (void)check_pc;
return;
#else
extern int g_psx_call_bail, g_precise_mode, g_ls_mode, g_ls_replay_active;
extern PSX_THREAD_LOCAL int g_psx_call_bail;
extern int g_precise_mode, g_ls_mode, g_ls_replay_active;
extern int psx_get_in_exception(void);
extern uint64_t g_guest_store_count, g_mmio_access_count;
+7 -6
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* psx_icache.c — R3000A instruction-cache FETCH cost model (faithful).
*
* FAITHFUL_TIMING_PLAN.md axis-2 (I-cache). Transcribed from the in-tree Beetle
@@ -14,7 +15,7 @@
#include <string.h>
int psx_icache_enabled(void) {
static int s = -1;
static PSX_THREAD_LOCAL int s = -1;
if (s < 0) {
const char* e = getenv("PSX_ICACHE");
s = (e == NULL || e[0] == '\0') ? 1 : (e[0] != '0');
@@ -25,16 +26,16 @@ int psx_icache_enabled(void) {
/* Per-word tag+validity, mirroring Beetle ICache[idx].TV. Init to a value that can
* never equal an aligned fetch address (aligned addrs have bits 0-1 = 0), so every
* line starts cold (miss). */
uint32_t g_psx_icache_tv[1024];
int g_psx_icache_active = -1;
PSX_THREAD_LOCAL uint32_t g_psx_icache_tv[1024];
PSX_THREAD_LOCAL int g_psx_icache_active = -1;
/* Whole-call shadow: a transactional view of the cache tags so an AOT/diff
* shadow replay can evolve the cache from the recorded entry state and then
* restore the live view (master's shadow machinery, retargeted at the global
* tag array the interpreter fast path exports). */
static uint32_t s_icache_shadow_entry[1024];
static uint32_t s_icache_shadow_live[1024];
static int s_icache_shadow_state = 0; /* 0 none, 1 recorded, 2 replay */
static PSX_THREAD_LOCAL uint32_t s_icache_shadow_entry[1024];
static PSX_THREAD_LOCAL uint32_t s_icache_shadow_live[1024];
static PSX_THREAD_LOCAL int s_icache_shadow_state = 0; /* 0 none, 1 recorded, 2 replay */
int psx_icache_shadow_record_begin(void) {
if (s_icache_shadow_state != 0) return 0;
+3 -2
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* psx_interpreter.c — Minimal R3000A interpreter for oracle builds.
*
* Implements the full MIPS I instruction set used by the PSX BIOS:
@@ -22,11 +23,11 @@
#include <stdlib.h>
/* I_STAT and I_MASK are owned by memory.c */
extern uint32_t i_stat;
extern PSX_THREAD_LOCAL uint32_t i_stat;
/* Central IRQ-raise choke point (interrupts.c) — also records the device ring. */
extern void psx_irq_raise(uint32_t bit, uint32_t detail);
extern void psx_irq_refresh_cause_ip2(void); /* interrupts.c — CAUSE.IP2 mirror */
extern uint32_t i_mask;
extern PSX_THREAD_LOCAL uint32_t i_mask;
#define COP0_SR 12
#define COP0_CAUSE 13
+15 -14
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* render_pass.c - host-timed render passes (psx_mod_render_pass_plan /
* psx_mod_render_pass, mod_plugins.h). See docs/RENDER_PASSES.md.
*
@@ -44,8 +45,8 @@ extern uint16_t *gpu_get_vram_ptr(void);
* the opt-in 8 MB RAM mod. Latched by memory_init() once per boot, so it
* cannot change inside a pass, but it can between sessions. */
extern uint32_t memory_get_ram_bytes(void);
extern uint32_t i_stat;
extern uint32_t i_mask;
extern PSX_THREAD_LOCAL uint32_t i_stat;
extern PSX_THREAD_LOCAL uint32_t i_mask;
/* gte.cpp: precision-tracking shadows (PGXP + the position cache) roll back
* with the machine, or they describe the words a pass wrote. */
extern void gte_precision_checkpoint_begin(void);
@@ -70,22 +71,22 @@ extern int psx_rewind_is_open(void);
extern int psx_presentation_fast_forward(void);
extern uint64_t g_guest_store_count;
extern uint64_t g_mmio_access_count;
extern uint32_t g_debug_last_store_pc;
extern uint32_t g_debug_current_func_addr;
extern int g_psx_dispatch_depth;
extern int g_psx_call_bail;
extern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
extern PSX_THREAD_LOCAL int g_psx_dispatch_depth;
extern PSX_THREAD_LOCAL int g_psx_call_bail;
extern int g_ls_mode;
extern int g_ls_replay_active;
static int s_netplay_allowed;
extern void (*g_overlay_flush_pending_cycles)(void);
extern PSX_THREAD_LOCAL void (*g_overlay_flush_pending_cycles)(void);
/* Host nesting that runtime frames set on entry and put back on exit. */
extern int g_call_unit_depth; /* overlay_loader_call_native */
extern int g_dma_exec_depth; /* dma.c kick / async writes */
extern int g_host_store_depth; /* memory.c psx_host_write_* */
extern int g_dma_cur_ch;
extern uint32_t g_dma_cur_madr;
extern uint32_t g_dma_cur_bcr;
extern uint32_t g_dma_initiator_pc;
extern PSX_THREAD_LOCAL int g_call_unit_depth; /* overlay_loader_call_native */
extern PSX_THREAD_LOCAL int g_dma_exec_depth; /* dma.c kick / async writes */
extern PSX_THREAD_LOCAL int g_host_store_depth; /* memory.c psx_host_write_* */
extern PSX_THREAD_LOCAL int g_dma_cur_ch;
extern PSX_THREAD_LOCAL uint32_t g_dma_cur_madr;
extern PSX_THREAD_LOCAL uint32_t g_dma_cur_bcr;
extern PSX_THREAD_LOCAL uint32_t g_dma_initiator_pc;
extern int g_dirty_interp_active; /* dirty-RAM interpreter */
extern int g_exec_phase;
extern int g_precise_mode;
+75
View File
@@ -10,6 +10,7 @@
* can never fall between a check and a wait. */
#include "render_thread.h"
#include "psx_thread_local.h"
#include <stdatomic.h>
#include <stdlib.h>
@@ -79,6 +80,70 @@ static void cpu_relax(void) {
}
#endif
/* List capture is independent of the live producer and every ring counter. */
struct RtCommandList {
unsigned char *data;
size_t capacity, used, pending;
int sealed;
};
static PSX_THREAD_LOCAL RtCommandList *t_capture;
static PSX_THREAD_LOCAL RtCaptureAbort t_capture_abort;
static void capture_fail(const char *reason) {
if (t_capture_abort) t_capture_abort(reason);
abort();
}
RtCommandList *rt_list_create(size_t capacity) {
if (capacity < RT_ALIGN || capacity > UINT32_MAX) return NULL;
RtCommandList *list = (RtCommandList *)calloc(1, sizeof(*list));
if (!list) return NULL;
list->data = (unsigned char *)malloc(capacity);
if (!list->data) { free(list); return NULL; }
list->capacity = capacity;
return list;
}
void rt_list_destroy(RtCommandList *list) {
if (!list) return;
if (list == t_capture) abort();
free(list->data); free(list);
}
void rt_capture_begin(RtCommandList *list, RtCaptureAbort failure) {
if (!list || t_capture) abort();
list->used = list->pending = 0;
list->sealed = 0;
t_capture = list; t_capture_abort = failure;
}
void rt_capture_end(void) {
if (!t_capture) return;
/* An abort can abandon the last uncommitted record. It is never visible. */
t_capture->pending = 0;
t_capture->sealed = 1;
t_capture = NULL; t_capture_abort = NULL;
}
int rt_capture_active(void) { return t_capture != NULL; }
size_t rt_list_bytes(const RtCommandList *list) {
return list && list->sealed ? list->used : 0;
}
static void *capture_record(uint16_t op, uint16_t flags, uint32_t payload) {
RtCommandList *list = t_capture;
size_t size = (sizeof(RtCmd) + (uint64_t)payload + RT_ALIGN - 1u) & ~(uint64_t)(RT_ALIGN - 1u);
if (!op || list->pending) capture_fail("command_order");
if (size > list->capacity - list->used) capture_fail("command_capacity");
RtCmd *cmd = (RtCmd *)(list->data + list->used);
*cmd = (RtCmd){(uint32_t)size, op, flags, payload, 0};
list->pending = size;
return cmd + 1;
}
void rt_list_execute(const RtCommandList *list, RtExecFn exec, void *user) {
if (!list || !list->sealed || !exec || t_capture) abort();
for (size_t offset = 0; offset < list->used;) {
const RtCmd *cmd = (const RtCmd *)(list->data + offset);
if (cmd->size < sizeof(*cmd) || cmd->size % RT_ALIGN ||
cmd->size > list->used - offset || cmd->payload > cmd->size - sizeof(*cmd)) abort();
exec(user, cmd, cmd + 1);
offset += cmd->size;
}
}
#define OP_PAD 0u /* filler to the ring end; consumer skips it */
#define F_FRAME 0x8000u /* op 0 with this flag: frame marker */
@@ -306,6 +371,7 @@ static int pred_frames(uint64_t limit) {
static int pred_released(uint64_t unused) { (void)unused; return atomic_load(&R.released); }
void *rt_cmd_begin(uint16_t op, uint16_t flags, uint32_t payload_bytes) {
if (t_capture) return capture_record(op, flags, payload_bytes);
if (!rt_running() || R.pending || op == OP_PAD) return NULL;
uint64_t size = align_up(sizeof(RtCmd) + (uint64_t)payload_bytes);
if (size > R.cap / 2) { R.oversize++; return NULL; }
@@ -338,6 +404,10 @@ void *rt_cmd_begin(uint16_t op, uint16_t flags, uint32_t payload_bytes) {
}
void rt_cmd_commit(void) {
if (t_capture) {
if (!t_capture->pending) capture_fail("command_commit");
t_capture->used += t_capture->pending; t_capture->pending = 0; return;
}
if (!R.pending) return;
R.pending = 0;
uint64_t end = R.pend_start + R.pend_size;
@@ -362,6 +432,7 @@ static void commit_marker(uint16_t flags) {
}
void rt_frame_end(void) {
if (t_capture) capture_fail("live_render_sync");
if (!rt_running()) return;
if (R.held) {
/* Nothing is queued while the context is held; count the frame as
@@ -389,12 +460,14 @@ int rt_frames_ahead(void) {
}
void rt_drain(void) {
if (t_capture) capture_fail("live_render_sync");
if (!rt_running() || R.held) return;
uint64_t w = atomic_load(&R.wpos);
emu_wait(pred_drained, w);
}
void rt_acquire(const char *reason) {
if (t_capture) capture_fail("live_render_sync");
if (!rt_running() || R.held) return;
uint64_t t0 = now_ns();
rt_drain();
@@ -415,6 +488,7 @@ void rt_acquire(const char *reason) {
}
void rt_release(void) {
if (t_capture) capture_fail("live_render_sync");
if (!rt_running() || !R.held) return;
R.ctx(R.user, 0);
mtx_lock(&R.mtx);
@@ -476,6 +550,7 @@ int rt_start(const RtConfig *cfg) {
}
void rt_stop(void) {
if (t_capture) capture_fail("live_render_sync");
if (!rt_running()) return;
int was_held = R.held;
if (!was_held) rt_drain();
+10 -9
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/*
* sio.c -- PS1 Serial I/O (SIO0) controller
*
@@ -21,7 +22,7 @@
#include <string.h>
/* I_STAT is owned by memory.c */
extern uint32_t i_stat;
extern PSX_THREAD_LOCAL uint32_t i_stat;
/* Central IRQ-raise choke point (interrupts.c) — also records the device ring. */
extern void psx_irq_raise(uint32_t bit, uint32_t detail);
@@ -492,9 +493,9 @@ static int s_card_handoff_count = 0;
static int s_card_handoff_armed = 0; /* set after LOAD-style probe abort */
static void card_handoff_push(uint8_t kind, uint8_t byte) {
extern uint32_t g_debug_current_func_addr;
extern uint32_t g_debug_last_store_pc;
extern uint32_t i_mask;
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
extern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
extern PSX_THREAD_LOCAL uint32_t i_mask;
extern uint64_t psx_get_cycle_count(void);
SioCardHandoffEntry *e = &s_card_handoff[s_card_handoff_idx];
e->kind = kind;
@@ -1959,7 +1960,7 @@ static void mc_process_byte(uint8_t tx_byte) {
static void sio_process_byte(uint8_t tx_byte) {
/* ---- Trace: capture pre-state ---- */
extern uint32_t g_debug_current_func_addr;
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
uint8_t trace_mc_pre = (uint8_t)mc_state;
uint8_t trace_dev_pre = (uint8_t)active_device;
uint8_t trace_irq_cd = (uint8_t)(sio_irq_countdown > 255 ? 255 : sio_irq_countdown);
@@ -2398,7 +2399,7 @@ void sio_write(uint32_t addr, uint32_t value) {
sio_mc_abort_ctrl = (uint16_t)value;
}
if (sio_txn_open) {
extern uint32_t g_debug_current_func_addr;
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
txn_close(SIO_TXN_END_ABORT_RESET, mc_state, g_debug_current_func_addr);
}
sio_stat = SIO_STAT_TX_RDY | SIO_STAT_TX_EMPTY;
@@ -2479,7 +2480,7 @@ void sio_write(uint32_t addr, uint32_t value) {
/* SELECT deassertion cancels the device's pending DSR pulse.
* It cannot create an ACK or preserve one for later delivery. */
if (active_device == DEV_MEMCARD && mc_state != MC_IDLE && sio_txn_open) {
extern uint32_t g_debug_current_func_addr;
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
txn_close(SIO_TXN_END_ABORT_OTHER, mc_state, g_debug_current_func_addr);
}
/* Persist mc_flag back to the active slot before resetting. mc_flag
@@ -2707,7 +2708,7 @@ static void sio_fire_ack_irq(void) {
card_handoff_push(5, 0);
}
extern uint32_t g_debug_current_func_addr;
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
extern uint8_t psx_read_byte(uint32_t addr);
SioIrqEntry *e = &sio_irq_buf[sio_irq_idx];
e->seq = sio_irq_seq;
@@ -2876,7 +2877,7 @@ void sio_tick(int cycles) {
event_ring_record_aux(EV_DEQ, (uint8_t)SRC_SIO, 1u); /* SIO shift IRQ fired */
/* SIO IRQ ring capture. */
extern uint32_t g_debug_current_func_addr;
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
extern uint8_t psx_read_byte(uint32_t addr);
SioIrqEntry *e = &sio_irq_buf[sio_irq_idx];
e->seq = sio_irq_seq;
+5 -4
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* starvation_ring.c — diagnostic-only.
*
* Always-on ring of the last 16K SIO/MMIO events with full SIO state
@@ -38,10 +39,10 @@ static uint64_t host_us_now(void) {
}
#endif
extern uint32_t g_debug_current_func_addr;
extern uint32_t g_debug_last_store_pc;
extern uint32_t i_stat;
extern uint32_t i_mask;
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
extern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
extern PSX_THREAD_LOCAL uint32_t i_stat;
extern PSX_THREAD_LOCAL uint32_t i_mask;
extern int psx_get_in_exception(void);
#if STARVATION_RING_ENABLED
+4 -3
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/*
* timers.c — PS1 root counters (3 timers), v4 clean port.
*
@@ -47,8 +48,8 @@ typedef struct {
int irq_line; /* toggle mode state */
} Timer;
static Timer timers[3];
static uint32_t timer_frac[3];
static PSX_THREAD_LOCAL Timer timers[3];
static PSX_THREAD_LOCAL uint32_t timer_frac[3];
/* Always-on RootCounter/Timer IRQ-fire telemetry (Tomba 2 RCnt-wait diag):
* counts how many times each timer raised its IRQ. If a game spins on an
@@ -67,7 +68,7 @@ void timers_get_debug(int t, uint16_t *counter, uint16_t *target,
}
/* I_STAT is owned by memory.c — we poke it directly via this extern. */
extern uint32_t i_stat;
extern PSX_THREAD_LOCAL uint32_t i_stat;
/* Central IRQ-raise choke point (interrupts.c) — also records the device ring. */
extern void psx_irq_raise(uint32_t bit, uint32_t detail);
+7 -7
View File
@@ -100,11 +100,11 @@ int psx_is_dispatchable(uint32_t pc)
}
/* Dispatch call contract state (see cpu_state.h for the model). */
int g_psx_call_bail = 0;
uint64_t g_psx_bail_first = 0;
uint64_t g_psx_bail_resolved = 0;
uint64_t g_psx_bail_flattened = 0;
uint64_t g_psx_bail_anomaly = 0;
PSX_THREAD_LOCAL int g_psx_call_bail = 0;
PSX_THREAD_LOCAL uint64_t g_psx_bail_first = 0;
PSX_THREAD_LOCAL uint64_t g_psx_bail_resolved = 0;
PSX_THREAD_LOCAL uint64_t g_psx_bail_flattened = 0;
PSX_THREAD_LOCAL uint64_t g_psx_bail_anomaly = 0;
/* ---- Deduped bail-source ledger (Tomba 2 wild-return / splash-spin diag) ----
* psx_call_contract (cpu_state.h) calls psx_bail_record() at each FIRST-detected
@@ -818,7 +818,7 @@ void psx_request_return_to_lobby(void)
void psx_scheduler_run(CPUState* cpu)
{
extern int g_psx_dispatch_depth;
extern PSX_THREAD_LOCAL int g_psx_dispatch_depth;
g_in_scheduler_run = 1;
g_sched_escape.reason = PSX_RUN_CONTINUE;
for (;;) {
@@ -844,7 +844,7 @@ void psx_scheduler_run(CPUState* cpu)
* or dispatch_nonlocal_call) would skip its depth restore; clear the
* nested-unit gate so IRQ checks are not wedged-off after the escape
* (backstop for the Ape memcard native<->interp fix). */
{ extern int g_call_unit_depth; g_call_unit_depth = 0; }
{ extern PSX_THREAD_LOCAL int g_call_unit_depth; g_call_unit_depth = 0; }
/* flush_resume / savestate longjmp skips generated bb_defer cleanup. */
{
extern PSX_THREAD_LOCAL int g_psx_cyc_bb_defer;
+3 -2
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* Shared link stubs for tests that compile runtime/src/gpu.c directly and
* execute GP0 packets without a GL context, mod bank or emulator core.
* Include right after "../src/gpu.c". Define GPU_EXEC_REAL_UI_GROUP before
@@ -7,8 +8,8 @@
uint64_t s_frame_count;
PSX_THREAD_LOCAL uint64_t psx_cycle_count; /* gpu_timeline.c stamps notes with it */
int g_exec_phase;
uint32_t g_debug_current_func_addr;
uint32_t g_debug_last_store_pc;
PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
CPUState *debug_cpu_ptr;
int g_psx_vram_dirty_tracking;
uint32_t g_psx_ram_size = 0x00200000u;
+6 -5
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
#define PSX_OVERLAY_DLL_BUILD 1
#include "overlay_loader.h"
#undef PSX_OVERLAY_DLL_BUILD
@@ -27,15 +28,15 @@ static uint8_t s_scratch[1024];
uint32_t g_psx_ram_size = RAM_SIZE;
uint32_t g_psx_ram_mask = RAM_SIZE - 1u;
uint32_t g_debug_current_func_addr;
uint32_t g_debug_last_store_pc;
PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
uint32_t g_overlay_region_floor;
uint32_t i_stat, i_mask;
PSX_THREAD_LOCAL uint32_t i_stat, i_mask;
int g_exec_phase;
int g_idle_note_suppress;
int g_psx_call_bail;
PSX_THREAD_LOCAL int g_psx_call_bail;
uint32_t g_dirty_ram_code_gen;
uint64_t g_psx_bail_first, g_psx_bail_resolved;
PSX_THREAD_LOCAL uint64_t g_psx_bail_first, g_psx_bail_resolved;
uint64_t s_frame_count;
int g_shadow_mmio_watch;
uint64_t g_shadow_mmio_hits;
+4 -4
View File
@@ -15,15 +15,15 @@ int g_stub_device_restores;
/* ---- data ---------------------------------------------------------------- */
CPUState *debug_cpu_ptr;
uint32_t g_debug_current_func_addr;
uint32_t g_debug_last_store_pc;
PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
volatile int g_ds_recording;
int g_event_step_conservative;
int g_ls_mode;
int g_ls_replay_active;
int g_ls_suppress_record;
int g_ram_read_watch_active;
void (*g_overlay_flush_pending_cycles)(void);
PSX_THREAD_LOCAL void (*g_overlay_flush_pending_cycles)(void);
PSX_THREAD_LOCAL uint32_t g_psx_cyc_batch;
PSX_THREAD_LOCAL uint32_t g_psx_cyc_batch_limit;
PSX_THREAD_LOCAL uint32_t *g_psx_cyc_local_acc;
@@ -34,7 +34,7 @@ void pgxp_invalidate_word(uint32_t addr) { (void)addr; }
#endif
#ifndef PSX_STUBS_REAL_ICACHE
/* The I-cache model (psx_icache.c). isc_store_test links the real one. */
uint32_t g_psx_icache_tv[1024];
PSX_THREAD_LOCAL uint32_t g_psx_icache_tv[1024];
void psx_icache_isc_store(uint32_t biu, uint32_t addr, uint32_t value) {
(void)biu; (void)addr; (void)value; __builtin_trap(); /* SR.IsC is never set here */
}
+5 -5
View File
@@ -16,8 +16,8 @@ struct CPUState;
uint8_t g_test_ram[0x200000];
PSX_THREAD_LOCAL uint64_t psx_cycle_count = 0;
PSX_THREAD_LOCAL uint64_t psx_next_service_cycle = 0;
uint32_t i_stat = 0;
uint32_t i_mask = 0;
PSX_THREAD_LOCAL uint32_t i_stat = 0;
PSX_THREAD_LOCAL uint32_t i_mask = 0;
uint32_t g_psx_ram_size = 0x200000u;
uint32_t g_psx_vblank_cycles = 564480u;
@@ -46,11 +46,11 @@ int psx_netplay_rb_recover_null_pc(struct CPUState *c, uint32_t *out_pc)
/* ---- Runtime globals the two units reference ---- */
uint64_t g_async_rfe_fire_count = 0;
uint32_t g_async_rfe_resume_pc = 0;
int g_call_unit_depth = 0;
PSX_THREAD_LOCAL int g_call_unit_depth = 0;
int g_cosim_dirty_pump_site = 0;
int g_dirty_interp_active = 0;
uint32_t g_dirty_safe_resume_pc = 0;
int g_dma_exec_depth = 0;
PSX_THREAD_LOCAL int g_dma_exec_depth = 0;
int g_event_step_conservative = 0;
int g_exec_phase = 0;
int g_idle_skip_enabled = 0;
@@ -60,7 +60,7 @@ PSX_THREAD_LOCAL uint32_t g_psx_cyc_batch = 0;
PSX_THREAD_LOCAL uint32_t g_psx_cyc_batch_limit = 0;
PSX_THREAD_LOCAL int g_psx_cyc_bb_defer = 0;
PSX_THREAD_LOCAL uint32_t *g_psx_cyc_local_acc = NULL;
int g_psx_dispatch_depth = 0;
PSX_THREAD_LOCAL int g_psx_dispatch_depth = 0;
PSX_THREAD_LOCAL int g_psx_guest_time_frozen = 0;
uint32_t g_sentinel_reach_async = 0;
uint64_t g_sentinel_reach_traps = 0;
+3 -2
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* test_bios_module_host.c — the host side of loadable BIOS backends
* (runtime/src/psx_bios_module.c) against fake modules built by
* runtime/CMakeLists.txt from tests/bios_module_fake.c.
@@ -47,8 +48,8 @@ int psx_mod_dispatch_guest_function(struct CPUState *cpu, uint32_t address) {
}
int (*g_psx_bios_hle_hook)(struct CPUState *cpu, uint32_t phys) = 0;
uint64_t g_dispatch_static_hits = 0;
uint32_t g_debug_current_func_addr = 0;
uint64_t g_psx_bail_flattened = 0;
PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr = 0;
PSX_THREAD_LOCAL uint64_t g_psx_bail_flattened = 0;
uint32_t g_psx_ram_mask = 0x1FFFFFu;
void psx_bios_hle_configure(uint32_t a, uint32_t b) { (void)a; (void)b; }
int autocompile_toolchain_available(void) { return 0; }
+1 -1
View File
@@ -8,7 +8,7 @@
static uint8_t ram[2*1024*1024], spad[1024], spuram[512*1024];
static uint16_t vram[1024*512];
static int boot_clears, cd_handoffs;
uint32_t i_stat, i_mask, g_psx_icache_tv[1024];
PSX_THREAD_LOCAL uint32_t i_stat, i_mask, g_psx_icache_tv[1024];
PSX_THREAD_LOCAL uint64_t psx_cycle_count;
PSX_THREAD_LOCAL uint32_t g_psx_gcs_frac;
uint32_t psx_guest_cycle_scale_config(void) { return 1; }
+3 -2
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/*
* Validate that COP0.CAUSE bit 10 (IP2) behaves as a COMBINATIONAL mirror of
* the interrupt-controller line, not as a latch.
@@ -34,8 +35,8 @@
/* ---- The unit under test -------------------------------------------------
* Mirrors runtime/src/interrupts.c. Kept in lockstep with it; if the two ever
* disagree this test is the thing that should be updated last, not first. */
uint32_t i_stat;
uint32_t i_mask;
PSX_THREAD_LOCAL uint32_t i_stat;
PSX_THREAD_LOCAL uint32_t i_mask;
static uint32_t *s_cause_ptr;
+2 -1
View File
@@ -1,8 +1,9 @@
#include "psx_thread_local.h"
/* Synthetic-sector integration tests for the actual CD controller. */
#include "../src/cdrom.c"
#define CHECK(x) do { if (!(x)) { fprintf(stderr, "FAIL line %d: %s\n", __LINE__, #x); exit(1); } } while (0)
uint32_t i_stat, g_debug_current_func_addr, g_debug_last_store_pc;
PSX_THREAD_LOCAL uint32_t i_stat, g_debug_current_func_addr, g_debug_last_store_pc;
uint64_t s_frame_count, psx_cycle_count;
static int test_dma_active;
int dma_cdrom_transfer_active(void) { return test_dma_active; }
+3 -3
View File
@@ -81,9 +81,9 @@ void gpu_ws_validate_linked_list_header(uint32_t addr, uint32_t h) { (void)addr;
void gpu_ws_validate_linked_list_node(uint32_t addr, uint32_t n) { (void)addr; (void)n; }
/* ---- observers and IRQ (inert) ----------------------------------------- */
uint32_t i_stat;
uint32_t g_debug_current_func_addr;
uint32_t g_debug_last_store_pc;
PSX_THREAD_LOCAL uint32_t i_stat;
PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
uint64_t s_frame_count;
uint64_t g_io_openbus_reads;
uint64_t g_io_openbus_writes;
+7 -7
View File
@@ -39,17 +39,17 @@
/* ---- link stubs for code reachable from exec_one_fetched ------------------ */
uint64_t s_frame_count;
uint32_t g_debug_current_func_addr;
uint32_t g_debug_last_store_pc;
PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
uint32_t g_dirty_ram_code_gen;
uint64_t g_dispatch_static_hits;
int g_rfe_escape_pending;
int g_exc_escape_reason;
int g_psx_call_bail;
uint64_t g_psx_bail_first;
uint64_t g_psx_bail_resolved;
uint32_t i_stat;
uint32_t i_mask;
PSX_THREAD_LOCAL int g_psx_call_bail;
PSX_THREAD_LOCAL uint64_t g_psx_bail_first;
PSX_THREAD_LOCAL uint64_t g_psx_bail_resolved;
PSX_THREAD_LOCAL uint32_t i_stat;
PSX_THREAD_LOCAL uint32_t i_mask;
PSX_THREAD_LOCAL uint64_t psx_cycle_count;
uint32_t g_psx_mod_function_entry_hooks;
uint32_t g_psx_mod_instruction_hooks;
+4 -4
View File
@@ -229,7 +229,7 @@ STUBS_C = r"""
uint64_t s_frame_count = 0;
PSX_THREAD_LOCAL uint64_t psx_cycle_count = 0;
uint32_t g_debug_last_store_pc = 0x80012340u;
PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc = 0x80012340u;
uint32_t g_psx_ram_size = 0x00200000u;
uint32_t g_psx_ram_mask = 0x001FFFFFu;
void psx_video_timing_set_pal(int pal) { (void)pal; }
@@ -256,10 +256,10 @@ uint16_t mod_texture_packet_bank(uint32_t p, const uint32_t *words, uint32_t n)
int mod_texture_packet_precision(uint32_t p, float q[3], float xy[6])
{ (void)p; (void)q; (void)xy; return 0; }
void psx_host_write_half(uint32_t addr, uint16_t val) { (void)addr; (void)val; }
uint32_t g_debug_current_func_addr = 0x80045670u;
PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr = 0x80045670u;
CPUState *debug_cpu_ptr = 0;
uint32_t i_stat = 0;
uint32_t i_mask = 0;
PSX_THREAD_LOCAL uint32_t i_stat = 0;
PSX_THREAD_LOCAL uint32_t i_mask = 0;
static uint16_t *g_vram;
uint32_t debug_guest_ra(void) { return 0x8000AAAAu; }
+3 -2
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* Production GTE deadline regressions. No BIOS, disc or copied guest code. */
#include "psx_cyc.h"
#include <assert.h>
@@ -5,10 +6,10 @@
int g_ls_replay_active = 0;
int g_ls_mode = 0;
int g_precise_mode = 0;
int g_psx_call_bail = 0;
PSX_THREAD_LOCAL int g_psx_call_bail = 0;
int g_boot_state_cpu_timing_restored = 0; /* no restored boot state in this fixture */
int psx_mod_host_output_scope(void) { return 0; } /* no host-output mod callback */
uint32_t i_mask = 0;
PSX_THREAD_LOCAL uint32_t i_mask = 0;
uint64_t g_guest_store_count = 0;
uint64_t g_mmio_access_count = 0;
+2 -1
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
#include "cpu_state.h"
#include "gte.h"
#include "hle_gte.h"
@@ -76,7 +77,7 @@ static int g_test_netplay_active = 0;
extern "C" int psx_netplay_active(void) { return g_test_netplay_active; }
extern "C" {
uint64_t s_frame_count = 0;
uint32_t g_debug_last_store_pc = 0;
PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc = 0;
}
uint64_t g_test_cycle = 0;
+3 -2
View File
@@ -1,9 +1,10 @@
#include "psx_thread_local.h"
#include "interrupts.c"
int psx_mod_function_entry_active(void) { return 0; }
int g_cosim_dirty_pump_site;
int g_call_unit_depth;
int g_psx_dispatch_depth;
PSX_THREAD_LOCAL int g_call_unit_depth;
PSX_THREAD_LOCAL int g_psx_dispatch_depth;
uint32_t g_dirty_safe_resume_pc;
static void check(int ok, const char* message) {
+3 -2
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/*
* Pin the idle-loop detector's store-counter extension
* ([runtime] idle_skip_store_counters, psx_cycles.c).
@@ -25,10 +26,10 @@
int g_ls_replay_active = 0;
int g_ls_mode = 0;
int g_precise_mode = 0;
int g_psx_call_bail = 0;
PSX_THREAD_LOCAL int g_psx_call_bail = 0;
int g_boot_state_cpu_timing_restored = 0; /* boot_state.c, read by psx_cycles.c */
int psx_mod_host_output_scope(void) { return 0; } /* mod_runtime.cpp */
uint32_t i_mask = 0;
PSX_THREAD_LOCAL uint32_t i_mask = 0;
uint64_t g_guest_store_count = 0;
uint64_t g_mmio_access_count = 0;
static uint32_t g_guest_last_store_addr = 0xFFFFFFFFu, g_guest_last_store_val;
+2 -2
View File
@@ -36,8 +36,8 @@ PSX_THREAD_LOCAL uint32_t g_psx_cyc_batch;
PSX_THREAD_LOCAL uint32_t g_psx_cyc_batch_limit;
PSX_THREAD_LOCAL int g_psx_cyc_bb_defer;
PSX_THREAD_LOCAL uint32_t *g_psx_cyc_local_acc;
int g_psx_icache_active; /* 0: no fetch cost */
uint32_t g_psx_icache_tv[1024];
PSX_THREAD_LOCAL int g_psx_icache_active; /* 0: no fetch cost */
PSX_THREAD_LOCAL uint32_t g_psx_icache_tv[1024];
static CPUState *s_device_cpu;
static unsigned s_services;
+2 -2
View File
@@ -38,12 +38,12 @@
extern void psx_write_word(uint32_t addr, uint32_t val);
extern void psx_write_half(uint32_t addr, uint16_t val);
extern void psx_write_byte(uint32_t addr, uint8_t val);
extern int g_host_store_depth;
extern PSX_THREAD_LOCAL int g_host_store_depth;
extern uint32_t psx_read_word(uint32_t addr);
extern uint8_t *memory_get_ram_ptr(void);
extern uint8_t *memory_get_scratchpad_ptr(void);
extern void memory_set_sr_ptr(const uint32_t *p);
extern int g_dma_exec_depth;
extern PSX_THREAD_LOCAL int g_dma_exec_depth;
extern PSX_THREAD_LOCAL uint64_t psx_cycle_count;
/* BIU values the kernel and the tests use (Beetle cpu.cpp BIU_* bits). */
+18
View File
@@ -17,10 +17,19 @@ extern uint8_t *memory_get_ram_ptr(void);
extern uint8_t *memory_get_scratchpad_ptr(void);
static atomic_int phase;
static PsxMemoryBacking *copy;
static PsxMemoryExecutionState *metadata;
extern PSX_THREAD_LOCAL uint32_t i_stat, i_mask;
extern uint32_t psx_read_word(uint32_t);
static uint32_t worker_status;
static uint8_t *live;
static void replay(void) {
CHECK(memory_get_ram_ptr() == live);
CHECK(memory_backing_bind(copy) == NULL);
memory_execution_bind(metadata, &worker_status);
g_psx_render_pass_active = 1;
CHECK(psx_read_word(0x1F801070u) == 0x35);
CHECK(psx_read_word(0x1F801074u) == 0x17);
i_stat = 0x62;
CHECK(g_psx_ram != live && g_psx_ram == memory_get_ram_ptr());
CHECK(g_psx_ram[0x700000] == 37);
CHECK(memory_get_scratchpad_ptr()[17] == 19);
@@ -29,6 +38,7 @@ static void replay(void) {
atomic_store_explicit(&phase, 1, memory_order_release);
while (atomic_load_explicit(&phase, memory_order_acquire) != 2) { }
CHECK(g_psx_ram[0x700000] == 71);
CHECK(i_stat == 0x62 && i_mask == 0x17);
CHECK(memory_backing_bind(NULL) == copy);
CHECK(g_psx_ram == live && live[0x700000] == 41);
}
@@ -44,6 +54,10 @@ int main(void) {
copy = memory_backing_create();
CHECK(copy);
memory_backing_capture(copy);
metadata = memory_execution_create();
CHECK(metadata);
i_stat = 0x35; i_mask = 0x17;
memory_execution_capture(metadata);
#ifdef _WIN32
HANDLE thread = CreateThread(NULL, 0, entry, NULL, 0, NULL);
CHECK(thread);
@@ -55,6 +69,8 @@ int main(void) {
CHECK(g_psx_ram == live && live[0x700000] == 37);
CHECK(memory_get_scratchpad_ptr()[17] == 19);
live[0x700000] = 41;
CHECK(i_stat == 0x35 && i_mask == 0x17);
i_stat = 0x43; i_mask = 0x29;
atomic_store_explicit(&phase, 2, memory_order_release);
#ifdef _WIN32
CHECK(WaitForSingleObject(thread, 10000) == WAIT_OBJECT_0);
@@ -67,6 +83,8 @@ int main(void) {
CHECK(psx_peek_word(0x80700000, &word) && (word & 255) == 71);
CHECK(memory_get_scratchpad_ptr()[17] == 91);
CHECK(memory_backing_bind(NULL) == copy);
CHECK(i_stat == 0x43 && i_mask == 0x29);
memory_execution_destroy(metadata);
memory_backing_destroy(copy);
return 0;
}
+16 -16
View File
@@ -29,13 +29,13 @@ HARNESS_COMMON_H = r"""
extern int g_ls_mode;
extern int g_ls_suppress_record;
extern volatile int g_ds_recording;
extern int g_dma_exec_depth;
extern int g_dma_cur_ch;
extern uint32_t g_dma_cur_madr;
extern uint32_t g_dma_cur_bcr;
extern uint32_t g_dma_initiator_pc;
extern uint32_t g_debug_last_store_pc;
extern uint32_t g_debug_current_func_addr;
extern PSX_THREAD_LOCAL int g_dma_exec_depth;
extern PSX_THREAD_LOCAL int g_dma_cur_ch;
extern PSX_THREAD_LOCAL uint32_t g_dma_cur_madr;
extern PSX_THREAD_LOCAL uint32_t g_dma_cur_bcr;
extern PSX_THREAD_LOCAL uint32_t g_dma_initiator_pc;
extern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
extern PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
extern uint64_t s_frame_count;
extern uint32_t g_overlay_region_floor;
extern uint32_t g_text_image_lo;
@@ -45,7 +45,7 @@ extern PSX_THREAD_LOCAL uint64_t g_psx_cycle_fast_limit;
extern int g_event_step_conservative;
extern int g_ls_replay_active;
extern CPUState *debug_cpu_ptr;
extern void (*g_overlay_flush_pending_cycles)(void);
extern PSX_THREAD_LOCAL void (*g_overlay_flush_pending_cycles)(void);
extern uint32_t g_dirty_ram_exec_page_bitmap[DIRTY_RAM_EXEC_PAGE_BITMAP_WORDS];
extern uint32_t g_dirty_ram_exec_pc_bitmap[DIRTY_RAM_EXEC_BITMAP_WORDS];
extern uint32_t g_dirty_ram_dispatch_pc_bitmap[DIRTY_RAM_EXEC_BITMAP_WORDS];
@@ -116,13 +116,13 @@ PsxBiosImageInfo psx_bios_image = {0};
int g_ls_mode = 0;
int g_ls_suppress_record = 0;
volatile int g_ds_recording = 0;
int g_dma_exec_depth = 0;
int g_dma_cur_ch = -1;
uint32_t g_dma_cur_madr = 0;
uint32_t g_dma_cur_bcr = 0;
uint32_t g_dma_initiator_pc = 0;
uint32_t g_debug_last_store_pc = 0;
uint32_t g_debug_current_func_addr = 0;
PSX_THREAD_LOCAL int g_dma_exec_depth = 0;
PSX_THREAD_LOCAL int g_dma_cur_ch = -1;
PSX_THREAD_LOCAL uint32_t g_dma_cur_madr = 0;
PSX_THREAD_LOCAL uint32_t g_dma_cur_bcr = 0;
PSX_THREAD_LOCAL uint32_t g_dma_initiator_pc = 0;
PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc = 0;
PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr = 0;
uint64_t s_frame_count = 0;
uint32_t g_overlay_region_floor = 0x200000u;
uint32_t g_text_image_lo = 0x10000u;
@@ -132,7 +132,7 @@ PSX_THREAD_LOCAL uint64_t g_psx_cycle_fast_limit = 0;
int g_event_step_conservative = 0;
int g_ls_replay_active = 0;
CPUState *debug_cpu_ptr = 0;
void (*g_overlay_flush_pending_cycles)(void) = 0;
PSX_THREAD_LOCAL void (*g_overlay_flush_pending_cycles)(void) = 0;
uint32_t g_dirty_ram_exec_page_bitmap[DIRTY_RAM_EXEC_PAGE_BITMAP_WORDS] = {0};
uint32_t g_dirty_ram_exec_pc_bitmap[DIRTY_RAM_EXEC_BITMAP_WORDS] = {0};
uint32_t g_dirty_ram_dispatch_pc_bitmap[DIRTY_RAM_EXEC_BITMAP_WORDS] = {0};
+1 -1
View File
@@ -10,7 +10,7 @@
* the trusted plugin's host-state partition, without creating a game/window. */
static uint8_t ram[2048], scratchpad[1024], spu_ram[16];
static uint16_t vram[1024 * 512];
uint32_t i_stat, i_mask, g_psx_icache_tv[1024];
PSX_THREAD_LOCAL uint32_t i_stat, i_mask, g_psx_icache_tv[1024];
PSX_THREAD_LOCAL uint64_t psx_cycle_count;
uint8_t* memory_get_ram_ptr(void) { return ram; }
uint32_t memory_get_ram_bytes(void) { return sizeof(ram); }
+3 -2
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* Production mult/div deadline regressions. No BIOS, disc or copied guest code.
* Companion of test_gte_deferred.c: the MULT/DIV completion deadline and the
* MFLO/MFHI stall must see CPU work still pending in the batch or a local
@@ -9,10 +10,10 @@
int g_ls_replay_active = 0;
int g_ls_mode = 0;
int g_precise_mode = 0;
int g_psx_call_bail = 0;
PSX_THREAD_LOCAL int g_psx_call_bail = 0;
int g_boot_state_cpu_timing_restored = 0; /* no restored boot state in this fixture */
int psx_mod_host_output_scope(void) { return 0; } /* no host-output mod callback */
uint32_t i_mask = 0;
PSX_THREAD_LOCAL uint32_t i_mask = 0;
uint64_t g_guest_store_count = 0;
uint64_t g_mmio_access_count = 0;
+3 -2
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
#include "mod_runtime.h"
#include "mod_packages.h"
#include "mod_plugins.h"
@@ -42,8 +43,8 @@ extern "C" void gpu_hd_textures_set_dump_enabled(int) {}
extern "C" int gpu_hd_textures_reload(char*, size_t) { return 0; }
/* sio.c's environment (as in test_sio_jogcon_protocol.c). */
extern "C" {
uint32_t i_stat = 0, i_mask = 0;
uint32_t g_debug_current_func_addr = 0, g_debug_last_store_pc = 0;
PSX_THREAD_LOCAL uint32_t i_stat = 0, i_mask = 0;
PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr = 0, g_debug_last_store_pc = 0;
int psx_get_in_exception(void) { return 0; }
uint64_t psx_get_cycle_count(void) { static uint64_t t; return t += 64; }
uint32_t memory_get_sr(void) { return 0; }
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* Overlay DLLs must update the runtime's store-PC breadcrumb (ABI v24).
*
* This TU is an overlay DLL in miniature: it is built with the same defines
@@ -14,7 +15,7 @@
/* Verbatim from code_generator.cpp's emitted extern block. Must still compile
* once cpu_state.h has mapped the name onto the host pointer. */
extern uint32_t g_debug_last_store_pc; /* exact PC of the executing SW/SH/SB — wtrace/readtrace producer attribution (debug_server.c) */
extern PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc; /* exact PC of the executing SW/SH/SB — wtrace/readtrace producer attribution (debug_server.c) */
static uint32_t host_last_store_pc;
static uint32_t host_ram_word;
+3 -2
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* DMA and host stores against the PGXP shadow, end to end through the REAL
* memory.c store paths and the REAL pgxp.cpp engine.
*
@@ -22,8 +23,8 @@
extern void psx_write_word(uint32_t addr, uint32_t val);
extern void psx_write_half(uint32_t addr, uint16_t val);
extern void psx_write_byte(uint32_t addr, uint8_t val);
extern int g_dma_exec_depth;
extern int g_host_store_depth;
extern PSX_THREAD_LOCAL int g_dma_exec_depth;
extern PSX_THREAD_LOCAL int g_host_store_depth;
extern void memory_set_sr_ptr(const uint32_t *p);
/* gte.cpp position cache: off here, so only the dataflow shadow can answer. */
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/*
* Pin cross-device causality at a scheduler deadline.
*
@@ -12,8 +13,8 @@
int g_ls_replay_active = 0;
int g_ls_mode = 0;
int g_precise_mode = 0;
int g_psx_call_bail = 0;
uint32_t i_mask = 0;
PSX_THREAD_LOCAL int g_psx_call_bail = 0;
PSX_THREAD_LOCAL uint32_t i_mask = 0;
uint64_t g_guest_store_count = 0;
uint64_t g_mmio_access_count = 0;
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* [timing] guest_cycle_scale against the real psx_cycles.c:
* - identical accounting across tiers: native per-instruction CPU charges,
* the batched psx_cyc_charge hot path and an overlay DLL's raw batch
@@ -33,8 +34,8 @@ int g_psx_load_delay = 1;
int g_ls_replay_active = 0;
int g_ls_mode = 0;
int g_precise_mode = 0;
int g_psx_call_bail = 0;
uint32_t i_mask = 0;
PSX_THREAD_LOCAL int g_psx_call_bail = 0;
PSX_THREAD_LOCAL uint32_t i_mask = 0;
uint64_t g_guest_store_count = 0;
uint64_t g_mmio_access_count = 0;
+5 -4
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* Shared PsxNetPad -> SIO apply (offline, netplay, selfcheck) and pad
* normalization, checked on the real SIO wire. */
@@ -11,10 +12,10 @@ uint32_t sio_snapshot_bytes(void);
void sio_snapshot_write(uint8_t *p);
int sio_snapshot_read(const uint8_t *p, uint32_t len);
uint32_t i_stat = 0;
uint32_t i_mask = 0;
uint32_t g_debug_current_func_addr = 0;
uint32_t g_debug_last_store_pc = 0;
PSX_THREAD_LOCAL uint32_t i_stat = 0;
PSX_THREAD_LOCAL uint32_t i_mask = 0;
PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr = 0;
PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc = 0;
int psx_get_in_exception(void) { return 0; }
uint8_t psx_read_byte(uint32_t addr) { (void)addr; return 0; }
uint32_t psx_read_word(uint32_t addr) { (void)addr; return 0; }
+77
View File
@@ -0,0 +1,77 @@
/* Concurrent worker capture never becomes a second ring producer. */
#include "render_thread.h"
#include <pthread.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#define CHECK(c) do { if (!(c)) abort(); } while (0)
static RtCommandList *list;
static atomic_int phase, executed;
static unsigned live_next, private_next;
static jmp_buf overflow;
static void fail_capture(const char *reason) {
CHECK(!strcmp(reason, "command_capacity")); longjmp(overflow, 1);
}
static void consume_private(void *user, const RtCmd *cmd, const void *payload) {
(void)user;
CHECK(rt_on_render_thread());
CHECK(cmd->op == 7 && cmd->payload == sizeof(uint32_t));
CHECK(*(const uint32_t *)payload == private_next++);
}
static void consume(void *user, const RtCmd *cmd, const void *payload) {
(void)user;
if (cmd->op == 1) {
CHECK(*(const uint32_t *)payload == live_next++);
} else {
CHECK(cmd->op == 2 && atomic_load_explicit(&phase, memory_order_acquire) == 2);
rt_list_execute(list, consume_private, NULL);
CHECK(private_next == 4096);
atomic_store_explicit(&executed, 1, memory_order_release);
}
}
static void context(void *user, int current) { (void)user; (void)current; }
static void *worker(void *user) {
(void)user;
rt_capture_begin(list, fail_capture);
CHECK(rt_capture_active());
atomic_store_explicit(&phase, 1, memory_order_release);
for (uint32_t i = 0; i < 4096; ++i) {
uint32_t *p = rt_cmd_begin(7, 0, sizeof(i));
CHECK(p); *p = i; rt_cmd_commit();
}
rt_capture_end();
CHECK(!rt_capture_active() && rt_list_bytes(list) == 4096 * 32u);
atomic_store_explicit(&phase, 2, memory_order_release);
return NULL;
}
int main(void) {
RtConfig cfg = {0};
cfg.ring_bytes = 1u << 20; cfg.max_frames = 2;
cfg.exec = consume; cfg.ctx = context;
CHECK(rt_start(&cfg));
list = rt_list_create(1u << 20); CHECK(list);
pthread_t thread; CHECK(!pthread_create(&thread, NULL, worker, NULL));
while (!atomic_load_explicit(&phase, memory_order_acquire)) { }
for (uint32_t i = 0; i < 16000; ++i) {
uint32_t *p = rt_cmd_begin(1, 0, sizeof(i));
CHECK(p); *p = i; rt_cmd_commit();
}
CHECK(!pthread_join(thread, NULL));
CHECK(rt_cmd_begin(2, 0, 0)); rt_cmd_commit();
rt_drain();
CHECK(live_next == 16000 && atomic_load_explicit(&executed, memory_order_acquire));
RtStats stats; rt_get_stats(&stats); CHECK(stats.records == 16001);
rt_stop();
/* Overflow abandons the extra; it cannot acquire or block the live ring. */
RtCommandList *small = rt_list_create(32); CHECK(small);
rt_capture_begin(small, fail_capture);
if (!setjmp(overflow)) {
CHECK(rt_cmd_begin(1, 0, 4)); rt_cmd_commit();
rt_cmd_begin(1, 0, 4); abort();
}
rt_capture_end(); CHECK(rt_list_bytes(small) == 32);
rt_list_destroy(small); rt_list_destroy(list);
return 0;
}
+13 -12
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* Render-pass watchdog abort (render_pass.c psx_mod_render_pass).
*
* A pass that runs away is cut off by a longjmp from the frozen clock's
@@ -43,21 +44,21 @@ int g_boot_state_cpu_timing_restored = 0;
int psx_mod_host_output_scope(void) { return 0; }
int g_ls_mode = 0;
int g_precise_mode = 0;
int g_psx_call_bail = 0;
int g_psx_dispatch_depth = 0;
uint32_t i_stat = 0, i_mask = 0;
PSX_THREAD_LOCAL int g_psx_call_bail = 0;
PSX_THREAD_LOCAL int g_psx_dispatch_depth = 0;
PSX_THREAD_LOCAL uint32_t i_stat = 0, i_mask = 0;
uint64_t g_guest_store_count = 0;
uint64_t g_mmio_access_count = 0;
uint32_t g_debug_last_store_pc = 0;
uint32_t g_debug_current_func_addr = 0;
uint32_t g_psx_icache_tv[1024];
PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc = 0;
PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr = 0;
PSX_THREAD_LOCAL uint32_t g_psx_icache_tv[1024];
uint64_t g_render_pass_dropped_writes[RENDER_PASS_DROP_CLASSES];
void (*g_overlay_flush_pending_cycles)(void) = NULL;
int g_call_unit_depth = 0;
int g_dma_exec_depth = 0;
int g_host_store_depth = 0;
int g_dma_cur_ch = -1;
uint32_t g_dma_cur_madr = 0, g_dma_cur_bcr = 0, g_dma_initiator_pc = 0;
PSX_THREAD_LOCAL void (*g_overlay_flush_pending_cycles)(void) = NULL;
PSX_THREAD_LOCAL int g_call_unit_depth = 0;
PSX_THREAD_LOCAL int g_dma_exec_depth = 0;
PSX_THREAD_LOCAL int g_host_store_depth = 0;
PSX_THREAD_LOCAL int g_dma_cur_ch = -1;
PSX_THREAD_LOCAL uint32_t g_dma_cur_madr = 0, g_dma_cur_bcr = 0, g_dma_initiator_pc = 0;
int g_dirty_interp_active = 0;
int g_exec_phase = 0;
uint32_t g_dirty_safe_resume_pc = 0;
+3 -2
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* Render-pass time freeze (psx_cycle_freeze.h psx_cycle_freeze_begin/end).
* A pass counts guest cycles but never services a device, and afterwards
* every clock value is exactly as before. Build/run:
@@ -14,8 +15,8 @@
int g_ls_replay_active = 0;
int g_ls_mode = 0;
int g_precise_mode = 0;
int g_psx_call_bail = 0;
uint32_t i_mask = 0;
PSX_THREAD_LOCAL int g_psx_call_bail = 0;
PSX_THREAD_LOCAL uint32_t i_mask = 0;
uint64_t g_guest_store_count = 0;
uint64_t g_mmio_access_count = 0;
+13 -12
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* Render-pass sandbox: what a pass may write and what it gets back.
*
* 1. Store policy (render_pass_plan.c render_pass_store_to, the function
@@ -57,21 +58,21 @@ int g_boot_state_cpu_timing_restored = 0;
int psx_mod_host_output_scope(void) { return 0; }
int g_ls_mode = 0;
int g_precise_mode = 0;
int g_psx_call_bail = 0;
int g_psx_dispatch_depth = 0;
uint32_t i_stat = 0, i_mask = 0;
PSX_THREAD_LOCAL int g_psx_call_bail = 0;
PSX_THREAD_LOCAL int g_psx_dispatch_depth = 0;
PSX_THREAD_LOCAL uint32_t i_stat = 0, i_mask = 0;
uint64_t g_guest_store_count = 0;
uint64_t g_mmio_access_count = 0;
uint32_t g_debug_last_store_pc = 0;
uint32_t g_debug_current_func_addr = 0;
uint32_t g_psx_icache_tv[1024];
PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc = 0;
PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr = 0;
PSX_THREAD_LOCAL uint32_t g_psx_icache_tv[1024];
uint64_t g_render_pass_dropped_writes[RENDER_PASS_DROP_CLASSES];
void (*g_overlay_flush_pending_cycles)(void) = NULL;
int g_call_unit_depth = 0;
int g_dma_exec_depth = 0;
int g_host_store_depth = 0;
int g_dma_cur_ch = -1;
uint32_t g_dma_cur_madr = 0, g_dma_cur_bcr = 0, g_dma_initiator_pc = 0;
PSX_THREAD_LOCAL void (*g_overlay_flush_pending_cycles)(void) = NULL;
PSX_THREAD_LOCAL int g_call_unit_depth = 0;
PSX_THREAD_LOCAL int g_dma_exec_depth = 0;
PSX_THREAD_LOCAL int g_host_store_depth = 0;
PSX_THREAD_LOCAL int g_dma_cur_ch = -1;
PSX_THREAD_LOCAL uint32_t g_dma_cur_madr = 0, g_dma_cur_bcr = 0, g_dma_initiator_pc = 0;
int g_dirty_interp_active = 0;
int g_exec_phase = 0;
uint32_t g_dirty_safe_resume_pc = 0;
+1 -1
View File
@@ -37,7 +37,7 @@
extern uint8_t g_test_ram[0x200000];
extern PSX_THREAD_LOCAL uint64_t psx_cycle_count;
extern uint32_t i_stat, i_mask;
extern PSX_THREAD_LOCAL uint32_t i_stat, i_mask;
extern uint32_t g_dirty_safe_resume_pc;
extern int (*g_test_recover_null_pc)(CPUState *, uint32_t *);
extern uint32_t psx_read_word(uint32_t addr);
+1 -1
View File
@@ -10,7 +10,7 @@
#include <string.h>
extern uint8_t g_test_ram[0x200000];
extern PSX_THREAD_LOCAL uint64_t psx_cycle_count;
extern uint32_t i_stat, i_mask;
extern PSX_THREAD_LOCAL uint32_t i_stat, i_mask;
extern uint32_t psx_read_word(uint32_t);
extern void psx_host_write_word(uint32_t, uint32_t);
#define HANDLER 0x80001000u
+2 -1
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* Device-level regression: guest RAM is not SIO hardware state.
* Uses production SIO with a synthetic clock/card, no BIOS or retail data.
* Independent references: Beetle frontio.cpp DoDSRIRQ/Write and irq.cpp
@@ -7,7 +8,7 @@
#include "../src/sio.c"
#include <stdio.h>
uint32_t i_stat, i_mask, g_debug_current_func_addr, g_debug_last_store_pc;
PSX_THREAD_LOCAL uint32_t i_stat, i_mask, g_debug_current_func_addr, g_debug_last_store_pc;
static uint64_t clock_now;
static int card_present;
static int checks, failures;
+5 -4
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/*
* test_sio_card_protocol.c — pin SIO memory card protocol against no$psx spec.
*
@@ -43,10 +44,10 @@
#endif
/* ---- Stubs for sio.c's external dependencies ---- */
uint32_t i_stat = 0;
uint32_t i_mask = 0;
uint32_t g_debug_current_func_addr = 0;
uint32_t g_debug_last_store_pc = 0;
PSX_THREAD_LOCAL uint32_t i_stat = 0;
PSX_THREAD_LOCAL uint32_t i_mask = 0;
PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr = 0;
PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc = 0;
int psx_get_in_exception(void) { return 0; }
uint8_t psx_read_byte(uint32_t addr) { (void)addr; return 0; }
uint32_t psx_read_word(uint32_t addr) { (void)addr; return 0; }
+5 -4
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* Plain digital pads shift their ID during every command byte.
* Synthetic bus transactions over the real sio.c; no BIOS/disc/core fixture. */
#include "sio.h"
@@ -10,10 +11,10 @@ uint32_t sio_snapshot_bytes(void);
void sio_snapshot_write(uint8_t *p);
int sio_snapshot_read(const uint8_t *p, uint32_t len);
uint32_t i_stat = 0;
uint32_t i_mask = 0;
uint32_t g_debug_current_func_addr = 0;
uint32_t g_debug_last_store_pc = 0;
PSX_THREAD_LOCAL uint32_t i_stat = 0;
PSX_THREAD_LOCAL uint32_t i_mask = 0;
PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr = 0;
PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc = 0;
int psx_get_in_exception(void) { return 0; }
uint8_t psx_read_byte(uint32_t addr) { (void)addr; return 0; }
uint32_t psx_read_word(uint32_t addr) { (void)addr; return 0; }
+5 -4
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* DualShock rumble protocol regression: 0x4D map negotiation must feed the
* small/large motor bytes from later 0x42 polls to the frontend-facing state. */
@@ -11,10 +12,10 @@ uint32_t sio_snapshot_bytes(void);
void sio_snapshot_write(uint8_t *p);
int sio_snapshot_read(const uint8_t *p, uint32_t len);
uint32_t i_stat = 0;
uint32_t i_mask = 0;
uint32_t g_debug_current_func_addr = 0;
uint32_t g_debug_last_store_pc = 0;
PSX_THREAD_LOCAL uint32_t i_stat = 0;
PSX_THREAD_LOCAL uint32_t i_mask = 0;
PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr = 0;
PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc = 0;
int psx_get_in_exception(void) { return 0; }
uint8_t psx_read_byte(uint32_t addr) { (void)addr; return 0; }
uint32_t psx_read_word(uint32_t addr) { (void)addr; return 0; }
+5 -4
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* JogCon wire protocol regression: ID, buttons, signed steering, direction,
* command response, and rollback state. */
@@ -11,10 +12,10 @@ uint32_t sio_snapshot_bytes(void);
void sio_snapshot_write(uint8_t *p);
int sio_snapshot_read(const uint8_t *p, uint32_t len);
uint32_t i_stat = 0;
uint32_t i_mask = 0;
uint32_t g_debug_current_func_addr = 0;
uint32_t g_debug_last_store_pc = 0;
PSX_THREAD_LOCAL uint32_t i_stat = 0;
PSX_THREAD_LOCAL uint32_t i_mask = 0;
PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr = 0;
PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc = 0;
int psx_get_in_exception(void) { return 0; }
uint8_t psx_read_byte(uint32_t addr) { (void)addr; return 0; }
uint32_t psx_read_word(uint32_t addr) { (void)addr; return 0; }
+5 -4
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* NeGcon wire protocol regression: ID 0x23 poll layout (twist, I, II, L),
* no config mode, device-swap edges against the 0x44 mode lock, deferred type
* requests during config, 0x44 keeping the device, snapshot of the pad mode,
@@ -13,10 +14,10 @@ uint32_t sio_snapshot_bytes(void);
void sio_snapshot_write(uint8_t *p);
int sio_snapshot_read(const uint8_t *p, uint32_t len);
uint32_t i_stat = 0;
uint32_t i_mask = 0;
uint32_t g_debug_current_func_addr = 0;
uint32_t g_debug_last_store_pc = 0;
PSX_THREAD_LOCAL uint32_t i_stat = 0;
PSX_THREAD_LOCAL uint32_t i_mask = 0;
PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr = 0;
PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc = 0;
int psx_get_in_exception(void) { return 0; }
uint8_t psx_read_byte(uint32_t addr) { (void)addr; return 0; }
uint32_t psx_read_word(uint32_t addr) { (void)addr; return 0; }
+5 -4
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/*
* test_sio_spu_istat_ownership.c — cross-device I_STAT ownership.
*
@@ -19,10 +20,10 @@
#include <string.h>
/* ---- Stubs for sio.c's external dependencies ---- */
uint32_t i_stat = 0;
uint32_t i_mask = 0;
uint32_t g_debug_current_func_addr = 0;
uint32_t g_debug_last_store_pc = 0;
PSX_THREAD_LOCAL uint32_t i_stat = 0;
PSX_THREAD_LOCAL uint32_t i_mask = 0;
PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr = 0;
PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc = 0;
int psx_get_in_exception(void) { return 0; }
uint8_t psx_read_byte(uint32_t addr) { (void)addr; return 0; }
uint32_t psx_read_word(uint32_t addr) { (void)addr; return 0; }
+3 -2
View File
@@ -1,3 +1,4 @@
#include "psx_thread_local.h"
/* Uncharged guest-time spans (psx_cycle_freeze.h psx_cycle_uncharged_*),
* the time layer of mod_plugins.h psx_mod_call_guest_uncharged. Code in a
* span keeps its effects but consumes no guest time; a span over budget
@@ -12,8 +13,8 @@
int g_ls_replay_active = 0;
int g_ls_mode = 0;
int g_precise_mode = 0;
int g_psx_call_bail = 0;
uint32_t i_mask = 0;
PSX_THREAD_LOCAL int g_psx_call_bail = 0;
PSX_THREAD_LOCAL uint32_t i_mask = 0;
uint64_t g_guest_store_count = 0;
uint64_t g_mmio_access_count = 0;
+7 -7
View File
@@ -37,8 +37,8 @@
/* ---- link stubs for code reachable from exec_one_fetched ------------------ */
uint64_t s_frame_count;
uint32_t g_debug_current_func_addr;
uint32_t g_debug_last_store_pc;
PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr;
PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc;
uint32_t g_dirty_ram_code_gen;
uint64_t g_dispatch_static_hits;
uint32_t g_psx_mod_instruction_hooks;
@@ -50,11 +50,11 @@ void psx_mod_instruction(CPUState *cpu, uint32_t pc, uint32_t instruction) {
}
int g_rfe_escape_pending;
int g_exc_escape_reason;
int g_psx_call_bail;
uint64_t g_psx_bail_first;
uint64_t g_psx_bail_resolved;
uint32_t i_stat;
uint32_t i_mask;
PSX_THREAD_LOCAL int g_psx_call_bail;
PSX_THREAD_LOCAL uint64_t g_psx_bail_first;
PSX_THREAD_LOCAL uint64_t g_psx_bail_resolved;
PSX_THREAD_LOCAL uint32_t i_stat;
PSX_THREAD_LOCAL uint32_t i_mask;
PSX_THREAD_LOCAL uint64_t psx_cycle_count;
/* Live RAM geometry (memory.c): retail 2 MiB, as the stub RAM below. */
uint32_t g_psx_ram_size = PSX_MAIN_RAM_RETAIL_BYTES;
+4 -4
View File
@@ -80,10 +80,10 @@ static uint32_t s_wtrace_head=2;
typedef struct { uint32_t pc; uint32_t gpr[32]; } CPUState;
static CPUState *debug_cpu_ptr = NULL;
static uint32_t s_frame_count = 0;
uint32_t g_debug_current_func_addr = 0;
uint32_t g_debug_last_store_pc = 0;
int g_dma_exec_depth = 0, g_dma_cur_ch = -1;
uint32_t g_dma_initiator_pc = 0;
PSX_THREAD_LOCAL uint32_t g_debug_current_func_addr = 0;
PSX_THREAD_LOCAL uint32_t g_debug_last_store_pc = 0;
PSX_THREAD_LOCAL int g_dma_exec_depth = 0, g_dma_cur_ch = -1;
PSX_THREAD_LOCAL uint32_t g_dma_initiator_pc = 0;
static void debug_server_send_line(const char *line) { puts(line); }
static void send_err(int id, const char *error) { (void)id; (void)error; abort(); }
"""