mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 18:25:35 -04:00
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>
This commit is contained in:
@@ -2449,6 +2449,13 @@ std::string CodeGenerator::translate_basic_block(
|
||||
std::vector<std::pair<uint32_t,uint32_t>> targets; // {runtime_addr, rom_addr}
|
||||
for (const auto& [runtime_addr, rom_addr] :
|
||||
exact_table.targets) {
|
||||
// A case value is the PC the jr reaches. A value
|
||||
// in another segment than this body's reaches the
|
||||
// same bytes as another code identity (§5.4): it
|
||||
// is left to the default tail-transfer, which
|
||||
// dispatches it with its full PC, instead of
|
||||
// continuing here with this segment's PCs.
|
||||
if (foreign_segment_case(runtime_addr, rom_addr)) continue;
|
||||
if (seen.insert(rom_addr).second &&
|
||||
(cfg.blocks.count(rom_addr) || extra_labels_.count(rom_addr))) {
|
||||
targets.push_back({runtime_addr, rom_addr});
|
||||
@@ -3042,6 +3049,9 @@ void CodeGenerator::scan_jr_tables(
|
||||
if (tb==0 || tc==0) continue;
|
||||
for (const auto& target_pair : exact_table.targets) {
|
||||
uint32_t t = target_pair.second;
|
||||
// Not a local edge: dispatched with its own segment (see the
|
||||
// switch emission in translate_basic_block).
|
||||
if (foreign_segment_case(target_pair.first, t)) continue;
|
||||
if (t >= cfg.function_start && t < cfg.function_end) {
|
||||
out_edges[baddr].push_back(t);
|
||||
if (!cfg.blocks.count(t))
|
||||
@@ -3537,7 +3547,8 @@ void CodeGenerator::emit_unaligned_helpers(std::ostream& ss, bool as_inline) con
|
||||
ss << "\n";
|
||||
}
|
||||
|
||||
std::string CodeGenerator::build_shared_decls_header(const std::vector<GeneratedFunction>& gen_funcs) const {
|
||||
std::string CodeGenerator::build_shared_decls_header(const std::vector<GeneratedFunction>& gen_funcs,
|
||||
const std::vector<std::string>& extra_decls) const {
|
||||
std::stringstream h;
|
||||
h << "/* Generated by PSXRecomp - shared declarations for split full.c shards. DO NOT EDIT. */\n";
|
||||
h << "#pragma once\n";
|
||||
@@ -3548,6 +3559,7 @@ std::string CodeGenerator::build_shared_decls_header(const std::vector<Generated
|
||||
h << "/* Forward declarations for all recompiled functions */\n";
|
||||
for (const auto& gf : gen_funcs) h << gf.signature << ";\n";
|
||||
for (const auto& decl : alias_body_decls_) h << decl << ";\n";
|
||||
for (const auto& decl : extra_decls) h << decl << ";\n";
|
||||
h << "\n";
|
||||
}
|
||||
return h.str();
|
||||
@@ -3801,6 +3813,8 @@ std::string CodeGenerator::generate_file(
|
||||
// dispatch generator consumes this so synthesized fallthrough functions
|
||||
// receive the same byte-precise native-validity ranges as ordinary entries.
|
||||
last_ranges_manifest_ = generate_ranges_manifest(functions_mut, cfgs_mut);
|
||||
last_functions_ = std::move(functions_mut);
|
||||
last_cfgs_ = std::move(cfgs_mut);
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user