diff --git a/docs/internal/upstream/msvc-portability-shaneomac.md b/docs/internal/upstream/msvc-portability-shaneomac.md new file mode 100644 index 00000000..5da3eec8 --- /dev/null +++ b/docs/internal/upstream/msvc-portability-shaneomac.md @@ -0,0 +1,41 @@ +# MSVC portability from Shaneomac PR #16 + +## Source and credit + +- Source PR: [mstan/psxrecomp#16](https://github.com/mstan/psxrecomp/pull/16) +- Source commit: [`df4cc5c70ab1e5d0fd2a8f817dd346905d4d0eeb`](https://github.com/mstan/psxrecomp/commit/df4cc5c70ab1e5d0fd2a8f817dd346905d4d0eeb) +- Author preserved by cherry-pick: Martin Penkava + ``. +- The source commit's Claude Fable 5 co-author trailer is also preserved. + +## Adapted scope + +The source commit was applied as a focused portability change: + +- MSVC bit-scan intrinsics replace unavailable GCC/Clang builtins; +- generated CPS startup markers use an MSVC `.CRT$XCU` initializer and retain + the constructor path for GCC/Clang; and +- runtime globals defined by C translation units receive explicit C linkage in + the C++ entry point. + +`recompiler/tests/test_msvc_cps_codegen.py` generates a minimal game dispatch +and checks that both compiler startup paths are emitted. + +## Validation in this branch + +- Visual Studio 2022/MSVC 19.41 built `psxrecomp-game`, `psxrecomp-bios`, and + `l2_structural_test`; the structural suite passed 44/44. +- The MSVC-built game recompiler passed + `test_msvc_cps_codegen.py` on generated output. +- A freshly generated BIOS runtime compiled all 63 C/C++ object steps under + MSVC, including the new intrinsic and C-linkage paths. The final BIOS-only + link remains blocked by the baseline's unrelated missing + `psx_game_address_in_text` and `psx_game_text_native_ok` definitions. No + change for that pre-existing game-symbol contract is included here. + +## Explicitly excluded + +No other PR #16 material was imported. In particular, this branch excludes the +SmackDown-specific batch files, dynamic-code discovery fallback, SPU changes, +primitive rejection, Vulkan diagnostics/optimization, and CRT shaders. Those +items require independent review and provenance records. diff --git a/recompiler/src/full_function_emitter.cpp b/recompiler/src/full_function_emitter.cpp index aed4c4fd..ed10a713 100644 --- a/recompiler/src/full_function_emitter.cpp +++ b/recompiler/src/full_function_emitter.cpp @@ -1656,9 +1656,17 @@ void FullFunctionEmitter::emit_dispatch( // RECURSION_BUG.md §25 — mark CPS mode at startup for runtime code that // must emit the CPS contract (the overlay sljit JIT, overlay_sljit.c). out += "\n/* CPS runtime-mode marker (overlay sljit JIT reads g_psx_cps_mode). */\n"; - out += "__attribute__((constructor)) static void psx_cps_mark_bios(void) {\n"; + out += "static void psx_cps_mark_bios(void) {\n"; out += " extern int g_psx_cps_mode; g_psx_cps_mode = 1;\n"; out += "}\n"; + // Run psx_cps_mark_bios before main(). __attribute__((constructor)) is + // GCC/Clang-only; MSVC uses a static initializer pointer in .CRT$XCU. + out += "#if defined(_MSC_VER)\n"; + out += "#pragma section(\".CRT$XCU\", read)\n"; + out += "__declspec(allocate(\".CRT$XCU\")) static void (*psx_cps_mark_bios_ctor)(void) = psx_cps_mark_bios;\n"; + out += "#else\n"; + out += "__attribute__((constructor)) static void psx_cps_mark_bios_ctor(void) { psx_cps_mark_bios(); }\n"; + out += "#endif\n"; } } diff --git a/recompiler/src/main_psx.cpp b/recompiler/src/main_psx.cpp index c7f5b811..f60d1fe7 100644 --- a/recompiler/src/main_psx.cpp +++ b/recompiler/src/main_psx.cpp @@ -1086,9 +1086,17 @@ int main(int argc, char** argv) { // JIT (overlay_sljit.c) emits the CPS contract. Static ctor: no clash // with the BIOS dispatch's marker. ds << "\n/* CPS runtime-mode marker (overlay sljit JIT reads g_psx_cps_mode). */\n"; - ds << "__attribute__((constructor)) static void psx_cps_mark_game(void) {\n"; + ds << "static void psx_cps_mark_game(void) {\n"; ds << " extern int g_psx_cps_mode; g_psx_cps_mode = 1;\n"; ds << "}\n"; + // Run psx_cps_mark_game before main(). __attribute__((constructor)) is + // GCC/Clang-only; MSVC uses a static initializer pointer in .CRT$XCU. + ds << "#if defined(_MSC_VER)\n"; + ds << "#pragma section(\".CRT$XCU\", read)\n"; + ds << "__declspec(allocate(\".CRT$XCU\")) static void (*psx_cps_mark_game_ctor)(void) = psx_cps_mark_game;\n"; + ds << "#else\n"; + ds << "__attribute__((constructor)) static void psx_cps_mark_game_ctor(void) { psx_cps_mark_game(); }\n"; + ds << "#endif\n"; } std::ofstream dispatch_file(dispatch_filename); diff --git a/recompiler/tests/test_msvc_cps_codegen.py b/recompiler/tests/test_msvc_cps_codegen.py new file mode 100644 index 00000000..02e21181 --- /dev/null +++ b/recompiler/tests/test_msvc_cps_codegen.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +"""Verify that CPS game output carries both MSVC and GCC/Clang startup hooks. + +Usage: python test_msvc_cps_codegen.py [--recompiler ] +""" + +import argparse +import os +import struct +import subprocess +import sys +import tempfile + + +LOAD = 0x80010000 + + +def make_exe(): + header = bytearray(2048) + header[0:8] = b"PS-X EXE" + struct.pack_into(" +#if defined(_MSC_VER) +#include /* MSVC intrinsics: _BitScanForward (no __builtin_ctz) */ +#endif #include "cpu_state.h" /* CPUState (guard-safe: cpu_state.h includes us last) */ #ifdef __cplusplus @@ -49,7 +52,13 @@ static inline void psx_cyc_base(CPUState* cpu) { static inline void psx_cyc_deps(CPUState* cpu, uint32_t reg_mask) { reg_mask &= 0xFFFFFFFEu; /* never touch ReadAbsorb[0] */ while (reg_mask) { +#if defined(_MSC_VER) + unsigned long _psx_ctz_idx; + _BitScanForward(&_psx_ctz_idx, reg_mask); + unsigned n = (unsigned)_psx_ctz_idx; +#else unsigned n = (unsigned)__builtin_ctz(reg_mask); +#endif cpu->read_absorb[n] = 0u; reg_mask &= reg_mask - 1u; } diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index 9668c6ec..f5d5717f 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -93,6 +93,21 @@ extern "C" uint64_t gte_get_exec_count(void); +/* Cross-language globals defined in C translation units. Declared extern "C" at + * file scope so MSVC gives them C linkage (matching the C definitions); without + * this MSVC name-mangles the C++ references and they fail to link. GCC/Clang do + * not mangle namespace-scope variables, so this is a no-op there. The existing + * block-scope `extern` redeclarations inside functions inherit this C linkage. */ +extern "C" { + extern uint64_t psx_cycle_count; + extern uint64_t s_frame_count; + extern uint32_t g_overlay_region_floor; + extern int g_psx_cps_mode; + extern uint64_t g_slice_fired, g_slice_irq_taken, g_dirty_ram_insns_run; + 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 "C" void memory_init(const char* bios_path); extern "C" void memory_set_sr_ptr(const uint32_t *p); diff --git a/runtime/src/psx_cycles.c b/runtime/src/psx_cycles.c index 1d92a7e5..1610d522 100644 --- a/runtime/src/psx_cycles.c +++ b/runtime/src/psx_cycles.c @@ -3,6 +3,9 @@ #include "psx_cycles.h" #include "cpu_state.h" #include +#if defined(_MSC_VER) +#include /* MSVC intrinsics: _BitScanReverse (no __builtin_clz) */ +#endif #include "cdrom.h" #include "dma.h" #include "interrupts.h" @@ -451,7 +454,13 @@ static const uint8_t PSX_MULT_TAB24[24] = { static inline uint32_t psx_clz32(uint32_t v) { /* v is never 0 here (callers OR in 0x400). */ +#if defined(_MSC_VER) + unsigned long _idx; + _BitScanReverse(&_idx, v); /* index of highest set bit */ + return (uint32_t)(31u - _idx); +#else return (uint32_t)__builtin_clz(v); +#endif } uint32_t psx_mult_latency_s(uint32_t rs) { /* MULT (signed): sign-fold magnitude */