mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-10-01 02:17:14 -04:00
Refactor runtime for move speed and better code style (#140)
* feat: added guestBranchKind enum to categorize branch types feat: added missingFunctionPolicy enum to define behaviors for missing function scenarios refactor: added handle guest branches and report missing functions feat lookupFunction to utilize new dispatch logic and improve error handling for unregistered functions * fix: fix test conflict * feat: added debug sound driver logs * feat: emmiter for return * feat: added recompiler reporter feat: added strict diagnostics flag for heavy debug calls * feat: staticc table insted of hashmap for runtime * feat: back file to ignore * feat: explode code across helpers and classes * feat: update codegen test feat: better guest nop check * feat: fix link problem on linux * feat: fix Segmentation fault
This commit is contained in:
@@ -0,0 +1,423 @@
|
||||
#include "ps2recomp/control_flow_analyzer.h"
|
||||
#include "ps2recomp/types.h"
|
||||
#include "ps2recomp/instructions.h"
|
||||
#include "ps2recomp/control_flow_utils.h"
|
||||
#include "ps2recomp/recompiler_reporter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
ControlFlowAnalyzer::ControlFlowAnalyzer(
|
||||
const std::vector<Section> §ions,
|
||||
const std::unordered_map<uint32_t, std::vector<uint32_t>> &configuredJumpTableTargetsByAddress,
|
||||
RecompilerReporter *reporter)
|
||||
: m_sections(sections),
|
||||
m_configJumpTableTargetsByAddress(configuredJumpTableTargetsByAddress),
|
||||
m_reporter(reporter)
|
||||
{
|
||||
}
|
||||
|
||||
ControlFlowAnalysisResult ControlFlowAnalyzer::analyze(
|
||||
const Function &function,
|
||||
const std::vector<Instruction> &instructions,
|
||||
const std::vector<Function> *allFunctions) const
|
||||
{
|
||||
ControlFlowAnalysisResult result;
|
||||
std::unordered_set<uint32_t> instructionAddresses;
|
||||
instructionAddresses.reserve(instructions.size());
|
||||
bool hasIndirectRegisterJump = false;
|
||||
std::vector<const Instruction *> indirectJumps;
|
||||
|
||||
auto isExecutableAddress = [&](uint32_t address) -> bool
|
||||
{
|
||||
for (const auto §ion : m_sections)
|
||||
{
|
||||
if (!section.isCode)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (address >= section.address && address < (section.address + section.size))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto findContainingExternalFunction = [&](uint32_t address) -> const Function *
|
||||
{
|
||||
if (!allFunctions || !isExecutableAddress(address))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const Function *best = nullptr;
|
||||
for (const auto &candidateFn : *allFunctions)
|
||||
{
|
||||
if (!candidateFn.isRecompiled || candidateFn.isStub || candidateFn.isSkipped)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (candidateFn.name.rfind("entry_", 0) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (address < candidateFn.start || address >= candidateFn.end)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!best || candidateFn.start > best->start)
|
||||
{
|
||||
best = &candidateFn;
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
};
|
||||
|
||||
auto queueExternalEntryTarget = [&](uint32_t target)
|
||||
{
|
||||
const Function *containingFn = findContainingExternalFunction(target);
|
||||
if (!containingFn)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (containingFn->start == function.start)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (target == containingFn->start)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
result.externalEntryPoints.insert(target);
|
||||
};
|
||||
|
||||
auto queueResumeEntryTarget = [&](uint32_t resumeAddr)
|
||||
{
|
||||
if (resumeAddr >= function.start && resumeAddr < function.end &&
|
||||
instructionAddresses.contains(resumeAddr))
|
||||
{
|
||||
result.entryPoints.insert(resumeAddr);
|
||||
result.resumeEntryPoints.insert(resumeAddr);
|
||||
}
|
||||
};
|
||||
|
||||
auto queueLoopResumeEntryTarget = [&](uint32_t target, uint32_t sourcePc)
|
||||
{
|
||||
if (target > sourcePc || target == function.start)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
queueResumeEntryTarget(target);
|
||||
};
|
||||
|
||||
for (const auto &inst : instructions)
|
||||
{
|
||||
instructionAddresses.insert(inst.address);
|
||||
if (inst.opcode == OPCODE_SPECIAL &&
|
||||
((inst.function == SPECIAL_JR && inst.rs != 31) ||
|
||||
inst.function == SPECIAL_JALR))
|
||||
{
|
||||
hasIndirectRegisterJump = true;
|
||||
indirectJumps.push_back(&inst);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &inst : instructions)
|
||||
{
|
||||
bool isStaticJump = (inst.opcode == OPCODE_J || inst.opcode == OPCODE_JAL);
|
||||
if (inst.isBranch && inst.opcode != OPCODE_J && inst.opcode != OPCODE_JAL)
|
||||
{
|
||||
const int32_t offsetBytes = (static_cast<int32_t>(static_cast<int16_t>(inst.simmediate)) << 2);
|
||||
const uint32_t target = static_cast<uint32_t>(
|
||||
static_cast<int64_t>(inst.address + 4u) + static_cast<int64_t>(offsetBytes));
|
||||
|
||||
if (target >= function.start && target < function.end &&
|
||||
instructionAddresses.contains(target))
|
||||
{
|
||||
result.entryPoints.insert(target);
|
||||
queueLoopResumeEntryTarget(target, inst.address);
|
||||
}
|
||||
else
|
||||
{
|
||||
queueExternalEntryTarget(target);
|
||||
}
|
||||
}
|
||||
else if (isStaticJump)
|
||||
{
|
||||
uint32_t target = buildAbsoluteJumpTarget(inst.address, inst.target);
|
||||
if (target >= function.start && target < function.end &&
|
||||
instructionAddresses.contains(target))
|
||||
{
|
||||
result.entryPoints.insert(target);
|
||||
queueLoopResumeEntryTarget(target, inst.address);
|
||||
|
||||
if (inst.opcode == OPCODE_JAL)
|
||||
{
|
||||
queueResumeEntryTarget(inst.address + 8u);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
queueExternalEntryTarget(target);
|
||||
|
||||
if (inst.opcode == OPCODE_JAL)
|
||||
{
|
||||
queueResumeEntryTarget(inst.address + 8u);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasIndirectRegisterJump)
|
||||
{
|
||||
bool needsIndirectFallback = false;
|
||||
for (const Instruction *jrInst : indirectJumps)
|
||||
{
|
||||
if (jrInst->function == SPECIAL_JALR)
|
||||
{
|
||||
queueResumeEntryTarget(jrInst->address + 8u);
|
||||
}
|
||||
|
||||
bool foundTable = false;
|
||||
|
||||
uint32_t jrReg = jrInst->rs;
|
||||
|
||||
int lwIndex = -1;
|
||||
uint32_t baseReg = 0;
|
||||
int32_t lwOffset = 0;
|
||||
|
||||
auto it = std::find_if(instructions.begin(), instructions.end(), [&](const Instruction &inst)
|
||||
{ return inst.address == jrInst->address; });
|
||||
if (it != instructions.end())
|
||||
{
|
||||
int jrIndex = std::distance(instructions.begin(), it);
|
||||
for (int i = jrIndex - 1; i >= 0 && i >= jrIndex - 20; --i)
|
||||
{
|
||||
const auto &inst = instructions[i];
|
||||
if ((inst.opcode == OPCODE_LW || inst.opcode == OPCODE_LWU) && inst.rt == jrReg)
|
||||
{
|
||||
lwIndex = i;
|
||||
baseReg = inst.rs;
|
||||
lwOffset = inst.simmediate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lwIndex != -1)
|
||||
{
|
||||
int adduIndex = -1;
|
||||
uint32_t tableBaseReg = 0;
|
||||
uint32_t indexReg = 0;
|
||||
for (int i = lwIndex - 1; i >= 0 && i >= lwIndex - 10; --i)
|
||||
{
|
||||
const auto &inst = instructions[i];
|
||||
if (inst.opcode == OPCODE_SPECIAL && inst.function == SPECIAL_ADDU && inst.rd == baseReg)
|
||||
{
|
||||
adduIndex = i;
|
||||
tableBaseReg = inst.rs;
|
||||
indexReg = inst.rt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t tableAddress = 0;
|
||||
bool foundTableAddress = false;
|
||||
|
||||
if (adduIndex != -1)
|
||||
{
|
||||
for (int i = adduIndex - 1; i >= 0 && i >= adduIndex - 20; --i)
|
||||
{
|
||||
const auto &inst = instructions[i];
|
||||
if (inst.opcode == OPCODE_LUI)
|
||||
{
|
||||
if (inst.rt == tableBaseReg || inst.rt == indexReg)
|
||||
{
|
||||
uint32_t high = inst.immediate << 16;
|
||||
uint32_t low = 0;
|
||||
for (int j = i + 1; j < adduIndex; ++j)
|
||||
{
|
||||
const auto &lowInst = instructions[j];
|
||||
if (lowInst.rs == inst.rt && lowInst.rt == inst.rt)
|
||||
{
|
||||
if (lowInst.opcode == OPCODE_ADDIU)
|
||||
{
|
||||
low = (uint32_t)lowInst.simmediate;
|
||||
}
|
||||
else if (lowInst.opcode == OPCODE_ORI)
|
||||
{
|
||||
low = lowInst.immediate;
|
||||
}
|
||||
}
|
||||
}
|
||||
tableAddress = high + low;
|
||||
foundTableAddress = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (foundTableAddress)
|
||||
{
|
||||
tableAddress += lwOffset;
|
||||
|
||||
const auto configuredTableIt = m_configJumpTableTargetsByAddress.find(tableAddress);
|
||||
if (configuredTableIt != m_configJumpTableTargetsByAddress.end())
|
||||
{
|
||||
std::vector<uint32_t> jrTargets;
|
||||
jrTargets.reserve(configuredTableIt->second.size());
|
||||
for (uint32_t target : configuredTableIt->second)
|
||||
{
|
||||
if (target >= function.start && target < function.end &&
|
||||
instructionAddresses.contains(target))
|
||||
{
|
||||
jrTargets.push_back(target);
|
||||
}
|
||||
else
|
||||
{
|
||||
queueExternalEntryTarget(target);
|
||||
}
|
||||
}
|
||||
|
||||
if (!jrTargets.empty())
|
||||
{
|
||||
std::sort(jrTargets.begin(), jrTargets.end());
|
||||
jrTargets.erase(std::unique(jrTargets.begin(), jrTargets.end()), jrTargets.end());
|
||||
result.jumpTableTargets[jrInst->address] = jrTargets;
|
||||
for (uint32_t target : jrTargets)
|
||||
{
|
||||
result.entryPoints.insert(target);
|
||||
}
|
||||
foundTable = true;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t unshiftedIndexReg = 0;
|
||||
for (int i = adduIndex - 1; i >= 0 && i >= adduIndex - 10; --i)
|
||||
{
|
||||
const auto &inst = instructions[i];
|
||||
if (inst.opcode == OPCODE_SPECIAL && inst.function == SPECIAL_SLL && (inst.rd == tableBaseReg || inst.rd == indexReg))
|
||||
{
|
||||
unshiftedIndexReg = inst.rt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t numCases = 0;
|
||||
if (unshiftedIndexReg != 0)
|
||||
{
|
||||
for (int i = adduIndex - 1; i >= 0 && i >= adduIndex - 30; --i)
|
||||
{
|
||||
const auto &inst = instructions[i];
|
||||
if ((inst.opcode == OPCODE_SLTIU || inst.opcode == OPCODE_SLTI) && inst.rs == unshiftedIndexReg)
|
||||
{
|
||||
numCases = inst.immediate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundTable && numCases > 0 && numCases <= 1000)
|
||||
{
|
||||
const Section *rodata = nullptr;
|
||||
for (const auto &sec : m_sections)
|
||||
{
|
||||
if (tableAddress >= sec.address && tableAddress < sec.address + sec.size)
|
||||
{
|
||||
rodata = &sec;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (rodata && rodata->data)
|
||||
{
|
||||
std::vector<uint32_t> jrTargets;
|
||||
bool validJumpTable = true;
|
||||
std::unordered_set<uint32_t> uniqueTargets;
|
||||
for (uint32_t i = 0; i < numCases; ++i)
|
||||
{
|
||||
uint32_t addr = tableAddress + i * 4;
|
||||
if (addr >= rodata->address && addr + 4 <= rodata->address + rodata->size)
|
||||
{
|
||||
uint32_t target = 0;
|
||||
std::memcpy(&target, rodata->data + (addr - rodata->address), 4);
|
||||
if (target >= function.start && target < function.end && instructionAddresses.contains(target))
|
||||
{
|
||||
if (!uniqueTargets.contains(target))
|
||||
{
|
||||
jrTargets.push_back(target);
|
||||
uniqueTargets.insert(target);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
queueExternalEntryTarget(target);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
validJumpTable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (validJumpTable && !jrTargets.empty())
|
||||
{
|
||||
result.jumpTableTargets[jrInst->address] = jrTargets;
|
||||
for (uint32_t t : jrTargets)
|
||||
{
|
||||
result.entryPoints.insert(t);
|
||||
}
|
||||
foundTable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!foundTable)
|
||||
{
|
||||
needsIndirectFallback = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsIndirectFallback)
|
||||
{
|
||||
if (m_reporter)
|
||||
{
|
||||
std::vector<uint32_t> jumpAddresses;
|
||||
jumpAddresses.reserve(indirectJumps.size());
|
||||
for (const Instruction *jrInst : indirectJumps)
|
||||
{
|
||||
jumpAddresses.push_back(jrInst->address);
|
||||
}
|
||||
m_reporter->recordIndirectFallbackPromotion(function.name, jumpAddresses, instructionAddresses.size());
|
||||
}
|
||||
|
||||
for (uint32_t addr : instructionAddresses)
|
||||
{
|
||||
if (addr >= function.start && addr < function.end)
|
||||
{
|
||||
result.entryPoints.insert(addr);
|
||||
// Keep labels and runtime registration for unresolved JR/JALR targets
|
||||
// without emitting a local switch over every possible target.
|
||||
result.indirectFallbackEntryPoints.insert(addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user