feat: better decoder (#40)

* feat: use rabbitizer for decode MIPs instructions

* feat: cleanup code generator

* feat: fix test and add a sefe check for generate functions
This commit is contained in:
Ranieri
2026-02-02 13:29:12 -03:00
committed by GitHub
parent d7306d2095
commit 5e9139dd22
6 changed files with 161 additions and 215 deletions
+29 -2
View File
@@ -1,9 +1,11 @@
cmake_minimum_required(VERSION 3.20)
project(PS2Recomp VERSION 0.1.0 LANGUAGES CXX)
project(PS2Recomp VERSION 0.1.0 LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
include(FetchContent)
@@ -54,6 +56,30 @@ FetchContent_MakeAvailable(libdwarf)
set(LIBDWARF_INCLUDE_DIR "${libdwarf_SOURCE_DIR}/src/lib/libdwarf")
FetchContent_Declare(
rabbitizer
GIT_REPOSITORY https://github.com/Decompollaborate/rabbitizer.git
GIT_TAG 1.14.3
)
FetchContent_MakeAvailable(rabbitizer)
if (NOT TARGET rabbitizer)
file(GLOB_RECURSE RABBITIZER_SOURCES CONFIGURE_DEPENDS
"${rabbitizer_SOURCE_DIR}/src/analysis/*.c"
"${rabbitizer_SOURCE_DIR}/src/common/*.c"
"${rabbitizer_SOURCE_DIR}/src/instructions/*.c"
"${rabbitizer_SOURCE_DIR}/src/instructions/*/*.c"
)
add_library(rabbitizer STATIC ${RABBITIZER_SOURCES})
target_include_directories(rabbitizer PUBLIC
"${rabbitizer_SOURCE_DIR}/include"
"${rabbitizer_SOURCE_DIR}/tables"
"${rabbitizer_SOURCE_DIR}/tables/tables"
)
endif()
file(GLOB_RECURSE PS2RECOMP_LIB_SOURCES CONFIGURE_DEPENDS
src/lib/*.cpp
)
@@ -87,6 +113,7 @@ PUBLIC
fmt::fmt
toml11::toml11
dwarf
rabbitizer
)
file(GLOB_RECURSE PS2RECOMP_EXE_SOURCES CONFIGURE_DEPENDS
@@ -108,4 +135,4 @@ install(TARGETS ps2_recomp ps2_recomp_lib
install(DIRECTORY include/
DESTINATION include
)
)
@@ -124,7 +124,7 @@ namespace ps2recomp
const Symbol *findSymbolByAddress(uint32_t address) const;
std::string getFunctionName(uint32_t address) const;
std::string getGeneratedFunctionName(const Function &function);
std::string sanitizeFunctionName(const std::string& name) const;
};
}
+3 -3
View File
@@ -80,8 +80,8 @@ namespace ps2recomp
OPCODE_SDC2 = 0x3E, // PS2 specific Store Quadword from Coprocessor 2 (VU0) - Overrides standard MIPS SDC2
OPCODE_SD = 0x3F, // Store Doubleword
// OPCODE_LQC2 = 0x36,
// OPCODE_SQC2 = 0x3E
OPCODE_LQC2 = OPCODE_LDC2,
OPCODE_SQC2 = OPCODE_SDC2
};
// SPECIAL Function (bits 5-0) for OPCODE_SPECIAL
@@ -775,4 +775,4 @@ namespace ps2recomp
} // namespace ps2recomp
#endif // PS2RECOMP_INSTRUCTIONS_H
#endif // PS2RECOMP_INSTRUCTIONS_H
+35 -36
View File
@@ -28,6 +28,20 @@ namespace ps2recomp
namespace ps2recomp
{
static bool isReservedCxxIdentifier(const std::string& name)
{
if (name.size() >= 2 && name[0] == '_' && name[1] == '_')
return true;
if (!name.empty() && name[0] == '_' && std::isupper(static_cast<unsigned char>(name[1])))
return true;
return false;
}
static bool isReservedCxxKeyword(const std::string& name)
{
return kKeywords.contains(name);
}
CodeGenerator::CodeGenerator(const std::vector<Symbol> &symbols)
{
for (auto &symbol : symbols)
@@ -57,27 +71,13 @@ namespace ps2recomp
const Symbol *sym = findSymbolByAddress(address);
if (sym && sym->isFunction)
{
return sym->name;
return CodeGenerator::sanitizeFunctionName(sym->name);
}
return "";
}
}
static bool isReservedCxxIdentifier(const std::string &name)
{
if (name.size() >= 2 && name[0] == '_' && name[1] == '_')
return true;
if (!name.empty() && name[0] == '_' && std::isupper(static_cast<unsigned char>(name[1])))
return true;
return false;
}
static bool isReservedCxxKeyword(const std::string &name)
{
return kKeywords.contains(name);
}
static std::string sanitizeFunctionName(const std::string &name)
std::string CodeGenerator::sanitizeFunctionName(const std::string &name) const
{
std::string sanitized = name;
@@ -96,16 +96,6 @@ namespace ps2recomp
return "ps2_" + sanitized;
}
std::string CodeGenerator::getGeneratedFunctionName(const Function &function)
{
std::string name = getFunctionName(function.start);
if (name.empty())
{
name = sanitizeFunctionName(function.name);
}
return name;
}
std::string CodeGenerator::handleBranchDelaySlots(const Instruction &branchInst, const Instruction &delaySlot,
const Function &function, const std::unordered_set<uint32_t> &internalTargets)
{
@@ -131,10 +121,13 @@ namespace ps2recomp
std::string funcName = getFunctionName(target);
if (!funcName.empty())
{
ss << " " << funcName << "(rdram, ctx, runtime);\n";
if (branchInst.opcode == OPCODE_J)
{
ss << " return;\n";
ss << " " << funcName << "(rdram, ctx, runtime); return;\n";
}
else
{
ss << " " << funcName << "(rdram, ctx, runtime);\n";
}
}
else
@@ -402,7 +395,13 @@ namespace ps2recomp
ss << "// Function: " << function.name << "\n";
ss << "// Address: 0x" << std::hex << function.start << " - 0x" << function.end << std::dec << "\n";
std::string sanitizedName = getGeneratedFunctionName(function);
std::string sanitizedName = getFunctionName(function.start);
if (sanitizedName.empty())
{
std::stringstream nameBuilder;
nameBuilder << "Errorfunc_" << std::hex << function.start; // this should never happen but lets put here just to track
sanitizedName = nameBuilder.str();
}
ss << "void " << sanitizedName << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) {\n\n";
for (size_t i = 0; i < instructions.size(); ++i)
@@ -2226,7 +2225,7 @@ namespace ps2recomp
if (!function.isRecompiled && !function.isStub)
continue;
std::string generatedName = getGeneratedFunctionName(function);
std::string generatedName = getFunctionName(function.start);
if (function.isStub)
{
@@ -2246,28 +2245,28 @@ namespace ps2recomp
}
ss << " // Register recompiled functions\n";
for (const auto & [first, second] : normalFunctions)
for (const auto &[first, second] : normalFunctions)
{
ss << " runtime.registerFunction(0x" << std::hex << first << std::dec
<< ", " << second << ");\n";
}
ss << "\n // Register stub functions\n";
for (const auto & [first, second] : stubFunctions)
for (const auto &[first, second] : stubFunctions)
{
ss << " runtime.registerFunction(0x" << std::hex << first << std::dec
<< ", " << second << ");\n";
}
ss << "\n // Register system call stubs\n";
for (const auto & [first, second] : systemCallFunctions)
for (const auto &[first, second] : systemCallFunctions)
{
ss << " runtime.registerFunction(0x" << std::hex << first << std::dec
<< ", " << second << ");\n";
}
ss << "\n // Register library stubs\n";
for (const auto & [first, second] : libraryFunctions)
for (const auto &[first, second] : libraryFunctions)
{
ss << " runtime.registerFunction(0x" << std::hex << first << std::dec
<< ", " << second << ");\n";
@@ -2287,7 +2286,7 @@ namespace ps2recomp
ss << "switch (ctx->r[" << indexReg << "]) {\n";
for (const auto & [index, target] : entries)
for (const auto &[index, target] : entries)
{
ss << " case " << index << ": {\n";
+2 -3
View File
@@ -281,7 +281,7 @@ namespace ps2recomp
{
if (function.isStub)
{
std::string generatedName = m_codeGenerator->getGeneratedFunctionName(function);
std::string generatedName = m_codeGenerator->getFunctionName(function.start);
std::stringstream stub;
stub << "void " << generatedName
<< "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { ";
@@ -483,8 +483,7 @@ namespace ps2recomp
continue;
}
std::string finalName = sanitizeFunctionName(function.name);
finalName = m_codeGenerator->getGeneratedFunctionName(function);
std::string finalName = m_codeGenerator->getFunctionName(function.start);
ss << "void " << finalName << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime);\n";
}
+91 -170
View File
@@ -1,4 +1,5 @@
#include "ps2recomp/r5900_decoder.h"
#include "rabbitizer.h"
#include <iostream>
namespace ps2recomp
@@ -59,194 +60,114 @@ namespace ps2recomp
inst.modificationInfo.modifiesMemory = false;
inst.modificationInfo.modifiesControl = false;
switch (inst.opcode)
RabbitizerInstruction rabbitizerInst;
RabbitizerInstructionR5900_init(&rabbitizerInst, rawInstruction, address);
RabbitizerInstructionR5900_processUniqueId(&rabbitizerInst);
const RabbitizerInstrDescriptor *desc = rabbitizerInst.descriptor;
inst.isBranch = RabbitizerInstrDescriptor_isBranch(desc);
inst.isJump = RabbitizerInstrDescriptor_isJump(desc);
inst.isCall = RabbitizerInstruction_isFunctionCall(&rabbitizerInst);
inst.isReturn = RabbitizerInstruction_isReturn(&rabbitizerInst) ||
rabbitizerInst.uniqueId == RABBITIZER_INSTR_ID_cpu_eret;
inst.hasDelaySlot = RabbitizerInstruction_hasDelaySlot(&rabbitizerInst);
inst.isLoad = RabbitizerInstrDescriptor_doesLoad(desc);
inst.isStore = RabbitizerInstrDescriptor_doesStore(desc);
inst.isMMI = inst.opcode == OPCODE_MMI;
inst.isVU = inst.opcode == OPCODE_COP2 ||
inst.opcode == OPCODE_LWC2 ||
inst.opcode == OPCODE_LDC2 ||
inst.opcode == OPCODE_SWC2 ||
inst.opcode == OPCODE_SDC2;
bool modifiesGpr = false;
if (RabbitizerInstrDescriptor_modifiesRs(desc) && inst.rs != 0)
{
case OPCODE_SPECIAL:
decodeSpecial(inst);
break;
modifiesGpr = true;
}
if (RabbitizerInstrDescriptor_modifiesRt(desc) && inst.rt != 0)
{
modifiesGpr = true;
}
if (RabbitizerInstrDescriptor_modifiesRd(desc) && inst.rd != 0)
{
modifiesGpr = true;
}
if (RabbitizerInstrDescriptor_doesLink(desc))
{
modifiesGpr = true;
}
inst.modificationInfo.modifiesGPR = modifiesGpr;
case OPCODE_REGIMM:
decodeRegimm(inst);
break;
inst.modificationInfo.modifiesFPR = RabbitizerInstrDescriptor_modifiesFs(desc) ||
RabbitizerInstrDescriptor_modifiesFt(desc) ||
RabbitizerInstrDescriptor_modifiesFd(desc);
case OPCODE_J:
decodeJType(inst);
break;
inst.modificationInfo.modifiesMemory = RabbitizerInstrDescriptor_doesStore(desc);
inst.modificationInfo.modifiesControl = RabbitizerInstrDescriptor_isBranch(desc) ||
RabbitizerInstrDescriptor_isJump(desc) ||
RabbitizerInstrDescriptor_isTrap(desc) ||
RabbitizerInstrDescriptor_modifiesHI(desc) ||
RabbitizerInstrDescriptor_modifiesLO(desc);
case OPCODE_JAL:
decodeJType(inst);
break;
if (rabbitizerInst.uniqueId == RABBITIZER_INSTR_ID_cpu_eret)
{
inst.isReturn = true;
inst.hasDelaySlot = false;
inst.modificationInfo.modifiesControl = true;
}
case OPCODE_BEQ:
case OPCODE_BNE:
case OPCODE_BLEZ:
case OPCODE_BGTZ:
case OPCODE_BEQL:
case OPCODE_BNEL:
case OPCODE_BLEZL:
case OPCODE_BGTZL:
decodeIType(inst);
inst.isBranch = true;
inst.hasDelaySlot = true;
inst.modificationInfo.modifiesControl = true; // PC potentially
break;
case OPCODE_DADDI:
case OPCODE_DADDIU:
decodeIType(inst);
if (inst.rt != 0)
inst.modificationInfo.modifiesGPR = true;
break;
case OPCODE_MMI:
decodeMMI(inst);
break;
case OPCODE_LQ:
decodeIType(inst);
inst.isLoad = true;
inst.isMultimedia = true; // 128-bit load
break;
case OPCODE_SQ:
decodeIType(inst);
inst.isStore = true;
inst.isMultimedia = true; // 128-bit store
inst.modificationInfo.modifiesMemory = true;
break;
case OPCODE_LB:
case OPCODE_LH:
case OPCODE_LW:
case OPCODE_LBU:
case OPCODE_LHU:
case OPCODE_LWU:
case OPCODE_LD:
inst.isLoad = true;
if (inst.rt != 0)
inst.modificationInfo.modifiesGPR = true;
break;
case OPCODE_LWL:
case OPCODE_LWR:
case OPCODE_LDL:
case OPCODE_LDR:
inst.isLoad = true;
if (inst.rt != 0)
inst.modificationInfo.modifiesGPR = true;
inst.modificationInfo.modifiesMemory = true;
break;
case OPCODE_LL:
case OPCODE_LLD:
decodeIType(inst);
if (inst.opcode == OPCODE_LL || inst.opcode == OPCODE_LLD)
{
inst.isLoad = true;
inst.modificationInfo.modifiesControl = true;
if (inst.rt != 0)
{
inst.modificationInfo.modifiesGPR = true;
}
// LL/LLD manipulate the load-linked bit in COP0 status
inst.modificationInfo.modifiesControl = true;
break;
case OPCODE_LWC1:
inst.isLoad = true;
inst.modificationInfo.modifiesFPR = true;
break;
case OPCODE_LDC1: // Not present/used on EE FPU
case OPCODE_LWC2: // Maybe unused
case OPCODE_LDC2: // VU Load
inst.isLoad = true;
inst.isVU = true;
inst.modificationInfo.modifiesVFR = true;
break;
case OPCODE_SB:
case OPCODE_SH:
case OPCODE_SW:
case OPCODE_SD:
inst.isStore = true;
inst.modificationInfo.modifiesMemory = true;
break;
case OPCODE_SWL:
case OPCODE_SWR:
case OPCODE_SDL:
case OPCODE_SDR:
inst.isStore = true;
inst.modificationInfo.modifiesMemory = true;
break;
case OPCODE_SWC1:
inst.isStore = true;
inst.modificationInfo.modifiesMemory = true;
break;
case OPCODE_SDC1: // Not present/used on EE FPU
case OPCODE_SWC2: // Potentially unused
case OPCODE_SDC2:
inst.isStore = true;
inst.isVU = true;
inst.modificationInfo.modifiesMemory = true;
break;
case OPCODE_SC:
case OPCODE_SCD:
inst.isStore = true;
inst.modificationInfo.modifiesMemory = true;
if (inst.rt != 0)
inst.modificationInfo.modifiesGPR = true; // Writes success/fail to rt
inst.modificationInfo.modifiesControl = true; // Reads/Clears LLBit
break;
case OPCODE_ADDI:
case OPCODE_ADDIU:
case OPCODE_SLTI:
case OPCODE_SLTIU:
case OPCODE_ANDI:
case OPCODE_ORI:
case OPCODE_XORI:
case OPCODE_LUI:
decodeIType(inst);
if (inst.rt != 0)
inst.modificationInfo.modifiesGPR = true;
break;
case OPCODE_CACHE:
decodeIType(inst);
inst.modificationInfo.modifiesControl = true; // Cache state
break;
case OPCODE_PREF:
decodeIType(inst);
break;
case OPCODE_COP0:
decodeCOP0(inst);
break;
case OPCODE_COP1:
decodeCOP1(inst);
break;
case OPCODE_COP2:
decodeCOP2(inst);
break;
default:
// Default to I-type for most other instructions
decodeIType(inst);
break;
}
// Generic multimedia flag if MMI or VU
if (inst.opcode == OPCODE_SC || inst.opcode == OPCODE_SCD)
{
inst.isStore = true;
inst.modificationInfo.modifiesControl = true;
if (inst.rt != 0)
{
inst.modificationInfo.modifiesGPR = true; // success flag to rt
}
}
if (inst.opcode == OPCODE_COP2)
{
decodeCOP2(inst);
}
else if (inst.opcode == OPCODE_LWC2 || inst.opcode == OPCODE_LDC2)
{
inst.isVU = true;
inst.modificationInfo.modifiesVFR = true;
}
else if (inst.opcode == OPCODE_SWC2 || inst.opcode == OPCODE_SDC2)
{
inst.isVU = true;
inst.modificationInfo.modifiesMemory = true;
}
if (inst.opcode == OPCODE_LQ || inst.opcode == OPCODE_SQ)
{
inst.isMultimedia = true;
}
if (inst.isMMI || inst.isVU)
{
inst.isMultimedia = true;
inst.vectorInfo.isVector = inst.isVU; // Only VU ops are truly vector
}
RabbitizerInstructionR5900_destroy(&rabbitizerInst);
return inst;
}