From b1be38310bbe313e1745f680f6dc3c6f12ecbd83 Mon Sep 17 00:00:00 2001 From: Rainchus Date: Sat, 4 Apr 2026 17:45:54 -0500 Subject: [PATCH] cross section relocs --- include/recompiler/context.h | 2 ++ src/config.cpp | 9 +++++++++ src/config.h | 1 + src/main.cpp | 3 ++- src/recompilation.cpp | 19 ++++++++++++++++--- 5 files changed, 30 insertions(+), 4 deletions(-) diff --git a/include/recompiler/context.h b/include/recompiler/context.h index e7b4431..2f38cec 100644 --- a/include/recompiler/context.h +++ b/include/recompiler/context.h @@ -235,6 +235,8 @@ namespace N64Recomp { bool skip_validating_reference_symbols = true; // Whether all function calls (excluding reference symbols) should go through lookup. bool use_lookup_for_all_function_calls = false; + // Whether function calls between relocatable sections is allowed. + bool cross_relocatable_section_function_calls = false; //// Only used by the CLI, TODO move this to a struct in the internal headers. // A mapping of function name to index in the functions vector diff --git a/src/config.cpp b/src/config.cpp index 1066aff..eb8e887 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -417,6 +417,15 @@ N64Recomp::Config::Config(const char* path) { use_mdebug = false; } + // Whether function calls across relocatable sections is allowed (optional) + std::optional cross_relocatable_section_function_calls_opt = input_data["cross_relocatable_section_function_calls"].value(); + if (cross_relocatable_section_function_calls_opt.has_value()) { + cross_relocatable_section_function_calls = cross_relocatable_section_function_calls_opt.value(); + } + else { + cross_relocatable_section_function_calls = false; + } + // Symbols to ignore when parsing mdebug (option, defaults to empty) toml::node_view mdebug_mappings_data = input_data["mdebug_file_mappings"]; if (mdebug_mappings_data.is_array()) { diff --git a/src/config.h b/src/config.h index 7d6db23..e636c60 100644 --- a/src/config.h +++ b/src/config.h @@ -44,6 +44,7 @@ namespace N64Recomp { bool use_absolute_symbols; bool unpaired_lo16_warnings; bool use_mdebug; + bool cross_relocatable_section_function_calls; bool trace_mode; bool allow_exports; bool strict_patch_mode; diff --git a/src/main.cpp b/src/main.cpp index f79580f..b9c58e3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -524,7 +524,8 @@ int main(int argc, char** argv) { func->name = func->name + "_recomp"; } - // Propogate the trace mode parameter. + // Propagate config params. + context.cross_relocatable_section_function_calls = config.cross_relocatable_section_function_calls; context.trace_mode = config.trace_mode; // Apply any single-instruction patches. diff --git a/src/recompilation.cpp b/src/recompilation.cpp index c228433..a67af67 100644 --- a/src/recompilation.cpp +++ b/src/recompilation.cpp @@ -18,6 +18,7 @@ enum class JalResolutionResult { Match, CreateStatic, Ambiguous, + AmbiguousWithRelocatable, Error }; @@ -34,6 +35,7 @@ JalResolutionResult resolve_jal(const N64Recomp::Context& context, size_t cur_se uint32_t section_vram_end = cur_section.ram_addr + cur_section.size; bool in_current_section = target_func_vram >= section_vram_start && target_func_vram < section_vram_end; bool exact_match_found = false; + bool found_relocatable_match = false; // Use a thread local to prevent reallocation across runs and to allow multi-threading in the future. thread_local std::vector matched_funcs{}; @@ -59,10 +61,13 @@ JalResolutionResult resolve_jal(const N64Recomp::Context& context, size_t cur_se break; } - // If the function's section isn't relocatable, add the function as a candidate. + // If the function's section isn't relocatable or if cross relocatable section function calls are enabled, add the function as a candidate. const auto& target_func_section = context.sections[target_func.section_index]; - if (!target_func_section.relocatable) { + if (context.cross_relocatable_section_function_calls || !target_func_section.relocatable) { matched_funcs.push_back(target_func_index); + if (target_func_section.relocatable) { + found_relocatable_match = true; + } } } } @@ -93,6 +98,11 @@ JalResolutionResult resolve_jal(const N64Recomp::Context& context, size_t cur_se } // If there's more than one match, use an indirect jump to resolve the function at runtime. else { + // Ambiguous matches where any of the matches are in a relocatable section must be reported differently than normal ambiguous matches. + // This is because the loaded address of the function may not be the original address, so a lookup using the original address would fail. + if (found_relocatable_match) { + return JalResolutionResult::AmbiguousWithRelocatable; + } return JalResolutionResult::Ambiguous; } } @@ -162,7 +172,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con uint16_t imm = instr.Get_immediate(); // Check if this instruction has a reloc. - if (section.relocs.size() > 0 && section.relocs[reloc_index].address == instr_vram) { + if (section.relocs.size() > 0 && section.relocs[reloc_index].address == instr_vram && section.relocs[reloc_index].type != N64Recomp::RelocType::R_MIPS_NONE) { has_reloc = true; // Get the reloc data for this instruction const auto& reloc = section.relocs[reloc_index]; @@ -326,6 +336,9 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con // If a game ever needs to jump between multiple relocatable sections, relocation will be necessary here. call_by_lookup = true; break; + case JalResolutionResult::AmbiguousWithRelocatable: + fmt::print(stderr, "Ambiguous jal target 0x{:08X} in function {}, but one or more matches were relocatable. Calling by lookup for relocatable functions with the unrelocated address is not supported.\n", target_func_vram, func.name); + return false; case JalResolutionResult::Error: fmt::print(stderr, "Internal error when resolving jal to address 0x{:08X} in function {}. Please report this issue.\n", target_func_vram, func.name); return false;