Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions docs/internal/upstream/msvc-portability-shaneomac.md
Original file line number Diff line number Diff line change
@@ -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.
10 changes: 9 additions & 1 deletion recompiler/src/full_function_emitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
}

Expand Down
10 changes: 9 additions & 1 deletion recompiler/src/main_psx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
98 changes: 98 additions & 0 deletions recompiler/tests/test_msvc_cps_codegen.py
Original file line number Diff line number Diff line change
@@ -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())
9 changes: 9 additions & 0 deletions runtime/include/psx_cyc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
}
Expand Down
15 changes: 15 additions & 0 deletions runtime/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
9 changes: 9 additions & 0 deletions runtime/src/psx_cycles.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 */
Expand Down