Feature/runtime ecosystem refactor (#107)

* feat: remove memory and pad from stub section

* feat: add support for resume entry targets in CodeGenerator (this allow jumps in address outside function)
feat: refactor entry point discovery one more try to reduce big generated file

* feat: optmizations for release build

* feat: remove unused  file

* feat: added some test cases for code gen

* feat: added log macro and remove win specific code

* feat: refactor runtime folder structure
feat: added reset sound driver RPC state and compatibility layout
feat: rename and added new test
feat: update RPC calls to use defined constants
feat: added more PSMC(16, 32)
feat: change cd read to try find the asset ignoring case sensitive
fix: fix some render problems
feat: add logs on pad
feat: added more RPC handles

* feat: added game override for code veronica

* feat: apply vita patch

* feat: flags to disable build

* feat: fix merges
feat: break a lot of tests

* feat: better throw error on empty cd path
feat: remove recompiler unusde function
feat: apply missing patch

* feat: gamedp is now part of lib
feat: missing file

* feat: small cleanup

* feat: missing vita changes

* feat: fix more merge

* feat: fix tests

* feat: last missing feature

* feat: added missing import

* feat: rename test local functions

* feat: init syscall on ps2 list

* feat: added DMA helpers

* feat: faster builds
feat: more implement for darkcloud

* feat: back missing file

* feat: added missing includes

* feat: remove test

* feat: missing include

* feat: read register funtion

* feat: build  fix

* feat: force  exit on detach thread
This commit is contained in:
Ranieri
2026-04-04 23:09:56 -03:00
committed by GitHub
parent 553a9027d8
commit 93e221feaa
137 changed files with 26594 additions and 15314 deletions
+178 -9
View File
@@ -153,6 +153,17 @@ namespace ps2recomp
}
}
void CodeGenerator::setResumeEntryTargets(const std::unordered_map<uint32_t, std::vector<uint32_t>> &resumeTargetsByOwner)
{
m_resumeEntryTargetsByOwner = resumeTargetsByOwner;
for (auto &[owner, targets] : m_resumeEntryTargetsByOwner)
{
(void)owner;
std::sort(targets.begin(), targets.end());
targets.erase(std::unique(targets.begin(), targets.end()), targets.end());
}
}
std::string CodeGenerator::getFunctionName(uint32_t address) const
{
auto it = m_renamedFunctions.find(address);
@@ -230,9 +241,14 @@ namespace ps2recomp
auto emitInternalTarget = [&](uint32_t target, uint32_t sourcePc, std::string_view indent)
{
ss << fmt::format("{}ctx->pc = 0x{:X}u;\n", indent, target);
if (target <= sourcePc)
const bool isCallLikeEdge =
(branchInst.opcode == OPCODE_JAL) ||
(branchInst.opcode == OPCODE_SPECIAL && branchInst.function == SPECIAL_JALR);
if (target <= sourcePc && !isCallLikeEdge)
{
ss << fmt::format("{}runtime->cooperativeGuestYield();\n", indent);
ss << fmt::format("{}if (runtime->shouldPreemptGuestExecution()) {{\n", indent);
ss << fmt::format("{} return;\n", indent);
ss << fmt::format("{}}}\n", indent);
ss << fmt::format("{}goto label_{:x};\n", indent, target);
}
else
@@ -652,7 +668,7 @@ namespace ps2recomp
CodeGenerator::~CodeGenerator() = default;
CodeGenerator::AnalysisResult CodeGenerator::collectInternalBranchTargets(
const Function &function, const std::vector<Instruction> &instructions)
const Function &function, const std::vector<Instruction> &instructions, const std::vector<Function> *allFunctions)
{
AnalysisResult result;
std::unordered_set<uint32_t> instructionAddresses;
@@ -660,6 +676,97 @@ namespace ps2recomp
bool hasIndirectRegisterJump = false;
std::vector<const Instruction*> indirectJumps;
auto isExecutableAddress = [&](uint32_t address) -> bool
{
for (const auto &section : 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);
@@ -685,6 +792,11 @@ namespace ps2recomp
instructionAddresses.contains(target))
{
result.entryPoints.insert(target);
queueLoopResumeEntryTarget(target, inst.address);
}
else
{
queueExternalEntryTarget(target);
}
}
else if (isStaticJump)
@@ -694,15 +806,20 @@ namespace ps2recomp
instructionAddresses.contains(target))
{
result.entryPoints.insert(target);
queueLoopResumeEntryTarget(target, inst.address);
if (inst.opcode == OPCODE_JAL)
{
uint32_t returnAddr = inst.address + 8;
if (returnAddr >= function.start && returnAddr < function.end &&
instructionAddresses.contains(returnAddr))
{
result.entryPoints.insert(returnAddr);
}
queueResumeEntryTarget(inst.address + 8u);
}
}
else
{
queueExternalEntryTarget(target);
if (inst.opcode == OPCODE_JAL)
{
queueResumeEntryTarget(inst.address + 8u);
}
}
}
@@ -712,6 +829,11 @@ namespace ps2recomp
{
bool needsJrFallback = false;
for (const Instruction* jrInst : indirectJumps) {
if (jrInst->function == SPECIAL_JALR)
{
queueResumeEntryTarget(jrInst->address + 8u);
}
bool foundTable = false;
uint32_t jrReg = jrInst->rs;
@@ -790,6 +912,10 @@ namespace ps2recomp
{
jrTargets.push_back(target);
}
else
{
queueExternalEntryTarget(target);
}
}
if (!jrTargets.empty())
@@ -849,6 +975,10 @@ namespace ps2recomp
uniqueTargets.insert(target);
}
}
else
{
queueExternalEntryTarget(target);
}
} else {
validJumpTable = false;
break;
@@ -909,6 +1039,20 @@ namespace ps2recomp
}
AnalysisResult analysisResult = collectInternalBranchTargets(function, instructions);
std::vector<uint32_t> resumeTargets(analysisResult.resumeEntryPoints.begin(),
analysisResult.resumeEntryPoints.end());
auto resumeIt = m_resumeEntryTargetsByOwner.find(function.start);
if (resumeIt != m_resumeEntryTargetsByOwner.end())
{
resumeTargets.insert(resumeTargets.end(), resumeIt->second.begin(), resumeIt->second.end());
}
std::sort(resumeTargets.begin(), resumeTargets.end());
resumeTargets.erase(std::unique(resumeTargets.begin(), resumeTargets.end()), resumeTargets.end());
for (uint32_t target : resumeTargets)
{
analysisResult.entryPoints.insert(target);
}
const std::unordered_set<uint32_t>& internalTargets = analysisResult.entryPoints;
ss << "// Function: " << function.name << "\n";
ss << "// Address: 0x" << std::hex << function.start << " - 0x" << function.end << std::dec << "\n";
@@ -926,6 +1070,16 @@ namespace ps2recomp
ss << " PS_LOG_ENTRY(\"" << sanitizedName << "\");\n";
ss << "#endif\n";
ss << "\n";
if (!resumeTargets.empty())
{
ss << " switch (ctx->pc) {\n";
for (uint32_t target : resumeTargets)
{
ss << " case 0x" << std::hex << target << "u: goto label_" << target << ";\n" << std::dec;
}
ss << " default: break;\n";
ss << " }\n\n";
}
ss << " ctx->pc = 0x" << std::hex << function.start << "u;\n"
<< std::dec;
ss << "\n";
@@ -3782,6 +3936,21 @@ namespace ps2recomp
emitRegistration(first, second);
}
ss << "\n // Register resumable entry points\n";
for (const auto &[ownerStart, targets] : m_resumeEntryTargetsByOwner)
{
const std::string ownerName = getFunctionName(ownerStart);
if (ownerName.empty())
{
continue;
}
for (uint32_t target : targets)
{
emitRegistration(target, ownerName);
}
}
ss << "\n // Register stub functions\n";
for (const auto &[first, second] : stubFunctions)
{
+166 -95
View File
@@ -241,16 +241,16 @@ namespace ps2recomp
size_t passCount = 0;
};
struct StaticEntryTarget
bool isEntryFunctionName(const std::string &name)
{
uint32_t target = 0u;
bool isCall = false;
};
return name.rfind("entry_", 0) == 0;
}
EntryDiscoveryStats discoverAdditionalEntryPointsImpl(
std::vector<Function> &functions,
std::unordered_map<uint32_t, std::vector<Instruction>> &decodedFunctions,
const std::vector<Section> &sections,
CodeGenerator *codeGenerator,
const std::function<bool(Function &)> &decodeExternalFunction)
{
std::unordered_set<uint32_t> existingStarts;
@@ -275,25 +275,6 @@ namespace ps2recomp
return false;
};
auto getStaticEntryTarget = [](const Instruction &inst) -> std::optional<StaticEntryTarget>
{
if (inst.opcode == OPCODE_J || inst.opcode == OPCODE_JAL)
{
StaticEntryTarget target{};
target.target = decodeAbsoluteJumpTarget(inst.address, inst.target);
target.isCall = (inst.opcode == OPCODE_JAL);
return target;
}
if (inst.opcode == OPCODE_SPECIAL &&
(inst.function == SPECIAL_JR || inst.function == SPECIAL_JALR))
{
return std::nullopt;
}
return std::nullopt;
};
auto isSimpleReturnThunkStart = [](const Instruction &inst) -> bool
{
return inst.opcode == OPCODE_SPECIAL &&
@@ -355,6 +336,30 @@ namespace ps2recomp
std::vector<Function> newEntries;
std::unordered_set<uint32_t> pendingStarts;
auto queuePendingEntry = [&](uint32_t target)
{
if (!isExecutableAddress(target))
{
return;
}
if (existingStarts.contains(target) || pendingStarts.contains(target))
{
return;
}
PendingEntry pending{};
pending.target = target;
if (const Function *containingFunction = findContainingFunction(target))
{
pending.containingStart = containingFunction->start;
pending.containingEnd = containingFunction->end;
}
pendingEntries.push_back(pending);
pendingStarts.insert(target);
};
for (const auto &function : functions)
{
if (!function.isRecompiled || function.isStub || function.isSkipped)
@@ -362,6 +367,11 @@ namespace ps2recomp
continue;
}
if (isEntryFunctionName(function.name))
{
continue;
}
auto decodedIt = decodedFunctions.find(function.start);
if (decodedIt == decodedFunctions.end())
{
@@ -369,60 +379,26 @@ namespace ps2recomp
}
const auto &instructions = decodedIt->second;
for (const auto &inst : instructions)
CodeGenerator::AnalysisResult analysisResult{};
if (codeGenerator)
{
auto targetOpt = getStaticEntryTarget(inst);
if (!targetOpt.has_value())
{
continue;
}
analysisResult = codeGenerator->collectInternalBranchTargets(
function, instructions, &functions);
}
else
{
analysisResult.resumeEntryPoints.clear();
analysisResult.externalEntryPoints.clear();
}
const StaticEntryTarget staticTarget = targetOpt.value();
const uint32_t target = staticTarget.target;
for (uint32_t target : analysisResult.externalEntryPoints)
{
queuePendingEntry(target);
}
if ((target & 0x3) != 0 || !isExecutableAddress(target))
{
continue;
}
if (existingStarts.contains(target) || pendingStarts.contains(target))
{
continue;
}
const bool targetInCurrentFunction = std::any_of(
instructions.begin(), instructions.end(),
[&](const Instruction &candidate)
{ return candidate.address == target; });
if (targetInCurrentFunction && !staticTarget.isCall)
{
// jumps with the current decoded function remain labels/gotos.
continue;
}
const Function *containingFunction = findContainingFunction(target);
if (targetInCurrentFunction)
{
PendingEntry pending{};
pending.target = target;
pending.containingStart = function.start;
pending.containingEnd = function.end;
pendingEntries.push_back(pending);
pendingStarts.insert(target);
continue;
}
PendingEntry pending{};
pending.target = target;
if (containingFunction)
{
pending.containingStart = containingFunction->start;
pending.containingEnd = containingFunction->end;
}
pendingEntries.push_back(pending);
pendingStarts.insert(target);
for (uint32_t target : analysisResult.resumeEntryPoints)
{
queuePendingEntry(target);
}
}
@@ -553,12 +529,6 @@ namespace ps2recomp
return stats;
}
bool isEntryFunctionName(const std::string &name)
{
return name.rfind("entry_", 0) == 0;
}
size_t resliceEntryFunctionsImpl(
std::vector<Function> &functions,
std::unordered_map<uint32_t, std::vector<Instruction>> &decodedFunctions)
@@ -1293,25 +1263,124 @@ namespace ps2recomp
void PS2Recompiler::discoverAdditionalEntryPoints()
{
const EntryDiscoveryStats stats = discoverAdditionalEntryPointsImpl(
m_functions,
m_decodedFunctions,
m_sections,
[&](Function &entryFunction)
{ return decodeFunction(entryFunction); });
if (stats.discoveredCount > 0)
m_resumeEntryTargetsByOwner.clear();
if (!m_codeGenerator)
{
std::cout << "Discovered " << stats.discoveredCount
<< " additional entry point(s) inside existing functions across "
<< stats.passCount << " pass(es)." << std::endl;
return;
}
const size_t reslicedCount = resliceEntryFunctionsImpl(m_functions, m_decodedFunctions);
if (reslicedCount > 0)
auto findContainingFunction = [&](uint32_t address) -> const Function *
{
std::cout << "Resliced " << reslicedCount
<< " entry function(s) after discovery." << std::endl;
const Function *best = nullptr;
for (const auto &function : m_functions)
{
if (!function.isRecompiled || function.isStub || function.isSkipped)
{
continue;
}
if (isEntryFunctionName(function.name))
{
continue;
}
if (address < function.start || address >= function.end)
{
continue;
}
auto decodedIt = m_decodedFunctions.find(function.start);
if (decodedIt == m_decodedFunctions.end())
{
continue;
}
const auto &decoded = decodedIt->second;
const bool hasAddress = std::any_of(decoded.begin(), decoded.end(),
[&](const Instruction &candidate)
{ return candidate.address == address; });
if (!hasAddress)
{
continue;
}
if (!best || function.start > best->start)
{
best = &function;
}
}
return best;
};
for (const auto &function : m_functions)
{
if (!function.isRecompiled || function.isStub || function.isSkipped)
{
continue;
}
if (isEntryFunctionName(function.name))
{
continue;
}
auto decodedIt = m_decodedFunctions.find(function.start);
if (decodedIt == m_decodedFunctions.end())
{
continue;
}
const auto &instructions = decodedIt->second;
CodeGenerator::AnalysisResult analysisResult =
m_codeGenerator->collectInternalBranchTargets(function, instructions, &m_functions);
auto &ownerTargets = m_resumeEntryTargetsByOwner[function.start];
ownerTargets.insert(ownerTargets.end(),
analysisResult.resumeEntryPoints.begin(),
analysisResult.resumeEntryPoints.end());
for (uint32_t target : analysisResult.externalEntryPoints)
{
const Function *owner = findContainingFunction(target);
if (!owner)
{
continue;
}
if (owner->start == target)
{
continue;
}
auto &targets = m_resumeEntryTargetsByOwner[owner->start];
targets.push_back(target);
}
}
size_t totalTargets = 0u;
for (auto it = m_resumeEntryTargetsByOwner.begin(); it != m_resumeEntryTargetsByOwner.end();)
{
auto &targets = it->second;
std::sort(targets.begin(), targets.end());
targets.erase(std::unique(targets.begin(), targets.end()), targets.end());
if (targets.empty())
{
it = m_resumeEntryTargetsByOwner.erase(it);
continue;
}
totalTargets += targets.size();
++it;
}
m_codeGenerator->setResumeEntryTargets(m_resumeEntryTargetsByOwner);
if (totalTargets > 0u)
{
std::cout << "Collected " << totalTargets
<< " resumable entry point(s) across "
<< m_resumeEntryTargetsByOwner.size()
<< " owner function(s)." << std::endl;
}
}
@@ -1542,10 +1611,12 @@ namespace ps2recomp
std::unordered_map<uint32_t, std::vector<Instruction>> &decodedFunctions,
const std::vector<Section> &sections)
{
CodeGenerator codeGenerator({}, sections);
const EntryDiscoveryStats stats = discoverAdditionalEntryPointsImpl(
functions,
decodedFunctions,
sections,
&codeGenerator,
[](Function &)
{ return false; });
return stats.discoveredCount;