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:
Sinan
2026-06-06 05:15:14 +02:00
committed by GitHub
parent 93e221feaa
commit ed8b3ebee1
10 changed files with 659 additions and 126 deletions
+36 -22
View File
@@ -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
{
+20
View File
@@ -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;
+448 -68
View File
@@ -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())
+39 -6
View File
@@ -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);