diff --git a/include/recomp_port.h b/include/recomp_port.h index 6e4d856..834115d 100644 --- a/include/recomp_port.h +++ b/include/recomp_port.h @@ -4,6 +4,8 @@ #include #include #include +#include +#include #ifdef _MSC_VER inline uint32_t byteswap(uint32_t val) { @@ -24,8 +26,12 @@ namespace RecompPort { std::string name; }; + struct Context { + std::vector functions; + std::unordered_map> functions_by_vram; + }; - bool recompile_function(const Function& func, std::string_view output_path); + bool recompile_function(const Context& context, const Function& func, std::string_view output_path); } #endif diff --git a/recomp.h b/recomp.h index 675f8ef..ef01f81 100644 --- a/recomp.h +++ b/recomp.h @@ -3,11 +3,23 @@ #include +#if 0 // treat GPRs as 32-bit, should be better codegen +typedef uint32_t gpr; + +#define SIGNED(val) \ + ((int32_t)(val)) +#else +typedef uint64_t gpr; + +#define SIGNED(val) \ + ((int64_t)(val)) +#endif + #define ADD32(a, b) \ - ((uint64_t)(int32_t)((a) + (b))) + ((gpr)(int32_t)((a) + (b))) #define SUB32(a, b) \ - ((uint64_t)(int32_t)((a) - (b))) + ((gpr)(int32_t)((a) - (b))) #define MEM_D(offset, reg) \ (*(int64_t*)((rdram) + (((reg) + (offset)) ^ 3))) @@ -36,6 +48,9 @@ #define S64(val) \ ((int64_t)(val)) +#define U64(val) \ + ((uint64_t)(val)) + #define MUL_S(val1, val2) \ ((val1) * (val2)) @@ -66,8 +81,6 @@ #define TRUNC_W_D(val) \ ((int32_t)(val)) -typedef uint64_t gpr; - typedef union { double d; struct { diff --git a/src/main.cpp b/src/main.cpp index b8326f7..7e2eda9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -231,6 +231,9 @@ int main(int argc, char** argv) { } ELFIO::elfio elf_file; + RabbitizerConfig_Cfg.pseudos.pseudoMove = false; + RabbitizerConfig_Cfg.pseudos.pseudoBeqz = false; + RabbitizerConfig_Cfg.pseudos.pseudoBnez = false; auto exit_failure = [] (const std::string& error_str) { fmt::print(stderr, error_str); @@ -282,8 +285,8 @@ int main(int argc, char** argv) { fmt::print("Num symbols: {}\n", symbols.get_symbols_num()); - std::vector functions{}; - functions.reserve(1024); + RecompPort::Context context{}; + context.functions.reserve(1024); for (int sym_index = 0; sym_index < symbols.get_symbols_num(); sym_index++) { std::string name; @@ -298,33 +301,36 @@ int main(int argc, char** argv) { symbols.get_symbol(sym_index, name, value, size, bind, type, section_index, other); - // Check if this symbol is a function - if (type == ELFIO::STT_FUNC) { + // Check if this symbol is a function or has no type (like a regular glabel would) + // Symbols with no type have a dummy entry created so that their symbol can be looked up for function calls + if (type == ELFIO::STT_FUNC || (type == ELFIO::STT_NOTYPE && section_index < section_rom_addrs.size())) { auto section_rom_addr = section_rom_addrs[section_index]; auto section_offset = value - elf_file.sections[section_index]->get_address(); const uint32_t* words = reinterpret_cast(elf_file.sections[section_index]->get_data() + section_offset); - functions.emplace_back( - static_cast(value), + uint32_t vram = static_cast(value); + uint32_t num_instructions = type == ELFIO::STT_FUNC ? size / 4 : 0; + context.functions_by_vram[vram].push_back(context.functions.size()); + context.functions.emplace_back( + vram, static_cast(section_offset + section_rom_addr), - std::span{ reinterpret_cast(words), size / 4 }, + std::span{ words, num_instructions }, std::move(name) ); } } - fmt::print("Function count: {}\n", functions.size()); + fmt::print("Function count: {}\n", context.functions.size()); //#pragma omp parallel for - for (size_t i = 0; i < functions.size(); i++) { - const auto& func = functions[i]; - if (!ignored_funcs.contains(func.name)) { - if (RecompPort::recompile_function(func, "out/" + func.name + ".c") == false) { + for (size_t i = 0; i < context.functions.size(); i++) { + const auto& func = context.functions[i]; + if (!ignored_funcs.contains(func.name) && func.words.size() != 0) { + if (RecompPort::recompile_function(context, func, "out/" + func.name + ".c") == false) { fmt::print(stderr, "Error recompiling {}\n", func.name); std::exit(EXIT_FAILURE); } } } - //RecompPort::recompile_function(functions.back(), "test.c"); return 0; } diff --git a/src/recompilation.cpp b/src/recompilation.cpp index ab5e98d..8d28d41 100644 --- a/src/recompilation.cpp +++ b/src/recompilation.cpp @@ -16,7 +16,7 @@ std::string_view ctx_gpr_prefix(int reg) { return ""; } -bool process_instruction(size_t instr_index, const std::vector& instructions, std::ofstream& output_file, bool indent, bool emit_link_branch, int link_branch_index, bool& needs_link_branch, bool& is_branch_likely) { +bool process_instruction(const RecompPort::Context& context, size_t instr_index, const std::vector& instructions, std::ofstream& output_file, bool indent, bool emit_link_branch, int link_branch_index, bool& needs_link_branch, bool& is_branch_likely) { const auto& instr = instructions[instr_index]; needs_link_branch = false; is_branch_likely = false; @@ -25,7 +25,7 @@ bool process_instruction(size_t instr_index, const std::vector(fmt::format_string fmt_str, Ts ...args) { + if (instr_index < instructions.size() - 1) { + bool dummy_needs_link_branch; + bool dummy_is_branch_likely; + process_instruction(context, instr_index + 1, instructions, output_file, false, false, link_branch_index, dummy_needs_link_branch, dummy_is_branch_likely); + } + print_indent(); + fmt::print(output_file, fmt_str, args...); + if (needs_link_branch) { + fmt::print(output_file, ";\n goto after_{};\n", link_branch_index); + } else { + fmt::print(output_file, ";\n"); + } + }; + auto print_branch = [&](fmt::format_string fmt_str, Ts ...args) { fmt::print(output_file, "{{\n "); if (instr_index < instructions.size() - 1) { bool dummy_needs_link_branch; bool dummy_is_branch_likely; - process_instruction(instr_index + 1, instructions, output_file, true, false, link_branch_index, dummy_needs_link_branch, dummy_is_branch_likely); + process_instruction(context, instr_index + 1, instructions, output_file, true, false, link_branch_index, dummy_needs_link_branch, dummy_is_branch_likely); } fmt::print(output_file, " "); fmt::print(output_file, fmt_str, args...); @@ -122,37 +137,38 @@ bool process_instruction(size_t instr_index, const std::vector> {})", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa); + print_line("{}{} = S32(SIGNED({}{}) >> {})", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa); break; case InstrId::cpu_srav: - print_line("{}{} = S32(S64({}{}) >> ({}{} & 31)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs); + print_line("{}{} = S32(SIGNED({}{}) >> ({}{} & 31))", 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_srlv: - print_line("{}{} = S32(U32({}{}) >> ({}{} & 31)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs); + print_line("{}{} = S32(U32({}{}) >> ({}{} & 31))", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs); break; case InstrId::cpu_slt: - print_line("{}{} = S64({}{}) < S64({}{}) ? 1 : 0", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); + print_line("{}{} = SIGNED({}{}) < SIGNED({}{}) ? 1 : 0", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); break; case InstrId::cpu_slti: - print_line("{}{} = S64({}{}) < {:#X} ? 1 : 0", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, (int16_t)imm); + print_line("{}{} = SIGNED({}{}) < {:#X} ? 1 : 0", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, (int16_t)imm); break; case InstrId::cpu_sltu: - print_line("{}{} = U64({}{}) < U64({}{}) ? 1 : 0", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); + print_line("{}{} = {}{} < {}{} ? 1 : 0", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); break; case InstrId::cpu_sltiu: - print_line("{}{} = U64({}{}) < {:#X} ? 1 : 0", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, (int16_t)imm); + print_line("{}{} = {}{} < {:#X} ? 1 : 0", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, (int16_t)imm); break; case InstrId::cpu_mult: print_line("uint64_t result = S64({}{}) * S64({}{}); lo = S32(result >> 0); hi = S32(result >> 32)", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); break; case InstrId::cpu_multu: - print_line("uint64_t result = {}{} * {}{}; lo = S32(result >> 0); hi = S32(result >> 32)", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); + print_line("uint64_t result = U64({}{}) * U64({}{}); lo = S32(result >> 0); hi = S32(result >> 32)", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); break; case InstrId::cpu_div: - print_line("lo = S32(S64({}{}) / S64({}{})); hi = S32(S64({}{}) % S64({}{}))", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); + // Cast to 64-bits before division to prevent artihmetic exception for s32(0x80000000) / -1 + print_line("lo = S32(S64(S32({}{})) / S64(S32({}{}))); hi = S32(S64(S32({}{})) % S64(S32({}{})))", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); break; case InstrId::cpu_divu: print_line("lo = S32(U32({}{}) / U32({}{})); hi = S32(U32({}{}) % U32({}{}))", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); @@ -219,26 +235,65 @@ bool process_instruction(size_t instr_index, const std::vectorsecond; + 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; + } + } + 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; + } + needs_link_branch = true; + print_unconditional_branch("{}(rdram, ctx)", context.functions[real_func_index].name); + 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) { + fmt::print(stderr, "Invalid return address reg for jalr: f{}\n", rd); + return false; + } needs_link_branch = true; - print_indent(); - // TODO index global function table - print_branch("{}(rdram, ctx)", "func_reg"); + print_unconditional_branch("LOOKUP_FUNC({}{})(rdram, ctx)", ctx_gpr_prefix(rs), rs); break; case InstrId::cpu_j: case InstrId::cpu_b: - print_indent(); - print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); + print_unconditional_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); break; case InstrId::cpu_jr: - print_indent(); if (rs == (int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_ra) { - print_branch("return"); + print_unconditional_branch("return"); } else { // TODO jump table handling } @@ -248,7 +303,7 @@ bool process_instruction(size_t instr_index, const std::vector= 0)", ctx_gpr_prefix(rs), rs); + print_branch_condition("if (SIGNED({}{}) >= 0)", ctx_gpr_prefix(rs), rs); print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); break; case InstrId::cpu_bgtzl: @@ -282,7 +327,7 @@ bool process_instruction(size_t instr_index, const std::vector 0)", ctx_gpr_prefix(rs), rs); + print_branch_condition("if (SIGNED({}{}) > 0)", ctx_gpr_prefix(rs), rs); print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); break; case InstrId::cpu_blezl: @@ -290,7 +335,7 @@ bool process_instruction(size_t instr_index, const std::vector instructions; @@ -692,7 +737,7 @@ bool RecompPort::recompile_function(const RecompPort::Function& func, std::strin ++cur_label; } // Process the current instruction and check for errors - if (process_instruction(instr_index, instructions, output_file, false, needs_link_branch, num_link_branches, needs_link_branch, is_branch_likely) == false) { + if (process_instruction(context, instr_index, instructions, output_file, false, needs_link_branch, num_link_branches, needs_link_branch, is_branch_likely) == false) { fmt::print(stderr, "Error in recompilation, clearing {}\n", output_path); output_file.clear(); return false;