Support for $gp relative jump table calls

This commit is contained in:
Ethan Lafrenais 2024-10-29 19:22:12 -04:00
parent d33d381617
commit a6efe6e82b
No known key found for this signature in database
GPG key ID: 928A0136009E2745
4 changed files with 78 additions and 2 deletions

View file

@ -86,6 +86,7 @@ namespace N64Recomp {
bool executable = false;
bool relocatable = false; // TODO is this needed? relocs being non-empty should be an equivalent check.
bool has_mips32_relocs = false;
uint32_t gp_ram_addr = 0;
};
struct ReferenceSection {

View file

@ -16,15 +16,19 @@ struct RegState {
uint32_t prev_addiu_vram;
uint32_t prev_addu_vram;
uint8_t prev_addend_reg;
// offset of lw rt,offset(gp)
uint32_t prev_got_offset;
bool valid_lui;
bool valid_addiu;
bool valid_addend;
bool valid_got_offset;
// For tracking a register that has been loaded from RAM
uint32_t loaded_lw_vram;
uint32_t loaded_addu_vram;
uint32_t loaded_address;
uint8_t loaded_addend_reg;
bool valid_loaded;
bool valid_gp_loaded;
RegState() = default;
@ -33,10 +37,12 @@ struct RegState {
prev_addiu_vram = 0;
prev_addu_vram = 0;
prev_addend_reg = 0;
prev_got_offset = 0;
valid_lui = false;
valid_addiu = false;
valid_addend = false;
valid_got_offset = false;
loaded_lw_vram = 0;
loaded_addu_vram = 0;
@ -44,6 +50,7 @@ struct RegState {
loaded_addend_reg = 0;
valid_loaded = false;
valid_gp_loaded = false;
}
};
@ -109,6 +116,24 @@ bool analyze_instruction(const rabbitizer::InstructionCpu& instr, const N64Recom
temp.valid_addend = true;
temp.prev_addend_reg = addend_reg;
temp.prev_addu_vram = instr.getVram();
} else if (reg_states[rs].valid_got_offset != reg_states[rt].valid_got_offset) {
// Track which of the two registers has the valid got offset state and which is the addend
int valid_got_offset_reg = reg_states[rs].valid_got_offset ? rs : rt;
int addend_reg = reg_states[rs].valid_got_offset ? rt : rs;
// Copy the got offset reg's state into the destination reg, then set the destination reg's addend to the other operand
temp = reg_states[valid_got_offset_reg];
temp.valid_addend = true;
temp.prev_addend_reg = addend_reg;
temp.prev_addu_vram = instr.getVram();
} else if (((rs == (int)RegId::GPR_O32_gp) || (rt == (int)RegId::GPR_O32_gp))
&& reg_states[rs].valid_gp_loaded != reg_states[rt].valid_gp_loaded) {
// `addu rd, rs, $gp` or `addu rd, $gp, rt` after valid $gp load, this is the last part of a $gp relative
// jump table call. Keep the register state intact.
int valid_gp_loaded_reg = reg_states[rs].valid_gp_loaded ? rs : rt;
int gp_reg = reg_states[rs].valid_gp_loaded ? rt : rs;
temp = reg_states[valid_gp_loaded_reg];
} else {
// Check if this is a move
check_move();
@ -178,6 +203,19 @@ bool analyze_instruction(const rabbitizer::InstructionCpu& instr, const N64Recom
temp.loaded_addu_vram = reg_states[base].prev_addu_vram;
}
}
// If the base register has a valid GOT offset and a valid addend before this, then this may be a load from a $gp relative jump table
else if (reg_states[base].valid_got_offset && reg_states[base].valid_addend) {
temp.valid_gp_loaded = true;
temp.loaded_lw_vram = instr.getVram();
temp.loaded_address = imm; // This address is relative for now, we'll calculate the absolute address later
temp.loaded_addend_reg = reg_states[base].prev_addend_reg;
temp.loaded_addu_vram = reg_states[base].prev_addu_vram;
temp.prev_got_offset = reg_states[base].prev_got_offset;
} else if (base == (int)RegId::GPR_O32_gp) {
// lw from the $gp register implies a read from the GOT
temp.prev_got_offset = imm;
temp.valid_got_offset = true;
}
reg_states[rt] = temp;
break;
case InstrId::cpu_jr:
@ -194,6 +232,18 @@ bool analyze_instruction(const rabbitizer::InstructionCpu& instr, const N64Recom
reg_states[rs].loaded_lw_vram,
reg_states[rs].loaded_addu_vram,
instr.getVram(),
std::nullopt,
std::vector<uint32_t>{}
);
} else if (reg_states[rs].valid_gp_loaded) {
stats.jump_tables.emplace_back(
reg_states[rs].loaded_address,
reg_states[rs].loaded_addend_reg,
0,
reg_states[rs].loaded_lw_vram,
reg_states[rs].loaded_addu_vram,
instr.getVram(),
reg_states[rs].prev_got_offset,
std::vector<uint32_t>{}
);
} else if (reg_states[rs].valid_lui && reg_states[rs].valid_addiu && !reg_states[rs].valid_addend && !reg_states[rs].valid_loaded) {
@ -236,6 +286,21 @@ bool N64Recomp::analyze_function(const N64Recomp::Context& context, const N64Rec
}
}
const Section* section = &context.sections[func.section_index];
// Calculate absolute addresses for jump tables that are relative to $gp
for (size_t i = 0; i < stats.jump_tables.size(); i++) {
JumpTable& cur_jtbl = stats.jump_tables[i];
if (cur_jtbl.got_offset.has_value()) {
uint32_t gp_ram_addr = section->gp_ram_addr;
uint32_t gp_rom_addr = gp_ram_addr + func.rom - func.vram;
uint32_t got_word = byteswap(*reinterpret_cast<const uint32_t*>(&context.rom[gp_rom_addr + cur_jtbl.got_offset.value()]));
cur_jtbl.vram += section->ram_addr + got_word;
}
}
// Sort jump tables by their address
std::sort(stats.jump_tables.begin(), stats.jump_tables.end(),
[](const JumpTable& a, const JumpTable& b)
@ -262,6 +327,13 @@ bool N64Recomp::analyze_function(const N64Recomp::Context& context, const N64Rec
// TODO same as above
uint32_t rom_addr = vram + func.rom - func.vram;
uint32_t jtbl_word = byteswap(*reinterpret_cast<const uint32_t*>(&context.rom[rom_addr]));
if (cur_jtbl.got_offset.has_value()) {
// $gp relative jump tables have values that are offsets from $gp,
// convert those to absolute addresses
jtbl_word += section->gp_ram_addr;
}
// Check if the entry is a valid address in the current function
if (jtbl_word < func.vram || jtbl_word > func.vram + func.words.size() * sizeof(func.words[0])) {
// If it's not then this is the end of the jump table

View file

@ -14,10 +14,11 @@ namespace N64Recomp {
uint32_t lw_vram;
uint32_t addu_vram;
uint32_t jr_vram;
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, 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), 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 jr_vram, 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), got_offset(got_offset), entries(std::move(entries)) {}
};
struct AbsoluteJump {

View file

@ -476,6 +476,7 @@ bool N64Recomp::Context::from_symbol_file(const std::filesystem::path& symbol_fi
std::optional<uint32_t> vram_addr = el["vram"].template value<uint32_t>();
std::optional<uint32_t> size = el["size"].template value<uint32_t>();
std::optional<std::string> name = el["name"].template value<std::string>();
std::optional<uint32_t> gp_ram_addr = el["gp"].template value<uint32_t>();
if (!rom_addr.has_value() || !vram_addr.has_value() || !size.has_value() || !name.has_value()) {
throw toml::parse_error("Section entry missing required field(s)", el.source());
@ -488,6 +489,7 @@ bool N64Recomp::Context::from_symbol_file(const std::filesystem::path& symbol_fi
section.ram_addr = vram_addr.value();
section.size = size.value();
section.name = name.value();
section.gp_ram_addr = gp_ram_addr.value_or(0);
section.executable = true;
// Read functions for the section.