Live recompiler support for PIC jump tables

This commit is contained in:
Ethan Lafrenais 2026-04-25 16:00:26 -04:00
parent 81213c1831
commit 1c2569e2e3
No known key found for this signature in database
GPG key ID: 928A0136009E2745
4 changed files with 28 additions and 7 deletions

View file

@ -44,13 +44,14 @@ namespace N64Recomp {
uint32_t rom;
uint32_t lw_vram;
uint32_t addu_vram;
uint32_t addu_gp_vram;
uint32_t jr_vram;
uint16_t section_index;
std::optional<uint32_t> got_offset;
std::vector<uint32_t> entries;
JumpTable(uint32_t vram, uint32_t addend_reg, uint32_t rom, uint32_t lw_vram, uint32_t addu_vram, uint32_t jr_vram, uint16_t section_index, std::optional<uint32_t> got_offset, std::vector<uint32_t>&& entries)
: vram(vram), addend_reg(addend_reg), rom(rom), lw_vram(lw_vram), addu_vram(addu_vram), jr_vram(jr_vram), section_index(section_index), got_offset(got_offset), entries(std::move(entries)) {}
JumpTable(uint32_t vram, uint32_t addend_reg, uint32_t rom, uint32_t lw_vram, uint32_t addu_vram, uint32_t addu_gp_vram, uint32_t jr_vram, uint16_t section_index, std::optional<uint32_t> got_offset, std::vector<uint32_t>&& entries)
: vram(vram), addend_reg(addend_reg), rom(rom), lw_vram(lw_vram), addu_vram(addu_vram), addu_gp_vram(addu_gp_vram), jr_vram(jr_vram), section_index(section_index), got_offset(got_offset), entries(std::move(entries)) {}
};
enum class RelocType : uint8_t {

View file

@ -28,6 +28,7 @@ struct RegState {
uint8_t loaded_addend_reg;
bool valid_loaded;
bool valid_got_loaded; // valid load through the GOT
uint32_t addu_gp_vram;
RegState() = default;
@ -50,6 +51,8 @@ struct RegState {
valid_loaded = false;
valid_got_loaded = false;
addu_gp_vram = 0;
}
};
@ -121,6 +124,7 @@ bool analyze_instruction(const rabbitizer::InstructionCpu& instr, const N64Recom
int valid_got_loaded_reg = reg_states[rs].valid_got_loaded ? rs : rt;
temp = reg_states[valid_got_loaded_reg];
temp.addu_gp_vram = instr.getVram();
}
// Exactly one of the two addend register states should have a valid lui at this time
else if (reg_states[rs].valid_lui != reg_states[rt].valid_lui) {
@ -232,6 +236,7 @@ bool analyze_instruction(const rabbitizer::InstructionCpu& instr, const N64Recom
0,
reg_states[rs].loaded_lw_vram,
reg_states[rs].loaded_addu_vram,
reg_states[rs].addu_gp_vram,
instr.getVram(),
0, // section index gets filled in later
std::nullopt,
@ -244,6 +249,7 @@ bool analyze_instruction(const rabbitizer::InstructionCpu& instr, const N64Recom
0,
reg_states[rs].loaded_lw_vram,
reg_states[rs].loaded_addu_vram,
reg_states[rs].addu_gp_vram,
instr.getVram(),
0, // section index gets filled in later
reg_states[rs].prev_got_offset,

View file

@ -920,11 +920,13 @@ int main(int argc, char** argv) {
std::string section_funcs_array_name = fmt::format("section_{}_{}_funcs", section_index, section_name_trimmed);
std::string section_relocs_array_name = section_relocs.empty() ? "nullptr" : fmt::format("section_{}_{}_relocs", section_index, section_name_trimmed);
std::string section_relocs_array_size = section_relocs.empty() ? "0" : fmt::format("ARRLEN({})", section_relocs_array_name);
std::string section_got_ram_addr = !section.got_ram_addr.has_value() ? "std::nullopt" : fmt::format("0x{:08X}", section.got_ram_addr.value());
// Write the section's table entry.
section_load_table += fmt::format(" {{ .rom_addr = 0x{0:08X}, .ram_addr = 0x{1:08X}, .size = 0x{2:08X}, .funcs = {3}, .num_funcs = ARRLEN({3}), .relocs = {4}, .num_relocs = {5}, .index = {6} }},\n",
section_load_table += fmt::format(" {{ .rom_addr = 0x{0:08X}, .ram_addr = 0x{1:08X}, .size = 0x{2:08X}, .funcs = {3}, .num_funcs = ARRLEN({3}), .relocs = {4}, .num_relocs = {5}, .index = {6}, .got_ram_addr = {7} }},\n",
section.rom_addr, section.ram_addr, section.size, section_funcs_array_name,
section_relocs_array_name, section_relocs_array_size, section_index);
section_relocs_array_name, section_relocs_array_size, section_index,
section_got_ram_addr);
// Write the section's functions.
fmt::print(overlay_file, "static FuncEntry {}[] = {{\n", section_funcs_array_name);

View file

@ -112,7 +112,7 @@ std::string_view ctx_gpr_prefix(int reg) {
}
template <typename GeneratorType>
bool process_instruction(GeneratorType& generator, const N64Recomp::Context& context, const N64Recomp::Function& func, size_t func_index, const N64Recomp::FunctionStats& stats, const std::unordered_set<uint32_t>& jtbl_lw_instructions, size_t instr_index, const std::vector<rabbitizer::InstructionCpu>& instructions, std::ostream& output_file, bool indent, bool emit_link_branch, int link_branch_index, size_t reloc_index, bool& needs_link_branch, bool& is_branch_likely, bool tag_reference_relocs, std::span<std::vector<uint32_t>> static_funcs_out) {
bool process_instruction(GeneratorType& generator, const N64Recomp::Context& context, const N64Recomp::Function& func, size_t func_index, const N64Recomp::FunctionStats& stats, const std::unordered_set<uint32_t>& jtbl_lw_instructions, const std::unordered_set<uint32_t>& jtbl_addu_gp_instructions, size_t instr_index, const std::vector<rabbitizer::InstructionCpu>& instructions, std::ostream& output_file, bool indent, bool emit_link_branch, int link_branch_index, size_t reloc_index, bool& needs_link_branch, bool& is_branch_likely, bool tag_reference_relocs, std::span<std::vector<uint32_t>> static_funcs_out) {
using namespace N64Recomp;
const auto& section = context.sections[func.section_index];
@ -150,6 +150,12 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con
assert(instr_id == InstrId::cpu_lw);
instr_id = InstrId::cpu_addiu;
}
// For PIC, the instruction after the jump table load should be an addu with the $gp register. This will interfere
// with calculating the jump table value later, so turn it into a nop.
if (section.got_ram_addr.has_value() && jtbl_addu_gp_instructions.contains(instr_vram)) {
assert(instr_id == InstrId::cpu_addu);
instr_id = InstrId::cpu_nop;
}
N64Recomp::RelocType reloc_type = N64Recomp::RelocType::R_MIPS_NONE;
bool has_reloc = false;
@ -215,7 +221,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con
if (reloc_index + 1 < section.relocs.size() && next_vram > section.relocs[reloc_index].address) {
next_reloc_index++;
}
if (!process_instruction(generator, context, func, func_index, stats, jtbl_lw_instructions, instr_index + 1, instructions, output_file, use_indent, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, tag_reference_relocs, static_funcs_out)) {
if (!process_instruction(generator, context, func, func_index, stats, jtbl_lw_instructions, jtbl_addu_gp_instructions, instr_index + 1, instructions, output_file, use_indent, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, tag_reference_relocs, static_funcs_out)) {
return false;
}
}
@ -807,6 +813,7 @@ bool recompile_function_impl(GeneratorType& generator, const N64Recomp::Context&
}
std::unordered_set<uint32_t> jtbl_lw_instructions{};
std::unordered_set<uint32_t> jtbl_addu_gp_instructions{};
// Add jump table labels into function
for (const auto& jtbl : stats.jump_tables) {
@ -816,6 +823,11 @@ bool recompile_function_impl(GeneratorType& generator, const N64Recomp::Context&
}
}
// Collect jump table addu reg, reg, $gp instructions for PIC
for (const auto& jtbl : stats.jump_tables) {
jtbl_addu_gp_instructions.insert(jtbl.addu_gp_vram);
}
// Second pass, emit code for each instruction and emit labels
auto cur_label = branch_labels.cbegin();
vram = func.vram;
@ -844,7 +856,7 @@ bool recompile_function_impl(GeneratorType& generator, const N64Recomp::Context&
}
// Process the current instruction and check for errors
if (process_instruction(generator, context, func, func_index, stats, jtbl_lw_instructions, instr_index, instructions, output_file, false, needs_link_branch, num_link_branches, reloc_index, needs_link_branch, is_branch_likely, tag_reference_relocs, static_funcs_out) == false) {
if (process_instruction(generator, context, func, func_index, stats, jtbl_lw_instructions, jtbl_addu_gp_instructions, instr_index, instructions, output_file, false, needs_link_branch, num_link_branches, reloc_index, needs_link_branch, is_branch_likely, tag_reference_relocs, static_funcs_out) == false) {
fmt::print(stderr, "Error in recompiling {}, clearing output file\n", func.name);
output_file.clear();
return false;