diff --git a/RSPRecomp/src/rsp_recomp.cpp b/RSPRecomp/src/rsp_recomp.cpp index 95637b1..abcfb26 100644 --- a/RSPRecomp/src/rsp_recomp.cpp +++ b/RSPRecomp/src/rsp_recomp.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "rabbitizer.hpp" #include "fmt/format.h" #include "fmt/ostream.h" @@ -187,7 +188,7 @@ BranchTargets get_branch_targets(const std::vector& return ret; } -bool process_instruction(size_t instr_index, const std::vector& instructions, std::ofstream& output_file, const BranchTargets& branch_targets, bool indent, bool in_delay_slot) { +bool process_instruction(size_t instr_index, const std::vector& instructions, std::ofstream& output_file, const BranchTargets& branch_targets, const std::unordered_set& unsupported_instructions, bool indent, bool in_delay_slot) { const auto& instr = instructions[instr_index]; uint32_t instr_vram = instr.getVram(); @@ -230,7 +231,7 @@ bool process_instruction(size_t instr_index, const std::vector(fmt::format_string fmt_str, Ts ...args) { if (instr_index < instructions.size() - 1) { uint32_t next_vram = instr_vram + 4; - process_instruction(instr_index + 1, instructions, output_file, branch_targets, false, true); + process_instruction(instr_index + 1, instructions, output_file, branch_targets, unsupported_instructions, false, true); } print_indent(); fmt::print(output_file, fmt_str, args...); @@ -241,7 +242,7 @@ bool process_instruction(size_t instr_index, const std::vector extra_indirect_branch_targets{}; +//const std::unordered_set unsupported_instructions{}; // OoT aspMain //constexpr size_t rsp_text_offset = 0xB89260; @@ -527,17 +545,35 @@ void write_indirect_jumps(std::ofstream& output_file, const BranchTargets& branc //std::string output_file_path = "../test/rsp/aspMain.cpp"; //std::string output_function_name = "aspMain"; //const std::vector extra_indirect_branch_targets{ 0x1F68, 0x1230, 0x114C, 0x1F18, 0x1E2C, 0x14F4, 0x1E9C, 0x1CB0, 0x117C, 0x17CC, 0x11E8, 0x1AA4, 0x1B34, 0x1190, 0x1C5C, 0x1220, 0x1784, 0x1830, 0x1A20, 0x1884, 0x1A84, 0x1A94, 0x1A48, 0x1BA0 }; +//const std::unordered_set unsupported_instructions{}; // MM's njpgdspMain is identical to OoT's -// MM aspMain -constexpr size_t rsp_text_offset = 0xC40FF0; -constexpr size_t rsp_text_size = 0x1000; -constexpr size_t rsp_text_address = 0x04001000; -std::string rom_file_path = "../../MMRecomp/mm.us.rev1.z64"; // uncompressed rom! -std::string output_file_path = "../../MMRecomp/rsp/aspMain.cpp"; -std::string output_function_name = "aspMain"; -const std::vector extra_indirect_branch_targets{ 0x1F80, 0x1250, 0x1154, 0x1094, 0x1E0C, 0x1514, 0x1E7C, 0x1C90, 0x1180, 0x1808, 0x11E8, 0x1ADC, 0x1B6C, 0x1194, 0x1EF8, 0x1240, 0x17C0, 0x186C, 0x1A58, 0x18BC, 0x1ABC, 0x1ACC, 0x1A80, 0x1BD4 }; +//// MM aspMain +//constexpr size_t rsp_text_offset = 0xC40FF0; +//constexpr size_t rsp_text_size = 0x1000; +//constexpr size_t rsp_text_address = 0x04001000; +//std::string rom_file_path = "../../MMRecomp/mm.us.rev1.z64"; // uncompressed rom! +//std::string output_file_path = "../../MMRecomp/rsp/aspMain.cpp"; +//std::string output_function_name = "aspMain"; +//const std::vector extra_indirect_branch_targets{ 0x1F80, 0x1250, 0x1154, 0x1094, 0x1E0C, 0x1514, 0x1E7C, 0x1C90, 0x1180, 0x1808, 0x11E8, 0x1ADC, 0x1B6C, 0x1194, 0x1EF8, 0x1240, 0x17C0, 0x186C, 0x1A58, 0x18BC, 0x1ABC, 0x1ACC, 0x1A80, 0x1BD4 }; +//const std::unordered_set unsupported_instructions{}; + +// BT n_aspMain +constexpr size_t rsp_text_offset = 0x1E4F3B0; +constexpr size_t rsp_text_size = 0xF80; +constexpr size_t rsp_text_address = 0x04001080; +std::string rom_file_path = "../../BTRecomp/banjotooie.decompressed.us.z64"; // uncompressed rom! +std::string output_file_path = "../../BTRecomp/rsp/n_aspMain.cpp"; +std::string output_function_name = "n_aspMain"; +const std::vector extra_indirect_branch_targets{ + // dispatch table + 0x1AE8, 0x143C, 0x1240, 0x1D84, 0x126C, 0x1B20, 0x12A8, 0x1214, 0x141C, 0x1310, 0x13CC, 0x12E4, 0x1FB0, 0x1358, 0x16EC, 0x1408 +}; +const std::unordered_set unsupported_instructions{ + // cmd_MP3 + 0x00001214 +}; #ifdef _MSC_VER inline uint32_t byteswap(uint32_t val) { @@ -589,6 +625,7 @@ int main() { } // Open output file and write beginning + std::filesystem::create_directories(std::filesystem::path{ output_file_path }.parent_path()); std::ofstream output_file(output_file_path); fmt::print(output_file, "#include \"rsp.h\"\n" @@ -603,7 +640,7 @@ int main() { " r1 = 0xFC0;\n", output_function_name); // Write each instruction for (size_t instr_index = 0; instr_index < instrs.size(); instr_index++) { - process_instruction(instr_index, instrs, output_file, branch_targets, false, false); + process_instruction(instr_index, instrs, output_file, branch_targets, unsupported_instructions, false, false); } // Terminate instruction code with a return to indicate that the microcode has run past its end diff --git a/include/recomp_port.h b/include/recomp_port.h index 77edd01..ccd3a9d 100644 --- a/include/recomp_port.h +++ b/include/recomp_port.h @@ -39,14 +39,23 @@ namespace RecompPort { uint32_t value; }; + struct FunctionSize { + std::string func_name; + uint32_t size_bytes; + }; + struct Config { int32_t entrypoint; + bool uses_mips3_float_mode; std::filesystem::path elf_path; std::filesystem::path output_func_path; std::filesystem::path relocatable_sections_path; std::vector stubbed_funcs; + std::vector ignored_funcs; DeclaredFunctionMap declared_funcs; std::vector instruction_patches; + std::vector manual_func_sizes; + std::string bss_section_suffix; Config(const char* path); bool good() { return !bad; } @@ -107,6 +116,7 @@ namespace RecompPort { std::vector function_addrs; std::vector relocs; std::string name; + ELFIO::Elf_Half bss_section_index = (ELFIO::Elf_Half)-1; bool executable = false; bool relocatable = false; }; @@ -128,6 +138,8 @@ namespace RecompPort { std::vector> section_functions; // The section names that were specified as relocatable std::unordered_set relocatable_sections; + // Functions with manual size overrides + std::unordered_map manually_sized_funcs; int executable_section_count; Context(const ELFIO::elfio& elf_file) { @@ -142,7 +154,7 @@ namespace RecompPort { }; bool analyze_function(const Context& context, const Function& function, const std::vector& instructions, FunctionStats& stats); - bool recompile_function(const Context& context, const Function& func, const std::filesystem::path& output_path, std::span> static_funcs); + bool recompile_function(const Context& context, const Config& config, const Function& func, const std::filesystem::path& output_path, std::span> static_funcs); } #endif diff --git a/src/analysis.cpp b/src/analysis.cpp index a3425ed..5068d85 100644 --- a/src/analysis.cpp +++ b/src/analysis.cpp @@ -199,7 +199,9 @@ bool analyze_instruction(const rabbitizer::InstructionCpu& instr, const RecompPo address, instr.getVram() ); - } else { + } + // Allow tail calls (TODO account for trailing nops due to bad function splits) + else if (instr.getVram() != func.vram + (func.words.size() - 2) * sizeof(func.words[0])) { // Inconclusive analysis fmt::print(stderr, "Failed to to find jump table for `jr {}` at 0x{:08X} in {}\n", RabbitizerRegister_getNameGpr(rs), instr.getVram(), func.name); return false; diff --git a/src/config.cpp b/src/config.cpp index debb9a3..446de3d 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -43,6 +43,32 @@ std::vector get_stubbed_funcs(const toml::value& patches_data) { return stubbed_funcs; } +std::vector get_ignored_funcs(const toml::value& patches_data) { + std::vector ignored_funcs{}; + + // Check if the ignored funcs array exists. + const auto& ignored_funcs_data = toml::find_or(patches_data, "ignored", toml::value{}); + + if (ignored_funcs_data.type() == toml::value_t::empty) { + // No stubs, nothing to do here. + return ignored_funcs; + } + + // Get the ignored funcs array as an array type. + const toml::array& ignored_funcs_array = ignored_funcs_data.as_array(); + + // Make room for all the ignored funcs in the array. + ignored_funcs.resize(ignored_funcs_array.size()); + + // Gather the stubs and place them into the array. + for (size_t stub_idx = 0; stub_idx < ignored_funcs_array.size(); stub_idx++) { + // Copy the entry into the ignored function list. + ignored_funcs[stub_idx] = ignored_funcs_array[stub_idx].as_string(); + } + + return ignored_funcs; +} + std::unordered_map arg_type_map{ {"u32", RecompPort::FunctionArgType::u32}, {"s32", RecompPort::FunctionArgType::s32}, @@ -84,7 +110,7 @@ RecompPort::DeclaredFunctionMap get_declared_funcs(const toml::value& patches_da const toml::array& funcs_array = funcs_data.as_array(); // Reserve room for all the funcs in the map. - declared_funcs.reserve(funcs_data.size()); + declared_funcs.reserve(funcs_array.size()); for (const toml::value& cur_func_val : funcs_array) { const std::string& func_name = toml::find(cur_func_val, "name"); const toml::array& args_in = toml::find(cur_func_val, "args"); @@ -95,6 +121,40 @@ RecompPort::DeclaredFunctionMap get_declared_funcs(const toml::value& patches_da return declared_funcs; } +std::vector get_func_sizes(const toml::value& patches_data) { + std::vector func_sizes{}; + + // Check if the func size array exists. + const toml::value& sizes_data = toml::find_or(patches_data, "function_sizes", toml::value{}); + if (sizes_data.type() == toml::value_t::empty) { + // No func size array, nothing to do here + return func_sizes; + } + + // Get the funcs array as an array type. + const toml::array& sizes_array = sizes_data.as_array(); + + // Reserve room for all the funcs in the map. + func_sizes.reserve(sizes_array.size()); + for (const toml::value& cur_func_size : sizes_array) { + const std::string& func_name = toml::find(cur_func_size, "name"); + uint32_t func_size = toml::find(cur_func_size, "size"); + + // Make sure the size is divisible by 4 + if (func_size & (4 - 1)) { + // It's not, so throw an error (and make it look like a normal toml one). + throw toml::type_error(toml::detail::format_underline( + std::string{ std::source_location::current().function_name() } + ": function size not divisible by 4", { + {cur_func_size.location(), ""} + }), cur_func_size.location()); + } + + func_sizes.emplace_back(func_name, func_size); + } + + return func_sizes; +} + std::vector get_instruction_patches(const toml::value& patches_data) { std::vector ret; @@ -155,6 +215,8 @@ RecompPort::Config::Config(const char* path) { elf_path = concat_if_not_empty(basedir, toml::find(input_data, "elf_path")); output_func_path = concat_if_not_empty(basedir, toml::find(input_data, "output_func_path")); relocatable_sections_path = concat_if_not_empty(basedir, toml::find_or(input_data, "relocatable_sections_path", "")); + uses_mips3_float_mode = toml::find_or(input_data, "uses_mips3_float_mode", false); + bss_section_suffix = toml::find_or(input_data, "bss_section_suffix", ".bss"); // Patches section (optional) const toml::value& patches_data = toml::find_or(config_data, "patches", toml::value{}); @@ -162,11 +224,17 @@ RecompPort::Config::Config(const char* path) { // Stubs array (optional) stubbed_funcs = get_stubbed_funcs(patches_data); + // Ignored funcs array (optional) + ignored_funcs = get_ignored_funcs(patches_data); + // Functions (optional) declared_funcs = get_declared_funcs(patches_data); // Single-instruction patches (optional) instruction_patches = get_instruction_patches(patches_data); + + // Manual function sizes (optional) + manual_func_sizes = get_func_sizes(patches_data); } } catch (const toml::syntax_error& err) { diff --git a/src/main.cpp b/src/main.cpp index 3d6a621..afb4075 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -540,6 +540,8 @@ std::unordered_set renamed_funcs{ "sincosf", "sinf", "cosf", + "__sinf", + "__cosf", "sqrt", "sqrtf", "memcpy", @@ -567,17 +569,8 @@ std::unordered_set renamed_funcs{ "roundf", "trunc", "truncf", - "vsprintf" -}; - -// Functions that weren't declared properly and thus have no size in the elf -std::unordered_map unsized_funcs{ - { "guMtxF2L", 0x64 }, - { "guScaleF", 0x48 }, - { "guTranslateF", 0x48 }, - { "guMtxIdentF", 0x48 }, - { "sqrtf", 0x8 }, - { "guMtxIdent", 0x4C }, + "vsprintf", + "__assert", }; bool read_symbols(RecompPort::Context& context, const ELFIO::elfio& elf_file, ELFIO::section* symtab_section, uint32_t entrypoint) { @@ -605,23 +598,22 @@ bool read_symbols(RecompPort::Context& context, const ELFIO::elfio& elf_file, EL } // Check if this symbol is the entrypoint - if (value == entrypoint /*&& type == ELFIO::STT_FUNC*/) { + if (value == entrypoint && type == ELFIO::STT_FUNC) { if (found_entrypoint_func) { - fmt::print(stderr, "Ambiguous entrypoint\n"); + fmt::print(stderr, "Ambiguous entrypoint: {}\n", name); return false; } found_entrypoint_func = true; + fmt::print("Found entrypoint, original name: {}\n", name); size = 0x50; // dummy size for entrypoints, should cover them all name = "recomp_entrypoint"; } - // Check if this symbol is unsized and if so populate its size from the unsized_funcs map - if (size == 0) { - auto size_find = unsized_funcs.find(name); - if (size_find != unsized_funcs.end()) { - size = size_find->second; - type = ELFIO::STT_FUNC; - } + // Check if this symbol has a size override + auto size_find = context.manually_sized_funcs.find(name); + if (size_find != context.manually_sized_funcs.end()) { + size = size_find->second; + type = ELFIO::STT_FUNC; } if (reimplemented_funcs.contains(name)) { @@ -653,7 +645,7 @@ bool read_symbols(RecompPort::Context& context, const ELFIO::elfio& elf_file, EL context.functions_by_vram[vram].push_back(context.functions.size()); // Find the entrypoint by rom address in case it doesn't have vram as its value - if (rom_address == 0x1000) { + if (rom_address == 0x1000 && type == ELFIO::STT_FUNC) { vram = entrypoint; found_entrypoint_func = true; name = "recomp_entrypoint"; @@ -719,7 +711,7 @@ std::optional get_segment(const std::vector& segments, ELF return std::nullopt; } -ELFIO::section* read_sections(RecompPort::Context& context, const ELFIO::elfio& elf_file) { +ELFIO::section* read_sections(RecompPort::Context& context, const RecompPort::Config& config, const ELFIO::elfio& elf_file) { ELFIO::section* symtab_section = nullptr; std::vector segments{}; segments.resize(elf_file.segments.size()); @@ -740,6 +732,7 @@ ELFIO::section* read_sections(RecompPort::Context& context, const ELFIO::elfio& //); std::unordered_map reloc_sections_by_name; + std::unordered_map bss_sections_by_name; // Iterate over every section to record rom addresses and find the symbol table fmt::print("Sections\n"); @@ -756,6 +749,10 @@ ELFIO::section* read_sections(RecompPort::Context& context, const ELFIO::elfio& if (type == ELFIO::SHT_SYMTAB) { symtab_section = section.get(); } + + if (context.relocatable_sections.contains(section_name)) { + section_out.relocatable = true; + } // Check if this section is a reloc section if (type == ELFIO::SHT_REL) { @@ -773,6 +770,16 @@ ELFIO::section* read_sections(RecompPort::Context& context, const ELFIO::elfio& } } + // If the section is bss (SHT_NOBITS) and ends with the bss suffix, add it to the bss section map + if (type == ELFIO::SHT_NOBITS && section_name.ends_with(config.bss_section_suffix)) { + std::string bss_target_section = section_name.substr(0, section_name.size() - config.bss_section_suffix.size()); + + // If this bss section is for a section that has been marked as relocatable, record it in the reloc section lookup + if (context.relocatable_sections.contains(bss_target_section)) { + bss_sections_by_name[bss_target_section] = section.get(); + } + } + // If this section isn't bss (SHT_NOBITS) and ends up in the rom (SHF_ALLOC), // find this section's rom address and copy it into the rom if (type != ELFIO::SHT_NOBITS && section->get_flags() & ELFIO::SHF_ALLOC && section->get_size() != 0) { @@ -831,97 +838,103 @@ ELFIO::section* read_sections(RecompPort::Context& context, const ELFIO::elfio& // TODO make sure that a reloc section was found for every section marked as relocatable - // Process reloc sections + // Process bss and reloc sections for (RecompPort::Section §ion_out : context.sections) { - // Check if a reloc section was found that corresponds with this section - auto reloc_find = reloc_sections_by_name.find(section_out.name); - if (reloc_find != reloc_sections_by_name.end()) { - // Mark the section as relocatable - section_out.relocatable = true; - // Create an accessor for the reloc section - ELFIO::relocation_section_accessor rel_accessor{ elf_file, reloc_find->second }; - // Allocate space for the relocs in this section - section_out.relocs.resize(rel_accessor.get_entries_num()); - // Track whether the previous reloc was a HI16 and its previous full_immediate - bool prev_hi = false; - uint32_t prev_hi_immediate = 0; - uint32_t prev_hi_symbol = std::numeric_limits::max(); + // Check if a bss section was found that corresponds with this section + auto bss_find = bss_sections_by_name.find(section_out.name); + if (bss_find != bss_sections_by_name.end()) { + section_out.bss_section_index = bss_find->second->get_index(); + } - for (size_t i = 0; i < section_out.relocs.size(); i++) { - // Get the current reloc - ELFIO::Elf64_Addr rel_offset; - ELFIO::Elf_Word rel_symbol; - unsigned int rel_type; - ELFIO::Elf_Sxword bad_rel_addend; // Addends aren't encoded in the reloc, so ignore this one - rel_accessor.get_entry(i, rel_offset, rel_symbol, rel_type, bad_rel_addend); + if (section_out.relocatable) { + // Check if a reloc section was found that corresponds with this section + auto reloc_find = reloc_sections_by_name.find(section_out.name); + if (reloc_find != reloc_sections_by_name.end()) { + // Create an accessor for the reloc section + ELFIO::relocation_section_accessor rel_accessor{ elf_file, reloc_find->second }; + // Allocate space for the relocs in this section + section_out.relocs.resize(rel_accessor.get_entries_num()); + // Track whether the previous reloc was a HI16 and its previous full_immediate + bool prev_hi = false; + uint32_t prev_hi_immediate = 0; + uint32_t prev_hi_symbol = std::numeric_limits::max(); - RecompPort::Reloc& reloc_out = section_out.relocs[i]; + for (size_t i = 0; i < section_out.relocs.size(); i++) { + // Get the current reloc + ELFIO::Elf64_Addr rel_offset; + ELFIO::Elf_Word rel_symbol; + unsigned int rel_type; + ELFIO::Elf_Sxword bad_rel_addend; // Addends aren't encoded in the reloc, so ignore this one + rel_accessor.get_entry(i, rel_offset, rel_symbol, rel_type, bad_rel_addend); - // Get the real full_immediate by extracting the immediate from the instruction - uint32_t instr_word = byteswap(*reinterpret_cast(context.rom.data() + section_out.rom_addr + rel_offset - section_out.ram_addr)); - rabbitizer::InstructionCpu instr{ instr_word, static_cast(rel_offset) }; - //context.rom section_out.rom_addr; + RecompPort::Reloc& reloc_out = section_out.relocs[i]; - reloc_out.address = rel_offset; - reloc_out.symbol_index = rel_symbol; - reloc_out.type = static_cast(rel_type); - reloc_out.needs_relocation = false; + // Get the real full_immediate by extracting the immediate from the instruction + uint32_t instr_word = byteswap(*reinterpret_cast(context.rom.data() + section_out.rom_addr + rel_offset - section_out.ram_addr)); + rabbitizer::InstructionCpu instr{ instr_word, static_cast(rel_offset) }; + //context.rom section_out.rom_addr; - std::string rel_symbol_name; - ELFIO::Elf64_Addr rel_symbol_value; - ELFIO::Elf_Xword rel_symbol_size; - unsigned char rel_symbol_bind; - unsigned char rel_symbol_type; - ELFIO::Elf_Half rel_symbol_section_index; - unsigned char rel_symbol_other; + reloc_out.address = rel_offset; + reloc_out.symbol_index = rel_symbol; + reloc_out.type = static_cast(rel_type); + reloc_out.needs_relocation = false; - bool found_rel_symbol = symbol_accessor.get_symbol( - rel_symbol, rel_symbol_name, rel_symbol_value, rel_symbol_size, rel_symbol_bind, rel_symbol_type, rel_symbol_section_index, rel_symbol_other); + std::string rel_symbol_name; + ELFIO::Elf64_Addr rel_symbol_value; + ELFIO::Elf_Xword rel_symbol_size; + unsigned char rel_symbol_bind; + unsigned char rel_symbol_type; + ELFIO::Elf_Half rel_symbol_section_index; + unsigned char rel_symbol_other; - reloc_out.target_section = rel_symbol_section_index; + bool found_rel_symbol = symbol_accessor.get_symbol( + rel_symbol, rel_symbol_name, rel_symbol_value, rel_symbol_size, rel_symbol_bind, rel_symbol_type, rel_symbol_section_index, rel_symbol_other); - bool rel_needs_relocation = false; + reloc_out.target_section = rel_symbol_section_index; - if (rel_symbol_section_index < context.sections.size()) { - rel_needs_relocation = context.sections[rel_symbol_section_index].relocatable; - } + bool rel_needs_relocation = false; - // Reloc pairing, see MIPS System V ABI documentation page 4-18 (https://refspecs.linuxfoundation.org/elf/mipsabi.pdf) - if (reloc_out.type == RecompPort::RelocType::R_MIPS_LO16) { - if (prev_hi) { - if (prev_hi_symbol != rel_symbol) { - fmt::print(stderr, "[WARN] Paired HI16 and LO16 relocations have different symbols\n" - " LO16 reloc index {} in section {} referencing symbol {} with offset 0x{:08X}\n", - i, section_out.name, reloc_out.symbol_index, reloc_out.address); + if (rel_symbol_section_index < context.sections.size()) { + rel_needs_relocation = context.sections[rel_symbol_section_index].relocatable; + } + + // Reloc pairing, see MIPS System V ABI documentation page 4-18 (https://refspecs.linuxfoundation.org/elf/mipsabi.pdf) + if (reloc_out.type == RecompPort::RelocType::R_MIPS_LO16) { + if (prev_hi) { + if (prev_hi_symbol != rel_symbol) { + fmt::print(stderr, "[WARN] Paired HI16 and LO16 relocations have different symbols\n" + " LO16 reloc index {} in section {} referencing symbol {} with offset 0x{:08X}\n", + i, section_out.name, reloc_out.symbol_index, reloc_out.address); + } + uint32_t rel_immediate = instr.getProcessedImmediate(); + uint32_t full_immediate = (prev_hi_immediate << 16) + (int16_t)rel_immediate; + + + // Set this and the previous HI16 relocs' relocated addresses + section_out.relocs[i - 1].target_address = full_immediate; + reloc_out.target_address = full_immediate; } + } else { + if (prev_hi) { + fmt::print(stderr, "Unpaired HI16 reloc index {} in section {} referencing symbol {} with offset 0x{:08X}\n", + i - 1, section_out.name, section_out.relocs[i - 1].symbol_index, section_out.relocs[i - 1].address); + return nullptr; + } + } + + if (reloc_out.type == RecompPort::RelocType::R_MIPS_HI16) { uint32_t rel_immediate = instr.getProcessedImmediate(); - uint32_t full_immediate = (prev_hi_immediate << 16) + (int16_t)rel_immediate; - - - // Set this and the previous HI16 relocs' relocated addresses - section_out.relocs[i - 1].target_address = full_immediate; - reloc_out.target_address = full_immediate; + prev_hi = true; + prev_hi_immediate = rel_immediate; + prev_hi_symbol = rel_symbol; + } else { + prev_hi = false; } - } else { - if (prev_hi) { - fmt::print(stderr, "Unpaired HI16 reloc index {} in section {} referencing symbol {} with offset 0x{:08X}\n", - i - 1, section_out.name, section_out.relocs[i - 1].symbol_index, section_out.relocs[i - 1].address); - return nullptr; + + if (reloc_out.type == RecompPort::RelocType::R_MIPS_32) { + // Nothing to do here } } - - if (reloc_out.type == RecompPort::RelocType::R_MIPS_HI16) { - uint32_t rel_immediate = instr.getProcessedImmediate(); - prev_hi = true; - prev_hi_immediate = rel_immediate; - prev_hi_symbol = rel_symbol; - } else { - prev_hi = false; - } - - if (reloc_out.type == RecompPort::RelocType::R_MIPS_32) { - // Nothing to do here - } } // Sort this section's relocs by address, which allows for binary searching and more efficient iteration during recompilation. @@ -968,7 +981,7 @@ void analyze_sections(RecompPort::Context& context, const ELFIO::elfio& elf_file ); } -bool read_list_file(const std::filesystem::path& filename, std::unordered_set& entries_out) { +bool read_list_file(const std::filesystem::path& filename, std::vector& entries_out) { std::ifstream input_file{ filename }; if (!input_file.good()) { return false; @@ -977,7 +990,7 @@ bool read_list_file(const std::filesystem::path& filename, std::unordered_set> entry) { - entries_out.emplace(std::move(entry)); + entries_out.emplace_back(std::move(entry)); } return true; @@ -1007,14 +1020,17 @@ int main(int argc, char** argv) { RabbitizerConfig_Cfg.pseudos.pseudoBnez = false; RabbitizerConfig_Cfg.pseudos.pseudoNot = false; - std::unordered_set relocatable_sections{}; + std::vector relocatable_sections_ordered{}; if (!config.relocatable_sections_path.empty()) { - if (!read_list_file(config.relocatable_sections_path, relocatable_sections)) { + if (!read_list_file(config.relocatable_sections_path, relocatable_sections_ordered)) { exit_failure("Failed to load the relocatable section list file: " + std::string(argv[4]) + "\n"); } } + std::unordered_set relocatable_sections{}; + relocatable_sections.insert(relocatable_sections_ordered.begin(), relocatable_sections_ordered.end()); + if (!elf_file.load(config.elf_path.string())) { exit_failure("Failed to load provided elf file\n"); } @@ -1031,7 +1047,7 @@ int main(int argc, char** argv) { context.relocatable_sections = std::move(relocatable_sections); // Read all of the sections in the elf and look for the symbol table section - ELFIO::section* symtab_section = read_sections(context, elf_file); + ELFIO::section* symtab_section = read_sections(context, config, elf_file); // Search the sections to see if any are overlays or TLB-mapped analyze_sections(context, elf_file); @@ -1041,6 +1057,17 @@ int main(int argc, char** argv) { exit_failure("No symbol table section found\n"); } + // Functions that weren't declared properly and thus have no size in the elf + //context.manually_sized_funcs.emplace("guMtxF2L", 0x64); + //context.manually_sized_funcs.emplace("guScaleF", 0x48); + //context.manually_sized_funcs.emplace("guTranslateF", 0x48); + //context.manually_sized_funcs.emplace("guMtxIdentF", 0x48); + //context.manually_sized_funcs.emplace("sqrtf", 0x8); + //context.manually_sized_funcs.emplace("guMtxIdent", 0x4C); + for (const auto& func_size : config.manual_func_sizes) { + context.manually_sized_funcs.emplace(func_size.func_name, func_size.size_bytes); + } + // Read all of the symbols in the elf and look for the entrypoint function bool found_entrypoint_func = read_symbols(context, elf_file, symtab_section, config.entrypoint); @@ -1050,6 +1077,8 @@ int main(int argc, char** argv) { fmt::print("Function count: {}\n", context.functions.size()); + std::filesystem::create_directories(config.output_func_path); + std::ofstream lookup_file{ config.output_func_path / "lookup.cpp" }; std::ofstream func_header_file{ config.output_func_path / "funcs.h" }; @@ -1087,6 +1116,19 @@ int main(int argc, char** argv) { context.functions[func_find->second].stubbed = true; } + // Ignore any functions specified in the config file. + for (const std::string& ignored_func : config.ignored_funcs) { + // Check if the specified function exists. + auto func_find = context.functions_by_name.find(ignored_func); + if (func_find == context.functions_by_name.end()) { + // Function doesn't exist, present an error to the user instead of silently failing to mark it as ignored. + // This helps prevent typos in the config file or functions renamed between versions from causing issues. + exit_failure(fmt::format("Function {} is set as ignored in the config file but does not exist!", ignored_func)); + } + // Mark the function as . + context.functions[func_find->second].ignored = true; + } + // Apply any single-instruction patches. for (const RecompPort::InstructionPatch& patch : config.instruction_patches) { // Check if the specified function exists. @@ -1102,7 +1144,7 @@ int main(int argc, char** argv) { // Check that the function actually contains this vram address. if (patch.vram < func_vram || patch.vram >= func_vram + func.words.size() * sizeof(func.words[0])) { - exit_failure(fmt::vformat("Function {} has an instruction patch for vram 0x{:08X} but doesn't contain that vram address!", fmt::make_format_args(patch.vram))); + exit_failure(fmt::format("Function {} has an instruction patch for vram 0x{:08X} but doesn't contain that vram address!", patch.func_name, (uint32_t)patch.vram)); } // Calculate the instruction index and modify the instruction. @@ -1119,7 +1161,7 @@ int main(int argc, char** argv) { "void {}(uint8_t* rdram, recomp_context* ctx);\n", func.name); //fmt::print(lookup_file, // " {{ 0x{:08X}u, {} }},\n", func.vram, func.name); - if (RecompPort::recompile_function(context, func, config.output_func_path / (func.name + ".c"), static_funcs_by_section) == false) { + if (RecompPort::recompile_function(context, config, func, config.output_func_path / (func.name + ".c"), static_funcs_by_section) == false) { //lookup_file.clear(); fmt::print(stderr, "Error recompiling {}\n", func.name); std::exit(EXIT_FAILURE); @@ -1188,7 +1230,7 @@ int main(int argc, char** argv) { "void {}(uint8_t* rdram, recomp_context* ctx);\n", func.name); //fmt::print(lookup_file, // " {{ 0x{:08X}u, {} }},\n", func.vram, func.name); - if (RecompPort::recompile_function(context, func, config.output_func_path / (func.name + ".c"), static_funcs_by_section) == false) { + if (RecompPort::recompile_function(context, config, func, config.output_func_path / (func.name + ".c"), static_funcs_by_section) == false) { //lookup_file.clear(); fmt::print(stderr, "Error recompiling {}\n", func.name); std::exit(EXIT_FAILURE); @@ -1226,13 +1268,24 @@ int main(int argc, char** argv) { "\n" ); + std::unordered_map relocatable_section_indices{}; + size_t written_sections = 0; + for (size_t section_index = 0; section_index < context.sections.size(); section_index++) { const auto& section = context.sections[section_index]; const auto& section_funcs = context.section_functions[section_index]; + if (section.name == ".cosection") { + fmt::print(""); + } + if (!section_funcs.empty()) { std::string_view section_name_trimmed{ section.name }; + if (section.relocatable) { + relocatable_section_indices.emplace(section.name, written_sections); + } + while (section_name_trimmed[0] == '.') { section_name_trimmed.remove_prefix(1); } @@ -1253,6 +1306,7 @@ int main(int argc, char** argv) { } fmt::print(overlay_file, "}};\n"); + written_sections++; } } section_load_table += "};\n"; @@ -1260,6 +1314,24 @@ int main(int argc, char** argv) { fmt::print(overlay_file, "{}", section_load_table); fmt::print(overlay_file, "const size_t num_sections = {};\n", context.sections.size()); + + + fmt::print(overlay_file, "static int overlay_sections_by_index[] = {{\n"); + for (const std::string& section : relocatable_sections_ordered) { + // Check if this is an empty overlay + if (section == "*") { + fmt::print(overlay_file, " -1,\n"); + } + else { + auto find_it = relocatable_section_indices.find(section); + if (find_it == relocatable_section_indices.end()) { + fmt::print(stderr, "Failed to find written section index of relocatable section: {}\n", section); + std::exit(EXIT_FAILURE); + } + fmt::print(overlay_file, " {},\n", relocatable_section_indices[section]); + } + } + fmt::print(overlay_file, "}};\n"); } return 0; diff --git a/src/recompilation.cpp b/src/recompilation.cpp index 2967f63..4c484d5 100644 --- a/src/recompilation.cpp +++ b/src/recompilation.cpp @@ -9,6 +9,7 @@ #include "recomp_port.h" using InstrId = rabbitizer::InstrId::UniqueId; +using Cop0Reg = rabbitizer::Registers::Cpu::Cop0; std::string_view ctx_gpr_prefix(int reg) { if (reg != 0) { @@ -17,7 +18,7 @@ std::string_view ctx_gpr_prefix(int reg) { return ""; } -bool process_instruction(const RecompPort::Context& context, const RecompPort::Function& func, const RecompPort::FunctionStats& stats, const std::unordered_set& skipped_insns, size_t instr_index, const std::vector& instructions, std::ofstream& output_file, bool indent, bool emit_link_branch, int link_branch_index, size_t reloc_index, bool& needs_link_branch, bool& is_branch_likely, std::span> static_funcs_out) { +bool process_instruction(const RecompPort::Context& context, const RecompPort::Config& config, const RecompPort::Function& func, const RecompPort::FunctionStats& stats, const std::unordered_set& skipped_insns, size_t instr_index, const std::vector& instructions, std::ofstream& output_file, bool indent, bool emit_link_branch, int link_branch_index, size_t reloc_index, bool& needs_link_branch, bool& is_branch_likely, std::span> static_funcs_out) { const auto& section = context.sections[func.section_index]; const auto& instr = instructions[instr_index]; needs_link_branch = false; @@ -46,27 +47,22 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::F uint32_t reloc_target_section_offset = 0; // Check if this instruction has a reloc. - if (section.relocatable && section.relocs[reloc_index].address == instr_vram) { + if (section.relocatable && section.relocs.size() > 0 && section.relocs[reloc_index].address == instr_vram) { // Get the reloc data for this instruction const auto& reloc = section.relocs[reloc_index]; reloc_section = reloc.target_section; // Some symbols are in a nonexistent section (e.g. absolute symbols), so check that the section is valid before doing anything else. // Absolute symbols will never need to be relocated so it's safe to skip this. if (reloc_section < context.sections.size()) { - // Check if the target section is also relocatable. References to symbols that are in a non-relocatable section don't need to - // be relocated, so we can skip those. - if (context.sections[reloc_section].relocatable) { - // For games where references between sections aren't relocated, ignore this reloc if it points to a different section - // TODO expose this as a config option - // TODO!!!!! also relocate references to the corresponding bss section!!!!! - if (reloc_section == func.section_index) { - // Record the reloc's data. - reloc_type = reloc.type; - reloc_target_section_offset = reloc.target_address - context.sections[reloc_section].ram_addr; - // Ignore all relocs that aren't HI16 or LO16. - if (reloc_type == RecompPort::RelocType::R_MIPS_HI16 || reloc_type == RecompPort::RelocType::R_MIPS_LO16) { - at_reloc = true; - } + // Ignore this reloc if it points to a different section. + // Also check if the reloc points to the bss section since that will also be relocated with the section. + if (reloc_section == func.section_index || reloc_section == section.bss_section_index) { + // Record the reloc's data. + reloc_type = reloc.type; + reloc_target_section_offset = reloc.target_address - section.ram_addr; + // Ignore all relocs that aren't HI16 or LO16. + if (reloc_type == RecompPort::RelocType::R_MIPS_HI16 || reloc_type == RecompPort::RelocType::R_MIPS_LO16) { + at_reloc = true; } } } @@ -96,7 +92,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::F if (reloc_index + 1 < section.relocs.size() && next_vram > section.relocs[reloc_index].address) { next_reloc_index++; } - process_instruction(context, func, stats, skipped_insns, instr_index + 1, instructions, output_file, false, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, static_funcs_out); + process_instruction(context, config, func, stats, skipped_insns, instr_index + 1, instructions, output_file, false, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, static_funcs_out); } print_indent(); fmt::vprint(output_file, fmt_str, fmt::make_format_args(args...)); @@ -117,7 +113,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::F if (reloc_index + 1 < section.relocs.size() && next_vram > section.relocs[reloc_index].address) { next_reloc_index++; } - process_instruction(context, func, stats, skipped_insns, instr_index + 1, instructions, output_file, true, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, static_funcs_out); + process_instruction(context, config, func, stats, skipped_insns, instr_index + 1, instructions, output_file, true, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, static_funcs_out); } fmt::print(output_file, " "); fmt::vprint(output_file, fmt_str, fmt::make_format_args(args...)); @@ -127,6 +123,76 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::F fmt::print(output_file, ";\n }}\n"); }; + auto print_func_call = [&](uint32_t target_func_vram) { + const auto matching_funcs_find = context.functions_by_vram.find(target_func_vram); + std::string jal_target_name; + uint32_t section_vram_start = section.ram_addr; + uint32_t section_vram_end = section.ram_addr + section.size; + // TODO the current section should be prioritized if the target jal is in its vram even if a function isn't known (i.e. static) + if (matching_funcs_find != context.functions_by_vram.end()) { + // If we found matches for the target function by vram, + const auto& matching_funcs_vec = matching_funcs_find->second; + size_t real_func_index; + bool ambiguous; + // If there is more than one corresponding function, look for any that have a nonzero size. + if (matching_funcs_vec.size() > 1) { + size_t nonzero_func_index = (size_t)-1; + bool found_nonzero_func = false; + for (size_t cur_func_index : matching_funcs_vec) { + const auto& cur_func = context.functions[cur_func_index]; + if (cur_func.words.size() != 0) { + if (found_nonzero_func) { + ambiguous = true; + break; + } + // If this section is relocatable and the target vram is in the section, don't call functions + // in any section other than this one. + if (cur_func.section_index == func.section_index || + !(section.relocatable && target_func_vram >= section_vram_start && target_func_vram < section_vram_end)) { + found_nonzero_func = true; + nonzero_func_index = cur_func_index; + } + } + } + if (nonzero_func_index == (size_t)-1) { + fmt::print(stderr, "[Warn] Potential jal resolution ambiguity\n"); + for (size_t cur_func_index : matching_funcs_vec) { + fmt::print(stderr, " {}\n", context.functions[cur_func_index].name); + } + nonzero_func_index = 0; + } + real_func_index = nonzero_func_index; + ambiguous = false; + } + else { + real_func_index = matching_funcs_vec.front(); + ambiguous = false; + } + if (ambiguous) { + fmt::print(stderr, "Ambiguous jal target: 0x{:08X}\n", target_func_vram); + for (size_t cur_func_index : matching_funcs_vec) { + const auto& cur_func = context.functions[cur_func_index]; + fmt::print(stderr, " {}\n", cur_func.name); + } + return false; + } + jal_target_name = context.functions[real_func_index].name; + } + else { + const auto& section = context.sections[func.section_index]; + if (target_func_vram >= section.ram_addr && target_func_vram < section.ram_addr + section.size) { + jal_target_name = fmt::format("static_{}_{:08X}", func.section_index, target_func_vram); + static_funcs_out[func.section_index].push_back(target_func_vram); + } + else { + fmt::print(stderr, "No function found for jal target: 0x{:08X}\n", target_func_vram); + return false; + } + } + needs_link_branch = true; + print_unconditional_branch("{}(rdram, ctx)", jal_target_name); + }; + if (indent) { print_indent(); } @@ -147,6 +213,8 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::F std::string unsigned_imm_string; std::string signed_imm_string; + + uint32_t func_vram_end = func.vram + func.words.size() * sizeof(func.words[0]); if (!at_reloc) { unsigned_imm_string = fmt::format("{:#X}", imm); @@ -154,12 +222,12 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::F } else { switch (reloc_type) { case RecompPort::RelocType::R_MIPS_HI16: - unsigned_imm_string = fmt::format("RELOC_HI16({}, {:#X})", reloc_section, reloc_target_section_offset); + unsigned_imm_string = fmt::format("RELOC_HI16({}, {:#X})", (uint32_t)func.section_index, reloc_target_section_offset); signed_imm_string = "(int16_t)" + unsigned_imm_string; reloc_handled = true; break; case RecompPort::RelocType::R_MIPS_LO16: - unsigned_imm_string = fmt::format("RELOC_LO16({}, {:#X})", reloc_section, reloc_target_section_offset); + unsigned_imm_string = fmt::format("RELOC_LO16({}, {:#X})", (uint32_t)func.section_index, reloc_target_section_offset); signed_imm_string = "(int16_t)" + unsigned_imm_string; reloc_handled = true; break; @@ -170,6 +238,33 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::F case InstrId::cpu_nop: fmt::print(output_file, "\n"); break; + // Cop0 (Limited functionality) + case InstrId::cpu_mfc0: + { + Cop0Reg reg = instr.Get_cop0d(); + switch (reg) { + case Cop0Reg::COP0_Status: + print_line("{}{} = cop0_status_read(ctx)", ctx_gpr_prefix(rt), rt); + break; + default: + fmt::print(stderr, "Unhandled cop0 register in mfc0: {}\n", (int)reg); + return false; + } + break; + } + case InstrId::cpu_mtc0: + { + Cop0Reg reg = instr.Get_cop0d(); + switch (reg) { + case Cop0Reg::COP0_Status: + print_line("cop0_status_write(ctx, {}{})", ctx_gpr_prefix(rt), rt); + break; + default: + fmt::print(stderr, "Unhandled cop0 register in mtc0: {}\n", (int)reg); + return false; + } + break; + } // Arithmetic case InstrId::cpu_lui: print_line("{}{} = S32({} << 16)", ctx_gpr_prefix(rt), rt, unsigned_imm_string); @@ -202,6 +297,10 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::F case InstrId::cpu_addiu: print_line("{}{} = ADD32({}{}, {})", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, signed_imm_string); break; + case InstrId::cpu_daddi: + case InstrId::cpu_daddiu: + print_line("{}{} = {}{} + {}", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, signed_imm_string); + break; case InstrId::cpu_and: print_line("{}{} = {}{} & {}{}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); break; @@ -226,21 +325,48 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::F case InstrId::cpu_sll: print_line("{}{} = S32({}{}) << {}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa); break; + case InstrId::cpu_dsll: + print_line("{}{} = {}{} << {}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa); + break; + case InstrId::cpu_dsll32: + print_line("{}{} = ((gpr)({}{})) << ({} + 32)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa); + break; case InstrId::cpu_sllv: print_line("{}{} = S32({}{}) << ({}{} & 31)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs); break; + case InstrId::cpu_dsllv: + print_line("{}{} = {}{} << ({}{} & 63)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs); + break; case InstrId::cpu_sra: - print_line("{}{} = S32(SIGNED({}{}) >> {})", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa); + print_line("{}{} = S32({}{}) >> {}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa); + break; + case InstrId::cpu_dsra: + print_line("{}{} = SIGNED({}{}) >> {}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa); + break; + case InstrId::cpu_dsra32: + print_line("{}{} = SIGNED({}{}) >> ({} + 32)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa); break; case InstrId::cpu_srav: - print_line("{}{} = S32(SIGNED({}{}) >> ({}{} & 31))", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs); + print_line("{}{} = S32({}{}) >> ({}{} & 31)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs); + break; + case InstrId::cpu_dsrav: + print_line("{}{} = SIGNED({}{}) >> ({}{} & 63)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs); break; case InstrId::cpu_srl: print_line("{}{} = S32(U32({}{}) >> {})", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa); break; + case InstrId::cpu_dsrl: + print_line("{}{} = {}{} >> {}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa); + break; + case InstrId::cpu_dsrl32: + print_line("{}{} = ((gpr)({}{})) >> ({} + 32)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa); + break; case InstrId::cpu_srlv: print_line("{}{} = S32(U32({}{}) >> ({}{} & 31))", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs); break; + case InstrId::cpu_dsrlv: + print_line("{}{} = {}{} >> ({}{} & 63))", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs); + break; case InstrId::cpu_slt: print_line("{}{} = SIGNED({}{}) < SIGNED({}{}) ? 1 : 0", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); break; @@ -273,7 +399,9 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::F print_line("{}{} = hi", ctx_gpr_prefix(rd), rd); break; // Loads - // TODO ld + case InstrId::cpu_ld: + print_line("{}{} = LD({}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base); + break; case InstrId::cpu_lw: print_line("{}{} = MEM_W({}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base); break; @@ -290,6 +418,9 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::F print_line("{}{} = MEM_BU({}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base); break; // Stores + case InstrId::cpu_sd: + print_line("SD({}{}, {}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base); + break; case InstrId::cpu_sw: print_line("MEM_W({}, {}{}) = {}{}", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt); break; @@ -342,67 +473,8 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::F // Branches case InstrId::cpu_jal: - { - uint32_t target_func_vram = instr.getBranchVramGeneric(); - const auto matching_funcs_find = context.functions_by_vram.find(target_func_vram); - std::string jal_target_name; - // TODO the current section should be prioritized if the target jal is in its vram even if a function isn't known (i.e. static) - if (matching_funcs_find != context.functions_by_vram.end()) { - // If we found matches for the target function by vram, - const auto& matching_funcs_vec = matching_funcs_find->second; - size_t real_func_index; - bool ambiguous; - // If there is more than one corresponding function, look for any that have a nonzero size - if (matching_funcs_vec.size() > 1) { - size_t nonzero_func_index = (size_t)-1; - bool found_nonzero_func = false; - for (size_t cur_func_index : matching_funcs_vec) { - const auto& cur_func = context.functions[cur_func_index]; - if (cur_func.words.size() != 0) { - if (found_nonzero_func) { - ambiguous = true; - break; - } - found_nonzero_func = true; - nonzero_func_index = cur_func_index; - } - } - if (nonzero_func_index == (size_t)-1) { - fmt::print(stderr, "[Warn] Potential jal resolution ambiguity\n"); - for (size_t cur_func_index : matching_funcs_vec) { - fmt::print(stderr, " {}\n", context.functions[cur_func_index].name); - } - nonzero_func_index = 0; - } - real_func_index = nonzero_func_index; - ambiguous = false; - } else { - real_func_index = matching_funcs_vec.front(); - ambiguous = false; - } - if (ambiguous) { - fmt::print(stderr, "Ambiguous jal target: 0x{:08X}\n", target_func_vram); - for (size_t cur_func_index : matching_funcs_vec) { - const auto& cur_func = context.functions[cur_func_index]; - fmt::print(stderr, " {}\n", cur_func.name); - } - return false; - } - jal_target_name = context.functions[real_func_index].name; - } else { - const auto& section = context.sections[func.section_index]; - if (target_func_vram >= section.ram_addr && target_func_vram < section.ram_addr + section.size) { - jal_target_name = fmt::format("static_{}_{:08X}", func.section_index, target_func_vram); - static_funcs_out[func.section_index].push_back(target_func_vram); - } else { - fmt::print(stderr, "No function found for jal target: 0x{:08X}\n", target_func_vram); - return false; - } - } - needs_link_branch = true; - print_unconditional_branch("{}(rdram, ctx)", jal_target_name); - break; - } + print_func_call(instr.getBranchVramGeneric()); + break; case InstrId::cpu_jalr: // jalr can only be handled with $ra as the return address register if (rd != (int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_ra) { @@ -419,7 +491,19 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::F if (branch_target == instr_vram) { print_line("void pause_self(uint8_t *rdram); pause_self(rdram)"); } - print_unconditional_branch("goto L_{:08X}", branch_target); + // Check if the branch is within this function + else if (branch_target >= func.vram && branch_target < func_vram_end) { + print_unconditional_branch("goto L_{:08X}", branch_target); + } + // Otherwise, check if it's a tail call + else if (instr_vram == func_vram_end - 2 * sizeof(func.words[0])) { + fmt::print("Tail call in {}\n", func.name); + print_func_call(branch_target); + } + else { + fmt::print(stderr, "Unhandled branch in {} at 0x{:08X} to 0x{:08X}\n", func.name, instr_vram, branch_target); + return false; + } } break; case InstrId::cpu_jr: @@ -439,7 +523,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::F if (reloc_index + 1 < section.relocs.size() && next_vram > section.relocs[reloc_index].address) { next_reloc_index++; } - process_instruction(context, func, stats, skipped_insns, instr_index + 1, instructions, output_file, false, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, static_funcs_out); + process_instruction(context, config, func, stats, skipped_insns, instr_index + 1, instructions, output_file, false, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, static_funcs_out); print_indent(); fmt::print(output_file, "switch (jr_addend_{:08X} >> 2) {{\n", cur_jtbl.jr_vram); for (size_t entry_index = 0; entry_index < cur_jtbl.entries.size(); entry_index++) { @@ -459,17 +543,28 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::F }); if (jump_find_result != stats.absolute_jumps.end()) { - needs_link_branch = true; print_unconditional_branch("LOOKUP_FUNC({})(rdram, ctx)", (uint64_t)(int32_t)jump_find_result->jump_target); // jr doesn't link so it acts like a tail call, meaning we should return directly after the jump returns print_line("return"); break; } + bool is_tail_call = instr_vram == func_vram_end - 2 * sizeof(func.words[0]); + if (is_tail_call) { + fmt::print("Indirect tail call in {}\n", func.name); + print_unconditional_branch("LOOKUP_FUNC({}{})(rdram, ctx)", ctx_gpr_prefix(rs), rs); + print_line("return"); + break; + } - fmt::print(stderr, "No jump table found for jr at 0x{:08X}\n", instr_vram); + fmt::print(stderr, "No jump table found for jr at 0x{:08X} and not tail call\n", instr_vram); } break; + case InstrId::cpu_syscall: + print_line("recomp_syscall_handler(rdram, ctx, 0x{:08X})", instr_vram); + // syscalls don't link, so treat it like a tail call + print_line("return"); + break; case InstrId::cpu_bnel: is_branch_likely = true; [[fallthrough]]; @@ -530,7 +625,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::F } else { // odd fpr - print_line("ctx->f{}.u32h = {}{}", fs - 1, ctx_gpr_prefix(rt), rt); + print_line("ctx->f_odd[({} - 1) * 2] = {}{}", fs, ctx_gpr_prefix(rt), rt); } break; case InstrId::cpu_mfc1: @@ -539,7 +634,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::F print_line("{}{} = (int32_t)ctx->f{}.u32l", ctx_gpr_prefix(rt), rt, fs); } else { // odd fpr - print_line("{}{} = (int32_t)ctx->f{}.u32h", ctx_gpr_prefix(rt), rt, fs - 1); + print_line("{}{} = (int32_t)ctx->f_odd[({} - 1) * 2]", ctx_gpr_prefix(rt), rt, fs); } break; //case InstrId::cpu_dmfc1: @@ -557,16 +652,12 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::F print_line("ctx->f{}.u32l = MEM_W({}, {}{})", ft, signed_imm_string, ctx_gpr_prefix(base), base); } else { // odd fpr - print_line("ctx->f{}.u32h = MEM_W({}, {}{})", ft - 1, signed_imm_string, ctx_gpr_prefix(base), base); + print_line("ctx->f_odd[({} - 1) * 2] = MEM_W({}, {}{})", ft, signed_imm_string, ctx_gpr_prefix(base), base); } break; case InstrId::cpu_ldc1: - if ((ft & 1) == 0) { - print_line("ctx->f{}.u64 = LD({}, {}{})", ft, signed_imm_string, ctx_gpr_prefix(base), base); - } else { - fmt::print(stderr, "Invalid operand for ldc1: f{}\n", ft); - return false; - } + print_line("CHECK_FR(ctx, {})", ft); + print_line("ctx->f{}.u64 = LD({}, {}{})", ft, signed_imm_string, ctx_gpr_prefix(base), base); break; case InstrId::cpu_swc1: if ((ft & 1) == 0) { @@ -574,66 +665,56 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::F print_line("MEM_W({}, {}{}) = ctx->f{}.u32l", signed_imm_string, ctx_gpr_prefix(base), base, ft); } else { // odd fpr - print_line("MEM_W({}, {}{}) = ctx->f{}.u32h", signed_imm_string, ctx_gpr_prefix(base), base, ft - 1); + print_line("MEM_W({}, {}{}) = ctx->f_odd[({} - 1) * 2]", signed_imm_string, ctx_gpr_prefix(base), base, ft); } break; case InstrId::cpu_sdc1: - if ((ft & 1) == 0) { - print_line("SD(ctx->f{}.u64, {}, {}{})", ft, signed_imm_string, ctx_gpr_prefix(base), base); - } else { - fmt::print(stderr, "Invalid operand for sdc1: f{}\n", ft); - return false; - } + print_line("CHECK_FR(ctx, {})", ft); + print_line("SD(ctx->f{}.u64, {}, {}{})", ft, signed_imm_string, ctx_gpr_prefix(base), base); break; // Cop1 compares case InstrId::cpu_c_lt_s: - if ((fs & 1) == 0 && (ft & 1) == 0) { - print_line("c1cs = ctx->f{}.fl < ctx->f{}.fl", fs, ft); - } else { - fmt::print(stderr, "Invalid operand for c.lt.s: f{} f{}\n", fs, ft); - return false; - } + print_line("CHECK_FR(ctx, {})", fs); + print_line("CHECK_FR(ctx, {})", ft); + print_line("c1cs = ctx->f{}.fl < ctx->f{}.fl", fs, ft); + break; + case InstrId::cpu_c_olt_s: + print_line("CHECK_FR(ctx, {})", fs); + print_line("CHECK_FR(ctx, {})", ft); + //print_line("*(volatile int*)0 = 0;"); + print_line("c1cs = ctx->f{}.fl < ctx->f{}.fl", fs, ft); break; case InstrId::cpu_c_lt_d: - if ((fs & 1) == 0 && (ft & 1) == 0) { - print_line("c1cs = ctx->f{}.d < ctx->f{}.d", fs, ft); - } else { - fmt::print(stderr, "Invalid operand for c.lt.d: f{} f{}\n", fs, ft); - return false; - } + print_line("CHECK_FR(ctx, {})", fs); + print_line("CHECK_FR(ctx, {})", ft); + print_line("c1cs = ctx->f{}.d < ctx->f{}.d", fs, ft); break; case InstrId::cpu_c_le_s: - if ((fs & 1) == 0 && (ft & 1) == 0) { - print_line("c1cs = ctx->f{}.fl <= ctx->f{}.fl", fs, ft); - } else { - fmt::print(stderr, "Invalid operand for c.le.s: f{} f{}\n", fs, ft); - return false; - } + print_line("CHECK_FR(ctx, {})", fs); + print_line("CHECK_FR(ctx, {})", ft); + print_line("c1cs = ctx->f{}.fl <= ctx->f{}.fl", fs, ft); + break; + case InstrId::cpu_c_ole_s: + print_line("CHECK_FR(ctx, {})", fs); + print_line("CHECK_FR(ctx, {})", ft); + //print_line("*(volatile int*)0 = 0;"); + print_line("c1cs = ctx->f{}.fl <= ctx->f{}.fl", fs, ft); break; case InstrId::cpu_c_le_d: - if ((fs & 1) == 0 && (ft & 1) == 0) { - print_line("c1cs = ctx->f{}.d <= ctx->f{}.d", fs, ft); - } else { - fmt::print(stderr, "Invalid operand for c.le.d: f{} f{}\n", fs, ft); - return false; - } + print_line("CHECK_FR(ctx, {})", fs); + print_line("CHECK_FR(ctx, {})", ft); + print_line("c1cs = ctx->f{}.d <= ctx->f{}.d", fs, ft); break; case InstrId::cpu_c_eq_s: - if ((fs & 1) == 0 && (ft & 1) == 0) { - print_line("c1cs = ctx->f{}.fl == ctx->f{}.fl", fs, ft); - } else { - fmt::print(stderr, "Invalid operand for c.eq.s: f{} f{}\n", fs, ft); - return false; - } + print_line("CHECK_FR(ctx, {})", fs); + print_line("CHECK_FR(ctx, {})", ft); + print_line("c1cs = ctx->f{}.fl == ctx->f{}.fl", fs, ft); break; case InstrId::cpu_c_eq_d: - if ((fs & 1) == 0 && (ft & 1) == 0) { - print_line("c1cs = ctx->f{}.d == ctx->f{}.d", fs, ft); - } else { - fmt::print(stderr, "Invalid operand for c.eq.d: f{} f{}\n", fs, ft); - return false; - } + print_line("CHECK_FR(ctx, {})", fs); + print_line("CHECK_FR(ctx, {})", ft); + print_line("c1cs = ctx->f{}.d == ctx->f{}.d", fs, ft); break; // Cop1 branches @@ -656,236 +737,148 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::F // Cop1 arithmetic case InstrId::cpu_mov_s: - if ((fd & 1) == 0 && (fs & 1) == 0) { - // even fpr - print_line("ctx->f{}.fl = ctx->f{}.fl", fd, fs); - } else { - fmt::print(stderr, "Invalid operand(s) for mov.s: f{} f{}\n", fd, fs); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("ctx->f{}.fl = ctx->f{}.fl", fd, fs); break; case InstrId::cpu_mov_d: - if ((fd & 1) == 0 && (fs & 1) == 0) { - // even fpr - print_line("ctx->f{}.d = ctx->f{}.d", fd, fs); - } else { - fmt::print(stderr, "Invalid operand(s) for mov.d: f{} f{}\n", fd, fs); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("ctx->f{}.d = ctx->f{}.d", fd, fs); break; case InstrId::cpu_neg_s: - if ((fd & 1) == 0 && (fs & 1) == 0) { - // even fpr - print_line("NAN_CHECK(ctx->f{}.fl)", fs); - print_line("ctx->f{}.fl = -ctx->f{}.fl", fd, fs); - } else { - fmt::print(stderr, "Invalid operand(s) for neg.s: f{} f{}\n", fd, fs); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("NAN_CHECK(ctx->f{}.fl)", fs); + print_line("ctx->f{}.fl = -ctx->f{}.fl", fd, fs); break; case InstrId::cpu_neg_d: - if ((fd & 1) == 0 && (fs & 1) == 0) { - // even fpr - print_line("NAN_CHECK(ctx->f{}.d)", fs); - print_line("ctx->f{}.d = -ctx->f{}.d", fd, fs); - } else { - fmt::print(stderr, "Invalid operand(s) for neg.d: f{} f{}\n", fd, fs); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("NAN_CHECK(ctx->f{}.d)", fs); + print_line("ctx->f{}.d = -ctx->f{}.d", fd, fs); break; case InstrId::cpu_abs_s: - if ((fd & 1) == 0 && (fs & 1) == 0) { - // even fpr - print_line("NAN_CHECK(ctx->f{}.fl)", fs); - print_line("ctx->f{}.fl = fabsf(ctx->f{}.fl)", fd, fs); - } else { - fmt::print(stderr, "Invalid operand(s) for abs.s: f{} f{}\n", fd, fs); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("NAN_CHECK(ctx->f{}.fl)", fs); + print_line("ctx->f{}.fl = fabsf(ctx->f{}.fl)", fd, fs); break; case InstrId::cpu_abs_d: - if ((fd & 1) == 0 && (fs & 1) == 0) { - // even fpr - print_line("NAN_CHECK(ctx->f{}.d)", fs); - print_line("ctx->f{}.d = fabs(ctx->f{}.d)", fd, fs); - } else { - fmt::print(stderr, "Invalid operand(s) for abs.d: f{} f{}\n", fd, fs); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("NAN_CHECK(ctx->f{}.d)", fs); + print_line("ctx->f{}.d = fabs(ctx->f{}.d)", fd, fs); break; case InstrId::cpu_sqrt_s: - if ((fd & 1) == 0 && (fs & 1) == 0) { - // even fpr - print_line("NAN_CHECK(ctx->f{}.fl)", fs); - print_line("ctx->f{}.fl = sqrtf(ctx->f{}.fl)", fd, fs); - } else { - fmt::print(stderr, "Invalid operand(s) for sqrt.s: f{} f{}\n", fd, fs); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("NAN_CHECK(ctx->f{}.fl)", fs); + print_line("ctx->f{}.fl = sqrtf(ctx->f{}.fl)", fd, fs); break; case InstrId::cpu_sqrt_d: - if ((fd & 1) == 0 && (fs & 1) == 0) { - // even fpr - print_line("NAN_CHECK(ctx->f{}.d)", fs); - print_line("ctx->f{}.d = sqrt(ctx->f{}.d)", fd, fs); - } else { - fmt::print(stderr, "Invalid operand(s) for sqrt.d: f{} f{}\n", fd, fs); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("NAN_CHECK(ctx->f{}.d)", fs); + print_line("ctx->f{}.d = sqrt(ctx->f{}.d)", fd, fs); break; case InstrId::cpu_add_s: - if ((fd & 1) == 0 && (fs & 1) == 0 && (ft & 1) == 0) { - // even fpr - print_line("NAN_CHECK(ctx->f{}.fl); NAN_CHECK(ctx->f{}.fl)", fs, ft); - print_line("ctx->f{}.fl = ctx->f{}.fl + ctx->f{}.fl", fd, fs, ft); - } else { - fmt::print(stderr, "Invalid operand(s) for add.s: f{} f{} f{}\n", fd, fs, ft); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("CHECK_FR(ctx, {})", ft); + print_line("NAN_CHECK(ctx->f{}.fl); NAN_CHECK(ctx->f{}.fl)", fs, ft); + print_line("ctx->f{}.fl = ctx->f{}.fl + ctx->f{}.fl", fd, fs, ft); break; case InstrId::cpu_add_d: - if ((fd & 1) == 0 && (fs & 1) == 0 && (ft & 1) == 0) { - // even fpr - print_line("NAN_CHECK(ctx->f{}.d); NAN_CHECK(ctx->f{}.d)", fs, ft); - print_line("ctx->f{}.d = ctx->f{}.d + ctx->f{}.d", fd, fs, ft); - } else { - fmt::print(stderr, "Invalid operand(s) for add.d: f{} f{} f{}\n", fd, fs, ft); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("CHECK_FR(ctx, {})", ft); + print_line("NAN_CHECK(ctx->f{}.d); NAN_CHECK(ctx->f{}.d)", fs, ft); + print_line("ctx->f{}.d = ctx->f{}.d + ctx->f{}.d", fd, fs, ft); break; case InstrId::cpu_sub_s: - if ((fd & 1) == 0 && (fs & 1) == 0 && (ft & 1) == 0) { - // even fpr - print_line("NAN_CHECK(ctx->f{}.fl); NAN_CHECK(ctx->f{}.fl)", fs, ft); - print_line("ctx->f{}.fl = ctx->f{}.fl - ctx->f{}.fl", fd, fs, ft); - } else { - fmt::print(stderr, "Invalid operand(s) for sub.s: f{} f{} f{}\n", fd, fs, ft); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("CHECK_FR(ctx, {})", ft); + print_line("NAN_CHECK(ctx->f{}.fl); NAN_CHECK(ctx->f{}.fl)", fs, ft); + print_line("ctx->f{}.fl = ctx->f{}.fl - ctx->f{}.fl", fd, fs, ft); break; case InstrId::cpu_sub_d: - if ((fd & 1) == 0 && (fs & 1) == 0 && (ft & 1) == 0) { - // even fpr - print_line("NAN_CHECK(ctx->f{}.d); NAN_CHECK(ctx->f{}.d)", fs, ft); - print_line("ctx->f{}.d = ctx->f{}.d - ctx->f{}.d", fd, fs, ft); - } else { - fmt::print(stderr, "Invalid operand(s) for sub.d: f{} f{} f{}\n", fd, fs, ft); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("CHECK_FR(ctx, {})", ft); + print_line("NAN_CHECK(ctx->f{}.d); NAN_CHECK(ctx->f{}.d)", fs, ft); + print_line("ctx->f{}.d = ctx->f{}.d - ctx->f{}.d", fd, fs, ft); break; case InstrId::cpu_mul_s: - if ((fd & 1) == 0 && (fs & 1) == 0 && (ft & 1) == 0) { - // even fpr - print_line("NAN_CHECK(ctx->f{}.fl); NAN_CHECK(ctx->f{}.fl)", fs, ft); - print_line("ctx->f{}.fl = MUL_S(ctx->f{}.fl, ctx->f{}.fl)", fd, fs, ft); - } else { - fmt::print(stderr, "Invalid operand(s) for mul.s: f{} f{} f{}\n", fd, fs, ft); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("CHECK_FR(ctx, {})", ft); + print_line("NAN_CHECK(ctx->f{}.fl); NAN_CHECK(ctx->f{}.fl)", fs, ft); + print_line("ctx->f{}.fl = MUL_S(ctx->f{}.fl, ctx->f{}.fl)", fd, fs, ft); break; case InstrId::cpu_mul_d: - if ((fd & 1) == 0 && (fs & 1) == 0 && (ft & 1) == 0) { - // even fpr - print_line("NAN_CHECK(ctx->f{}.d); NAN_CHECK(ctx->f{}.d)", fs, ft); - print_line("ctx->f{}.d = MUL_D(ctx->f{}.d, ctx->f{}.d)", fd, fs, ft); - } else { - fmt::print(stderr, "Invalid operand(s) for mul.d: f{} f{} f{}\n", fd, fs, ft); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("CHECK_FR(ctx, {})", ft); + print_line("NAN_CHECK(ctx->f{}.d); NAN_CHECK(ctx->f{}.d)", fs, ft); + print_line("ctx->f{}.d = MUL_D(ctx->f{}.d, ctx->f{}.d)", fd, fs, ft); break; case InstrId::cpu_div_s: - if ((fd & 1) == 0 && (fs & 1) == 0 && (ft & 1) == 0) { - // even fpr - print_line("NAN_CHECK(ctx->f{}.fl); NAN_CHECK(ctx->f{}.fl)", fs, ft); - print_line("ctx->f{}.fl = DIV_S(ctx->f{}.fl, ctx->f{}.fl)", fd, fs, ft); - } else { - fmt::print(stderr, "Invalid operand(s) for div.s: f{} f{} f{}\n", fd, fs, ft); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("CHECK_FR(ctx, {})", ft); + print_line("NAN_CHECK(ctx->f{}.fl); NAN_CHECK(ctx->f{}.fl)", fs, ft); + print_line("ctx->f{}.fl = DIV_S(ctx->f{}.fl, ctx->f{}.fl)", fd, fs, ft); break; case InstrId::cpu_div_d: - if ((fd & 1) == 0 && (fs & 1) == 0 && (ft & 1) == 0) { - // even fpr - print_line("NAN_CHECK(ctx->f{}.d); NAN_CHECK(ctx->f{}.d)", fs, ft); - print_line("ctx->f{}.d = DIV_D(ctx->f{}.d, ctx->f{}.d)", fd, fs, ft); - } else { - fmt::print(stderr, "Invalid operand(s) for div.d: f{} f{} f{}\n", fd, fs, ft); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("CHECK_FR(ctx, {})", ft); + print_line("NAN_CHECK(ctx->f{}.d); NAN_CHECK(ctx->f{}.d)", fs, ft); + print_line("ctx->f{}.d = DIV_D(ctx->f{}.d, ctx->f{}.d)", fd, fs, ft); break; case InstrId::cpu_cvt_s_w: - if ((fd & 1) == 0 && (fs & 1) == 0) { - // even fpr - print_line("ctx->f{}.fl = CVT_S_W(ctx->f{}.u32l)", fd, fs); - } else { - fmt::print(stderr, "Invalid operand(s) for cvt.s.w: f{} f{}\n", fd, fs); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("ctx->f{}.fl = CVT_S_W(ctx->f{}.u32l)", fd, fs); break; case InstrId::cpu_cvt_d_w: - if ((fd & 1) == 0 && (fs & 1) == 0) { - // even fpr - print_line("ctx->f{}.d = CVT_D_W(ctx->f{}.u32l)", fd, fs); - } else { - fmt::print(stderr, "Invalid operand(s) for cvt.d.w: f{} f{}\n", fd, fs); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("ctx->f{}.d = CVT_D_W(ctx->f{}.u32l)", fd, fs); break; case InstrId::cpu_cvt_d_s: - if ((fd & 1) == 0 && (fs & 1) == 0) { - // even fpr - print_line("NAN_CHECK(ctx->f{}.fl)", fs); - print_line("ctx->f{}.d = CVT_D_S(ctx->f{}.fl)", fd, fs); - } else { - fmt::print(stderr, "Invalid operand(s) for cvt.d.s: f{} f{}\n", fd, fs); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("NAN_CHECK(ctx->f{}.fl)", fs); + print_line("ctx->f{}.d = CVT_D_S(ctx->f{}.fl)", fd, fs); break; case InstrId::cpu_cvt_s_d: - if ((fd & 1) == 0 && (fs & 1) == 0) { - // even fpr - print_line("NAN_CHECK(ctx->f{}.d)", fs); - print_line("ctx->f{}.fl = CVT_S_D(ctx->f{}.d)", fd, fs); - } else { - fmt::print(stderr, "Invalid operand(s) for cvt.s.d: f{} f{}\n", fd, fs); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("NAN_CHECK(ctx->f{}.d)", fs); + print_line("ctx->f{}.fl = CVT_S_D(ctx->f{}.d)", fd, fs); break; case InstrId::cpu_trunc_w_s: - if ((fd & 1) == 0 && (fs & 1) == 0) { - // even fpr - print_line("ctx->f{}.u32l = TRUNC_W_S(ctx->f{}.fl)", fd, fs); - } else { - fmt::print(stderr, "Invalid operand(s) for trunc.w.s: f{} f{}\n", fd, fs); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("ctx->f{}.u32l = TRUNC_W_S(ctx->f{}.fl)", fd, fs); break; case InstrId::cpu_trunc_w_d: - if ((fd & 1) == 0 && (fs & 1) == 0) { - // even fpr - print_line("ctx->f{}.u32l = TRUNC_W_D(ctx->f{}.d)", fd, fs); - } else { - fmt::print(stderr, "Invalid operand(s) for trunc.w.d: f{} f{}\n", fd, fs); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("ctx->f{}.u32l = TRUNC_W_D(ctx->f{}.d)", fd, fs); break; //case InstrId::cpu_trunc_l_s: - // if ((fd & 1) == 0 && (fs & 1) == 0) { - // // even fpr - // print_line("ctx->f{}.u64 = TRUNC_L_S(ctx->f{}.fl)", fd, fs); - // } else { - // fmt::print(stderr, "Invalid operand(s) for trunc.l.s: f{} f{}\n", fd, fs); - // return false; - // } + // print_line("CHECK_FR(ctx, {})", fd); + // print_line("CHECK_FR(ctx, {})", fs); + // print_line("ctx->f{}.u64 = TRUNC_L_S(ctx->f{}.fl)", fd, fs); // break; //case InstrId::cpu_trunc_l_d: - // if ((fd & 1) == 0 && (fs & 1) == 0) { - // // even fpr - // print_line("ctx->f{}.u64 = TRUNC_L_D(ctx->f{}.d)", fd, fs); - // } else { - // fmt::print(stderr, "Invalid operand(s) for trunc.l.d: f{} f{}\n", fd, fs); - // return false; - // } + // print_line("CHECK_FR(ctx, {})", fd); + // print_line("CHECK_FR(ctx, {})", fs); + // print_line("ctx->f{}.u64 = TRUNC_L_D(ctx->f{}.d)", fd, fs); // break; case InstrId::cpu_ctc1: if (cop1_cs != 31) { @@ -902,76 +895,44 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::F print_line("{}{} = rounding_mode", ctx_gpr_prefix(rt), rt); break; case InstrId::cpu_cvt_w_s: - if ((fd & 1) == 0 && (fs & 1) == 0) { - // even fpr - print_line("ctx->f{}.u32l = CVT_W_S(ctx->f{}.fl)", fd, fs); - } else { - fmt::print(stderr, "Invalid operand(s) for cvt.w.s: f{} f{}\n", fd, fs); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("ctx->f{}.u32l = CVT_W_S(ctx->f{}.fl)", fd, fs); break; case InstrId::cpu_cvt_w_d: - if ((fd & 1) == 0 && (fs & 1) == 0) { - // even fpr - print_line("ctx->f{}.u32l = CVT_W_D(ctx->f{}.d)", fd, fs); - } else { - fmt::print(stderr, "Invalid operand(s) for cvt.w.d: f{} f{}\n", fd, fs); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("ctx->f{}.u32l = CVT_W_D(ctx->f{}.d)", fd, fs); break; case InstrId::cpu_round_w_s: - if ((fd & 1) == 0 && (fs & 1) == 0) { - // even fpr - print_line("ctx->f{}.u32l = lroundf(ctx->f{}.fl)", fd, fs); - } else { - fmt::print(stderr, "Invalid operand(s) for round.w.s: f{} f{}\n", fd, fs); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("ctx->f{}.u32l = lroundf(ctx->f{}.fl)", fd, fs); break; case InstrId::cpu_round_w_d: - if ((fd & 1) == 0 && (fs & 1) == 0) { - // even fpr - print_line("ctx->f{}.u32l = lround(ctx->f{}.d)", fd, fs); - } else { - fmt::print(stderr, "Invalid operand(s) for round.w.d: f{} f{}\n", fd, fs); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("ctx->f{}.u32l = lround(ctx->f{}.d)", fd, fs); break; case InstrId::cpu_ceil_w_s: - if ((fd & 1) == 0 && (fs & 1) == 0) { - // even fpr - print_line("ctx->f{}.u32l = S32(ceilf(ctx->f{}.fl))", fd, fs); - } else { - fmt::print(stderr, "Invalid operand(s) for ceil.w.s: f{} f{}\n", fd, fs); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("ctx->f{}.u32l = S32(ceilf(ctx->f{}.fl))", fd, fs); break; case InstrId::cpu_ceil_w_d: - if ((fd & 1) == 0 && (fs & 1) == 0) { - // even fpr - print_line("ctx->f{}.u32l = S32(ceil(ctx->f{}.d))", fd, fs); - } else { - fmt::print(stderr, "Invalid operand(s) for ceil.w.d: f{} f{}\n", fd, fs); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("ctx->f{}.u32l = S32(ceil(ctx->f{}.d))", fd, fs); break; case InstrId::cpu_floor_w_s: - if ((fd & 1) == 0 && (fs & 1) == 0) { - // even fpr - print_line("ctx->f{}.u32l = S32(floorf(ctx->f{}.fl))", fd, fs); - } else { - fmt::print(stderr, "Invalid operand(s) for floor.w.s: f{} f{}\n", fd, fs); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("ctx->f{}.u32l = S32(floorf(ctx->f{}.fl))", fd, fs); break; case InstrId::cpu_floor_w_d: - if ((fd & 1) == 0 && (fs & 1) == 0) { - // even fpr - print_line("ctx->f{}.u32l = S32(floor(ctx->f{}.d))", fd, fs); - } else { - fmt::print(stderr, "Invalid operand(s) for floor.w.d: f{} f{}\n", fd, fs); - return false; - } + print_line("CHECK_FR(ctx, {})", fd); + print_line("CHECK_FR(ctx, {})", fs); + print_line("ctx->f{}.u32l = S32(floor(ctx->f{}.d))", fd, fs); break; default: fmt::print(stderr, "Unhandled instruction: {}\n", instr.getOpcodeName()); @@ -986,16 +947,43 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::F return true; } -bool RecompPort::recompile_function(const RecompPort::Context& context, const RecompPort::Function& func, const std::filesystem::path& output_path, std::span> static_funcs_out) { +bool compare_files(const std::filesystem::path& file1_path, const std::filesystem::path& file2_path) { + static std::vector file1_buf(65536); + static std::vector file2_buf(65536); + + std::ifstream file1(file1_path, std::ifstream::ate | std::ifstream::binary); //open file at the end + std::ifstream file2(file2_path, std::ifstream::ate | std::ifstream::binary); //open file at the end + const std::ifstream::pos_type fileSize = file1.tellg(); + + file1.rdbuf()->pubsetbuf(file1_buf.data(), file1_buf.size()); + file2.rdbuf()->pubsetbuf(file2_buf.data(), file2_buf.size()); + + if (fileSize != file2.tellg()) { + return false; //different file size + } + + file1.seekg(0); //rewind + file2.seekg(0); //rewind + + std::istreambuf_iterator begin1(file1); + std::istreambuf_iterator begin2(file2); + + return std::equal(begin1, std::istreambuf_iterator(), begin2); //Second argument is end-of-range iterator +} + +bool RecompPort::recompile_function(const RecompPort::Context& context, const RecompPort::Config& config, const RecompPort::Function& func, const std::filesystem::path& output_path, std::span> static_funcs_out) { //fmt::print("Recompiling {}\n", func.name); std::vector instructions; // Open the output file and write the file header - std::ofstream output_file{ output_path }; + std::filesystem::path temp_path = output_path; + temp_path.replace_extension(".tmp"); + std::ofstream output_file{ temp_path }; if (!output_file.good()) { - fmt::print(stderr, "Failed to open file for writing: {}\n", output_path.string() ); + fmt::print(stderr, "Failed to open file for writing: {}\n", temp_path.string() ); return false; } + fmt::print(output_file, "#include \"recomp.h\"\n" "#include \"disable_warnings.h\"\n" @@ -1053,7 +1041,7 @@ bool RecompPort::recompile_function(const RecompPort::Context& context, const Re bool needs_link_branch = false; bool in_likely_delay_slot = false; const auto& section = context.sections[func.section_index]; - bool needs_reloc = section.relocatable; + bool needs_reloc = section.relocatable && section.relocs.size() > 0; size_t reloc_index = 0; for (size_t instr_index = 0; instr_index < instructions.size(); ++instr_index) { bool had_link_branch = needs_link_branch; @@ -1075,8 +1063,13 @@ bool RecompPort::recompile_function(const RecompPort::Context& context, const Re } } + + if (section.name == ".anseq") { + std::this_thread::yield(); + } + // Process the current instruction and check for errors - if (process_instruction(context, func, stats, skipped_insns, instr_index, instructions, output_file, false, needs_link_branch, num_link_branches, reloc_index, needs_link_branch, is_branch_likely, static_funcs_out) == false) { + if (process_instruction(context, config, func, stats, skipped_insns, instr_index, instructions, output_file, false, needs_link_branch, num_link_branches, reloc_index, needs_link_branch, is_branch_likely, static_funcs_out) == false) { fmt::print(stderr, "Error in recompilation, clearing {}\n", output_path.string()); output_file.clear(); return false; @@ -1100,5 +1093,17 @@ bool RecompPort::recompile_function(const RecompPort::Context& context, const Re // Terminate the function fmt::print(output_file, ";}}\n"); + output_file.close(); + + // If a file of the target name exists and it's identical to the output file, delete the output file. + // This prevents updating the existing file so that it doesn't need to be rebuilt. + if (std::filesystem::exists(output_path) && compare_files(output_path, temp_path)) { + std::filesystem::remove(temp_path); + } + // Otherwise, rename the new file to the target path. + else { + std::filesystem::rename(temp_path, output_path); + } + return true; }