mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-10-01 18:31:21 -04:00
Reduce recompiler output memory usage, and added multi-threading to recomp process (#128)
* feat(recomp): Reduce recompiler output memory usage Stream output generation, add low-memory config controls, and avoid pathological indirect-jump switch expansion in generated C++. Low-memory mode now avoids retaining per-instruction disassembly strings while still emitting asm comments during output generation. Output workers are bounded/configurable, combined output is streamed, and decoded buffers are released after generation. Also document the new output memory settings. * fix(recomp): added tests for unregistered JR/JALR, updated fallback logic to cover JR/JALR, moved Rabbitizer formatting into R5900Decoder
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
#include "ps2recomp/code_generator.h"
|
||||
#include "ps2recomp/instructions.h"
|
||||
#include "ps2recomp/ps2_recompiler.h"
|
||||
#include "ps2recomp/r5900_decoder.h"
|
||||
#include "ps2recomp/types.h"
|
||||
#include "ps2_runtime_calls.h"
|
||||
#include <fmt/format.h>
|
||||
@@ -12,6 +13,7 @@
|
||||
#include <iostream>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
@@ -164,6 +166,11 @@ namespace ps2recomp
|
||||
}
|
||||
}
|
||||
|
||||
void CodeGenerator::setEmitInstructionComments(bool emitInstructionComments)
|
||||
{
|
||||
m_emitInstructionComments = emitInstructionComments;
|
||||
}
|
||||
|
||||
std::string CodeGenerator::getFunctionName(uint32_t address) const
|
||||
{
|
||||
auto it = m_renamedFunctions.find(address);
|
||||
@@ -223,11 +230,16 @@ namespace ps2recomp
|
||||
std::string delaySlotSuffix = "";
|
||||
if (hasValidDelaySlot) {
|
||||
delaySlotPrefix = "ctx->in_delay_slot = true; ctx->branch_pc = 0x" + fmt::format("{:X}", branchInst.address) + "u;\n ";
|
||||
delaySlotCode = " // 0x" + fmt::format("{:x}", delaySlot.address) + ": 0x" + fmt::format("{:x}", delaySlot.raw);
|
||||
if (!delaySlot.disassembly.empty()) {
|
||||
delaySlotCode += " " + delaySlot.disassembly;
|
||||
if (m_emitInstructionComments)
|
||||
{
|
||||
delaySlotCode = " // 0x" + fmt::format("{:x}", delaySlot.address) + ": 0x" + fmt::format("{:x}", delaySlot.raw);
|
||||
std::string disassembly = R5900Decoder::disassembleInstruction(delaySlot);
|
||||
if (!disassembly.empty()) {
|
||||
delaySlotCode += " " + disassembly;
|
||||
}
|
||||
delaySlotCode += " (Delay Slot)\n ";
|
||||
}
|
||||
delaySlotCode += " (Delay Slot)\n " + translateInstruction(delaySlot);
|
||||
delaySlotCode += translateInstruction(delaySlot);
|
||||
delaySlotSuffix = "\n ctx->in_delay_slot = false;";
|
||||
}
|
||||
|
||||
@@ -259,20 +271,18 @@ namespace ps2recomp
|
||||
|
||||
std::vector<uint32_t> sortedInternalTargets;
|
||||
if (branchInst.opcode == OPCODE_SPECIAL &&
|
||||
(branchInst.function == SPECIAL_JR || branchInst.function == SPECIAL_JALR) &&
|
||||
((branchInst.function == SPECIAL_JR && branchInst.rs != 31) ||
|
||||
branchInst.function == SPECIAL_JALR) &&
|
||||
!internalTargets.empty())
|
||||
{
|
||||
// Only emit local indirect-jump switches for jump tables we actually resolved.
|
||||
// Falling back to every internal target here can duplicate huge switches at each
|
||||
// indirect branch. Unresolved JR/JALR targets are registered as resumable entries
|
||||
// instead, so runtime dispatch can re-enter this function at ctx->pc.
|
||||
auto jtIt = analysisResult.jumpTableTargets.find(branchInst.address);
|
||||
if (jtIt != analysisResult.jumpTableTargets.end()) {
|
||||
sortedInternalTargets = jtIt->second;
|
||||
std::sort(sortedInternalTargets.begin(), sortedInternalTargets.end());
|
||||
} else {
|
||||
sortedInternalTargets.reserve(internalTargets.size());
|
||||
for (uint32_t t : internalTargets)
|
||||
{
|
||||
sortedInternalTargets.push_back(t);
|
||||
}
|
||||
std::sort(sortedInternalTargets.begin(), sortedInternalTargets.end());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -827,7 +837,7 @@ namespace ps2recomp
|
||||
|
||||
if (hasIndirectRegisterJump)
|
||||
{
|
||||
bool needsJrFallback = false;
|
||||
bool needsIndirectFallback = false;
|
||||
for (const Instruction* jrInst : indirectJumps) {
|
||||
if (jrInst->function == SPECIAL_JALR)
|
||||
{
|
||||
@@ -997,19 +1007,19 @@ namespace ps2recomp
|
||||
}
|
||||
}
|
||||
if (!foundTable) {
|
||||
if (!(jrInst->function == SPECIAL_JALR))
|
||||
{
|
||||
needsJrFallback = true;
|
||||
}
|
||||
needsIndirectFallback = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsJrFallback) {
|
||||
if (needsIndirectFallback) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1093,11 +1103,15 @@ namespace ps2recomp
|
||||
ss << "label_" << std::hex << inst.address << std::dec << ":\n";
|
||||
}
|
||||
|
||||
ss << " // 0x" << std::hex << inst.address << ": 0x" << inst.raw << std::dec;
|
||||
if (!inst.disassembly.empty()) {
|
||||
ss << " " << inst.disassembly;
|
||||
if (m_emitInstructionComments)
|
||||
{
|
||||
ss << " // 0x" << std::hex << inst.address << ": 0x" << inst.raw << std::dec;
|
||||
std::string disassembly = R5900Decoder::disassembleInstruction(inst);
|
||||
if (!disassembly.empty()) {
|
||||
ss << " " << disassembly;
|
||||
}
|
||||
ss << "\n";
|
||||
}
|
||||
ss << "\n";
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <thread>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
@@ -29,6 +32,21 @@ namespace ps2recomp
|
||||
config.ghidraMapPath = toml::find_or<std::string>(general, "ghidra_output", "");
|
||||
config.outputPath = toml::find<std::string>(general, "output");
|
||||
config.singleFileOutput = toml::find_or<bool>(general, "single_file_output", false);
|
||||
config.lowMemoryMode = toml::find_or<bool>(general, "low_memory_mode", config.lowMemoryMode);
|
||||
const int64_t configuredOutputWorkers = toml::find_or<int64_t>(
|
||||
general,
|
||||
"output_worker_threads",
|
||||
toml::find_or<int64_t>(general, "output_worker_thread", config.outputWorkerThreads));
|
||||
const int64_t clampedOutputWorkers = std::clamp<int64_t>(
|
||||
configuredOutputWorkers,
|
||||
0,
|
||||
std::thread::hardware_concurrency() * 2);
|
||||
if (configuredOutputWorkers != clampedOutputWorkers)
|
||||
{
|
||||
std::cerr << "Warning: output_worker_threads value " << configuredOutputWorkers
|
||||
<< " is out of range; clamped to " << clampedOutputWorkers << "." << std::endl;
|
||||
}
|
||||
config.outputWorkerThreads = static_cast<uint32_t>(clampedOutputWorkers);
|
||||
config.patchSyscalls = toml::find_or<bool>(general, "patch_syscalls", config.patchSyscalls);
|
||||
config.patchCop0 = toml::find_or<bool>(general, "patch_cop0", config.patchCop0);
|
||||
config.patchCache = toml::find_or<bool>(general, "patch_cache", config.patchCache);
|
||||
@@ -234,6 +252,8 @@ namespace ps2recomp
|
||||
general["ghidra_output"] = config.ghidraMapPath;
|
||||
general["output"] = config.outputPath;
|
||||
general["single_file_output"] = config.singleFileOutput;
|
||||
general["low_memory_mode"] = config.lowMemoryMode;
|
||||
general["output_worker_threads"] = static_cast<int64_t>(config.outputWorkerThreads);
|
||||
general["patch_syscalls"] = config.patchSyscalls;
|
||||
general["patch_cop0"] = config.patchCop0;
|
||||
general["patch_cache"] = config.patchCache;
|
||||
|
||||
@@ -11,10 +11,15 @@
|
||||
#include <stdexcept>
|
||||
#include <filesystem>
|
||||
#include <cctype>
|
||||
#include <condition_variable>
|
||||
#include <exception>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <unordered_set>
|
||||
#include <optional>
|
||||
#include <limits>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@@ -77,6 +82,36 @@ namespace ps2recomp
|
||||
return function.isRecompiled || function.isStub || function.isSkipped;
|
||||
}
|
||||
|
||||
size_t resolveOutputWorkerCount(uint32_t configuredWorkerCount)
|
||||
{
|
||||
if (configuredWorkerCount > 0)
|
||||
{
|
||||
return configuredWorkerCount;
|
||||
}
|
||||
|
||||
const unsigned int hardwareWorkers = std::thread::hardware_concurrency();
|
||||
if (hardwareWorkers >= 2)
|
||||
{
|
||||
return static_cast<size_t>(hardwareWorkers - 1);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void writeCombinedOutputPreamble(std::ostream &output)
|
||||
{
|
||||
output << "#include \"ps2_recompiled_functions.h\"\n\n";
|
||||
output << "#include \"ps2_runtime_macros.h\"\n";
|
||||
output << "#include \"ps2_runtime.h\"\n";
|
||||
output << "#include \"ps2_recompiled_stubs.h\"\n";
|
||||
output << "#include \"ps2_syscalls.h\"\n";
|
||||
output << "#include \"ps2_stubs.h\"\n";
|
||||
output << "#ifdef _DEBUG\n";
|
||||
output << "#include \"ps2_log.h\"\n";
|
||||
output << "#endif\n";
|
||||
output << "\n";
|
||||
}
|
||||
|
||||
enum class PatchClass
|
||||
{
|
||||
Generic,
|
||||
@@ -850,6 +885,7 @@ namespace ps2recomp
|
||||
m_codeGenerator->setRelocationCallNames(relocationCallNames);
|
||||
m_codeGenerator->setBootstrapInfo(m_bootstrapInfo);
|
||||
m_codeGenerator->setConfiguredJumpTables(m_config.jumpTables);
|
||||
m_codeGenerator->setEmitInstructionComments(true);
|
||||
|
||||
fs::create_directories(m_config.outputPath);
|
||||
|
||||
@@ -1050,110 +1086,451 @@ namespace ps2recomp
|
||||
|
||||
generateFunctionHeader();
|
||||
|
||||
std::vector<const Function *> outputFunctions;
|
||||
outputFunctions.reserve(m_functions.size());
|
||||
for (const auto &function : m_functions)
|
||||
{
|
||||
if (shouldGenerateCodeForFunction(function))
|
||||
{
|
||||
outputFunctions.push_back(&function);
|
||||
}
|
||||
}
|
||||
|
||||
const size_t outputWorkerCount = m_config.lowMemoryMode ? 1 : resolveOutputWorkerCount(m_config.outputWorkerThreads);
|
||||
if (outputFunctions.size() > 1 && outputWorkerCount > 1)
|
||||
{
|
||||
std::cout << "Generating function output with " << outputWorkerCount << " worker(s)." << std::endl;
|
||||
}
|
||||
|
||||
const auto &generatedStubs = m_generatedStubs;
|
||||
const auto &decodedFunctions = m_decodedFunctions;
|
||||
|
||||
auto generateFunctionCode = [&](CodeGenerator &codeGenerator, const Function &function, bool useHeaders) -> std::string
|
||||
{
|
||||
try
|
||||
{
|
||||
if (function.isStub || function.isSkipped)
|
||||
{
|
||||
if (!useHeaders)
|
||||
{
|
||||
return generatedStubs.at(function.start);
|
||||
}
|
||||
|
||||
std::stringstream stubFile;
|
||||
stubFile << "#include \"ps2_runtime.h\"\n";
|
||||
stubFile << "#include \"ps2_syscalls.h\"\n";
|
||||
stubFile << "#include \"ps2_stubs.h\"\n";
|
||||
stubFile << "#ifdef _DEBUG\n";
|
||||
stubFile << "#include \"ps2_log.h\"\n";
|
||||
stubFile << "#endif\n";
|
||||
stubFile << "\n";
|
||||
stubFile << generatedStubs.at(function.start) << "\n";
|
||||
return stubFile.str();
|
||||
}
|
||||
|
||||
const auto &instructions = decodedFunctions.at(function.start);
|
||||
return codeGenerator.generateFunction(function, instructions, useHeaders);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::cerr << "Error generating code for function "
|
||||
<< function.name << " (start 0x"
|
||||
<< std::hex << function.start << std::dec << "): "
|
||||
<< e.what() << std::endl;
|
||||
throw;
|
||||
}
|
||||
};
|
||||
|
||||
if (m_config.singleFileOutput)
|
||||
{
|
||||
std::stringstream combinedOutput;
|
||||
|
||||
combinedOutput << "#include \"ps2_recompiled_functions.h\"\n\n";
|
||||
combinedOutput << "#include \"ps2_runtime_macros.h\"\n";
|
||||
combinedOutput << "#include \"ps2_runtime.h\"\n";
|
||||
combinedOutput << "#include \"ps2_recompiled_stubs.h\"\n";
|
||||
combinedOutput << "#include \"ps2_syscalls.h\"\n";
|
||||
combinedOutput << "#include \"ps2_stubs.h\"\n";
|
||||
combinedOutput << "#ifdef _DEBUG\n";
|
||||
combinedOutput << "#include \"ps2_log.h\"\n";
|
||||
combinedOutput << "#endif\n";
|
||||
combinedOutput << "\n";
|
||||
|
||||
for (const auto &function : m_functions)
|
||||
fs::path outputPath = fs::path(m_config.outputPath) / "ps2_recompiled_functions.cpp";
|
||||
std::ofstream combinedOutput(outputPath);
|
||||
if (!combinedOutput)
|
||||
{
|
||||
if (!shouldGenerateCodeForFunction(function))
|
||||
throw std::runtime_error("Failed to open combined output: " + outputPath.string());
|
||||
}
|
||||
|
||||
writeCombinedOutputPreamble(combinedOutput);
|
||||
|
||||
if (outputWorkerCount <= 1)
|
||||
{
|
||||
for (const Function *function : outputFunctions)
|
||||
{
|
||||
continue;
|
||||
combinedOutput << generateFunctionCode(*m_codeGenerator, *function, false) << "\n\n";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
struct CompletedCode
|
||||
{
|
||||
size_t outputIndex = 0;
|
||||
std::string code;
|
||||
};
|
||||
|
||||
std::mutex outputMutex;
|
||||
std::condition_variable workAvailable;
|
||||
std::condition_variable resultAvailable;
|
||||
std::queue<size_t> pendingWork;
|
||||
std::queue<CompletedCode> readyCode;
|
||||
std::unordered_map<size_t, std::string> completedCode;
|
||||
std::exception_ptr workerException;
|
||||
bool stopWorkers = false;
|
||||
size_t outstandingWork = 0;
|
||||
size_t nextFunction = 0;
|
||||
size_t nextOutputIndex = 0;
|
||||
const size_t maxBufferedOutput = std::max<size_t>(outputWorkerCount * 2, outputWorkerCount + 1);
|
||||
|
||||
auto workerMain = [&]()
|
||||
{
|
||||
CodeGenerator generator(*m_codeGenerator);
|
||||
while (true)
|
||||
{
|
||||
size_t outputIndex = 0;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(outputMutex);
|
||||
workAvailable.wait(lock, [&]()
|
||||
{ return stopWorkers || !pendingWork.empty(); });
|
||||
if (stopWorkers)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
outputIndex = pendingWork.front();
|
||||
pendingWork.pop();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
std::string code = generateFunctionCode(generator, *outputFunctions[outputIndex], false);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(outputMutex);
|
||||
readyCode.push(CompletedCode{outputIndex, std::move(code)});
|
||||
--outstandingWork;
|
||||
}
|
||||
resultAvailable.notify_one();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(outputMutex);
|
||||
if (!workerException)
|
||||
{
|
||||
workerException = std::current_exception();
|
||||
}
|
||||
--outstandingWork;
|
||||
stopWorkers = true;
|
||||
}
|
||||
resultAvailable.notify_one();
|
||||
workAvailable.notify_all();
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<std::thread> workers;
|
||||
workers.reserve(outputWorkerCount);
|
||||
for (size_t i = 0; i < outputWorkerCount; ++i)
|
||||
{
|
||||
workers.emplace_back(workerMain);
|
||||
}
|
||||
|
||||
auto stopAndJoinWorkers = [&]()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(outputMutex);
|
||||
stopWorkers = true;
|
||||
}
|
||||
workAvailable.notify_all();
|
||||
for (std::thread &worker : workers)
|
||||
{
|
||||
if (worker.joinable())
|
||||
{
|
||||
worker.join();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto scheduleAvailableWork = [&]()
|
||||
{
|
||||
bool scheduledAny = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(outputMutex);
|
||||
while (nextFunction < outputFunctions.size() &&
|
||||
outstandingWork + completedCode.size() < maxBufferedOutput &&
|
||||
!stopWorkers)
|
||||
{
|
||||
pendingWork.push(nextFunction++);
|
||||
++outstandingWork;
|
||||
scheduledAny = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (scheduledAny)
|
||||
{
|
||||
workAvailable.notify_all();
|
||||
}
|
||||
};
|
||||
|
||||
auto flushCompletedOutput = [&]()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
auto completedIt = completedCode.find(nextOutputIndex);
|
||||
if (completedIt == completedCode.end())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
combinedOutput << completedIt->second << "\n\n";
|
||||
completedCode.erase(completedIt);
|
||||
++nextOutputIndex;
|
||||
|
||||
if (!combinedOutput)
|
||||
{
|
||||
throw std::runtime_error("Failed while writing combined output: " + outputPath.string());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
if (function.isStub || function.isSkipped)
|
||||
scheduleAvailableWork();
|
||||
|
||||
while (nextOutputIndex < outputFunctions.size())
|
||||
{
|
||||
combinedOutput << m_generatedStubs.at(function.start) << "\n\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto &instructions = m_decodedFunctions.at(function.start);
|
||||
std::string code = m_codeGenerator->generateFunction(function, instructions, false);
|
||||
combinedOutput << code << "\n\n";
|
||||
std::unique_lock<std::mutex> lock(outputMutex);
|
||||
resultAvailable.wait(lock, [&]()
|
||||
{ return workerException || !readyCode.empty() || (outstandingWork == 0 && nextFunction >= outputFunctions.size()); });
|
||||
|
||||
if (workerException)
|
||||
{
|
||||
lock.unlock();
|
||||
stopAndJoinWorkers();
|
||||
std::rethrow_exception(workerException);
|
||||
}
|
||||
|
||||
while (!readyCode.empty())
|
||||
{
|
||||
CompletedCode completed = std::move(readyCode.front());
|
||||
readyCode.pop();
|
||||
completedCode.emplace(completed.outputIndex, std::move(completed.code));
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
flushCompletedOutput();
|
||||
scheduleAvailableWork();
|
||||
|
||||
if (nextOutputIndex >= outputFunctions.size())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> finalLock(outputMutex);
|
||||
if (outstandingWork == 0 && nextFunction >= outputFunctions.size() && completedCode.empty())
|
||||
{
|
||||
throw std::runtime_error("Internal error: combined output completion queue is missing index " + std::to_string(nextOutputIndex));
|
||||
}
|
||||
}
|
||||
|
||||
stopAndJoinWorkers();
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
catch (...)
|
||||
{
|
||||
std::cerr << "Error generating code for function "
|
||||
<< function.name << " (start 0x"
|
||||
<< std::hex << function.start << "): "
|
||||
<< e.what() << std::endl;
|
||||
stopAndJoinWorkers();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
fs::path outputPath = fs::path(m_config.outputPath) / "ps2_recompiled_functions.cpp";
|
||||
if (!writeToFile(outputPath.string(), combinedOutput.str()))
|
||||
combinedOutput.close();
|
||||
if (!combinedOutput)
|
||||
{
|
||||
throw std::runtime_error("Failed to write combined output: " + outputPath.string());
|
||||
throw std::runtime_error("Failed to finish combined output: " + outputPath.string());
|
||||
}
|
||||
|
||||
std::cout << "Wrote recompiled to combined output to: " << outputPath << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto &function : m_functions)
|
||||
struct GeneratedFile
|
||||
{
|
||||
if (!shouldGenerateCodeForFunction(function))
|
||||
fs::path outputPath;
|
||||
std::string code;
|
||||
};
|
||||
|
||||
std::vector<fs::path> outputPaths;
|
||||
outputPaths.reserve(outputFunctions.size());
|
||||
for (const Function *function : outputFunctions)
|
||||
{
|
||||
outputPaths.push_back(getOutputPath(*function));
|
||||
}
|
||||
|
||||
auto generateFile = [&](CodeGenerator &codeGenerator, size_t outputIndex) -> GeneratedFile
|
||||
{
|
||||
return GeneratedFile{outputPaths[outputIndex], generateFunctionCode(codeGenerator, *outputFunctions[outputIndex], true)};
|
||||
};
|
||||
|
||||
auto writeGeneratedFile = [&](GeneratedFile generated)
|
||||
{
|
||||
fs::create_directories(generated.outputPath.parent_path());
|
||||
if (!writeToFile(generated.outputPath.string(), generated.code))
|
||||
{
|
||||
continue;
|
||||
throw std::runtime_error("Failed to write function output: " + generated.outputPath.string());
|
||||
}
|
||||
};
|
||||
|
||||
if (outputWorkerCount <= 1)
|
||||
{
|
||||
for (size_t outputIndex = 0; outputIndex < outputFunctions.size(); ++outputIndex)
|
||||
{
|
||||
writeGeneratedFile(generateFile(*m_codeGenerator, outputIndex));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::mutex outputMutex;
|
||||
std::condition_variable workAvailable;
|
||||
std::condition_variable resultAvailable;
|
||||
std::queue<size_t> pendingWork;
|
||||
std::queue<GeneratedFile> readyFiles;
|
||||
std::exception_ptr workerException;
|
||||
bool stopWorkers = false;
|
||||
size_t outstandingWork = 0;
|
||||
size_t nextFunction = 0;
|
||||
const size_t maxBufferedOutput = std::max<size_t>(outputWorkerCount * 2, outputWorkerCount + 1);
|
||||
|
||||
auto workerMain = [&]()
|
||||
{
|
||||
CodeGenerator generator(*m_codeGenerator);
|
||||
while (true)
|
||||
{
|
||||
size_t outputIndex = 0;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(outputMutex);
|
||||
workAvailable.wait(lock, [&]()
|
||||
{ return stopWorkers || !pendingWork.empty(); });
|
||||
if (stopWorkers)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
outputIndex = pendingWork.front();
|
||||
pendingWork.pop();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
GeneratedFile generated = generateFile(generator, outputIndex);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(outputMutex);
|
||||
readyFiles.push(std::move(generated));
|
||||
--outstandingWork;
|
||||
}
|
||||
resultAvailable.notify_one();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(outputMutex);
|
||||
if (!workerException)
|
||||
{
|
||||
workerException = std::current_exception();
|
||||
}
|
||||
--outstandingWork;
|
||||
stopWorkers = true;
|
||||
}
|
||||
resultAvailable.notify_one();
|
||||
workAvailable.notify_all();
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<std::thread> workers;
|
||||
workers.reserve(outputWorkerCount);
|
||||
for (size_t i = 0; i < outputWorkerCount; ++i)
|
||||
{
|
||||
workers.emplace_back(workerMain);
|
||||
}
|
||||
|
||||
std::string code;
|
||||
auto stopAndJoinWorkers = [&]()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(outputMutex);
|
||||
stopWorkers = true;
|
||||
}
|
||||
workAvailable.notify_all();
|
||||
for (std::thread &worker : workers)
|
||||
{
|
||||
if (worker.joinable())
|
||||
{
|
||||
worker.join();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto scheduleAvailableWork = [&]()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(outputMutex);
|
||||
while (nextFunction < outputFunctions.size() &&
|
||||
outstandingWork < maxBufferedOutput &&
|
||||
!stopWorkers)
|
||||
{
|
||||
pendingWork.push(nextFunction++);
|
||||
++outstandingWork;
|
||||
}
|
||||
}
|
||||
workAvailable.notify_all();
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
if (function.isStub || function.isSkipped)
|
||||
{
|
||||
std::stringstream stubFile;
|
||||
stubFile << "#include \"ps2_runtime.h\"\n";
|
||||
stubFile << "#include \"ps2_syscalls.h\"\n";
|
||||
stubFile << "#include \"ps2_stubs.h\"\n";
|
||||
stubFile << "#ifdef _DEBUG\n";
|
||||
stubFile << "#include \"ps2_log.h\"\n";
|
||||
stubFile << "#endif\n";
|
||||
stubFile << "\n";
|
||||
stubFile << m_generatedStubs.at(function.start) << "\n";
|
||||
code = stubFile.str();
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto &instructions = m_decodedFunctions.at(function.start);
|
||||
code = m_codeGenerator->generateFunction(function, instructions, true);
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::cerr << "Error generating code for function "
|
||||
<< function.name << " (start 0x"
|
||||
<< std::hex << function.start << "): "
|
||||
<< e.what() << std::endl;
|
||||
throw;
|
||||
}
|
||||
scheduleAvailableWork();
|
||||
|
||||
fs::path outputPath = getOutputPath(function);
|
||||
fs::create_directories(outputPath.parent_path());
|
||||
if (!writeToFile(outputPath.string(), code))
|
||||
size_t writtenCount = 0;
|
||||
while (writtenCount < outputFunctions.size())
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(outputMutex);
|
||||
resultAvailable.wait(lock, [&]()
|
||||
{ return workerException || !readyFiles.empty() || (outstandingWork == 0 && nextFunction >= outputFunctions.size()); });
|
||||
|
||||
if (workerException)
|
||||
{
|
||||
lock.unlock();
|
||||
stopAndJoinWorkers();
|
||||
std::rethrow_exception(workerException);
|
||||
}
|
||||
|
||||
if (readyFiles.empty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
GeneratedFile generated = std::move(readyFiles.front());
|
||||
readyFiles.pop();
|
||||
lock.unlock();
|
||||
|
||||
writeGeneratedFile(std::move(generated));
|
||||
++writtenCount;
|
||||
scheduleAvailableWork();
|
||||
}
|
||||
|
||||
stopAndJoinWorkers();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
throw std::runtime_error("Failed to write function output: " + outputPath.string());
|
||||
stopAndJoinWorkers();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "Wrote individual function files to: " << m_config.outputPath << std::endl;
|
||||
}
|
||||
|
||||
m_decodedFunctions.clear();
|
||||
|
||||
std::string registerFunctions = m_codeGenerator->generateFunctionRegistration(m_functions, m_generatedStubs);
|
||||
m_generatedStubs.clear();
|
||||
|
||||
fs::path registerPath = fs::path(m_config.outputPath) / "register_functions.cpp";
|
||||
if (!writeToFile(registerPath.string(), registerFunctions))
|
||||
@@ -1338,6 +1715,9 @@ namespace ps2recomp
|
||||
ownerTargets.insert(ownerTargets.end(),
|
||||
analysisResult.resumeEntryPoints.begin(),
|
||||
analysisResult.resumeEntryPoints.end());
|
||||
ownerTargets.insert(ownerTargets.end(),
|
||||
analysisResult.indirectFallbackEntryPoints.begin(),
|
||||
analysisResult.indirectFallbackEntryPoints.end());
|
||||
|
||||
for (uint32_t target : analysisResult.externalEntryPoints)
|
||||
{
|
||||
@@ -1428,7 +1808,7 @@ namespace ps2recomp
|
||||
}
|
||||
}
|
||||
|
||||
Instruction inst = m_decoder->decodeInstruction(address, rawInstruction);
|
||||
Instruction inst = m_decoder->decodeInstruction(address, rawInstruction, !m_config.lowMemoryMode);
|
||||
|
||||
auto mmioIt = m_config.mmioByInstructionAddress.find(address);
|
||||
if (mmioIt != m_config.mmioByInstructionAddress.end())
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
#include "ps2recomp/r5900_decoder.h"
|
||||
#include "rabbitizer.h"
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
static std::string disassembleRabbitizerInstruction(RabbitizerInstruction &rabbitizerInst)
|
||||
{
|
||||
std::string disassembly;
|
||||
const size_t bufferSize = RabbitizerInstruction_getSizeForBuffer(&rabbitizerInst, 0, 0);
|
||||
if (bufferSize > 0)
|
||||
{
|
||||
std::vector<char> buffer(bufferSize + 1, '\0');
|
||||
RabbitizerInstruction_disassemble(&rabbitizerInst, buffer.data(), nullptr, 0, 0);
|
||||
disassembly = buffer.data();
|
||||
}
|
||||
|
||||
return disassembly;
|
||||
}
|
||||
|
||||
R5900Decoder::R5900Decoder()
|
||||
{
|
||||
@@ -13,7 +27,29 @@ namespace ps2recomp
|
||||
{
|
||||
}
|
||||
|
||||
Instruction R5900Decoder::decodeInstruction(uint32_t address, uint32_t rawInstruction) const
|
||||
std::string R5900Decoder::disassembleInstruction(uint32_t address, uint32_t rawInstruction)
|
||||
{
|
||||
RabbitizerInstruction rabbitizerInst;
|
||||
RabbitizerInstructionR5900_init(&rabbitizerInst, rawInstruction, address);
|
||||
RabbitizerInstructionR5900_processUniqueId(&rabbitizerInst);
|
||||
|
||||
std::string disassembly = disassembleRabbitizerInstruction(rabbitizerInst);
|
||||
|
||||
RabbitizerInstructionR5900_destroy(&rabbitizerInst);
|
||||
return disassembly;
|
||||
}
|
||||
|
||||
std::string R5900Decoder::disassembleInstruction(const Instruction &inst)
|
||||
{
|
||||
if (!inst.disassembly.empty())
|
||||
{
|
||||
return inst.disassembly;
|
||||
}
|
||||
|
||||
return disassembleInstruction(inst.address, inst.raw);
|
||||
}
|
||||
|
||||
Instruction R5900Decoder::decodeInstruction(uint32_t address, uint32_t rawInstruction, bool includeDisassembly) const
|
||||
{
|
||||
Instruction inst;
|
||||
|
||||
@@ -177,12 +213,9 @@ namespace ps2recomp
|
||||
inst.vectorInfo.isVector = inst.isVU; // Only VU ops are truly vector
|
||||
}
|
||||
|
||||
size_t bufferSize = RabbitizerInstruction_getSizeForBuffer(&rabbitizerInst, 0, 0);
|
||||
if (bufferSize > 0)
|
||||
if (includeDisassembly)
|
||||
{
|
||||
std::vector<char> buffer(bufferSize + 1, '\0');
|
||||
RabbitizerInstruction_disassemble(&rabbitizerInst, buffer.data(), nullptr, 0, 0);
|
||||
inst.disassembly = buffer.data();
|
||||
inst.disassembly = disassembleRabbitizerInstruction(rabbitizerInst);
|
||||
}
|
||||
|
||||
RabbitizerInstructionR5900_destroy(&rabbitizerInst);
|
||||
|
||||
Reference in New Issue
Block a user