mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
Share build-selected guest HLE bindings and implementation metadata
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.
|
||||
@@ -158,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,120 @@
|
||||
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(GENERATE 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")
|
||||
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()
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user