mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
6b6e2c712e
Overlay shards are compiled at the KSEG0 address of their bytes: captures record 0x80000000|offset and the manifest parser keys entries there. The emitter bakes that segment into every PC a shard produces: the $ra a jal/jalr writes, jump and exception-resume PCs, and I-cache fetch tags. Dispatch matched candidates by the segment-stripped address, so a KUSEG or KSEG1 alias of the same bytes ran the KSEG0 body at the wrong architectural PC. Found in R4 (SLUS-00797) with OpenBIOS: the exception handler enters its RAM patch slots at KUSEG (0x0000281C, 0x0000357C). The kernel-page shard linked $ra=0x8000282C where the interpreter (and hardware) gives 0x0000282C, and fetched 0x8000281C/0x8000357C, missing lines the kernel had just filled with KUSEG tags: +4 cycles per slot, so the next VBlank IRQ landed at a different instruction and warm (native) vs cold (interpreted) overlay-cache runs saved different registers from frame 194 on. A KSEG1 entry would also be charged as cached. PSX_OVERLAY_CODE_SEGMENT (overlay_loader.h) names the rule for capture, the manifest parser, overlay_loader_dispatch and the generated static overlay table: a PC outside it falls to the interpreter, which runs at the PC it was handed. CPS continuations already failed closed this way (their entry switch only knows KSEG0 PCs); function entries now match. overlay_loader_status reports segment_alias_interp. Static game code has the same shape for KUSEG-linked EXEs (the parser normalizes their headers to KSEG0); that needs segment-relative emission and is not changed here. Shard cache tags are unchanged. Tests: test_overlay_segment_gate.py drives the real loader (pair-dedup harness, which also gains the RAM-geometry globals it no longer linked without) with a KSEG0 fixture shard: KUSEG and KSEG1 dispatches stay interpreted and never call it, KSEG0 still runs native. It fails with the gate removed. test_compile_overlays_static_split.py covers the generated static table and rejects a non-KSEG0 entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
61 lines
2.4 KiB
Python
61 lines
2.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Build the real overlay loader and prove shards run only for their segment.
|
|
|
|
Overlay shards are compiled for the KSEG0 address of their bytes and bake that
|
|
segment into every PC they produce: jal/jalr link values, jump and exception
|
|
resume PCs, I-cache fetch tags. OpenBIOS's exception path enters its RAM patch
|
|
slots at KUSEG (0x0000281C), and a KSEG0 shard run there wrote $ra=0x8000282C
|
|
instead of 0x0000282C and missed I-cache lines the kernel had just filled with
|
|
KUSEG tags: R4 warm (native) and cold (interpreted) overlay-cache runs took the
|
|
next VBlank IRQ at different instructions and split at guest frame 194.
|
|
|
|
This drives the loader harness (overlay_pair_dedup_harness.c) with a fixture
|
|
shard at 0x80010000: KUSEG and KSEG1 aliases must fall to the interpreter
|
|
without calling the shard, and the KSEG0 PC must still run native.
|
|
|
|
Usage: python runtime/tests/test_overlay_segment_gate.py
|
|
Exit 0 = PASS.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
import pathlib
|
|
import platform
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
|
|
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))
|
|
import test_overlay_pair_dedup_runtime as pair # noqa: E402
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--gcc", default=shutil.which("gcc") or shutil.which("cc"))
|
|
args = parser.parse_args()
|
|
if not args.gcc:
|
|
raise SystemExit("gcc/cc is required")
|
|
ext = ".dll" if platform.system() == "Windows" else ".so"
|
|
exe = ".exe" if platform.system() == "Windows" else ""
|
|
with tempfile.TemporaryDirectory(
|
|
prefix="psx-segment-gate-", ignore_cleanup_errors=True) as raw:
|
|
tmp = pathlib.Path(raw)
|
|
shard = tmp / f"shard{ext}"
|
|
harness = tmp / f"segment-harness{exe}"
|
|
pair.compile_fixture(args.gcc, shard, instance=1)
|
|
pair.compile_harness(args.gcc, harness)
|
|
cache = tmp / "cache"
|
|
first = pair.publish(cache, "gcc", f"00010000_11111111{ext}", shard,
|
|
pair.manifest([(0x80010000, 4), (0x80010004, 4)]))
|
|
os.environ.pop("PSX_PAIR_TEST_TRACE", None)
|
|
pair.run([str(harness), str(cache), "segment-alias", str(first),
|
|
str(tmp / f"unused{ext}")])
|
|
print("PASS: overlay shards run only for the segment they were compiled for")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|