From 2113defcfc107347e1e0cb6277156ce5ebd231bf Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Thu, 10 Sep 2026 17:20:27 -0700 Subject: [PATCH] Fix overlay Clang exports, PGO, and scaffold cache --- runtime/include/cpu_state.h | 11 +++- .../include/overlay_dispatch_preamble.c.inc | 23 ++------- runtime/include/psx_cycles.h | 2 +- runtime/runtime.cmake | 50 +++++++++++++++++++ tools/compile_overlays.py | 9 ++-- tools/new_project_layout/probe_disc.py | 5 ++ 6 files changed, 74 insertions(+), 26 deletions(-) diff --git a/runtime/include/cpu_state.h b/runtime/include/cpu_state.h index 66c1b786..61645d6c 100644 --- a/runtime/include/cpu_state.h +++ b/runtime/include/cpu_state.h @@ -74,7 +74,16 @@ typedef struct CPUState { * Static recompiled code and the interpreter share host cycle state directly, * so their barrier compiles to nothing. */ #ifdef PSX_OVERLAY_DLL_BUILD -void overlay_flush_cycles(void); +/* Kept here rather than in psx_cycles.h because cpu_state.h is the first + * generated-code include that declares the store barrier. The preamble is + * inserted after all generated includes, so both declaration and definition + * must carry the same export attribute for Clang. */ +# if defined(_WIN32) +# define PSX_OVERLAY_EXPORT __declspec(dllexport) +# else +# define PSX_OVERLAY_EXPORT __attribute__((visibility("default"))) +# endif +PSX_OVERLAY_EXPORT void overlay_flush_cycles(void); static inline void psx_store_cycle_barrier(void) { overlay_flush_cycles(); } #else static inline void psx_store_cycle_barrier(void) { } diff --git a/runtime/include/overlay_dispatch_preamble.c.inc b/runtime/include/overlay_dispatch_preamble.c.inc index 346ba1ff..546b68fa 100644 --- a/runtime/include/overlay_dispatch_preamble.c.inc +++ b/runtime/include/overlay_dispatch_preamble.c.inc @@ -4,12 +4,7 @@ static OverlayCallbacks g_cbs; static uint32_t s_pending_cycles; -#ifdef _WIN32 -__declspec(dllexport) -#else -__attribute__((visibility("default"))) -#endif -void overlay_flush_cycles(void) { +PSX_OVERLAY_EXPORT void overlay_flush_cycles(void) { uint32_t cycles = s_pending_cycles; s_pending_cycles = 0; if (cycles && g_cbs.advance_cycles) g_cbs.advance_cycles(cycles); @@ -24,19 +19,11 @@ int *g_psx_call_bail_p = &s_bail_dummy; uint64_t *g_psx_bail_first_p = &s_ctr_dummy0; uint64_t *g_psx_bail_resolved_p = &s_ctr_dummy1; -#ifdef _WIN32 -__declspec(dllexport) -#else -__attribute__((visibility("default"))) -#endif -int overlay_abi(void) { return PSX_OVERLAY_ABI_VERSION | (PSX_OVERLAY_FLAVOR << 16); } +PSX_OVERLAY_EXPORT int overlay_abi(void) { + return PSX_OVERLAY_ABI_VERSION | (PSX_OVERLAY_FLAVOR << 16); +} -#ifdef _WIN32 -__declspec(dllexport) -#else -__attribute__((visibility("default"))) -#endif -void overlay_init(const OverlayCallbacks *cbs) { +PSX_OVERLAY_EXPORT void overlay_init(const OverlayCallbacks *cbs) { g_cbs = *cbs; if (g_cbs.call_bail_flag) g_psx_call_bail_p = g_cbs.call_bail_flag; if (g_cbs.bail_first) g_psx_bail_first_p = g_cbs.bail_first; diff --git a/runtime/include/psx_cycles.h b/runtime/include/psx_cycles.h index 25ba1f3b..e2e0cbf1 100644 --- a/runtime/include/psx_cycles.h +++ b/runtime/include/psx_cycles.h @@ -126,7 +126,7 @@ static inline void psx_advance_cycles(uint32_t cycles) { /* Publish deferred charges (IRQ edge / MMIO / savestate). Overlay DLLs keep * their pending total in the callback shim rather than these host globals. */ #if defined(PSX_OVERLAY_DLL_BUILD) -void overlay_flush_cycles(void); +PSX_OVERLAY_EXPORT void overlay_flush_cycles(void); static inline void psx_cyc_local_publish(void) { } static inline void psx_cyc_batch_flush(void) { overlay_flush_cycles(); } #else diff --git a/runtime/runtime.cmake b/runtime/runtime.cmake index 788d6fcd..2df092bb 100644 --- a/runtime/runtime.cmake +++ b/runtime/runtime.cmake @@ -8,6 +8,13 @@ if(NOT DEFINED PSXRECOMP_ROOT) get_filename_component(PSXRECOMP_ROOT "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE) endif() +# The rebuild CLI configures this as `generate` for its instrumented pass and +# `use` after it has merged the collected profile. Keep the switch here, +# beside the target it must affect: an otherwise unconsumed cache entry makes a +# costly PGO rebuild/train cycle a silent no-op. +set(PSX_PGO "" CACHE STRING "PGO mode: empty, generate, or use") +set_property(CACHE PSX_PGO PROPERTY STRINGS "" generate use) + include("${PSXRECOMP_ROOT}/cmake/psx_dependency_archive.cmake") include("${PSXRECOMP_ROOT}/runtime/chd_dependency.cmake") @@ -1359,6 +1366,49 @@ function(psxrecomp_add_runtime_target target) # CMAKE_CXX_STANDARD; mod_packages.cpp must not compile as a pre-17 dialect. target_compile_features(${target} PRIVATE c_std_11 cxx_std_17) + if(NOT PSX_PGO STREQUAL "") + if(NOT PSX_PGO STREQUAL "generate" AND NOT PSX_PGO STREQUAL "use") + message(FATAL_ERROR + "PSX_PGO must be empty, 'generate', or 'use' (got '${PSX_PGO}')") + endif() + set(_psx_pgo_dir "${CMAKE_BINARY_DIR}/pgo") + if(CMAKE_C_COMPILER_ID MATCHES "Clang") + if(PSX_PGO STREQUAL "generate") + target_compile_options(${target} PRIVATE -fprofile-instr-generate) + target_link_options(${target} PRIVATE -fprofile-instr-generate) + else() + set(_psx_pgo_profile "${_psx_pgo_dir}/default.profdata") + target_compile_options(${target} PRIVATE + "-fprofile-instr-use=${_psx_pgo_profile}" + -Wno-profile-instr-out-of-date + -Wno-profile-instr-unprofiled) + target_link_options(${target} PRIVATE + "-fprofile-instr-use=${_psx_pgo_profile}") + endif() + elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU") + # GCC writes .gcda files directly; run_pgo_train already recognizes + # those as a completed training run, so no llvm-profdata merge is + # required for this branch. + if(PSX_PGO STREQUAL "generate") + target_compile_options(${target} PRIVATE + "-fprofile-generate=${_psx_pgo_dir}") + target_link_options(${target} PRIVATE + "-fprofile-generate=${_psx_pgo_dir}") + else() + target_compile_options(${target} PRIVATE + "-fprofile-use=${_psx_pgo_dir}" + -Wno-missing-profile) + target_link_options(${target} PRIVATE + "-fprofile-use=${_psx_pgo_dir}") + endif() + else() + message(FATAL_ERROR + "PSX_PGO requires a Clang or GNU compiler (got ${CMAKE_C_COMPILER_ID})") + endif() + unset(_psx_pgo_dir) + unset(_psx_pgo_profile) + endif() + # Game-specific executable name. Every title instantiates this function with # the same CMake target name ("psx-runtime"), so without this they ALL produce # an identical "psx-runtime.exe" — launching or killing one title's process by diff --git a/tools/compile_overlays.py b/tools/compile_overlays.py index 8891fd54..c84ef67c 100644 --- a/tools/compile_overlays.py +++ b/tools/compile_overlays.py @@ -2892,12 +2892,9 @@ def overlay_pair_id(src: str, func_ids: list, def add_overlay_pair_export(src: str, pair_id: int) -> str: """Add the optional v2-pair binding export without changing the shard ABI.""" return src + f'''\n -#ifdef _WIN32 -__declspec(dllexport) -#else -__attribute__((visibility("default"))) -#endif -uint64_t overlay_pair_id(void) {{ return UINT64_C(0x{pair_id:016X}); }} +PSX_OVERLAY_EXPORT uint64_t overlay_pair_id(void) {{ + return UINT64_C(0x{pair_id:016X}); +}} ''' diff --git a/tools/new_project_layout/probe_disc.py b/tools/new_project_layout/probe_disc.py index 6cd2a04a..234a6a1a 100755 --- a/tools/new_project_layout/probe_disc.py +++ b/tools/new_project_layout/probe_disc.py @@ -572,6 +572,11 @@ def render_game_toml(p: DiscProbe, *, disc_rel: str, out_dir: str, players: int, "[runtime]", 'window_title = "' + toml_escape(p.display_name + " Recompiled") + '"', 'memcard_dir = "saves"', + # Enable captured dirty-RAM overlays by default. A freshly probed + # project otherwise never initializes the cache pipeline and silently + # interprets every runtime-loaded overlay until a maintainer discovers + # and adds this setting by hand. + "overlay_cache = true", "", "[video]", 'renderer = "opengl"',