mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 10:19:47 -04:00
master
8 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
91cd6db705 |
guest_cycle_scale: one CPU charge for every tier, declarative gate, rollback
Review of #561. Guest time now has two entry points (psx_cycles.h): the CPU charge psx_cpu_charge() (instruction base, load fudge/region/completion, i-cache refills, BIOS HLE costs), which applies [timing] guest_cycle_scale, and the unscaled device advance psx_advance_cycles() (DMA, idle skips, mul/div and GTE deadline waits). The emitters' block-mode charges emit psx_cpu_charge; overlay DLLs accumulate it raw and publish through the cpu_charge callback (was advance_cycles, same slot) into the same host function, so nothing is scaled at the DLL boundary. The batched psx_cyc_charge path, the dirty-RAM interpreter, psx_cyc_readmem, i-cache refills and bios_hle.c all charge through it. At scale 1 it is exactly psx_advance_cycles. Gate in psxrecomp: [timing] guest_cycle_scale_gate, RAM predicates judged at every VBlank edge (deterministic, works online, no mod needed), plus psx_mod_set_guest_cycle_scale_gate() in mod_plugins.h for custom logic or to tie the scale to a mod (guest_cycle_scale_gated = true; every mod session reset shuts it). The carried fraction and both gate states are a boot_state section (BS_SEC_GCS, written only when the scale is not 1), so savestates and the netplay rollback snapshot re-simulate the same cycles. psx_guest_cycle_scale_gate_test pins tier identity, the gates and rollback re-simulation against the real psx_cycles.c. OpenBIOS regenerated (emitter stamp); retail SCPH1001 is not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
fdc354543d |
tools, tests: the segment probe loads an overlay; ledger guard overlay-segment-shards
docs/SEGMENT_AWARE_CODE.md §5.7, §7. The synthetic probe gains an overlay phase that loads code the way a game loads an overlay. After the three probe_run sequences it: - leaves the critical section the BIOS started it in, because the kernel's CD driver waits on CD interrupts; - reads its own EXE file back from the disc through the BIOS file calls (B0:32 open, B0:34 read, B0:36 close; the kernel DMAs the sectors) into RAM at 0x000A0000; - calls the copy of a position-independent ov_run there through KUSEG, KSEG0 and KSEG1, timing each run with Timer 2. It records the T2 deltas and ov_getpc links at R+0x28-R+0x3C, and the file descriptor and byte count at R+0x40/0x44. In the runtime that code is CD-DMA'd RAM code. Capture records its entries with their segments, and compile_overlays.py builds a shard per segment. The phase replaces the `j .` spin with a `j` of the same size, so every earlier label keeps its address. The static probe code and every result word it wrote before are unchanged, and so are its probe_run cycle-watch hits on Beetle and native. Beetle on both BIOSes: T2 56 / 56 / 82 and links 0x000A0A2C / 0x800A0A2C / 0xA00A0A2C, the same shape as probe_run. The ledger gains the regression guard overlay-segment-shards (§5.7). It builds the capture record overlay_capture.c writes for the probe's disc-loaded ov_run (schema v3), splits it with capture_segment_views(), and compiles each view as the fragment pass does, with compile_overlays.py's own helpers and the real recompiler. It requires: - every PC each shard bakes, and its names, to be in its segment; - the manifest's S and F records; - ov_run's straight run to cost Beetle's fetch-model cycles: line leaders cached at KUSEG and KSEG0, +4 per fetch at KSEG1. KNOWN_GAPS stays empty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a0ae0c4f6a |
runtime, recompiler: compare call-contract return PCs exactly
psx_call_contract and the BIOS dispatch loop compared a return PC with the call's link physically ((a ^ b) & 0x1FFFFFFF). A PC carries a segment, and the link a call writes is in the segment of the body that made it (docs/SEGMENT_AWARE_CODE.md §5.5). A guest that returns to another segment's alias of the call site did not return to that body, so letting the suspended C continuation run there would execute it with the wrong segment's PCs. §5.5 deferred these checks to this PR so one call contract has one rule. All five are exact now: - psx_call_contract (cpu_state.h): the bail-resolve PC and the $ra check; - the emitted BIOS dispatch loop: the return boundary, the bail resolve, the wild-return test and the exception-stack straddle. Since B routes every baked link through runtime_pc() and C/D compile each body for the segment it runs in, a legitimate return always matches in full. The ledger gains the exact-return-contract guard: it compiles psx_call_contract and checks that another segment's alias starts a bail and resolves nothing, and that the emitted OpenBIOS dispatch has the four exact checks and no masked one. OpenBIOS and SCPH-1001 dispatch C change in those four lines; game C does not change. cpu_state.h is in the codegen hash. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
6a76eb452b |
recompiler: compile segment variants of game code from segment-qualified seeds
A seed that names the image's bytes in another segment than its link segment (0xA00100F0 for a KUSEG-linked EXE, 0x00012345 for a KSEG0 one) now compiles a segment variant (docs/SEGMENT_AWARE_CODE.md §5.4): the home functions the seed reaches through direct edges (calls, tail jumps, split pieces, fall-throughs, call returns; a seed at a CPS continuation roots its owner, an alias entry its alias group) are compiled again through a view of the image in that segment. The variant's names, rows and every PC it bakes are that segment's, and a KSEG1 variant charges a fetch per instruction (#429's rule through B's runtime_pc()). A cached variant is the home C with the segment swapped. Exact-match config sites move into the variant's segment, so a variant gets every hook the home body gets. A seed that names no home dispatch entry stops the build, and so does a seed in a segment that does not map physical memory (0x20000000-0x7FFFFFFF or KSEG2: Beetle's addr_mask leaves them unmasked, so they address no RAM and are no alias of the image's bytes). tools/collect_game_misses.py reports such a segment miss instead of writing it as a seed. The dispatch table holds a row per compiled segment of a word, adjacent and in PC order; when any word has more than one row the lookup (indexed and binary search) searches the word's rows for the exact PC. Two rows for one PC remain a build error. A title without variants gets the same dispatch source as before. A jump-table case whose value is in another segment than its body is no longer a local case: the default tail-transfer dispatches it with its full PC. This applies to every compile. In a variant it is the common case (a KSEG0 variant of KUSEG-linked code). A home or overlay compile has one only when its table holds another segment's PCs of its own bytes, for example a KSEG0-compiled overlay shard of code that runs at KUSEG. Such a case used to continue in the KSEG0 body with KSEG0 links and fetch tags. Now it dispatches: a game-text PC with no row in its segment is a recorded segment miss, and #417's gate sends an overlay PC to the interpreter; both run it at its own PC (PR E returns overlay code to native per-segment shards). R4's game C and its 26 overlay shard sources are unchanged. Ledger: segment-variants and kseg1-fetch-charge close (KNOWN_GAPS is empty) and become regression guards; resume-pc-segment now requires each row's key and resume PC to be in its body's segment, and segment-variants checks every PC a variant body bakes. New segment_variants_test; kuseg_dispatch_lookup and emitter_runtime_pc_test cover variant rows, closure, equality with the home C, per-instruction KSEG1 charging, foreign jump-table cases at home and in variants, data stubs (no row), alias groups declared for every shard and seeds outside the physical segments. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d67f0f9396 |
recompiler: compile a KUSEG-linked EXE for its link segment; exact dispatch
A PS-X EXE header may carry KUSEG addresses (Alien Resurrection SLUS-00633, Kula World SCES-01000); the BIOS then runs the program at KUSEG PCs, so its links, EPCs and I-cache tags are KUSEG. The parser folded such a header to KSEG0, so the whole program compiled for a segment it never runs in, and the dispatch lookup matched any segment by physical address (docs/SEGMENT_AWARE_CODE.md §3.2, §5.3, §5.5). - EXE parser: keeps the header's addresses. link_segment() is the load address's segment; the load address and entry must share it and lie in the 8 MiB RAM window of KUSEG, KSEG0 or KSEG1 (KSEG1 compiles as an uncached home segment). phys_load_address()/contains_phys() do byte addressing; the BSS overlap check and the text_size bound are physical. - function_analysis: the three JAL scans take the target's top bits from the delay slot's PC instead of assuming KSEG0 (unchanged for KSEG0). - Seeds are range-checked by physical address. A seed in the link segment is an entry (0x000100E4 was silently dropped before). A seed for the same bytes in another segment asks for a segment variant (§5.4): it is listed in a WARNING, never folded into the home body. Seeds outside the image are counted. The producer_range, cross_call_allow and hosted_interior directives have no variant form: a foreign spelling is refused with the link-segment one instead of a bare "invalid" error. - Config code sites that are matched exactly (widescreen and cull sites, mod hooks, hot/data-shard funcs, the VSync-query function and its event-horizon return PCs, persisted options) and name the image's bytes in another segment are refused with the spelling that matches, since they would now match nothing. Kinds matched by physical address (byte patches, the full-word-guarded cull keep/angle/aspect-cone and signed-X-bound sites, dome call sites) reach the same bytes in any spelling and are not refused. Overlay compiles are exempt until PR E. - tools/collect_game_misses.py wrote every dirty-RAM miss as a KSEG0 seed, which a KUSEG-linked image now reports instead of compiling. It writes them in the link segment (--segment, --game-toml, or a segmented text range; it refuses to guess from a physical one) and adds the segment_misses rows at their full PC. - Dispatch source moves to game_dispatch_emitter.cpp. The lookup indexes the physical word, then requires row.addr == addr; a second row for one word is an error until variants share the table. psx_game_address_in_text stays physical, so a segment miss takes the clean-text-miss path (the previous commit records it). For a KSEG0 title the game C is byte-identical; its dispatch file changes only in psx_game_find_entry (the exact-PC compare) and its comment. Tests: - segment_aware_codegen closes link-segment, fetch-tag-segment, irq-resume-segment, resume-pc-segment, store-pc-segment, home-seed-accepted, alias-fetch-coherence and segment-miss; they stay as regression guards, and resume-pc-segment now also checks the dispatch rows' keys and resume PCs. - kuseg_dispatch_lookup: KUSEG rows, names and baked PCs; aliases miss while text stays physical; variant seeds reported; a function reached only by jal and a SaveState-style continuation are discovered in a KUSEG image (kills the two discovery scans reverted to KSEG0; the third, the invalid-JAL data heuristic, reads the same bits either way); mixed-segment headers rejected; a KSEG1 image charged per instruction; foreign config sites refused; the RAM-mirror contract as before. - kuseg_dispatch_lookup also compiles the lookup's binary-search form (a table spanning 2 MiB or more) and queries every segment's alias of each row; refuses a KSEG0 event-horizon site and KSEG0 seeds directives with their KUSEG spelling; accepts a KSEG0 signed_x_bound site and applies it. - emitter_runtime_pc_test: the dispatch rows follow runtime_pc() and keep the compile identity in func_ names (kills B's two surviving mutants: 76 and 12 failing checks); two rows on one physical word are an error. - collect_game_misses (new, tools/tests): link-segment seeds from each source, segment misses at their full PC, no guessing, no duplicates, against a fake server using the runtime's own summary row format. - reachable_discovery_test: the parser keeps KUSEG, accepts KSEG1, rejects mixed segments and non-RAM load addresses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
980274a405 |
recompiler: route every baked guest PC through runtime_pc()
Segment-aware code, rollout PR B (docs/SEGMENT_AWARE_CODE.md §5.2). Game/overlay emitter: CodeGenerator gains a code segment and runtime_pc(addr) = code_seg | phys. Links (including a jalr in a branch delay slot), fetch tags and the uncached-fetch test in emit_pre_icache, interrupt resume PCs, CPS exit PCs and continuation keys, store-PC stamps, the reserved-instruction EPC, the slice resume PC, call_by_address and stale-guard targets, the call contract's return PC, cosim PCs, the VSync-query hook's base PC, the data stubs' unknown-dispatch PC and the dispatch rows' addr/resume_pc all go through it. func_/block_ names, .ranges and entry-hook ids keep the compile address. The VSync-query hook is not emitted for an uncached code segment: its hand-timed body charges cached fetches only. The segment defaults to the image's own, so runtime_pc(addr) == addr for every existing input and KSEG0 titles regenerate byte for byte. BIOS emitter: StrictTranslator::translate(d, runtime_pc) stamps stores, the syscall EPC and the break and unaligned-access PCs with the PC the code runs at; the psx_ldd_ temporaries keep the ROM address. The full-function emitter passes relocate_ra() to every translate() call and publishes the runtime PC on a fallthrough. Relocated kernel and shell code therefore stamps its RAM address, as the interpreter does. memory.c re-keys, in the same change, the seven SCPH-1001 store-PC keys that named ROM addresses of relocated code: RAM-0 filters 0x00000F00 (ROM 0xBFC10A00), 0x80056EB4, 0x800585E4, 0x80058788, 0x80059C50 (ROM 0xBFC3EEB4/405E4/40788/41C50), the opt-in Tomba EvCB key 0x00001CE4 (ROM 0xBFC117E4) and the GP0 source key 0x80050B1C (ROM 0xBFC38B1C). Without the re-key the new stamps would silently disable all seven. A RAM PC is ambiguous where the ROM key was not, so each key goes through scph1001_relocated_store(pc, rom): it matches only while the active image is SCPH-1001 and the word at pc is still the ROM word copied there. Other BIOSes and game code at those addresses never match, as before. Tests: emitter_runtime_pc_test (new, CTest, run with PSX_CPS unset and set to 0) compiles a small program through generate_all_functions with the code segment left default, KSEG0, KUSEG and KSEG1, in CPS, CPS-overlay and legacy mode. It requires every emission path to be reached and checks every guest-visible PC class, the identity keys, physical-only differences and KSEG1 per-instruction fetch charging, and the translator's runtime PCs in every PC-bearing form. The ledger gains two regression guards: bios-runtime-pc (OpenBIOS output, plus every translate() call site) and store-pc-keys-runtime (memory.c keys against SCPH1001.toml's windows). KNOWN_GAPS is unchanged: B closes no ledger id on its own (§5.2). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d51a34a766 |
docs: record the segment-aware decisions and the store-PC correction
The owner settled §10's four questions on 2026-09-29, all as recommended: - I-cache tags follow Beetle; the hardware bit-31 difference is recorded in ACCURACY_BURNDOWN axis 4. - Segment misses in static game code interpret loudly until the title is regenerated. - Extra segments are requested through segment-qualified seeds. - The overlay cache uses per-segment subdirectories. The doc called g_debug_last_store_pc a debug/identity hook. It is not: memory.c's RAM 0x0-0xF store filters compare it with exact PCs in every build, the interpreter stamps the full PC, and #420 (ABI v24) forwards overlay stamps to the host. §3.1 now lists it as a baked PC, and §5.2 routes it through runtime_pc(). Consequences: - Option A, re-measured with the 1,261 store-PC stamps in the three R4 shards: +3.8 % / +27.6 % __TEXT (was +3.2 % / +27.2 %). - New ledger id store-pc-segment (10 KSEG0 stamps in the synthetic EXE's home bodies), closed by §5.3. The ledger now has 10 known gaps. - §9: several filter keys are ROM addresses of BIOS code that runs relocated in SCPH1001. The BIOS emitter stamps ROM addresses and the interpreter stamps runtime PCs, so the keys must be re-keyed when the BIOS stamp moves to runtime_pc(). §8 notes that rollout PR A (fix/uncached-fetch-per-insn, based on master) is being prepared. A has since landed as #429, so the ledger does not list bios-kseg1-fetch-charge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9f8ee72f4d |
docs: design segment-aware code, with a KUSEG-linked acceptance ledger
Compiled code bakes KSEG0 into link values, CPS/IRQ resume PCs and I-cache tags. KUSEG-linked EXEs run KUSEG PCs through those bodies (and their KUSEG seeds are dropped), and overlay code executed at KUSEG/KSEG1 stays interpreted after #417. Until #429, both emitters also charged the uncached KSEG1 fetch only at cache-line leaders, so compiled BIOS ROM code was 4 cycles short on 5,477 of OpenBIOS's 9,592 KSEG1 instruction sites (Beetle and the interp charge every fetch). docs/SEGMENT_AWARE_CODE.md recommends per-segment compiled variants (code identity = full VA, emitter segment as a per-compile constant, exact-segment dispatch, per-instruction fetch in uncached bodies, segment-aware capture and cache keys) over segment-relative emission, which measures +3.2 % / +27.2 % __TEXT on R4 shards. tools/segment_testrom/gen_segment_exe.py emits a synthetic KUSEG-linked PS-X EXE that also works as an oracle probe. The new ctest segment_aware_codegen runs the real recompilers on it and on OpenBIOS and checks emitted PCs, the emitted dispatch lookup and fetch cycles (through runtime/src/psx_icache.c) against a Beetle ReadInstruction transcription. It passes only while the observed gaps equal its ledger (9 today). #429 (rollout A) fixed the BIOS undercharge on master before this landed, so KNOWN_GAPS does not list bios-kseg1-fetch-charge: the OpenBIOS check finds no uncharged KSEG1 site and stays as a regression guard that reports the id as a new gap. The last commit of this series brings the doc up to date with #429. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |