mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
67bdf98eaf
docs/SEGMENT_AWARE_CODE.md §5.7 (decision 4). #417 made every KUSEG and KSEG1 overlay dispatch fall to the interpreter, because every shard was compiled for KSEG0. The loader now keys shards by segment, so a shard compiled for KUSEG or KSEG1 runs for PCs of that segment. Cache layout. KSEG0 shards keep their directory and {phys}_{crc} names, so no existing cache is invalidated. KUSEG and KSEG1 shards live in the cache tag's seg-kuseg/ and seg-kseg1/ subdirectories, under the same names; both tiers scan them, and a shard's segment is its directory's (cache_seg_from_path). Manifest. A KUSEG or KSEG1 manifest starts with `S <segment>` (00000000 or A0000000) before its first F. F entries are full VAs in that segment; the parser no longer forces them to KSEG0. R records stay KSEG0-spelled byte extents. Without S a manifest is KSEG0, as before. Refused, when indexing, when loading and through live publication alike: - a manifest whose segment differs from its directory's; - an F entry outside its segment; - S after an F, S twice, or S that is not a RAM segment base. Dispatch. Each candidate records its segment. The physical indexes stay (the bytes are one identity), and every lookup matches the PC's segment: - the entry chain (idx_head_seg); - CPS continuation ownership; - the lazy manifest index and try_load_region; - the interpreter's native-call contract. The lazy-miss and range-owner memos are keyed by the full PC. A PC in a segment that maps no RAM never runs a shard. Exports are resolved by their full VA. Counters. segment_alias_interp (from #417) now counts KUSEG/KSEG1 dispatches the loader leaves to the interpreter because no shard of their segment is cached or valid yet. The new segment_native counts those a shard of their own segment runs. Both are in the TCP overlay_loader_status reply. A warm cache takes the first to 0. Full-VA compares: - the overlay idle note's return test compares the resume PC with $ra exactly, and counts it as internal only for a running shard of its segment; - the shadow diff's two "returned to the caller" tests compare the exact PC; - the diff mode's kernel-window exclusion tests the physical address, so KSEG0 and KUSEG kernel candidates are treated alike. Before, it tested the full PC, so only a KUSEG PC ever matched, and #417's gate never let one reach it. This is opt-in (PSX_OVERLAY_DIFF). Tests (loader harness, overlay_segment_gate): - a KUSEG shard runs natively for its KUSEG PCs, entry and continuation, while KSEG0/KSEG1 aliases are interpreted; - one shard per segment for the same word, preloaded and lazy; - a stale own-segment shard is interpreted rather than handed to another segment's; - a KUSEG continuation where KSEG0 has an entry at that word, and a lazy one whose competing KSEG0 bundle is newer; - refused manifests, including through live publication; - the native-call contract; - a source check that the interpreter records the segment from the full PC, and that no site clears dispatch evidence without its segment bits. The harness gains a segment-run scenario that drives real compiled shards through a CPS dispatch loop. overlay_init_guard and overlay_retry_c11 follow the per-segment lookup, and so do the disabled interpreter_perf_guards' negative-miss and try_load_region checks (it still stops where it did on D). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
88 lines
2.8 KiB
Python
88 lines
2.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Structural regression test for pre-init overlay-loader entry points.
|
|
|
|
The public dispatch/candidate/native-call entry points can be reached during
|
|
early BIOS initialization, before overlay_loader_init() constructs their index
|
|
tables. They must fail closed on s_active before consulting those tables.
|
|
|
|
Usage: python runtime/tests/test_overlay_init_guard.py
|
|
Exit 0 = PASS.
|
|
"""
|
|
|
|
from pathlib import Path
|
|
import re
|
|
import sys
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
SOURCE = ROOT / "runtime" / "src" / "overlay_loader.c"
|
|
|
|
|
|
def function_body(source: str, name: str) -> str:
|
|
match = re.search(rf"\b(?:int|void)\s+{re.escape(name)}\s*\([^;]*?\)\s*\{{", source, re.S)
|
|
if not match:
|
|
raise AssertionError(f"missing function definition: {name}")
|
|
|
|
start = match.end()
|
|
depth = 1
|
|
for pos in range(start, len(source)):
|
|
if source[pos] == "{":
|
|
depth += 1
|
|
elif source[pos] == "}":
|
|
depth -= 1
|
|
if depth == 0:
|
|
return source[start:pos]
|
|
raise AssertionError(f"unterminated function definition: {name}")
|
|
|
|
|
|
def require_before(body: str, guard_pattern: str, state_pattern: str, name: str) -> None:
|
|
guard = re.search(guard_pattern, body)
|
|
state = re.search(state_pattern, body)
|
|
if not guard:
|
|
raise AssertionError(f"{name}: missing pre-init s_active guard")
|
|
if not state:
|
|
raise AssertionError(f"{name}: test could not find protected index access")
|
|
if guard.start() > state.start():
|
|
raise AssertionError(f"{name}: s_active guard occurs after index access")
|
|
|
|
|
|
def main() -> int:
|
|
source = SOURCE.read_text(encoding="utf-8")
|
|
|
|
# overlay_loader_dispatch guards, then hands the PC to the per-segment
|
|
# lookup (docs/SEGMENT_AWARE_CODE.md §5.7), which reads the index tables.
|
|
dispatch = function_body(source, "overlay_loader_dispatch")
|
|
require_before(
|
|
dispatch,
|
|
r"if\s*\(\s*!s_active\s*\)\s*return\s+0\s*;",
|
|
r"(?:idx_head(?:_seg)?|overlay_loader_dispatch_seg)\s*\(",
|
|
"overlay_loader_dispatch",
|
|
)
|
|
|
|
candidate = function_body(source, "overlay_loader_is_candidate")
|
|
require_before(
|
|
candidate,
|
|
r"if\s*\(\s*!s_active\s*\)\s*return\s+0\s*;",
|
|
r"(?:idx_head|exact_entry_has)\s*\(",
|
|
"overlay_loader_is_candidate",
|
|
)
|
|
|
|
call_native = function_body(source, "overlay_loader_call_native")
|
|
require_before(
|
|
call_native,
|
|
r"if\s*\(\s*!s_active\s*\|\|\s*!s_native_exec\s*\)\s*return\s+0\s*;",
|
|
r"idx_head(?:_seg)?\s*\(",
|
|
"overlay_loader_call_native",
|
|
)
|
|
|
|
print("PASS: overlay loader entry points fail closed before initialization")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
sys.exit(main())
|
|
except AssertionError as exc:
|
|
print(f"FAIL: {exc}")
|
|
sys.exit(1)
|