mirror of
https://github.com/Mr-Wiseguy/N64Recomp.git
synced 2024-12-28 18:36:26 +00:00
Implemented function stubbing
This commit is contained in:
parent
fba0085946
commit
7df3e28c76
|
@ -70,6 +70,7 @@ namespace RecompPort {
|
||||||
ELFIO::Elf_Half section_index;
|
ELFIO::Elf_Half section_index;
|
||||||
bool ignored;
|
bool ignored;
|
||||||
bool reimplemented;
|
bool reimplemented;
|
||||||
|
bool stubbed;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class RelocType : uint8_t {
|
enum class RelocType : uint8_t {
|
||||||
|
@ -113,6 +114,8 @@ namespace RecompPort {
|
||||||
std::vector<Section> sections;
|
std::vector<Section> sections;
|
||||||
std::vector<Function> functions;
|
std::vector<Function> functions;
|
||||||
std::unordered_map<uint32_t, std::vector<size_t>> functions_by_vram;
|
std::unordered_map<uint32_t, std::vector<size_t>> functions_by_vram;
|
||||||
|
// A mapping of function name to index in the functions vector
|
||||||
|
std::unordered_map<std::string, size_t> functions_by_name;
|
||||||
std::vector<uint8_t> rom;
|
std::vector<uint8_t> rom;
|
||||||
// A list of the list of each function (by index in `functions`) in a given section
|
// A list of the list of each function (by index in `functions`) in a given section
|
||||||
std::vector<std::vector<size_t>> section_functions;
|
std::vector<std::vector<size_t>> section_functions;
|
||||||
|
@ -124,7 +127,8 @@ namespace RecompPort {
|
||||||
sections.resize(elf_file.sections.size());
|
sections.resize(elf_file.sections.size());
|
||||||
section_functions.resize(elf_file.sections.size());
|
section_functions.resize(elf_file.sections.size());
|
||||||
functions.reserve(1024);
|
functions.reserve(1024);
|
||||||
functions_by_vram.reserve(1024);
|
functions_by_vram.reserve(functions.capacity());
|
||||||
|
functions_by_name.reserve(functions.capacity());
|
||||||
rom.reserve(8 * 1024 * 1024);
|
rom.reserve(8 * 1024 * 1024);
|
||||||
executable_section_count = 0;
|
executable_section_count = 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,7 +100,7 @@ RecompPort::Config::Config(const char* path) {
|
||||||
|
|
||||||
// Patches section (optional)
|
// Patches section (optional)
|
||||||
const toml::value& patches_data = toml::find_or<toml::value>(config_data, "patches", toml::value{});
|
const toml::value& patches_data = toml::find_or<toml::value>(config_data, "patches", toml::value{});
|
||||||
if (patches_data.type() == toml::value_t::empty) {
|
if (patches_data.type() != toml::value_t::empty) {
|
||||||
// Stubs array (optional)
|
// Stubs array (optional)
|
||||||
get_stubbed_funcs(stubbed_funcs, patches_data);
|
get_stubbed_funcs(stubbed_funcs, patches_data);
|
||||||
|
|
||||||
|
|
14
src/main.cpp
14
src/main.cpp
|
@ -665,6 +665,7 @@ bool read_symbols(RecompPort::Context& context, const ELFIO::elfio& elf_file, EL
|
||||||
if (num_instructions > 0) {
|
if (num_instructions > 0) {
|
||||||
context.section_functions[section_index].push_back(context.functions.size());
|
context.section_functions[section_index].push_back(context.functions.size());
|
||||||
}
|
}
|
||||||
|
context.functions_by_name[name] = context.functions.size();
|
||||||
context.functions.emplace_back(
|
context.functions.emplace_back(
|
||||||
vram,
|
vram,
|
||||||
rom_address,
|
rom_address,
|
||||||
|
@ -1069,6 +1070,19 @@ int main(int argc, char** argv) {
|
||||||
|
|
||||||
fmt::print("Working dir: {}\n", std::filesystem::current_path().string());
|
fmt::print("Working dir: {}\n", std::filesystem::current_path().string());
|
||||||
|
|
||||||
|
// Stub out any functions specified in the config file.
|
||||||
|
for (const std::string& stubbed_func : config.stubbed_funcs) {
|
||||||
|
// Check if the specified function exists.
|
||||||
|
auto func_find = context.functions_by_name.find(stubbed_func);
|
||||||
|
if (func_find == context.functions_by_name.end()) {
|
||||||
|
// Function doesn't exist, present an error to the user instead of silently failing to stub it out.
|
||||||
|
// This helps prevent typos in the config file or functions renamed between versions from causing issues.
|
||||||
|
exit_failure(fmt::format("Function {} is stubbed out in the config file but does not exist!", stubbed_func));
|
||||||
|
}
|
||||||
|
// Mark the function as stubbed.
|
||||||
|
context.functions[func_find->second].stubbed = true;
|
||||||
|
}
|
||||||
|
|
||||||
//#pragma omp parallel for
|
//#pragma omp parallel for
|
||||||
for (size_t i = 0; i < context.functions.size(); i++) {
|
for (size_t i = 0; i < context.functions.size(); i++) {
|
||||||
const auto& func = context.functions[i];
|
const auto& func = context.functions[i];
|
||||||
|
|
|
@ -997,91 +997,94 @@ bool RecompPort::recompile_function(const RecompPort::Context& context, const Re
|
||||||
" int c1cs = 0; \n", // cop1 conditional signal
|
" int c1cs = 0; \n", // cop1 conditional signal
|
||||||
func.name);
|
func.name);
|
||||||
|
|
||||||
// Use a set to sort and deduplicate labels
|
// Skip analysis and recompilation of this function is stubbed.
|
||||||
std::set<uint32_t> branch_labels;
|
if (!func.stubbed) {
|
||||||
instructions.reserve(func.words.size());
|
// Use a set to sort and deduplicate labels
|
||||||
|
std::set<uint32_t> branch_labels;
|
||||||
|
instructions.reserve(func.words.size());
|
||||||
|
|
||||||
// First pass, disassemble each instruction and collect branch labels
|
// First pass, disassemble each instruction and collect branch labels
|
||||||
uint32_t vram = func.vram;
|
uint32_t vram = func.vram;
|
||||||
for (uint32_t word : func.words) {
|
for (uint32_t word : func.words) {
|
||||||
const auto& instr = instructions.emplace_back(byteswap(word), vram);
|
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 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) {
|
if (instr.isBranch() || instr.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_j) {
|
||||||
branch_labels.insert((uint32_t)instr.getBranchVramGeneric());
|
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<uint32_t> 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++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Advance the vram address by the size of one instruction
|
||||||
|
vram += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process the current instruction and check for errors
|
// Analyze function
|
||||||
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) {
|
RecompPort::FunctionStats stats{};
|
||||||
fmt::print(stderr, "Error in recompilation, clearing {}\n", output_path.string() );
|
if (!RecompPort::analyze_function(context, func, instructions, stats)) {
|
||||||
|
fmt::print(stderr, "Failed to analyze {}\n", func.name);
|
||||||
output_file.clear();
|
output_file.clear();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// If a link return branch was generated, advance the number of link return branches
|
|
||||||
if (had_link_branch) {
|
std::unordered_set<uint32_t> skipped_insns{};
|
||||||
num_link_branches++;
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Now that the instruction has been processed, emit a skip label for the likely branch if needed
|
|
||||||
if (in_likely_delay_slot) {
|
// Second pass, emit code for each instruction and emit labels
|
||||||
fmt::print(output_file, " skip_{}:\n", num_likely_branches);
|
auto cur_label = branch_labels.cbegin();
|
||||||
num_likely_branches++;
|
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.string());
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
// 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
|
// Terminate the function
|
||||||
|
|
Loading…
Reference in a new issue