fix: fixed entry slicing logic to avoid “same tail repeated” generation (#67)

This commit is contained in:
Ranieri
2026-02-18 23:34:27 -03:00
committed by GitHub
parent 8b1f4e00f2
commit 4334e74342
5 changed files with 488 additions and 201 deletions
@@ -32,6 +32,10 @@ namespace ps2recomp
void generateOutput();
static StubTarget resolveStubTarget(const std::string& name);
static size_t DiscoverAdditionalEntryPoints(
std::vector<Function> &functions,
std::unordered_map<uint32_t, std::vector<Instruction>> &decodedFunctions,
const std::vector<Section> &sections);
private:
ConfigManager m_configManager;
+326 -201
View File
@@ -14,6 +14,7 @@
#include <unordered_set>
#include <optional>
#include <limits>
#include <functional>
namespace fs = std::filesystem;
@@ -233,6 +234,283 @@ namespace ps2recomp
selector.name = trimmed;
return selector;
}
struct EntryDiscoveryStats
{
size_t discoveredCount = 0;
size_t passCount = 0;
};
EntryDiscoveryStats discoverAdditionalEntryPointsImpl(
std::vector<Function> &functions,
std::unordered_map<uint32_t, std::vector<Instruction>> &decodedFunctions,
const std::vector<Section> &sections,
const std::function<bool(Function &)> &decodeExternalFunction)
{
std::unordered_set<uint32_t> existingStarts;
for (const auto &function : functions)
{
existingStarts.insert(function.start);
}
auto isExecutableAddress = [&](uint32_t address) -> bool
{
for (const auto &section : sections)
{
if (!section.isCode)
{
continue;
}
if (address >= section.address && address < (section.address + section.size))
{
return true;
}
}
return false;
};
auto getStaticEntryTarget = [](const Instruction &inst) -> std::optional<uint32_t>
{
if (inst.opcode == OPCODE_J || inst.opcode == OPCODE_JAL)
{
return decodeAbsoluteJumpTarget(inst.address, inst.target);
}
if (inst.opcode == OPCODE_SPECIAL &&
(inst.function == SPECIAL_JR || inst.function == SPECIAL_JALR))
{
return std::nullopt;
}
return std::nullopt;
};
auto findContainingFunction = [&](uint32_t address) -> const Function *
{
const Function *best = nullptr;
for (const auto &function : functions)
{
if (address < function.start || address >= function.end)
{
continue;
}
if (!function.isRecompiled || function.isStub || function.isSkipped)
{
continue;
}
auto decodedIt = decodedFunctions.find(function.start);
if (decodedIt == 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;
};
EntryDiscoveryStats stats{};
while (true)
{
++stats.passCount;
struct PendingEntry
{
uint32_t target = 0;
std::optional<uint32_t> containingStart;
uint32_t containingEnd = 0;
};
std::vector<PendingEntry> pendingEntries;
std::vector<Function> newEntries;
std::unordered_set<uint32_t> pendingStarts;
for (const auto &function : functions)
{
if (!function.isRecompiled || function.isStub || function.isSkipped)
{
continue;
}
auto decodedIt = decodedFunctions.find(function.start);
if (decodedIt == decodedFunctions.end())
{
continue;
}
const auto &instructions = decodedIt->second;
for (const auto &inst : instructions)
{
auto targetOpt = getStaticEntryTarget(inst);
if (!targetOpt.has_value())
{
continue;
}
uint32_t target = targetOpt.value();
if ((target & 0x3) != 0 || !isExecutableAddress(target))
{
continue;
}
if (existingStarts.contains(target) || pendingStarts.contains(target))
{
continue;
}
const Function *containingFunction = findContainingFunction(target);
if (containingFunction && containingFunction->start == function.start)
{
// Internal branches within the same function are handled as labels/gotos and should not produce separate entry wrappers.
continue;
}
PendingEntry pending{};
pending.target = target;
if (containingFunction)
{
pending.containingStart = containingFunction->start;
pending.containingEnd = containingFunction->end;
}
pendingEntries.push_back(pending);
pendingStarts.insert(target);
}
}
if (pendingEntries.empty())
{
break;
}
std::sort(pendingEntries.begin(), pendingEntries.end(),
[](const PendingEntry &a, const PendingEntry &b)
{ return a.target < b.target; });
std::vector<uint32_t> boundaryStarts;
boundaryStarts.reserve(existingStarts.size() + pendingStarts.size());
boundaryStarts.insert(boundaryStarts.end(), existingStarts.begin(), existingStarts.end());
boundaryStarts.insert(boundaryStarts.end(), pendingStarts.begin(), pendingStarts.end());
std::sort(boundaryStarts.begin(), boundaryStarts.end());
boundaryStarts.erase(std::unique(boundaryStarts.begin(), boundaryStarts.end()), boundaryStarts.end());
auto findNextBoundaryStart = [&](uint32_t address) -> std::optional<uint32_t>
{
auto it = std::upper_bound(boundaryStarts.begin(), boundaryStarts.end(), address);
if (it == boundaryStarts.end())
{
return std::nullopt;
}
return *it;
};
std::unordered_set<uint32_t> successfulStarts;
for (const auto &pending : pendingEntries)
{
const uint32_t target = pending.target;
Function entryFunction;
std::stringstream name;
name << "entry_" << std::hex << target;
entryFunction.name = name.str();
entryFunction.start = target;
entryFunction.isStub = false;
entryFunction.isSkipped = false;
entryFunction.isRecompiled = true;
if (pending.containingStart.has_value())
{
auto containingDecodedIt = decodedFunctions.find(*pending.containingStart);
if (containingDecodedIt == decodedFunctions.end())
{
continue;
}
const auto &containingInstructions = containingDecodedIt->second;
auto sliceIt = std::find_if(containingInstructions.begin(), containingInstructions.end(),
[&](const Instruction &candidate)
{ return candidate.address == target; });
if (sliceIt == containingInstructions.end())
{
continue;
}
uint32_t sliceEndAddress = pending.containingEnd;
auto nextStartOpt = findNextBoundaryStart(target);
if (nextStartOpt.has_value() && nextStartOpt.value() < sliceEndAddress)
{
sliceEndAddress = nextStartOpt.value();
}
if (sliceEndAddress <= target)
{
continue;
}
auto sliceEndIt = std::find_if(sliceIt, containingInstructions.end(),
[&](const Instruction &candidate)
{ return candidate.address >= sliceEndAddress; });
if (sliceEndIt == sliceIt)
{
continue;
}
std::vector<Instruction> slicedInstructions(sliceIt, sliceEndIt);
decodedFunctions[target] = std::move(slicedInstructions);
entryFunction.end = sliceEndAddress;
}
else
{
auto nextStartOpt = findNextBoundaryStart(target);
if (!nextStartOpt.has_value() || nextStartOpt.value() <= target)
{
continue;
}
entryFunction.end = nextStartOpt.value();
if (!decodeExternalFunction(entryFunction))
{
continue;
}
}
newEntries.push_back(entryFunction);
successfulStarts.insert(target);
}
if (newEntries.empty())
{
break;
}
stats.discoveredCount += newEntries.size();
functions.insert(functions.end(), newEntries.begin(), newEntries.end());
existingStarts.insert(successfulStarts.begin(), successfulStarts.end());
std::sort(functions.begin(), functions.end(),
[](const Function &a, const Function &b)
{ return a.start < b.start; });
}
return stats;
}
}
PS2Recompiler::PS2Recompiler(const std::string &configPath)
@@ -481,6 +759,19 @@ namespace ps2recomp
{
sanitized = "func";
}
if (sanitized.rfind("entry_", 0) == 0)
{
std::stringstream expectedStartName;
expectedStartName << "entry_" << std::hex << function.start;
if (sanitized == expectedStartName.str())
{
std::stringstream entryName;
entryName << sanitized << "_0x" << std::hex << function.end;
return entryName.str();
}
}
std::stringstream ss;
ss << sanitized << "_0x" << std::hex << function.start;
return ss.str();
@@ -784,203 +1075,18 @@ namespace ps2recomp
void PS2Recompiler::discoverAdditionalEntryPoints()
{
std::unordered_set<uint32_t> existingStarts;
for (const auto &function : m_functions)
const EntryDiscoveryStats stats = discoverAdditionalEntryPointsImpl(
m_functions,
m_decodedFunctions,
m_sections,
[&](Function &entryFunction)
{ return decodeFunction(entryFunction); });
if (stats.discoveredCount > 0)
{
existingStarts.insert(function.start);
}
auto getStaticEntryTarget = [](const Instruction &inst) -> std::optional<uint32_t>
{
if (inst.opcode == OPCODE_J || inst.opcode == OPCODE_JAL)
{
return decodeAbsoluteJumpTarget(inst.address, inst.target);
}
if (inst.opcode == OPCODE_SPECIAL &&
(inst.function == SPECIAL_JR || inst.function == SPECIAL_JALR))
{
return std::nullopt;
}
return std::nullopt;
};
auto findContainingFunction = [&](uint32_t address) -> const Function *
{
const Function *best = nullptr;
for (const auto &function : m_functions)
{
if (address < function.start || address >= function.end)
{
continue;
}
if (!function.isRecompiled || function.isStub || function.isSkipped)
{
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;
};
auto findNextFunctionStart = [&](uint32_t address) -> std::optional<uint32_t>
{
uint32_t nextStart = std::numeric_limits<uint32_t>::max();
for (const auto &function : m_functions)
{
if (function.start > address && function.start < nextStart)
{
nextStart = function.start;
}
}
if (nextStart == std::numeric_limits<uint32_t>::max())
{
return std::nullopt;
}
return nextStart;
};
size_t totalDiscovered = 0;
size_t passCount = 0;
while (true)
{
++passCount;
std::vector<Function> newEntries;
for (const auto &function : m_functions)
{
if (!function.isRecompiled || function.isStub || function.isSkipped)
{
continue;
}
auto decodedIt = m_decodedFunctions.find(function.start);
if (decodedIt == m_decodedFunctions.end())
{
continue;
}
const auto &instructions = decodedIt->second;
for (const auto &inst : instructions)
{
auto targetOpt = getStaticEntryTarget(inst);
if (!targetOpt.has_value())
{
continue;
}
uint32_t target = targetOpt.value();
if ((target & 0x3) != 0 || !m_elfParser->isValidAddress(target))
{
continue;
}
if (existingStarts.contains(target))
{
continue;
}
const Function *containingFunction = findContainingFunction(target);
if (containingFunction && containingFunction->start == function.start)
{
// Internal branches within the same function are handled as labels/gotos and should not produce separate entry wrappers.
continue;
}
Function entryFunction;
std::stringstream name;
name << "entry_" << std::hex << target;
entryFunction.name = name.str();
entryFunction.start = target;
entryFunction.isStub = false;
entryFunction.isSkipped = false;
entryFunction.isRecompiled = true;
if (containingFunction)
{
auto containingDecodedIt = m_decodedFunctions.find(containingFunction->start);
if (containingDecodedIt == m_decodedFunctions.end())
{
continue;
}
const auto &containingInstructions = containingDecodedIt->second;
auto sliceIt = std::find_if(containingInstructions.begin(), containingInstructions.end(),
[&](const Instruction &candidate)
{ return candidate.address == target; });
if (sliceIt == containingInstructions.end())
{
continue;
}
std::vector<Instruction> slicedInstructions(sliceIt, containingInstructions.end());
m_decodedFunctions[target] = slicedInstructions;
entryFunction.end = containingFunction->end;
}
else
{
auto nextStartOpt = findNextFunctionStart(target);
if (!nextStartOpt.has_value() || nextStartOpt.value() <= target)
{
continue;
}
entryFunction.end = nextStartOpt.value();
if (!decodeFunction(entryFunction))
{
continue;
}
}
newEntries.push_back(entryFunction);
existingStarts.insert(target);
}
}
if (newEntries.empty())
{
break;
}
totalDiscovered += newEntries.size();
m_functions.insert(m_functions.end(), newEntries.begin(), newEntries.end());
std::sort(m_functions.begin(), m_functions.end(),
[](const Function &a, const Function &b)
{ return a.start < b.start; });
}
if (totalDiscovered > 0)
{
std::cout << "Discovered " << totalDiscovered
std::cout << "Discovered " << stats.discoveredCount
<< " additional entry point(s) inside existing functions across "
<< passCount << " pass(es)." << std::endl;
<< stats.passCount << " pass(es)." << std::endl;
}
}
@@ -1108,10 +1214,12 @@ namespace ps2recomp
std::filesystem::path PS2Recompiler::getOutputPath(const Function &function) const
{
std::string safeName;
bool usedRenamedName = false;
auto renameIt = m_functionRenames.find(function.start);
if (renameIt != m_functionRenames.end() && !renameIt->second.empty())
{
safeName = renameIt->second;
usedRenamedName = true;
}
else
{
@@ -1130,13 +1238,16 @@ namespace ps2recomp
safeName = ss.str();
}
std::stringstream suffix;
suffix << "_0x" << std::hex << function.start;
const std::string suffixText = suffix.str();
if (safeName.size() < suffixText.size() ||
safeName.compare(safeName.size() - suffixText.size(), suffixText.size(), suffixText) != 0)
if (!usedRenamedName)
{
safeName += suffixText;
std::stringstream suffix;
suffix << "_0x" << std::hex << function.start;
const std::string suffixText = suffix.str();
if (safeName.size() < suffixText.size() ||
safeName.compare(safeName.size() - suffixText.size(), suffixText.size(), suffixText) != 0)
{
safeName += suffixText;
}
}
std::filesystem::path outputPath = m_config.outputPath;
@@ -1166,6 +1277,20 @@ namespace ps2recomp
return sanitized;
}
size_t PS2Recompiler::DiscoverAdditionalEntryPoints(
std::vector<Function> &functions,
std::unordered_map<uint32_t, std::vector<Instruction>> &decodedFunctions,
const std::vector<Section> &sections)
{
const EntryDiscoveryStats stats = discoverAdditionalEntryPointsImpl(
functions,
decodedFunctions,
sections,
[](Function &)
{ return false; });
return stats.discoveredCount;
}
StubTarget PS2Recompiler::resolveStubTarget(const std::string &name)
{
if (!ps2_runtime_calls::resolveSyscallName(name).empty())
+1
View File
@@ -11,6 +11,7 @@ add_executable(ps2x_tests
src/r5900_decoder_tests.cpp
src/elf_analyzer_tests.cpp
src/ps2_runtime_io_tests.cpp
src/ps2_recompiler_tests.cpp
)
option(PRINT_GENERATED_CODE "Print generated code in tests" OFF)
+2
View File
@@ -4,6 +4,7 @@ void register_code_generator_tests();
void register_r5900_decoder_tests();
void register_elf_analyzer_tests();
void register_ps2_runtime_io_tests();
void register_ps2_recompiler_tests();
int main()
{
@@ -11,5 +12,6 @@ int main()
register_r5900_decoder_tests();
register_elf_analyzer_tests();
register_ps2_runtime_io_tests();
register_ps2_recompiler_tests();
return MiniTest::Run();
}
+155
View File
@@ -0,0 +1,155 @@
#include "MiniTest.h"
#include "ps2recomp/ps2_recompiler.h"
#include "ps2recomp/instructions.h"
#include "ps2recomp/types.h"
#include <algorithm>
#include <unordered_map>
#include <vector>
using namespace ps2recomp;
static Instruction makeNopLike(uint32_t address)
{
Instruction inst{};
inst.address = address;
inst.opcode = OPCODE_ADDIU;
inst.rt = 0;
inst.raw = 0;
return inst;
}
static Instruction makeAbsJump(uint32_t address, uint32_t target, uint32_t opcode)
{
Instruction inst{};
inst.address = address;
inst.opcode = opcode;
inst.target = (target >> 2) & 0x03FFFFFFu;
inst.hasDelaySlot = true;
inst.raw = (opcode << 26) | inst.target;
return inst;
}
static Function makeFunction(const std::string &name, uint32_t start, uint32_t end)
{
Function fn{};
fn.name = name;
fn.start = start;
fn.end = end;
fn.isRecompiled = true;
fn.isStub = false;
fn.isSkipped = false;
return fn;
}
void register_ps2_recompiler_tests()
{
MiniTest::Case("PS2Recompiler", [](TestCase &tc)
{
tc.Run("additional entries split at nearest discovered boundary", [](TestCase &t) {
std::vector<Section> sections = {
{".text", 0x1000u, 0x3000u, 0u, true, false, false, true, nullptr}
};
std::vector<Function> functions = {
makeFunction("container", 0x1000u, 0x1018u),
makeFunction("caller", 0x2000u, 0x2010u)
};
std::unordered_map<uint32_t, std::vector<Instruction>> decodedFunctions;
decodedFunctions[0x1000u] = {
makeNopLike(0x1000u),
makeNopLike(0x1004u),
makeNopLike(0x1008u),
makeNopLike(0x100Cu),
makeNopLike(0x1010u),
makeNopLike(0x1014u)
};
decodedFunctions[0x2000u] = {
makeAbsJump(0x2000u, 0x1008u, OPCODE_JAL),
makeNopLike(0x2004u),
makeAbsJump(0x2008u, 0x100Cu, OPCODE_J),
makeNopLike(0x200Cu)
};
size_t discovered = PS2Recompiler::DiscoverAdditionalEntryPoints(
functions, decodedFunctions, sections);
t.Equals(discovered, static_cast<size_t>(2),
"expected two additional entries to be discovered");
auto findByStart = [&](uint32_t start) -> const Function* {
auto it = std::find_if(functions.begin(), functions.end(),
[&](const Function &fn) { return fn.start == start; });
if (it == functions.end())
{
return nullptr;
}
return &(*it);
};
const Function *entry1008 = findByStart(0x1008u);
const Function *entry100C = findByStart(0x100Cu);
t.IsNotNull(entry1008, "entry at 0x1008 should exist");
t.IsNotNull(entry100C, "entry at 0x100C should exist");
if (entry1008 && entry100C)
{
t.Equals(entry1008->end, 0x100Cu,
"entry 0x1008 should end at nearest discovered start 0x100C");
t.Equals(entry100C->end, 0x1018u,
"entry 0x100C should end at containing function end");
}
auto decoded1008It = decodedFunctions.find(0x1008u);
auto decoded100CIt = decodedFunctions.find(0x100Cu);
t.IsTrue(decoded1008It != decodedFunctions.end(), "decoded slice for 0x1008 should exist");
t.IsTrue(decoded100CIt != decodedFunctions.end(), "decoded slice for 0x100C should exist");
if (decoded1008It != decodedFunctions.end())
{
t.Equals(decoded1008It->second.size(), static_cast<size_t>(1),
"entry 0x1008 slice should stop before 0x100C");
if (!decoded1008It->second.empty())
{
t.Equals(decoded1008It->second.front().address, 0x1008u,
"entry 0x1008 slice should begin at 0x1008");
}
}
if (decoded100CIt != decodedFunctions.end() && !decoded100CIt->second.empty())
{
t.Equals(decoded100CIt->second.front().address, 0x100Cu,
"entry 0x100C slice should begin at 0x100C");
}
});
tc.Run("non-executable section targets are ignored", [](TestCase &t) {
std::vector<Section> sections = {
{".text", 0x1000u, 0x2000u, 0u, true, false, false, true, nullptr},
{".data", 0x3000u, 0x1000u, 0u, false, true, false, false, nullptr}
};
std::vector<Function> functions = {
makeFunction("data_container", 0x3000u, 0x3010u),
makeFunction("caller", 0x1800u, 0x1810u)
};
std::unordered_map<uint32_t, std::vector<Instruction>> decodedFunctions;
decodedFunctions[0x3000u] = {
makeNopLike(0x3000u),
makeNopLike(0x3004u),
makeNopLike(0x3008u),
makeNopLike(0x300Cu)
};
decodedFunctions[0x1800u] = {
makeAbsJump(0x1800u, 0x3004u, OPCODE_J),
makeNopLike(0x1804u)
};
size_t discovered = PS2Recompiler::DiscoverAdditionalEntryPoints(
functions, decodedFunctions, sections);
t.Equals(discovered, static_cast<size_t>(0),
"non-executable targets should not produce additional entries");
const bool hasDataEntry = std::any_of(functions.begin(), functions.end(),
[](const Function &fn) { return fn.start == 0x3004u; });
t.IsFalse(hasDataEntry, "target in data section must not produce entry wrapper");
}); });
}