mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
Merge pull request #594 from RetroPortingToolKit/fix/tomba-session-capture-20261008
Share build-time guest HLE selection and restore snapshot capture guards
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
# Build-selected guest implementations
|
||||
|
||||
`psxrecomp_select_guest_implementation()` binds native HLE replacements to
|
||||
generated guest functions. Choose HLE or LLE at build time. Both must satisfy
|
||||
the title's documented caller contract; HLE may remove internal instruction
|
||||
work and approximate timing. Preserve normal gameplay pacing and a functioning
|
||||
LLE build. No runtime selector or cross-build state conversion is required.
|
||||
|
||||
After creating the runtime target and its optional PGXP sibling:
|
||||
|
||||
```cmake
|
||||
set(GEOMETRY_IMPL "HLE" CACHE STRING "HLE or LLE")
|
||||
set(geometry_targets psx-runtime)
|
||||
if(TARGET psx-runtime-pgxp)
|
||||
list(APPEND geometry_targets psx-runtime-pgxp)
|
||||
endif()
|
||||
file(GLOB geometry_shards "${CMAKE_CURRENT_SOURCE_DIR}/generated/GAME_full_*.c")
|
||||
psxrecomp_select_guest_implementation(
|
||||
NAME geometry IMPLEMENTATION ${GEOMETRY_IMPL}
|
||||
TARGETS ${geometry_targets}
|
||||
GENERATED_SOURCES ${geometry_shards}
|
||||
SYMBOLS func_80000100 func_80000200
|
||||
HLE_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/geometry_hle.c"
|
||||
REFERENCE_PREFIX geometry_lle_
|
||||
SNAPSHOT_TAG 0x47454F01)
|
||||
```
|
||||
|
||||
The HLE source supplies the original function names/signatures. The helper
|
||||
finds exactly one definition of each symbol, renames that defining shard's
|
||||
original symbol (for example `geometry_lle_func_80000100`) and leaves dispatch
|
||||
declarations bound to HLE. The reference bodies can be used in isolated test
|
||||
builds. Product HLE does not need to execute them. LLE compiles the originals
|
||||
without renaming or HLE sources. Do not edit generated code to install HLE.
|
||||
|
||||
All targets using the same generated source files must select the same
|
||||
implementation in that CMake build. Use separate build directories for HLE
|
||||
and LLE. Call the helper once per routine family, listing every runtime variant.
|
||||
|
||||
Each HLE family needs a stable, distinct, nonzero 32-bit `SNAPSHOT_TAG`.
|
||||
Change its tag when saved private execution state becomes incompatible.
|
||||
Selected family tags are XOR-composed into `PSX_SAVESTATE_IMPL_TAG`, which the
|
||||
snapshot loader checks before applying state. All-LLE uses zero. Memory-card
|
||||
formats are unaffected. Tags identify compatible execution boundaries rather
|
||||
than transferring state between implementations.
|
||||
|
||||
The helper records selections in target properties, compile definitions,
|
||||
startup output and crash-report build identity. The build's CMake cache
|
||||
records the title's developer-facing selection. Snapshot tests cover tagged
|
||||
and untagged serialization; `test_guest_implementation.py` compiles and runs
|
||||
HLE/LLE dispatch for two runtime variants and verifies retained references,
|
||||
combined family tags, missing symbols and duplicate definitions.
|
||||
|
||||
Shared GTE/device math remains available to native routines. Title-specific
|
||||
records, pointer ownership, custom register dependencies, animation and packet
|
||||
layouts belong in the title adapter unless several games establish the same
|
||||
contract. Tomba's F4/GT3/GT4 geometry batches are the first consumer of this
|
||||
helper, retaining the complete enhanced-renderer widescreen scene.
|
||||
@@ -80,6 +80,7 @@ Columns: **N** = native, **D** = DuckStation oracle.
|
||||
| `set_input` | ✓ | ✓ | `buttons`, optional `frames`, optional `lx`, `ly`, `rx`, `ry`, `pad_type`, optional `port` | Override pad1 buttons and optional analog axes (PS1 inverted bitmask, 0 = pressed; axes 0-255). Debug builds also accept `pad_type` (0 digital, 1 DualShock, 2 JogCon) to test an emulated device identity. Holds until `clear_input` on both backends; pass `frames=N` (beetle) to auto-release after N frames. Runtime: `layer="host"` instead arms a virtual P1 gamepad (`buttons`, `lx`..`ry`, `lt`/`rt` 0-255) that feeds the normal offline input path (controller source, title pad transform, trigger values) and host shortcut polling, also headless; a plain override still wins while armed; `clear_input` disarms it. `port=2` (psx-runtime) drives pad 2 instead (digital buttons; plugs a digital pad into port 2 while driven) for headless two-player modes; `press` takes `port` too |
|
||||
| `clear_input` | ✓ | ✓ | optional `port` | Remove input and analog axis overrides (`port` 1 or 2 clears one port; default both) |
|
||||
| `rewind_status` | ✓ | | — | Local Rewind: `enabled`, `open`, `title_blocked` (`psx_mod_set_rewind_blocked`) and `snaps` held in the ring |
|
||||
| `snapshot_status` | ✓ | | — | Shared save/rewind capture guards: `callback_active`, `host_call_depth`, resume `site`/`pc`, and `safe`. A busy boundary can legitimately report unsafe; use `savestate_status` to check whether a requested operation completed |
|
||||
| `turbo` | ✓ | | `enabled` | Enable/disable TCP-controlled frontend turbo for fast-forward validation |
|
||||
| `turbo_state` | ✓ | | — | Query TCP-controlled turbo state |
|
||||
| `pause` | ✓ | | — | **REMOVED** — still registered, but always returns an error. Query a ring buffer (`fn_entry_tail`, `wtrace_dump`, `gpu_frame_dump`) instead of synthesizing a snapshot |
|
||||
@@ -768,6 +769,7 @@ Regenerate with `python tools/gen_tcp_commands.py`; `--check` fails if this bloc
|
||||
| `sio_irq_window` | ✓ | | |
|
||||
| `sio_pc_trace` | ✓ | | |
|
||||
| `sio_pc_window` | ✓ | | |
|
||||
| `snapshot_status` | ✓ | | |
|
||||
| `sio_state` | ✓ | | ✓ |
|
||||
| `sio_trace` | ✓ | ✓ | |
|
||||
| `sio_trace_reset` | | ✓ | |
|
||||
|
||||
@@ -118,6 +118,13 @@ if(BUILD_TESTING)
|
||||
target_link_options(boot_state_handoff_test PRIVATE -flto -Wl,--gc-sections)
|
||||
target_link_libraries(boot_state_handoff_test PRIVATE ZLIB::ZLIB)
|
||||
add_test(NAME boot_state_handoff_test COMMAND boot_state_handoff_test)
|
||||
add_executable(boot_state_impl_tag_test tests/test_boot_state_handoff.c)
|
||||
target_include_directories(boot_state_impl_tag_test PRIVATE include src)
|
||||
target_compile_definitions(boot_state_impl_tag_test PRIVATE PSX_COSIM=1 PSX_SAVESTATE_IMPL_TAG=0x54474801u)
|
||||
target_compile_options(boot_state_impl_tag_test PRIVATE -O2 -flto -ffunction-sections -fdata-sections -UNDEBUG)
|
||||
target_link_options(boot_state_impl_tag_test PRIVATE -flto -Wl,--gc-sections)
|
||||
target_link_libraries(boot_state_impl_tag_test PRIVATE ZLIB::ZLIB)
|
||||
add_test(NAME boot_state_impl_tag_test COMMAND boot_state_impl_tag_test)
|
||||
|
||||
add_executable(static_match_cache_test tests/test_static_match_cache.c
|
||||
src/overlay_loader.c src/crc32.c)
|
||||
@@ -151,6 +158,11 @@ if(BUILD_TESTING)
|
||||
set(PSX_GL_READBACK_COMPILER_BIN "${_gl_test_default_bin}" CACHE PATH "GCC/MinGW bin for GL fixture")
|
||||
find_package(Python3 QUIET COMPONENTS Interpreter)
|
||||
if(Python3_Interpreter_FOUND)
|
||||
add_test(NAME guest_implementation_binding_test COMMAND "${Python3_EXECUTABLE}"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/test_guest_implementation.py"
|
||||
--cmake "${CMAKE_COMMAND}" --compiler "${CMAKE_C_COMPILER}"
|
||||
--generator "${CMAKE_GENERATOR}"
|
||||
--work-root "${CMAKE_CURRENT_BINARY_DIR}/hle-binding-fixtures")
|
||||
add_test(NAME host_launch_timings_protocol_test COMMAND ${CMAKE_COMMAND} -E env
|
||||
"CC=${CMAKE_C_COMPILER}" "CXX=${CMAKE_CXX_COMPILER}" "${Python3_EXECUTABLE}"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/test_host_launch_timings_protocol.py")
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
include_guard(GLOBAL)
|
||||
|
||||
# Bind native guest-function replacements at build time. The original generated
|
||||
# bodies remain buildable as LLE, and are renamed only in their defining shards
|
||||
# for HLE reference tests. Dispatch declarations keep the original symbols.
|
||||
# Every target sharing these generated sources must use the same selection.
|
||||
function(psxrecomp_select_guest_implementation)
|
||||
cmake_parse_arguments(IMPL "" "NAME;IMPLEMENTATION;SNAPSHOT_TAG;REFERENCE_PREFIX"
|
||||
"TARGETS;GENERATED_SOURCES;HLE_SOURCES;SYMBOLS" ${ARGN})
|
||||
if(IMPL_UNPARSED_ARGUMENTS OR NOT IMPL_NAME MATCHES "^[A-Za-z_][A-Za-z0-9_]*$")
|
||||
message(FATAL_ERROR "Guest implementation needs a C-identifier NAME and valid arguments")
|
||||
endif()
|
||||
if(NOT IMPL_IMPLEMENTATION MATCHES "^(HLE|LLE)$")
|
||||
message(FATAL_ERROR "${IMPL_NAME}: IMPLEMENTATION must be HLE or LLE")
|
||||
endif()
|
||||
if(NOT IMPL_TARGETS OR NOT IMPL_GENERATED_SOURCES OR NOT IMPL_SYMBOLS)
|
||||
message(FATAL_ERROR "${IMPL_NAME}: TARGETS, GENERATED_SOURCES and SYMBOLS are required")
|
||||
endif()
|
||||
if(NOT IMPL_REFERENCE_PREFIX)
|
||||
set(IMPL_REFERENCE_PREFIX "psx_lle_${IMPL_NAME}_")
|
||||
endif()
|
||||
if(NOT IMPL_REFERENCE_PREFIX MATCHES "^[A-Za-z_][A-Za-z0-9_]*$")
|
||||
message(FATAL_ERROR "${IMPL_NAME}: invalid REFERENCE_PREFIX")
|
||||
endif()
|
||||
set(_tag 0)
|
||||
if(IMPL_IMPLEMENTATION STREQUAL "HLE")
|
||||
if(NOT IMPL_HLE_SOURCES OR NOT IMPL_SNAPSHOT_TAG MATCHES "^(0[xX][0-9A-Fa-f]+|[0-9]+)$")
|
||||
message(FATAL_ERROR "${IMPL_NAME}: HLE needs HLE_SOURCES and a numeric nonzero SNAPSHOT_TAG")
|
||||
endif()
|
||||
math(EXPR _tag "${IMPL_SNAPSHOT_TAG}")
|
||||
if(_tag LESS 1 OR _tag GREATER 4294967295)
|
||||
message(FATAL_ERROR "${IMPL_NAME}: SNAPSHOT_TAG must fit a nonzero uint32_t")
|
||||
endif()
|
||||
endif()
|
||||
foreach(_symbol IN LISTS IMPL_SYMBOLS)
|
||||
if(NOT _symbol MATCHES "^[A-Za-z_][A-Za-z0-9_]*$")
|
||||
message(FATAL_ERROR "${IMPL_NAME}: invalid guest symbol ${_symbol}")
|
||||
endif()
|
||||
set(_definition_count_${_symbol} 0)
|
||||
endforeach()
|
||||
foreach(_source IN LISTS IMPL_GENERATED_SOURCES)
|
||||
get_filename_component(_source "${_source}" ABSOLUTE)
|
||||
if(NOT EXISTS "${_source}")
|
||||
message(FATAL_ERROR "${IMPL_NAME}: missing generated source ${_source}")
|
||||
endif()
|
||||
file(READ "${_source}" _text)
|
||||
foreach(_symbol IN LISTS IMPL_SYMBOLS)
|
||||
string(REGEX MATCHALL "(^|\n)[ \t]*void[ \t\r\n]+${_symbol}[ \t\r\n]*\\([^)]*\\)[ \t\r\n]*\\{" _definitions "${_text}")
|
||||
list(LENGTH _definitions _count)
|
||||
math(EXPR _definition_count_${_symbol} "${_definition_count_${_symbol}} + ${_count}")
|
||||
if(_count GREATER 0 AND IMPL_IMPLEMENTATION STREQUAL "HLE")
|
||||
get_property(_claimed SOURCE "${_source}" PROPERTY "PSX_GUEST_BINDING_${_symbol}")
|
||||
if(_claimed)
|
||||
message(FATAL_ERROR "${IMPL_NAME}: ${_symbol} is already bound by ${_claimed}")
|
||||
endif()
|
||||
set_property(SOURCE "${_source}" PROPERTY "PSX_GUEST_BINDING_${_symbol}" "${IMPL_NAME}")
|
||||
set_property(SOURCE "${_source}" APPEND PROPERTY COMPILE_DEFINITIONS
|
||||
"${_symbol}=${IMPL_REFERENCE_PREFIX}${_symbol}")
|
||||
endif()
|
||||
endforeach()
|
||||
endforeach()
|
||||
foreach(_symbol IN LISTS IMPL_SYMBOLS)
|
||||
if(NOT _definition_count_${_symbol} EQUAL 1)
|
||||
message(FATAL_ERROR "${IMPL_NAME}: expected one definition of ${_symbol}, found ${_definition_count_${_symbol}}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(_identity "${CMAKE_CURRENT_BINARY_DIR}/guest-implementations/${IMPL_NAME}.c")
|
||||
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/guest-implementations")
|
||||
file(CONFIGURE OUTPUT "${_identity}" CONTENT
|
||||
"#include \"mod_plugins.h\"\n#include <stdio.h>\nPSX_MOD_CONSTRUCTOR(psx_impl_${IMPL_NAME}) { fprintf(stdout, \"psxrecomp guest implementation: ${IMPL_NAME}=${IMPL_IMPLEMENTATION}\\n\"); }\n" @ONLY)
|
||||
foreach(_target IN LISTS IMPL_TARGETS)
|
||||
if(NOT TARGET ${_target})
|
||||
message(FATAL_ERROR "${IMPL_NAME}: unknown target ${_target}")
|
||||
endif()
|
||||
get_target_property(_names ${_target} PSX_GUEST_IMPLEMENTATION_NAMES)
|
||||
if(NOT _names)
|
||||
set(_names "")
|
||||
endif()
|
||||
if(IMPL_NAME IN_LIST _names)
|
||||
message(FATAL_ERROR "${_target}: duplicate guest implementation ${IMPL_NAME}")
|
||||
endif()
|
||||
list(APPEND _names "${IMPL_NAME}")
|
||||
set_property(TARGET ${_target} PROPERTY PSX_GUEST_IMPLEMENTATION_NAMES "${_names}")
|
||||
get_target_property(_selections ${_target} PSX_GUEST_IMPLEMENTATIONS)
|
||||
if(NOT _selections)
|
||||
set(_selections "")
|
||||
endif()
|
||||
list(APPEND _selections "${IMPL_NAME}=${IMPL_IMPLEMENTATION}")
|
||||
set_property(TARGET ${_target} PROPERTY PSX_GUEST_IMPLEMENTATIONS "${_selections}")
|
||||
list(JOIN _selections "," _selection_text)
|
||||
|
||||
get_target_property(_tags ${_target} PSX_GUEST_IMPLEMENTATION_TAGS)
|
||||
if(NOT _tags)
|
||||
set(_tags "")
|
||||
endif()
|
||||
if(_tag GREATER 0 AND _tag IN_LIST _tags)
|
||||
message(FATAL_ERROR "${_target}: each HLE family needs a distinct SNAPSHOT_TAG")
|
||||
endif()
|
||||
list(APPEND _tags "${_tag}")
|
||||
set_property(TARGET ${_target} PROPERTY PSX_GUEST_IMPLEMENTATION_TAGS "${_tags}")
|
||||
set(_combined_tag 0)
|
||||
foreach(_family_tag IN LISTS _tags)
|
||||
math(EXPR _combined_tag "${_combined_tag} ^ ${_family_tag}")
|
||||
endforeach()
|
||||
get_target_property(_definitions ${_target} COMPILE_DEFINITIONS)
|
||||
if(NOT _definitions)
|
||||
set(_definitions "")
|
||||
endif()
|
||||
list(FILTER _definitions EXCLUDE REGEX "^PSX_(SAVESTATE_IMPL_TAG|BUILD_IMPLEMENTATIONS)=")
|
||||
set_property(TARGET ${_target} PROPERTY COMPILE_DEFINITIONS "${_definitions}")
|
||||
target_compile_definitions(${_target} PRIVATE
|
||||
"PSX_SAVESTATE_IMPL_TAG=${_combined_tag}u"
|
||||
"PSX_BUILD_IMPLEMENTATIONS=\"${_selection_text}\"")
|
||||
target_sources(${_target} PRIVATE "${_identity}")
|
||||
if(IMPL_IMPLEMENTATION STREQUAL "HLE")
|
||||
target_sources(${_target} PRIVATE ${IMPL_HLE_SOURCES})
|
||||
endif()
|
||||
endforeach()
|
||||
message(STATUS "psxrecomp guest implementation: ${IMPL_NAME}=${IMPL_IMPLEMENTATION}, tag=${_tag}")
|
||||
endfunction()
|
||||
@@ -5,6 +5,10 @@
|
||||
#include <stdint.h>
|
||||
#include "cpu_state.h"
|
||||
|
||||
/* Title adapters can require support before selecting HLE execution boundaries.
|
||||
* PSX_SAVESTATE_IMPL_TAG is a compile definition on boot_state.c, default zero. */
|
||||
#define PSX_BOOT_STATE_IMPL_TAG_VERSION 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -66,7 +70,7 @@ typedef struct {
|
||||
uint32_t entry_pc; /* game PS-EXE entry PC */
|
||||
uint32_t codegen_hash; /* PSX_OVERLAY_CODEGEN_HASH (auto-gen by cmake) */
|
||||
int32_t abi_tag; /* PSX_OVERLAY_ABI_TAG (abi version | flavor<<16) */
|
||||
uint32_t codegen_ver; /* PSX_OVERLAY_CODEGEN_VER */
|
||||
uint32_t codegen_ver; /* codegen version XOR build PSX_SAVESTATE_IMPL_TAG */
|
||||
/* ---- layout ---- */
|
||||
uint32_t section_count; /* number of sections that follow */
|
||||
uint32_t reserved; /* v8: enhancement-memory layout cookie; vanilla 0 */
|
||||
|
||||
@@ -14,6 +14,8 @@ struct CPUState;
|
||||
void psx_snapshot_host_call_begin(void);
|
||||
void psx_snapshot_host_call_end(void);
|
||||
void psx_snapshot_host_call_reset(void);
|
||||
unsigned psx_snapshot_host_call_depth(void);
|
||||
void psx_snapshot_host_call_restore(unsigned depth);
|
||||
|
||||
/* IRQ bit positions in I_STAT/I_MASK */
|
||||
#define IRQ_VBLANK 0
|
||||
|
||||
@@ -98,11 +98,14 @@ void mod_runtime_on_savestate_loaded(void);
|
||||
* launcher commit and before renderer/window initialization. */
|
||||
void mod_runtime_activate_plugins(void);
|
||||
void mod_runtime_on_vblank(void);
|
||||
/* Host-only context for a nested render transaction. Restore the interrupted
|
||||
* callback's depth/owner after longjmp; never serialize this into guest saves. */
|
||||
/* Host-only callback continuation. Exception/render landings restore the live
|
||||
* outer callback; scheduler escapes restore an empty context after abandoning
|
||||
* all callbacks. Never serialize this into guest saves. */
|
||||
typedef struct ModFunctionEntryContext {
|
||||
uint32_t depth;
|
||||
const void *plugin;
|
||||
struct CPUState *cpu;
|
||||
int finished;
|
||||
} ModFunctionEntryContext;
|
||||
void mod_runtime_function_entry_context_save(ModFunctionEntryContext *out);
|
||||
void mod_runtime_function_entry_context_restore(const ModFunctionEntryContext *in);
|
||||
|
||||
@@ -22,6 +22,7 @@ include("${PSXRECOMP_ROOT}/runtime/openxr_dependency.cmake")
|
||||
include("${PSXRECOMP_ROOT}/runtime/overlay_static_sources.cmake")
|
||||
include("${PSXRECOMP_ROOT}/runtime/netplay_dependency.cmake")
|
||||
include("${PSXRECOMP_ROOT}/runtime/texture_image_dependency.cmake")
|
||||
include("${PSXRECOMP_ROOT}/runtime/guest_implementation.cmake")
|
||||
|
||||
# Default to an optimized build. The recompiled game is a huge (~270 MB) block of
|
||||
# generated C; with no CMAKE_BUILD_TYPE the compiler emits it at -O0 and the game
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
#include "fntrace.h"
|
||||
#include "mod_memory.h"
|
||||
#include "overlay_api.h" /* PSX_OVERLAY_CODEGEN_HASH / _ABI_TAG / _CODEGEN_VER */
|
||||
|
||||
/* Build-selected HLE may use private execution boundaries. Zero preserves
|
||||
* the existing snapshot key for titles that do not select an implementation. */
|
||||
#ifndef PSX_SAVESTATE_IMPL_TAG
|
||||
#define PSX_SAVESTATE_IMPL_TAG 0u
|
||||
#endif
|
||||
#define BOOT_STATE_IMPLEMENTATION_KEY ((uint32_t)PSX_OVERLAY_CODEGEN_VER ^ (uint32_t)PSX_SAVESTATE_IMPL_TAG)
|
||||
#include "dirty_ram_interp.h"
|
||||
#include "gpu.h" /* gpu_get_vram — CPU-auth mirror under dual-raster */
|
||||
#include "gpu_render.h" /* gr_vram_transfer_in / gr_vram_transfer_out */
|
||||
@@ -390,7 +397,7 @@ static int boot_state_save_to(BsOut* o, const CPUState* cpu,
|
||||
h.entry_pc = entry_pc;
|
||||
h.codegen_hash = (uint32_t)PSX_OVERLAY_CODEGEN_HASH;
|
||||
h.abi_tag = (int32_t)PSX_OVERLAY_ABI_TAG;
|
||||
h.codegen_ver = (uint32_t)PSX_OVERLAY_CODEGEN_VER;
|
||||
h.codegen_ver = BOOT_STATE_IMPLEMENTATION_KEY;
|
||||
h.section_count = 17 + (psx_mod_memory_snapshot_bytes() ? 1u : 0u) +
|
||||
(psx_guest_cycle_scale_config() != 1u ? 1u : 0u);
|
||||
|
||||
@@ -894,9 +901,9 @@ int boot_state_check_buffer(const uint8_t* file, size_t file_len,
|
||||
(int)h.abi_tag, (int)PSX_OVERLAY_ABI_TAG);
|
||||
boot_state_append_reason(reason, reason_cap, part);
|
||||
}
|
||||
if (h.codegen_ver != (uint32_t)PSX_OVERLAY_CODEGEN_VER) {
|
||||
if (h.codegen_ver != BOOT_STATE_IMPLEMENTATION_KEY) {
|
||||
snprintf(part, sizeof(part), "codegen_ver=%u(want %u)",
|
||||
(unsigned)h.codegen_ver, (unsigned)PSX_OVERLAY_CODEGEN_VER);
|
||||
(unsigned)h.codegen_ver, (unsigned)BOOT_STATE_IMPLEMENTATION_KEY);
|
||||
boot_state_append_reason(reason, reason_cap, part);
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,11 @@ static const char *kReportPath = "psx_last_run_report.json";
|
||||
#ifndef PSX_BUILD_REV
|
||||
#define PSX_BUILD_REV "unknown"
|
||||
#endif
|
||||
#ifdef PSX_BUILD_IMPLEMENTATIONS
|
||||
static const char *kBuildId = PSX_BUILD_REV " (" __DATE__ " " __TIME__ ") implementations=" PSX_BUILD_IMPLEMENTATIONS;
|
||||
#else
|
||||
static const char *kBuildId = PSX_BUILD_REV " (" __DATE__ " " __TIME__ ")";
|
||||
#endif
|
||||
|
||||
/* CPU state pointer (set by debug server at init). */
|
||||
extern CPUState *debug_cpu_ptr;
|
||||
|
||||
@@ -9413,6 +9413,18 @@ static void handle_savestate_status(int id, const char *json)
|
||||
send_fmt("{\"id\":%d,\"ok\":true,%s}", id, status);
|
||||
}
|
||||
|
||||
static void handle_snapshot_status(int id, const char *json)
|
||||
{
|
||||
(void)json;
|
||||
extern int psx_mod_function_entry_active(void);
|
||||
const uint32_t pc = psx_irq_resume_context_snapshot_pc();
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"callback_active\":%s,"
|
||||
"\"host_call_depth\":%u,\"site\":%d,\"pc\":\"0x%08X\",\"safe\":%s}",
|
||||
id, psx_mod_function_entry_active() ? "true" : "false",
|
||||
psx_snapshot_host_call_depth(), psx_irq_resume_context_snapshot_site(),
|
||||
pc, psx_irq_resume_context_snapshot_safe_at(pc) ? "true" : "false");
|
||||
}
|
||||
|
||||
static void handle_turbo(int id, const char *json)
|
||||
{
|
||||
int enabled = json_get_int(json, "enabled", -1);
|
||||
@@ -15841,6 +15853,7 @@ static const CmdEntry s_commands[] = {
|
||||
{ "input_route_status",handle_input_route_status },
|
||||
{ "savestate", handle_savestate },
|
||||
{ "savestate_status", handle_savestate_status },
|
||||
{ "snapshot_status", handle_snapshot_status },
|
||||
{ "turbo", handle_turbo },
|
||||
{ "turbo_state", handle_turbo_state },
|
||||
{ "pause", handle_pause },
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
*/
|
||||
|
||||
#include "interrupts.h"
|
||||
#include "mod_runtime.h"
|
||||
#include "psx_memory.h"
|
||||
#include "sio.h"
|
||||
#include "timers.h"
|
||||
@@ -653,6 +654,8 @@ void psx_snapshot_host_call_end(void) {
|
||||
if (s_snapshot_host_call_depth) --s_snapshot_host_call_depth;
|
||||
}
|
||||
void psx_snapshot_host_call_reset(void) { s_snapshot_host_call_depth = 0; }
|
||||
unsigned psx_snapshot_host_call_depth(void) { return s_snapshot_host_call_depth; }
|
||||
void psx_snapshot_host_call_restore(unsigned depth) { s_snapshot_host_call_depth = depth; }
|
||||
|
||||
uint32_t psx_irq_resume_context_snapshot_pc(void)
|
||||
{
|
||||
@@ -1829,9 +1832,18 @@ irq_deliver_eval:
|
||||
* own live frame. */
|
||||
jmp_buf saved_exception_jmpbuf;
|
||||
memcpy(&saved_exception_jmpbuf, &exception_jmpbuf, sizeof(jmp_buf));
|
||||
ModFunctionEntryContext saved_mod_callback;
|
||||
mod_runtime_function_entry_context_save(&saved_mod_callback);
|
||||
const unsigned saved_host_call_depth = psx_snapshot_host_call_depth();
|
||||
g_exc_setjmp_epoch++; /* new setjmp frame armed (see decl above) */
|
||||
for (;;) {
|
||||
int jmp_val = setjmp(exception_jmpbuf);
|
||||
if (jmp_val != 0) {
|
||||
/* RFE/RestoreState can skip a callback and its guest-call adapter.
|
||||
* Preserve callbacks below this handler; discard only its scopes. */
|
||||
mod_runtime_function_entry_context_restore(&saved_mod_callback);
|
||||
psx_snapshot_host_call_restore(saved_host_call_depth);
|
||||
}
|
||||
if (jmp_val == 2) {
|
||||
/* RestoreState redirect: re-dispatch to cpu->pc.
|
||||
* GPRs were already set by RestoreState — do NOT restore.
|
||||
|
||||
@@ -3068,12 +3068,16 @@ 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;
|
||||
}
|
||||
|
||||
extern "C" void mod_runtime_function_entry_context_restore(const ModFunctionEntryContext *in) {
|
||||
PSXRecompV4::function_entry_depth = in->depth;
|
||||
PSXRecompV4::state().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;
|
||||
}
|
||||
|
||||
/* Apply the committed plan's disc writes and overlays to one sector. The
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "gpu_gl_renderer.h"
|
||||
#include "mod_plugins.h"
|
||||
#include "mod_runtime.h"
|
||||
#include "interrupts.h"
|
||||
#include "overlay_loader.h"
|
||||
#include "psx_icache.h"
|
||||
#include "timers.h"
|
||||
@@ -124,6 +125,7 @@ typedef struct RenderPassNesting {
|
||||
uint32_t ov_inprogress;
|
||||
void (*ov_flush)(void);
|
||||
ModFunctionEntryContext mod_entry;
|
||||
unsigned snapshot_host_call_depth;
|
||||
PSXModRenderView render_pose;
|
||||
uint32_t span_lo, span_hi; /* open guest span (dirty_ram_run_span) */
|
||||
} RenderPassNesting;
|
||||
@@ -144,6 +146,7 @@ static void nesting_save(RenderPassNesting *n) {
|
||||
overlay_loader_native_nesting(&n->ov_active_depth, &n->ov_inprogress);
|
||||
n->ov_flush = g_overlay_flush_pending_cycles;
|
||||
mod_runtime_function_entry_context_save(&n->mod_entry);
|
||||
n->snapshot_host_call_depth = psx_snapshot_host_call_depth();
|
||||
gte_render_pose_get(&n->render_pose);
|
||||
dirty_ram_span_get(&n->span_lo, &n->span_hi);
|
||||
}
|
||||
@@ -164,6 +167,7 @@ static void nesting_restore(const RenderPassNesting *n) {
|
||||
overlay_loader_set_native_nesting(n->ov_active_depth, n->ov_inprogress);
|
||||
g_overlay_flush_pending_cycles = n->ov_flush;
|
||||
mod_runtime_function_entry_context_restore(&n->mod_entry);
|
||||
psx_snapshot_host_call_restore(n->snapshot_host_call_depth);
|
||||
gte_render_pose_set(&n->render_pose);
|
||||
dirty_ram_span_set(n->span_lo, n->span_hi);
|
||||
}
|
||||
@@ -185,7 +189,10 @@ static int nesting_balanced(const RenderPassNesting *n) {
|
||||
now.ov_flush == n->ov_flush &&
|
||||
now.span_lo == n->span_lo && now.span_hi == n->span_hi &&
|
||||
now.mod_entry.depth == n->mod_entry.depth &&
|
||||
now.mod_entry.plugin == n->mod_entry.plugin;
|
||||
now.mod_entry.plugin == n->mod_entry.plugin &&
|
||||
now.mod_entry.cpu == n->mod_entry.cpu &&
|
||||
now.mod_entry.finished == n->mod_entry.finished &&
|
||||
now.snapshot_host_call_depth == n->snapshot_host_call_depth;
|
||||
}
|
||||
|
||||
/* After a watchdog abort, before the restore: which exits the longjmp
|
||||
@@ -223,6 +230,11 @@ static int nesting_describe(const RenderPassNesting *n, int bb_defer_ck,
|
||||
RP_NOTE(now.mod_entry.depth != n->mod_entry.depth, "mod entries %+d",
|
||||
(int)now.mod_entry.depth - (int)n->mod_entry.depth);
|
||||
RP_NOTE(now.mod_entry.plugin != n->mod_entry.plugin, "mod owner");
|
||||
RP_NOTE(now.mod_entry.cpu != n->mod_entry.cpu ||
|
||||
now.mod_entry.finished != n->mod_entry.finished, "mod completion context");
|
||||
RP_NOTE(now.snapshot_host_call_depth != n->snapshot_host_call_depth,
|
||||
"host adapter depth %u->%u", n->snapshot_host_call_depth,
|
||||
now.snapshot_host_call_depth);
|
||||
RP_NOTE(now.span_hi != n->span_hi, "guest span");
|
||||
#undef RP_NOTE
|
||||
return count;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "cpu_state.h"
|
||||
#include "psx_memory.h"
|
||||
#include "interrupts.h"
|
||||
#include "mod_runtime.h"
|
||||
#include "psx_bss.h"
|
||||
#include "psx_runtime.h" /* fix B: psx_exc_escape_reason_t + g_exc_escape_reason */
|
||||
#include "debug_server.h"
|
||||
@@ -813,6 +814,10 @@ void psx_scheduler_run(CPUState* cpu)
|
||||
for (;;) {
|
||||
if (setjmp(g_scheduler_jmpbuf) != 0) {
|
||||
psx_snapshot_host_call_reset();
|
||||
/* The jump also abandoned mod callbacks. Their native epilogues
|
||||
* cannot clear the snapshot gate or restore callback ownership. */
|
||||
const ModFunctionEntryContext no_callback = {0};
|
||||
mod_runtime_function_entry_context_restore(&no_callback);
|
||||
/* A structured escape unwound the native stack to here. Reset the
|
||||
* invariants the skipped psx_dispatch_impl frames would otherwise
|
||||
* own. No exception_jmpbuf frame is ever skipped (switch fires only
|
||||
|
||||
@@ -9,6 +9,10 @@ static uint16_t vram[1024*512];
|
||||
static int boot_clears, cd_handoffs;
|
||||
uint32_t i_stat, i_mask, g_psx_icache_tv[1024];
|
||||
uint64_t psx_cycle_count;
|
||||
uint32_t g_psx_gcs_frac;
|
||||
uint32_t psx_guest_cycle_scale_config(void) { return 1; }
|
||||
void psx_guest_cycle_scale_snapshot(uint32_t out[3]) { memset(out,0,12); }
|
||||
void psx_guest_cycle_scale_restore(const uint32_t in[3]) { (void)in; }
|
||||
uint8_t* memory_get_ram_ptr(void) { return ram; }
|
||||
uint32_t memory_get_ram_bytes(void) { return sizeof ram; }
|
||||
uint8_t* memory_get_scratchpad_ptr(void) { return spad; }
|
||||
@@ -68,6 +72,16 @@ int main(void) {
|
||||
/* Malformed, missing and duplicate phase sections must reject before
|
||||
* changing CPU, RAM or the previously active handoff latch. */
|
||||
cpu.pc=0xdeadbeef; ram[0x100]=0x55; fntrace_restore_game_started(!started);
|
||||
/* A different build-selected implementation must reject atomically,
|
||||
* including when its only difference is an HLE execution boundary. */
|
||||
PstR key_reader; uint32_t implementation_key;
|
||||
pst_r_init(&key_reader, saved + 24, 4);
|
||||
assert(pst_r_u32(&key_reader, &implementation_key));
|
||||
assert(implementation_key == BOOT_STATE_IMPLEMENTATION_KEY);
|
||||
u32(saved + 24, implementation_key ^ 0x54474801u);
|
||||
assert(!boot_state_load_buffer(saved,len,123,0x801b0000,&cpu));
|
||||
assert(cpu.pc==0xdeadbeef && ram[0x100]==0x55 && fntrace_is_game_started()==!started);
|
||||
u32(saved + 24, implementation_key);
|
||||
saved[len-4]=2;
|
||||
assert(!boot_state_load_buffer(saved,len,123,0x801b0000,&cpu));
|
||||
saved[len-4]=started;
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
"""Compile/run real CMake bindings, including the retained reference bodies."""
|
||||
import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--cmake', required=True)
|
||||
parser.add_argument('--compiler', required=True)
|
||||
parser.add_argument('--generator', default='Ninja')
|
||||
parser.add_argument('--work-root', type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
framework = Path(__file__).resolve().parents[2]
|
||||
args.work_root.mkdir(parents=True, exist_ok=True)
|
||||
root = Path(tempfile.mkdtemp(prefix='binding-', dir=args.work_root)).resolve()
|
||||
assert root.is_relative_to(args.work_root.resolve())
|
||||
|
||||
def run(command, ok=True):
|
||||
result = subprocess.run(command, text=True, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT)
|
||||
if (result.returncode == 0) != ok:
|
||||
raise AssertionError(result.stdout)
|
||||
return result.stdout
|
||||
|
||||
def case(name, implementation, *, duplicate=False, missing=False, second=False):
|
||||
source = root / name
|
||||
source.mkdir()
|
||||
(source / 'cpu.h').write_text('typedef struct { int value; } CPUState;\n')
|
||||
generated = '#include "cpu.h"\nvoid func_80000100(CPUState* cpu) { cpu->value = 41; }\n'
|
||||
generated += 'void func_80000200(CPUState* cpu) { cpu->value = 42; }\n'
|
||||
(source / 'original.c').write_text(generated)
|
||||
(source / 'dispatch.c').write_text('''#include "cpu.h"
|
||||
#include <stdio.h>
|
||||
void func_80000100(CPUState*);
|
||||
int main(void) {
|
||||
CPUState cpu = {0}; func_80000100(&cpu);
|
||||
printf("value=%d tag=%u identity=%s\\n", cpu.value,
|
||||
PSX_SAVESTATE_IMPL_TAG, PSX_BUILD_IMPLEMENTATIONS);
|
||||
return 0;
|
||||
}
|
||||
''')
|
||||
native = '''#include "cpu.h"
|
||||
void reference_func_80000100(CPUState*);
|
||||
void func_80000100(CPUState* cpu) { reference_func_80000100(cpu); cpu->value += 100; }
|
||||
'''
|
||||
if second:
|
||||
native += '''void reference_func_80000200(CPUState*);
|
||||
void func_80000200(CPUState* cpu) { reference_func_80000200(cpu); cpu->value += 100; }
|
||||
'''
|
||||
(source / 'native.c').write_text(native)
|
||||
sources = '${CMAKE_CURRENT_SOURCE_DIR}/original.c'
|
||||
if duplicate:
|
||||
(source / 'duplicate.c').write_text(generated)
|
||||
sources += ' ${CMAKE_CURRENT_SOURCE_DIR}/duplicate.c'
|
||||
symbol = 'func_missing' if missing else 'func_80000100'
|
||||
calls = f'''psxrecomp_select_guest_implementation(
|
||||
NAME demo IMPLEMENTATION {implementation}
|
||||
TARGETS base pgxp GENERATED_SOURCES {sources}
|
||||
HLE_SOURCES "${{CMAKE_CURRENT_SOURCE_DIR}}/native.c"
|
||||
SYMBOLS {symbol} REFERENCE_PREFIX reference_ SNAPSHOT_TAG 0x54474801)
|
||||
'''
|
||||
if second:
|
||||
calls += f'''psxrecomp_select_guest_implementation(
|
||||
NAME other IMPLEMENTATION HLE TARGETS base pgxp
|
||||
GENERATED_SOURCES {sources} HLE_SOURCES "${{CMAKE_CURRENT_SOURCE_DIR}}/native.c"
|
||||
SYMBOLS func_80000200 REFERENCE_PREFIX reference_ SNAPSHOT_TAG 0x00010000)
|
||||
'''
|
||||
cmake = f'''cmake_minimum_required(VERSION 3.20)
|
||||
project(binding C)
|
||||
include("{framework.as_posix()}/runtime/guest_implementation.cmake")
|
||||
foreach(target base pgxp)
|
||||
add_executable(${{target}} original.c dispatch.c)
|
||||
target_include_directories(${{target}} PRIVATE "{framework.as_posix()}/runtime/include")
|
||||
endforeach()
|
||||
{calls}'''
|
||||
(source / 'CMakeLists.txt').write_text(cmake)
|
||||
build = source / 'build'
|
||||
configured = run([args.cmake, '-S', str(source), '-B', str(build),
|
||||
'-G', args.generator, f'-DCMAKE_C_COMPILER={args.compiler}'],
|
||||
ok=not (duplicate or missing))
|
||||
if duplicate or missing:
|
||||
assert 'expected one definition' in configured, configured
|
||||
return
|
||||
run([args.cmake, '--build', str(build), '--config', 'Release'])
|
||||
value = 141 if implementation == 'HLE' else 41
|
||||
tag = 0x54474801 if implementation == 'HLE' else 0
|
||||
identity = f'demo={implementation}'
|
||||
if second:
|
||||
tag ^= 0x10000
|
||||
identity += ',other=HLE'
|
||||
for target in ('base', 'pgxp'):
|
||||
exe = build / (target + ('.exe' if os.name == 'nt' else ''))
|
||||
if not exe.exists():
|
||||
exe = build / 'Release' / exe.name
|
||||
output = run([str(exe)])
|
||||
assert f'value={value} tag={tag} identity={identity}' in output, output
|
||||
assert f'guest implementation: demo={implementation}' in output, output
|
||||
|
||||
case('lle', 'LLE')
|
||||
case('hle', 'HLE')
|
||||
case('multiple-families', 'HLE', second=True)
|
||||
case('missing-symbol', 'HLE', missing=True)
|
||||
case('duplicate-definition', 'HLE', duplicate=True)
|
||||
print('PASS: HLE/LLE dispatch, reference retention, variant identity, composed tags and invalid bindings')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -29,6 +29,17 @@ int main(void) {
|
||||
check(psx_irq_resume_context_snapshot_safe_at(pc), "scheduler escape releases abandoned scope");
|
||||
psx_snapshot_host_call_end(); /* no underflow */
|
||||
check(psx_irq_resume_context_snapshot_safe_at(pc), "unmatched end stays safe");
|
||||
psx_snapshot_host_call_begin();
|
||||
const unsigned outer_depth = psx_snapshot_host_call_depth();
|
||||
jmp_buf handler;
|
||||
if (setjmp(handler) == 0) {
|
||||
psx_snapshot_host_call_begin();
|
||||
longjmp(handler, 1); /* handler adapter's cleanup is skipped */
|
||||
}
|
||||
psx_snapshot_host_call_restore(outer_depth);
|
||||
check(!psx_irq_resume_context_snapshot_safe_at(pc), "exception preserves live outer adapter");
|
||||
psx_snapshot_host_call_end();
|
||||
check(psx_irq_resume_context_snapshot_safe_at(pc), "outer adapter returns without a leaked gate");
|
||||
check(!psx_irq_resume_context_snapshot_safe_at(pc + 4), "existing resume-PC guard retained");
|
||||
g_call_unit_depth = 1;
|
||||
check(!psx_irq_resume_context_snapshot_safe_at(pc), "existing call-unit guard retained");
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "cpu_state.h"
|
||||
|
||||
#include <array>
|
||||
#include <csetjmp>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
@@ -166,6 +167,7 @@ static int disabled_entry_hits;
|
||||
static int unselected_entry_hits;
|
||||
static uint32_t active_entry_last;
|
||||
static int entry_test_mode, nested_result;
|
||||
static std::jmp_buf callback_escape;
|
||||
static uint32_t nested_pc;
|
||||
static void test_active_entry(CPUState* cpu, uint32_t address) {
|
||||
active_entry_hits++;
|
||||
@@ -190,6 +192,22 @@ static void test_active_entry(CPUState* cpu, uint32_t address) {
|
||||
interrupted_entry_cpu = nullptr;
|
||||
if (!psx_mod_finish_function(cpu)) failures++;
|
||||
cpu->gpr[2] = 0x87654321u;
|
||||
} else if (entry_test_mode == 5) {
|
||||
/* Guest scheduling/RFE can abandon the callback's C++ scope. */
|
||||
std::longjmp(callback_escape, 1);
|
||||
} else if (entry_test_mode == 6) {
|
||||
if (!psx_mod_finish_function(cpu)) failures++;
|
||||
ModFunctionEntryContext interrupted{};
|
||||
mod_runtime_function_entry_context_save(&interrupted);
|
||||
if (setjmp(callback_escape) == 0) {
|
||||
CPUState nested{};
|
||||
entry_test_mode = 5;
|
||||
psx_mod_function_entry(&nested, address);
|
||||
failures++; /* the nested callback must escape */
|
||||
}
|
||||
mod_runtime_function_entry_context_restore(&interrupted);
|
||||
if (!psx_mod_function_entry_active() || !psx_mod_finish_function(cpu)) failures++;
|
||||
entry_test_mode = 6;
|
||||
}
|
||||
}
|
||||
static void test_disabled_entry(CPUState*, uint32_t) { disabled_entry_hits++; }
|
||||
@@ -783,6 +801,22 @@ int main() {
|
||||
"entry completion survives VBlank callbacks during a native call");
|
||||
entry_test_mode = 0;
|
||||
|
||||
entry_test_mode = 5;
|
||||
if (setjmp(callback_escape) == 0) {
|
||||
psx_mod_function_entry(&entry_cpu, 0x80003000u);
|
||||
check(false, "callback must escape without its native cleanup");
|
||||
}
|
||||
check(psx_mod_function_entry_active(), "escape reproduces the stranded capture gate");
|
||||
const ModFunctionEntryContext no_callback{};
|
||||
mod_runtime_function_entry_context_restore(&no_callback);
|
||||
check(!psx_mod_function_entry_active() && !psx_mod_finish_function(&entry_cpu),
|
||||
"scheduler landing releases the gate and clears abandoned completion ownership");
|
||||
entry_test_mode = 6;
|
||||
check(psx_mod_function_entry(&entry_cpu, 0x80003000u) == 1 &&
|
||||
entry_cpu.pc == entry_cpu.gpr[31] && !psx_mod_function_entry_active(),
|
||||
"nested exception landing preserves the live outer callback and completion");
|
||||
entry_test_mode = 0;
|
||||
|
||||
entry_cpu.gpr[4] = 35u;
|
||||
const uint32_t guest_sp = entry_cpu.gpr[29];
|
||||
check(g_psx_mod_guest_functions == 1 &&
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
#include "psx_cyc.h"
|
||||
|
||||
static ModFunctionEntryContext s_mod_entry;
|
||||
static unsigned s_host_call_depth;
|
||||
unsigned psx_snapshot_host_call_depth(void) { return s_host_call_depth; }
|
||||
void psx_snapshot_host_call_restore(unsigned depth) { s_host_call_depth = depth; }
|
||||
void mod_runtime_function_entry_context_save(ModFunctionEntryContext *out) {
|
||||
*out = s_mod_entry;
|
||||
}
|
||||
@@ -251,8 +254,12 @@ static void guest_frame(CPUState *cpu, const Frames *f, int level) {
|
||||
uint32_t prev_ov_ip = s_ov_inprogress;
|
||||
void (*prev_flush)(void) = g_overlay_flush_pending_cycles;
|
||||
ModFunctionEntryContext prev_mod = s_mod_entry;
|
||||
const unsigned prev_host_call = s_host_call_depth;
|
||||
s_host_call_depth++;
|
||||
s_mod_entry.depth++;
|
||||
s_mod_entry.plugin = f;
|
||||
s_mod_entry.cpu = cpu;
|
||||
s_mod_entry.finished = !s_mod_entry.finished;
|
||||
(void)guard;
|
||||
psx_cyc_bb_defer_begin();
|
||||
g_call_unit_depth = prev_unit + 1; /* overlay_loader_call_native */
|
||||
@@ -282,6 +289,7 @@ static void guest_frame(CPUState *cpu, const Frames *f, int level) {
|
||||
}
|
||||
|
||||
s_mod_entry = prev_mod;
|
||||
s_host_call_depth = prev_host_call;
|
||||
s_ld.armed = 0; /* retired on interpreter exit */
|
||||
g_precise_mode = prev_precise;
|
||||
g_dirty_interp_active = prev_active;
|
||||
@@ -353,6 +361,7 @@ static int stereo_draw(struct CPUState *cpu, void *user, uint32_t eye) {
|
||||
|
||||
typedef struct Live {
|
||||
ModFunctionEntryContext mod_entry;
|
||||
unsigned host_call_depth;
|
||||
int bb_defer, unit, dma, host, dma_ch, active, phase, precise, dispatch;
|
||||
int ov_depth;
|
||||
uint32_t ov_ip, batch, resume, span_lo, span_hi;
|
||||
@@ -364,6 +373,7 @@ typedef struct Live {
|
||||
static void snap(Live *l) {
|
||||
memset(l, 0, sizeof *l);
|
||||
l->mod_entry = s_mod_entry;
|
||||
l->host_call_depth = s_host_call_depth;
|
||||
l->bb_defer = g_psx_cyc_bb_defer;
|
||||
l->unit = g_call_unit_depth;
|
||||
l->dma = g_dma_exec_depth;
|
||||
@@ -395,6 +405,9 @@ static void check_live(const Live *a, const char *when) {
|
||||
CHECK(a->field == b.field, m); } while (0)
|
||||
SAME(mod_entry.depth, "mod callback depth");
|
||||
SAME(mod_entry.plugin, "mod callback owner");
|
||||
SAME(mod_entry.cpu, "mod callback CPU");
|
||||
SAME(mod_entry.finished, "mod callback completion");
|
||||
SAME(host_call_depth, "host adapter depth");
|
||||
SAME(bb_defer, "g_psx_cyc_bb_defer");
|
||||
SAME(unit, "g_call_unit_depth");
|
||||
SAME(dma, "g_dma_exec_depth");
|
||||
@@ -442,6 +455,9 @@ int main(void) {
|
||||
* the interpreter's resume latch and a pending load of its own. */
|
||||
s_mod_entry.depth = 1;
|
||||
s_mod_entry.plugin = &cpu;
|
||||
s_mod_entry.cpu = &cpu;
|
||||
s_mod_entry.finished = 1;
|
||||
s_host_call_depth = 2;
|
||||
g_psx_cyc_bb_defer = 2;
|
||||
g_psx_cyc_batch = 9;
|
||||
g_psx_dispatch_depth = 3;
|
||||
|
||||
@@ -39,6 +39,9 @@
|
||||
#include "timers.h"
|
||||
|
||||
static ModFunctionEntryContext s_mod_entry;
|
||||
static unsigned s_host_call_depth;
|
||||
unsigned psx_snapshot_host_call_depth(void) { return s_host_call_depth; }
|
||||
void psx_snapshot_host_call_restore(unsigned depth) { s_host_call_depth = depth; }
|
||||
void mod_runtime_function_entry_context_save(ModFunctionEntryContext *out) {
|
||||
*out = s_mod_entry;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user