mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
build: MSVC portability for runtime and generated code
- psx_cyc.h / psx_cycles.c: _BitScanForward/_BitScanReverse shims for __builtin_ctz/__builtin_clz under _MSC_VER - full_function_emitter.cpp / main_psx.cpp: emit a portable .CRT$XCU init-pointer instead of GCC-only __attribute__((constructor)) in generated dispatch/game code - main.cpp: declare cross-language globals extern "C" so MSVC links them against their C definitions (no-op for GCC/Clang) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
committed by
Matthew Stanley
parent
dde268dc0f
commit
05d4f35bd6
@@ -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
|
||||
`<mpenkava1337@gmail.com>`.
|
||||
- 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.
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 <psxrecomp-game.exe>]
|
||||
"""
|
||||
|
||||
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("<I", header, 0x10, LOAD)
|
||||
struct.pack_into("<I", header, 0x18, LOAD)
|
||||
body = struct.pack("<II", 0x03E00008, 0x00000000) # jr ra; nop
|
||||
struct.pack_into("<I", header, 0x1C, len(body))
|
||||
return bytes(header) + body
|
||||
|
||||
|
||||
def generate_dispatch(recompiler):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
exe = os.path.join(tmp, "tiny.psx")
|
||||
seeds = os.path.join(tmp, "seeds.txt")
|
||||
out = os.path.join(tmp, "out")
|
||||
with open(exe, "wb") as stream:
|
||||
stream.write(make_exe())
|
||||
with open(seeds, "w", encoding="utf-8") as stream:
|
||||
stream.write(f"0x{LOAD:08X}\n")
|
||||
result = subprocess.run(
|
||||
[recompiler, exe, "--seeds", seeds, "--out-dir", out],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode:
|
||||
raise RuntimeError(result.stderr or result.stdout)
|
||||
dispatches = [
|
||||
os.path.join(out, name)
|
||||
for name in os.listdir(out)
|
||||
if name.endswith("_dispatch.c")
|
||||
]
|
||||
if len(dispatches) != 1:
|
||||
raise RuntimeError(
|
||||
"expected one generated dispatch, found "
|
||||
f"{[os.path.basename(path) for path in dispatches]}; "
|
||||
f"generator output:\n{result.stdout}"
|
||||
)
|
||||
with open(dispatches[0], encoding="utf-8") as stream:
|
||||
return stream.read()
|
||||
|
||||
|
||||
def main():
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
default = os.path.normpath(
|
||||
os.path.join(here, "..", "build", "psxrecomp-game.exe")
|
||||
)
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--recompiler", default=default)
|
||||
args = parser.parse_args()
|
||||
|
||||
if not os.path.isfile(args.recompiler):
|
||||
print(f"FAIL: recompiler not found: {args.recompiler}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
try:
|
||||
generated = generate_dispatch(args.recompiler)
|
||||
except (OSError, RuntimeError) as exc:
|
||||
print(f"FAIL: generation failed: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
required = (
|
||||
"static void psx_cps_mark_game(void)",
|
||||
"#if defined(_MSC_VER)",
|
||||
'#pragma section(".CRT$XCU", read)',
|
||||
'__declspec(allocate(".CRT$XCU")) static void '
|
||||
"(*psx_cps_mark_game_ctor)(void) = psx_cps_mark_game;",
|
||||
"__attribute__((constructor)) static void psx_cps_mark_game_ctor(void) "
|
||||
"{ psx_cps_mark_game(); }",
|
||||
)
|
||||
missing = [text for text in required if text not in generated]
|
||||
if missing:
|
||||
for text in missing:
|
||||
print(f"FAIL: generated dispatch is missing: {text}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print("PASS: generated CPS startup hooks cover MSVC and GCC/Clang.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -28,6 +28,9 @@
|
||||
#define PSX_CYC_H
|
||||
|
||||
#include <stdint.h>
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h> /* 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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
#include "psx_cycles.h"
|
||||
#include "cpu_state.h"
|
||||
#include <stdlib.h>
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h> /* 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 */
|
||||
|
||||
Reference in New Issue
Block a user