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>
80 lines
2.1 KiB
C
80 lines
2.1 KiB
C
#include "overlay_api.h"
|
|
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#ifdef _WIN32
|
|
#define TEST_EXPORT __declspec(dllexport)
|
|
#else
|
|
#define TEST_EXPORT __attribute__((visibility("default")))
|
|
#endif
|
|
|
|
#ifndef TEST_PAIR_ID
|
|
#define TEST_PAIR_ID UINT64_C(0x1020304050607080)
|
|
#endif
|
|
|
|
#ifndef TEST_MARKER
|
|
#define TEST_MARKER 0xC001CAFEu
|
|
#endif
|
|
|
|
#ifndef TEST_INSTANCE
|
|
#define TEST_INSTANCE 0
|
|
#endif
|
|
|
|
static int s_init_count;
|
|
static int s_flush_count;
|
|
static int s_call_count;
|
|
|
|
static void trace(const char *event) {
|
|
const char *path = getenv("PSX_PAIR_TEST_TRACE");
|
|
if (!path || !*path) return;
|
|
FILE *out = fopen(path, "ab");
|
|
if (!out) return;
|
|
fprintf(out, "%s %d\n", event, TEST_INSTANCE);
|
|
fclose(out);
|
|
}
|
|
|
|
TEST_EXPORT int overlay_abi(void) { return PSX_OVERLAY_ABI_TAG; }
|
|
TEST_EXPORT uint64_t overlay_pair_id(void) { return TEST_PAIR_ID; }
|
|
|
|
TEST_EXPORT void overlay_init(const OverlayCallbacks *callbacks) {
|
|
(void)callbacks;
|
|
s_init_count++;
|
|
trace("init");
|
|
}
|
|
|
|
TEST_EXPORT void overlay_flush_cycles(void) {
|
|
s_flush_count++;
|
|
trace("flush");
|
|
}
|
|
|
|
TEST_EXPORT int test_init_count(void) { return s_init_count; }
|
|
TEST_EXPORT int test_flush_count(void) { return s_flush_count; }
|
|
TEST_EXPORT int test_call_count(void) { return s_call_count; }
|
|
|
|
/* A shard's exports are named by the full VA of the segment it was compiled
|
|
* for (docs/SEGMENT_AWARE_CODE.md §5.1, §5.7): KSEG0 by default, KUSEG or
|
|
* KSEG1 when the segment tests build it that way. */
|
|
#if defined(TEST_SEGMENT_KUSEG)
|
|
#define ENTRY(lo) func_0001##lo
|
|
#elif defined(TEST_SEGMENT_KSEG1)
|
|
#define ENTRY(lo) func_A001##lo
|
|
#else
|
|
#define ENTRY(lo) func_8001##lo
|
|
#endif
|
|
|
|
TEST_EXPORT void ENTRY(0000)(CPUState *cpu) {
|
|
s_call_count++;
|
|
cpu->gpr[2] = TEST_MARKER;
|
|
cpu->gpr[6] = TEST_INSTANCE; /* which shard ran */
|
|
cpu->gpr[7] = cpu->pc; /* the PC it was entered for (CPS routing) */
|
|
trace("call");
|
|
}
|
|
|
|
#ifndef TEST_PARTIAL_EXPORTS
|
|
TEST_EXPORT void ENTRY(0004)(CPUState *cpu) { cpu->gpr[3] = TEST_MARKER; }
|
|
TEST_EXPORT void ENTRY(0008)(CPUState *cpu) { cpu->gpr[4] = TEST_MARKER; }
|
|
TEST_EXPORT void ENTRY(000C)(CPUState *cpu) { cpu->gpr[5] = TEST_MARKER; }
|
|
#endif
|