#include #include #include #include "rabbitizer.hpp" #include "fmt/format.h" #include "fmt/ostream.h" #include "recomp_port.h" using InstrId = rabbitizer::InstrId::UniqueId; std::string_view ctx_gpr_prefix(int reg) { if (reg != 0) { return "ctx->r"; } 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) { const auto& section = context.sections[func.section_index]; const auto& instr = instructions[instr_index]; needs_link_branch = false; is_branch_likely = false; // Output a comment with the original instruction if (instr.isBranch() || instr.getUniqueId() == InstrId::cpu_j) { fmt::print(output_file, " // {}\n", instr.disassemble(0, fmt::format("L_{:08X}", (uint32_t)instr.getBranchVramGeneric()))); } else if (instr.getUniqueId() == InstrId::cpu_jal) { fmt::print(output_file, " // {}\n", instr.disassemble(0, fmt::format("0x{:08X}", (uint32_t)instr.getBranchVramGeneric()))); } else { fmt::print(output_file, " // {}\n", instr.disassemble(0)); } uint32_t instr_vram = instr.getVram(); if (skipped_insns.contains(instr_vram)) { return true; } bool at_reloc = false; bool reloc_handled = false; RecompPort::RelocType reloc_type = RecompPort::RelocType::R_MIPS_NONE; uint32_t reloc_section = 0; uint32_t reloc_target_section_offset = 0; // Check if this instruction has a reloc. if (section.relocatable && 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; } } } } } auto print_indent = [&]() { fmt::print(output_file, " "); }; auto print_line = [&](fmt::format_string fmt_str, Ts ...args) { print_indent(); fmt::print(output_file, fmt_str, args...); fmt::print(output_file, ";\n"); }; auto print_branch_condition = [&](fmt::format_string fmt_str, Ts ...args) { fmt::print(output_file, fmt_str, args...); fmt::print(output_file, " "); }; auto print_unconditional_branch = [&](fmt::format_string fmt_str, Ts ...args) { if (instr_index < instructions.size() - 1) { bool dummy_needs_link_branch; bool dummy_is_branch_likely; size_t next_reloc_index = reloc_index; uint32_t next_vram = instr_vram + 4; 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); } 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; size_t next_reloc_index = reloc_index; uint32_t next_vram = instr_vram + 4; 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); } fmt::print(output_file, " "); fmt::print(output_file, fmt_str, args...); if (needs_link_branch) { fmt::print(output_file, ";\n goto after_{}", link_branch_index); } fmt::print(output_file, ";\n }}\n"); }; if (indent) { print_indent(); } int rd = (int)instr.GetO32_rd(); int rs = (int)instr.GetO32_rs(); int base = rs; int rt = (int)instr.GetO32_rt(); int sa = (int)instr.Get_sa(); int fd = (int)instr.GetO32_fd(); int fs = (int)instr.GetO32_fs(); int ft = (int)instr.GetO32_ft(); int cop1_cs = (int)instr.Get_cop1cs(); uint16_t imm = instr.Get_immediate(); std::string unsigned_imm_string; std::string signed_imm_string; if (!at_reloc) { unsigned_imm_string = fmt::format("{:#X}", imm); signed_imm_string = fmt::format("{:#X}", (int16_t)imm); } else { switch (reloc_type) { case RecompPort::RelocType::R_MIPS_HI16: unsigned_imm_string = fmt::format("RELOC_HI16({}, {:#X})", reloc_section, 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); signed_imm_string = "(int16_t)" + unsigned_imm_string; reloc_handled = true; break; } } switch (instr.getUniqueId()) { case InstrId::cpu_nop: fmt::print(output_file, "\n"); break; // Arithmetic case InstrId::cpu_lui: print_line("{}{} = S32({} << 16)", ctx_gpr_prefix(rt), rt, unsigned_imm_string); break; case InstrId::cpu_add: case InstrId::cpu_addu: { // Check if this addu belongs to a jump table load auto find_result = std::find_if(stats.jump_tables.begin(), stats.jump_tables.end(), [instr_vram](const RecompPort::JumpTable& jtbl) { return jtbl.addu_vram == instr_vram; }); // If so, create a temp to preserve the addend register's value if (find_result != stats.jump_tables.end()) { const RecompPort::JumpTable& cur_jtbl = *find_result; print_line("gpr jr_addend_{:08X} = {}{}", cur_jtbl.jr_vram, ctx_gpr_prefix(cur_jtbl.addend_reg), cur_jtbl.addend_reg); } } print_line("{}{} = ADD32({}{}, {}{})", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); break; case InstrId::cpu_daddu: print_line("{}{} = {}{} + {}{}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); break; case InstrId::cpu_negu: // pseudo instruction for subu x, 0, y case InstrId::cpu_sub: case InstrId::cpu_subu: print_line("{}{} = SUB32({}{}, {}{})", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); break; case InstrId::cpu_addi: case InstrId::cpu_addiu: print_line("{}{} = ADD32({}{}, {})", 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; case InstrId::cpu_andi: print_line("{}{} = {}{} & {}", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, unsigned_imm_string); break; case InstrId::cpu_or: print_line("{}{} = {}{} | {}{}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); break; case InstrId::cpu_ori: print_line("{}{} = {}{} | {}", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, unsigned_imm_string); break; case InstrId::cpu_nor: print_line("{}{} = ~({}{} | {}{})", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); break; case InstrId::cpu_xor: print_line("{}{} = {}{} ^ {}{}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); break; case InstrId::cpu_xori: print_line("{}{} = {}{} ^ {}", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, unsigned_imm_string); break; case InstrId::cpu_sll: print_line("{}{} = S32({}{}) << {}", 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_sra: print_line("{}{} = S32(SIGNED({}{}) >> {})", 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); 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); 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; case InstrId::cpu_slti: print_line("{}{} = SIGNED({}{}) < {} ? 1 : 0", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, signed_imm_string); break; case InstrId::cpu_sltu: 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("{}{} = {}{} < {} ? 1 : 0", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, signed_imm_string); break; case InstrId::cpu_mult: print_line("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("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: // 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); break; case InstrId::cpu_mflo: print_line("{}{} = lo", ctx_gpr_prefix(rd), rd); break; case InstrId::cpu_mfhi: print_line("{}{} = hi", ctx_gpr_prefix(rd), rd); break; // Loads // TODO ld case InstrId::cpu_lw: print_line("{}{} = MEM_W({}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base); break; case InstrId::cpu_lh: print_line("{}{} = MEM_H({}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base); break; case InstrId::cpu_lb: print_line("{}{} = MEM_B({}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base); break; case InstrId::cpu_lhu: print_line("{}{} = MEM_HU({}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base); break; case InstrId::cpu_lbu: print_line("{}{} = MEM_BU({}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base); break; // Stores case InstrId::cpu_sw: print_line("MEM_W({}, {}{}) = {}{}", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt); break; case InstrId::cpu_sh: print_line("MEM_H({}, {}{}) = {}{}", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt); break; case InstrId::cpu_sb: print_line("MEM_B({}, {}{}) = {}{}", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt); break; // TODO lwl, lwr // examples: // reg = 11111111 01234567 // mem @ x = 89ABCDEF // LWL x + 0 -> FFFFFFFF 89ABCDEF // LWL x + 1 -> FFFFFFFF ABCDEF67 // LWL x + 2 -> FFFFFFFF CDEF4567 // LWL x + 3 -> FFFFFFFF EF234567 // LWR x + 0 -> 00000000 01234589 // LWR x + 1 -> 00000000 012389AB // LWR x + 2 -> 00000000 0189ABCD // LWR x + 3 -> FFFFFFFF 89ABCDEF case InstrId::cpu_lwl: print_line("{}{} = do_lwl(rdram, {}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base); //print_line("{}{} = MEM_WL({}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base); break; case InstrId::cpu_lwr: //print_line("{}{} = do_lwr(rdram, {}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base); //print_line("//{}{} = MEM_WR({}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base); break; case InstrId::cpu_swl: print_line("do_swl(rdram, {}, {}{}, {}{})", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt); //print_line("MEM_WL({}, {}{}) = {}{}", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt); break; case InstrId::cpu_swr: //print_line("do_swr(rdram, {}, {}{}, {}{})", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt); //print_line("//MEM_WR({}, {}{}) = {}{}", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt); break; // 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; } 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_unconditional_branch("LOOKUP_FUNC({}{})(rdram, ctx)", ctx_gpr_prefix(rs), rs); break; case InstrId::cpu_j: case InstrId::cpu_b: { uint32_t branch_target = instr.getBranchVramGeneric(); if (branch_target == instr_vram) { print_line("void pause_self(uint8_t *rdram); pause_self(rdram)"); } print_unconditional_branch("goto L_{:08X}", branch_target); } break; case InstrId::cpu_jr: if (rs == (int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_ra) { print_unconditional_branch("return"); } else { auto jtbl_find_result = std::find_if(stats.jump_tables.begin(), stats.jump_tables.end(), [instr_vram](const RecompPort::JumpTable& jtbl) { return jtbl.jr_vram == instr_vram; }); if (jtbl_find_result != stats.jump_tables.end()) { const RecompPort::JumpTable& cur_jtbl = *jtbl_find_result; bool dummy_needs_link_branch, dummy_is_branch_likely; size_t next_reloc_index = reloc_index; uint32_t next_vram = instr_vram + 4; 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); 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++) { print_indent(); print_line("case {}: goto L_{:08X}; break", entry_index, cur_jtbl.entries[entry_index]); } print_indent(); print_line("default: switch_error(__func__, 0x{:08X}, 0x{:08X})", instr_vram, cur_jtbl.vram); print_indent(); fmt::print(output_file, "}}\n"); break; } auto jump_find_result = std::find_if(stats.absolute_jumps.begin(), stats.absolute_jumps.end(), [instr_vram](const RecompPort::AbsoluteJump& jump) { return jump.instruction_vram == instr_vram; }); 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; } fmt::print(stderr, "No jump table found for jr at 0x{:08X}\n", instr_vram); } break; case InstrId::cpu_bnel: is_branch_likely = true; [[fallthrough]]; case InstrId::cpu_bne: print_indent(); print_branch_condition("if ({}{} != {}{})", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); break; case InstrId::cpu_beql: is_branch_likely = true; [[fallthrough]]; case InstrId::cpu_beq: print_indent(); print_branch_condition("if ({}{} == {}{})", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt); print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); break; case InstrId::cpu_bgezl: is_branch_likely = true; [[fallthrough]]; case InstrId::cpu_bgez: print_indent(); 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: is_branch_likely = true; [[fallthrough]]; case InstrId::cpu_bgtz: print_indent(); 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: is_branch_likely = true; [[fallthrough]]; case InstrId::cpu_blez: print_indent(); print_branch_condition("if (SIGNED({}{}) <= 0)", ctx_gpr_prefix(rs), rs); print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); break; case InstrId::cpu_bltzl: is_branch_likely = true; [[fallthrough]]; case InstrId::cpu_bltz: print_indent(); print_branch_condition("if (SIGNED({}{}) < 0)", ctx_gpr_prefix(rs), rs); print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); break; case InstrId::cpu_break: print_line("do_break({})", instr_vram); break; // Cop1 loads/stores case InstrId::cpu_mtc1: if ((fs & 1) == 0) { // even fpr print_line("ctx->f{}.u32l = {}{}", fs, ctx_gpr_prefix(rt), rt); } else { // odd fpr print_line("ctx->f{}.u32h = {}{}", fs - 1, ctx_gpr_prefix(rt), rt); } break; case InstrId::cpu_mfc1: if ((fs & 1) == 0) { // even fpr 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); } break; //case InstrId::cpu_dmfc1: // if ((fs & 1) == 0) { // // even fpr // print_line("{}{} = ctx->f{}.u64", ctx_gpr_prefix(rt), rt, fs); // } else { // fmt::print(stderr, "Invalid operand for dmfc1: f{}\n", fs); // return false; // } // break; case InstrId::cpu_lwc1: if ((ft & 1) == 0) { // even fpr 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); } 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; } break; case InstrId::cpu_swc1: if ((ft & 1) == 0) { // even fpr 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); } 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; } 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; } 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; } 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; } 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; } 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; } 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; } break; // Cop1 branches case InstrId::cpu_bc1tl: is_branch_likely = true; [[fallthrough]]; case InstrId::cpu_bc1t: print_indent(); print_branch_condition("if (c1cs)", ctx_gpr_prefix(rs), rs); print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); break; case InstrId::cpu_bc1fl: is_branch_likely = true; [[fallthrough]]; case InstrId::cpu_bc1f: print_indent(); print_branch_condition("if (!c1cs)", ctx_gpr_prefix(rs), rs); print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric()); break; // 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; // } // 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; // } // break; case InstrId::cpu_ctc1: if (cop1_cs != 31) { fmt::print(stderr, "Invalid FP control register for ctc1: {}\n", cop1_cs); return false; } print_line("rounding_mode = ({}{}) & 0x3", ctx_gpr_prefix(rt), rt); break; case InstrId::cpu_cfc1: if (cop1_cs != 31) { fmt::print(stderr, "Invalid FP control register for cfc1: {}\n", cop1_cs); return false; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } break; default: fmt::print(stderr, "Unhandled instruction: {}\n", instr.getOpcodeName()); return false; } // TODO is this used? if (emit_link_branch) { fmt::print(output_file, " after_{}:\n", link_branch_index); } return true; } bool RecompPort::recompile_function(const RecompPort::Context& context, const RecompPort::Function& func, std::string_view 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.data() }; if (!output_file.good()) { fmt::print(stderr, "Failed to open file for writing: {}\n", output_path); return false; } fmt::print(output_file, "#include \"recomp.h\"\n" "#include \"disable_warnings.h\"\n" "\n" "void {}(uint8_t* rdram, recomp_context* ctx) {{\n" // these variables shouldn't need to be preserved across function boundaries, so make them local for more efficient output " uint64_t hi = 0, lo = 0, result = 0;\n" " unsigned int rounding_mode = DEFAULT_ROUNDING_MODE;\n" " int c1cs = 0; \n", // cop1 conditional signal func.name); // Use a set to sort and deduplicate labels std::set branch_labels; instructions.reserve(func.words.size()); // First pass, disassemble each instruction and collect branch labels uint32_t vram = func.vram; for (uint32_t word : func.words) { const auto& instr = instructions.emplace_back(byteswap(word), vram); // If this is a branch or a direct jump, add it to the local label list if (instr.isBranch() || instr.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_j) { branch_labels.insert((uint32_t)instr.getBranchVramGeneric()); } // Advance the vram address by the size of one instruction vram += 4; } // Analyze function RecompPort::FunctionStats stats{}; if (!RecompPort::analyze_function(context, func, instructions, stats)) { fmt::print(stderr, "Failed to analyze {}\n", func.name); output_file.clear(); return false; } std::unordered_set skipped_insns{}; // Add jump table labels into function for (const auto& jtbl : stats.jump_tables) { skipped_insns.insert(jtbl.lw_vram); for (uint32_t jtbl_entry : jtbl.entries) { branch_labels.insert(jtbl_entry); } } // Second pass, emit code for each instruction and emit labels auto cur_label = branch_labels.cbegin(); vram = func.vram; int num_link_branches = 0; int num_likely_branches = 0; bool needs_link_branch = false; bool in_likely_delay_slot = false; const auto& section = context.sections[func.section_index]; bool needs_reloc = section.relocatable; size_t reloc_index = 0; for (size_t instr_index = 0; instr_index < instructions.size(); ++instr_index) { bool had_link_branch = needs_link_branch; bool is_branch_likely = false; // If we're in the delay slot of a likely instruction, emit a goto to skip the instruction before any labels if (in_likely_delay_slot) { fmt::print(output_file, " goto skip_{};\n", num_likely_branches); } // If there are any other branch labels to insert and we're at the next one, insert it if (cur_label != branch_labels.end() && vram >= *cur_label) { fmt::print(output_file, "L_{:08X}:\n", *cur_label); ++cur_label; } // If this is a relocatable section, advance the reloc index until we reach the last one or until we get to/pass the current instruction if (needs_reloc) { while (reloc_index < (section.relocs.size() - 1) && section.relocs[reloc_index].address < vram) { reloc_index++; } } // 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) { fmt::print(stderr, "Error in recompilation, clearing {}\n", output_path); output_file.clear(); return false; } // If a link return branch was generated, advance the number of link return branches if (had_link_branch) { num_link_branches++; } // Now that the instruction has been processed, emit a skip label for the likely branch if needed if (in_likely_delay_slot) { fmt::print(output_file, " skip_{}:\n", num_likely_branches); num_likely_branches++; } // Mark the next instruction as being in a likely delay slot if the in_likely_delay_slot = is_branch_likely; // Advance the vram address by the size of one instruction vram += 4; } // Terminate the function fmt::print(output_file, ";}}\n"); return true; }