Feature/agressive recompiler (#146)

* feat: added guestBranchKind enum to categorize branch types
feat: added missingFunctionPolicy enum to define behaviors for missing function scenarios
refactor: added handle guest branches and report missing functions
feat lookupFunction to utilize new dispatch logic and improve error handling for unregistered functions

* fix: fix test conflict

* feat: added debug sound driver logs

* feat: emmiter for return

* feat: added recompiler reporter
feat: added strict diagnostics flag for heavy debug calls

* feat: staticc table insted of hashmap for runtime

* feat: back file to ignore

* feat: explode code across helpers and classes

* feat: update codegen test
feat: better guest nop check

* feat: fix link problem on linux

* feat: fix Segmentation fault

* feat: added recompile replace for DMA and MMIO
feat: added a clean memory helpers
feat: use memory helpers across the project
feat: fix ucrt on msvc

* feat: undo messup merge
This commit is contained in:
Ranieri
2026-07-07 10:14:25 -03:00
committed by GitHub
parent 61621b8313
commit 52edf07657
24 changed files with 1756 additions and 70 deletions
+18 -1
View File
@@ -23,6 +23,7 @@
#include <algorithm>
#include <cstring>
#include <unordered_set>
#include <utility>
#include <unordered_map>
#include <iostream>
#include <cctype>
@@ -209,11 +210,27 @@ namespace ps2recomp
return emitter.emit();
}
std::string CodeGenerator::handleBranchDelaySlots(
const Instruction &branchInst,
const Instruction &delaySlot,
const Function &function,
const AnalysisResult &analysisResult,
std::string delaySlotOverride)
{
ControlFlowEmitter emitter(*this, branchInst, delaySlot, function, analysisResult, std::move(delaySlotOverride));
return emitter.emit();
}
CodeGenerator::~CodeGenerator() = default;
std::string CodeGenerator::translateInstruction(const Instruction &inst)
{
return InstructionTranslator(*this).translate(inst);
return InstructionTranslator(*this).translate(inst, {});
}
std::string CodeGenerator::translateInstruction(const Instruction &inst, const MemoryAccessHint &memoryHint)
{
return InstructionTranslator(*this).translate(inst, memoryHint);
}
std::string CodeGenerator::translateSpecialInstruction(const Instruction &inst)
+11 -3
View File
@@ -8,6 +8,7 @@
#include <algorithm>
#include <fmt/format.h>
#include <sstream>
#include <utility>
namespace ps2recomp
{
@@ -15,12 +16,14 @@ namespace ps2recomp
const Instruction &branchInst,
const Instruction &delaySlot,
const Function &function,
const CodeGenerator::AnalysisResult &analysisResult)
const CodeGenerator::AnalysisResult &analysisResult,
std::string delaySlotOverride)
: m_gen(generator),
m_branchInst(branchInst),
m_delaySlot(delaySlot),
m_function(function),
m_analysisResult(analysisResult)
m_analysisResult(analysisResult),
m_delaySlotOverride(std::move(delaySlotOverride))
{
}
@@ -41,7 +44,7 @@ namespace ps2recomp
bool ControlFlowEmitter::hasRealDelaySlot() const
{
return !isGuestNop(m_delaySlot);
return !m_delaySlotOverride.empty() || !isGuestNop(m_delaySlot);
}
bool ControlFlowEmitter::isCallLikeEdge() const
@@ -100,6 +103,11 @@ namespace ps2recomp
return {};
}
if (!m_delaySlotOverride.empty())
{
return m_delaySlotOverride;
}
std::string code;
if (m_gen.m_emitInstructionComments)
{
+48 -3
View File
@@ -1,5 +1,6 @@
#include "ps2recomp/Emitters/function_emitter.h"
#include "ps2recomp/code_generator.h"
#include "ps2recomp/gif_dma_kick_analyzer.h"
#include "ps2recomp/instructions.h"
#include "ps2recomp/r5900_decoder.h"
#include "ps2recomp/recompiler_reporter.h"
@@ -69,6 +70,8 @@ namespace ps2recomp
}
const std::unordered_set<uint32_t> &internalTargets = analysisResult.entryPoints;
ConstantRegisterState constantRegisters;
GifDmaKickPlan gifDmaKickPlan{};
ss << "// Function: " << function.name << "\n";
ss << "// Address: 0x" << std::hex << function.start << " - 0x" << function.end << std::dec << "\n";
@@ -106,6 +109,7 @@ namespace ps2recomp
if (internalTargets.contains(inst.address))
{
constantRegisters.clear();
ss << "label_" << std::hex << inst.address << std::dec << ":\n";
}
@@ -145,24 +149,65 @@ namespace ps2recomp
ss << "label_" << std::hex << delaySlot->address << std::dec << ":\n";
}
ss << cg.handleBranchDelaySlots(inst, *delaySlot, function, analysisResult);
if (gifDmaKickPlan.valid &&
gifDmaKickPlan.completesInDelaySlot &&
gifDmaKickPlan.branchIndex == i &&
hasDecodedDelaySlot)
{
ss << cg.handleBranchDelaySlots(
inst,
*delaySlot,
function,
analysisResult,
gifDmaDelaySlotOverride(*delaySlot, gifDmaKickPlan, cg.m_emitInstructionComments));
gifDmaKickPlan = {};
}
else
{
ss << cg.handleBranchDelaySlots(inst, *delaySlot, function, analysisResult);
}
if (hasDecodedDelaySlot)
{
++i; // Skip delay slot instruction (handled inside branch logic)
}
constantRegisters.clear();
}
else
{
if (!gifDmaKickPlan.valid)
{
gifDmaKickPlan = tryBuildGifDmaKickPlan(instructions, i, constantRegisters, internalTargets);
}
if (gifDmaKickPlan.suppresses(i))
{
const size_t slot = gifDmaKickPlan.slotFor(i);
emitGifDmaCapture(ss, gifDmaKickPlan, slot, " ");
if (gifDmaKickPlan.completesAt(i))
{
ss << " ctx->pc = 0x" << std::hex << inst.address << "u;\n"
<< std::dec;
ss << " " << gifDmaKickCall(gifDmaKickPlan) << "\n";
gifDmaKickPlan = {};
}
updateConstantRegisters(inst, constantRegisters);
continue;
}
ss << " ctx->pc = 0x" << std::hex << inst.address << "u;\n"
<< std::dec;
ss << " " << cg.translateInstruction(inst);
const MemoryAccessHint memoryHint = resolveMemoryAccessHint(inst, constantRegisters);
ss << " " << cg.translateInstruction(inst, memoryHint);
if (inst.isMmio)
{
ss << " // MMIO: 0x" << std::hex << inst.mmioAddress << std::dec;
}
ss << "\n";
updateConstantRegisters(inst, constantRegisters);
}
}
catch (const std::exception &e)
@@ -0,0 +1,374 @@
#include "ps2recomp/gif_dma_kick_analyzer.h"
#include "ps2recomp/instructions.h"
#include "ps2recomp/r5900_decoder.h"
#include <algorithm>
#include <ostream>
#include <sstream>
namespace ps2recomp
{
namespace
{
uint32_t add32(uint32_t lhs, uint32_t rhs)
{
return lhs + rhs;
}
std::string formatU32Literal(uint32_t value)
{
std::ostringstream ss;
ss << "0x" << std::hex << value << "u";
return ss.str();
}
std::string gprU32Expression(uint32_t reg)
{
std::ostringstream ss;
ss << "GPR_U32(ctx, " << std::dec << reg << ")";
return ss.str();
}
std::string gifDmaKickTempName(uint32_t address, size_t slot)
{
std::ostringstream ss;
ss << "gifDmaKickValue_" << std::hex << address << "_" << std::dec << slot;
return ss.str();
}
bool isReturnWithDelaySlot(const Instruction &inst)
{
return inst.opcode == OPCODE_SPECIAL &&
inst.function == SPECIAL_JR &&
inst.rs == 31u &&
inst.hasDelaySlot;
}
bool tryMatchGifDmaStore(size_t instructionIndex,
const Instruction &inst,
const MemoryAccessHint &hint,
const ConstantRegisterState &constants,
uint32_t target,
size_t slot,
GifDmaKickPlan &plan)
{
if (inst.opcode != OPCODE_SW || !hint.hasAddress || hint.address != target)
return false;
plan.storeIndices[slot] = instructionIndex;
uint32_t constantValue = 0u;
if (constants.read(inst.rt, constantValue))
{
plan.values[slot] = formatU32Literal(constantValue);
}
else
{
const std::string tempName = gifDmaKickTempName(inst.address, slot);
plan.values[slot] = tempName;
plan.captureExpressions[slot] = gprU32Expression(inst.rt);
plan.captures[slot] = true;
}
return true;
}
}
ConstantRegisterState::ConstantRegisterState()
{
clear();
}
void ConstantRegisterState::clear()
{
known.fill(false);
values.fill(0u);
known[0] = true;
}
bool ConstantRegisterState::read(uint32_t reg, uint32_t &value) const
{
if (reg >= known.size() || !known[reg])
return false;
value = values[reg];
return true;
}
void ConstantRegisterState::write(uint32_t reg, uint32_t value)
{
if (reg == 0 || reg >= known.size())
return;
known[reg] = true;
values[reg] = value;
}
void ConstantRegisterState::invalidate(uint32_t reg)
{
if (reg == 0 || reg >= known.size())
return;
known[reg] = false;
values[reg] = 0u;
}
bool GifDmaKickPlan::suppresses(size_t index) const
{
return valid && std::find(storeIndices.begin(), storeIndices.end(), index) != storeIndices.end();
}
bool GifDmaKickPlan::completesAt(size_t index) const
{
return valid && index == endIndex;
}
size_t GifDmaKickPlan::slotFor(size_t index) const
{
for (size_t i = 0; i < storeIndices.size(); ++i)
{
if (storeIndices[i] == index)
return i;
}
return storeIndices.size();
}
bool isDirectMemoryAccess(const Instruction &inst)
{
switch (inst.opcode)
{
case OPCODE_LB:
case OPCODE_LH:
case OPCODE_LW:
case OPCODE_LBU:
case OPCODE_LHU:
case OPCODE_LWU:
case OPCODE_LQ:
case OPCODE_LD:
case OPCODE_LWC1:
case OPCODE_LDC2:
case OPCODE_SB:
case OPCODE_SH:
case OPCODE_SW:
case OPCODE_SQ:
case OPCODE_SD:
case OPCODE_SWC1:
case OPCODE_SDC2:
return true;
default:
return false;
}
}
MemoryAccessHint resolveMemoryAccessHint(const Instruction &inst, const ConstantRegisterState &constants)
{
MemoryAccessHint hint{};
if (!isDirectMemoryAccess(inst))
return hint;
uint32_t base = 0u;
if (!constants.read(inst.rs, base))
return hint;
hint.hasAddress = true;
hint.address = add32(base, inst.simmediate);
return hint;
}
void updateConstantRegisters(const Instruction &inst, ConstantRegisterState &constants)
{
uint32_t lhs = 0u;
uint32_t rhs = 0u;
switch (inst.opcode)
{
case OPCODE_LUI:
constants.write(inst.rt, inst.immediate << 16);
return;
case OPCODE_ORI:
if (constants.read(inst.rs, lhs))
constants.write(inst.rt, lhs | (inst.immediate & 0xFFFFu));
else
constants.invalidate(inst.rt);
return;
case OPCODE_ADDIU:
if (constants.read(inst.rs, lhs))
constants.write(inst.rt, add32(lhs, inst.simmediate));
else
constants.invalidate(inst.rt);
return;
case OPCODE_ANDI:
if (constants.read(inst.rs, lhs))
constants.write(inst.rt, lhs & (inst.immediate & 0xFFFFu));
else
constants.invalidate(inst.rt);
return;
case OPCODE_XORI:
if (constants.read(inst.rs, lhs))
constants.write(inst.rt, lhs ^ (inst.immediate & 0xFFFFu));
else
constants.invalidate(inst.rt);
return;
case OPCODE_LB:
case OPCODE_LH:
case OPCODE_LWL:
case OPCODE_LW:
case OPCODE_LBU:
case OPCODE_LHU:
case OPCODE_LWR:
case OPCODE_LWU:
case OPCODE_LDL:
case OPCODE_LDR:
case OPCODE_LQ:
case OPCODE_LL:
case OPCODE_LD:
constants.invalidate(inst.rt);
return;
case OPCODE_SC:
constants.invalidate(inst.rt);
return;
case OPCODE_SPECIAL:
switch (inst.function)
{
case SPECIAL_ADDU:
case SPECIAL_DADDU:
if (constants.read(inst.rs, lhs) && constants.read(inst.rt, rhs))
constants.write(inst.rd, add32(lhs, rhs));
else
constants.invalidate(inst.rd);
return;
case SPECIAL_OR:
if (constants.read(inst.rs, lhs) && constants.read(inst.rt, rhs))
constants.write(inst.rd, lhs | rhs);
else
constants.invalidate(inst.rd);
return;
default:
break;
}
break;
default:
break;
}
if (inst.modificationInfo.modifiesGPR || inst.modificationInfo.modifiesControl)
constants.clear();
}
std::string gifDmaKickCall(const GifDmaKickPlan &plan)
{
std::ostringstream ss;
ss << "runtime->kickGifDmaChainFromMMIO(rdram, ctx, "
<< plan.values[0] << ", "
<< plan.values[1] << ", "
<< plan.values[2] << ", "
<< plan.values[3] << ");";
return ss.str();
}
void emitGifDmaCapture(std::ostream &out, const GifDmaKickPlan &plan, size_t slot, std::string_view indent)
{
if (slot >= plan.captures.size() || !plan.captures[slot])
return;
out << indent << "uint32_t " << plan.values[slot] << " = " << plan.captureExpressions[slot] << ";\n";
}
std::string gifDmaDelaySlotOverride(const Instruction &delaySlot, const GifDmaKickPlan &plan, bool emitComments)
{
std::ostringstream code;
const size_t slot = plan.slotFor(plan.endIndex);
if (emitComments)
{
code << "// 0x" << std::hex << delaySlot.address << ": 0x" << delaySlot.raw << std::dec;
std::string disassembly = R5900Decoder::disassembleInstruction(delaySlot);
if (!disassembly.empty())
code << " " << disassembly;
code << " (Delay Slot)\n";
}
if (slot < plan.captures.size() && plan.captures[slot])
code << "uint32_t " << plan.values[slot] << " = " << plan.captureExpressions[slot] << ";\n";
code << gifDmaKickCall(plan);
return code.str();
}
GifDmaKickPlan tryBuildGifDmaKickPlan(const std::vector<Instruction> &instructions,
size_t startIndex,
const ConstantRegisterState &constants,
const std::unordered_set<uint32_t> &internalTargets)
{
static constexpr std::array<uint32_t, 4> kTargets = {
0x1000E020u, // D_PCR
0x1000E010u, // D_STAT
0x1000A030u, // GIF TADR
0x1000A000u, // GIF CHCR
};
static constexpr size_t kMaxScanInstructions = 32u;
GifDmaKickPlan plan{};
if (startIndex >= instructions.size())
return plan;
ConstantRegisterState scanConstants = constants;
size_t matched = 0;
const size_t scanEnd = std::min(instructions.size(), startIndex + kMaxScanInstructions);
for (size_t j = startIndex; j < scanEnd; ++j)
{
const Instruction &inst = instructions[j];
if (j != startIndex && matched > 0 && internalTargets.contains(inst.address))
return {};
const MemoryAccessHint hint = resolveMemoryAccessHint(inst, scanConstants);
if (isDirectMemoryAccess(inst))
{
if (matched >= kTargets.size() ||
!tryMatchGifDmaStore(j, inst, hint, scanConstants, kTargets[matched], matched, plan))
{
return {};
}
++matched;
updateConstantRegisters(inst, scanConstants);
if (matched == kTargets.size())
{
plan.valid = true;
plan.endIndex = j;
return plan;
}
continue;
}
if (isReturnWithDelaySlot(inst))
{
const size_t delayIndex = j + 1u;
if (matched != kTargets.size() - 1u ||
delayIndex >= instructions.size() ||
instructions[delayIndex].address != inst.address + 4u ||
internalTargets.contains(instructions[delayIndex].address))
{
return {};
}
const Instruction &delayInst = instructions[delayIndex];
const MemoryAccessHint delayHint = resolveMemoryAccessHint(delayInst, scanConstants);
if (!tryMatchGifDmaStore(delayIndex, delayInst, delayHint, scanConstants, kTargets[matched], matched, plan))
return {};
plan.valid = true;
plan.completesInDelaySlot = true;
plan.branchIndex = j;
plan.endIndex = delayIndex;
return plan;
}
if (inst.hasDelaySlot || inst.isBranch || inst.isJump || inst.modificationInfo.modifiesControl)
return {};
updateConstantRegisters(inst, scanConstants);
}
return {};
}
}
+117 -14
View File
@@ -4,42 +4,145 @@
#include "ps2recomp/instructions.h"
#include "ps2recomp/types.h"
#include "ps2recomp/control_flow_utils.h"
#include "runtime/ps2_address.h"
#include <fmt/format.h>
#include <sstream>
#include <cmath>
namespace ps2recomp
{
namespace
{
std::string addressLiteral(uint32_t address)
{
return fmt::format("0x{:X}u", address);
}
uint32_t memoryAccessSize(int width)
{
return static_cast<uint32_t>(width / 8);
}
std::string memoryValueType(int width)
{
switch (width)
{
case 8:
return "uint8_t";
case 16:
return "uint16_t";
case 32:
return "uint32_t";
case 64:
return "uint64_t";
default:
return "";
}
}
std::string genFastWrite(int width, uint32_t address, const std::string &val)
{
const std::string addr = addressLiteral(address);
if (width == 128)
{
return fmt::format(
"do {{ __m128i _value = ({}); "
"const uint64_t _lo = static_cast<uint64_t>(PS2_EXTRACT_EPI64_0(_value)); "
"const uint64_t _hi = static_cast<uint64_t>(PS2_EXTRACT_EPI64_1(_value)); "
"ps2TraceGuestWrite(rdram, {}, 16u, _lo, _hi, \"WRITE128\", ctx); "
"FAST_WRITE128({}, _value); }} while (0)",
val, addr, addr);
}
const std::string valueType = memoryValueType(width);
return fmt::format(
"do {{ {} _value = static_cast<{}>({}); "
"ps2TraceGuestWrite(rdram, {}, {}u, _value, 0u, \"WRITE{}\", ctx); "
"FAST_WRITE{}({}, _value); }} while (0)",
valueType, valueType, val, addr, memoryAccessSize(width), width, width, addr);
}
}
InstructionTranslator::InstructionTranslator(CodeGenerator &codeGenerator)
: m_codeGenerator(codeGenerator)
{
}
std::string InstructionTranslator::translate(const Instruction &inst)
MemoryAccessHint InstructionTranslator::effectiveMemoryHintFor(const Instruction &inst, const MemoryAccessHint &memoryHint) const
{
MemoryAccessHint effectiveMemoryHint = memoryHint;
if (inst.isMmio)
{
effectiveMemoryHint.hasAddress = true;
effectiveMemoryHint.address = inst.mmioAddress;
}
return effectiveMemoryHint;
}
std::string InstructionTranslator::translateMemoryRead(const Instruction &inst,
const MemoryAccessHint &memoryHint,
int width,
const std::string &addr) const
{
if (memoryHint.hasAddress)
{
const uint32_t resolvedAddress = memoryHint.address;
const std::string resolvedAddressExpr = addressLiteral(resolvedAddress);
if (inst.isMmio || Ps2IsSpecialAddress(resolvedAddress))
{
return fmt::format("runtime->Load{}(rdram, ctx, {})", width, resolvedAddressExpr);
}
return fmt::format("FAST_READ{}({})", width, resolvedAddressExpr);
}
if (inst.isMmio)
{
return fmt::format("runtime->Load{}(rdram, ctx, {})", width, addr);
}
return fmt::format("READ{}({})", width, addr);
}
std::string InstructionTranslator::translateMemoryWrite(const Instruction &inst,
const MemoryAccessHint &memoryHint,
int width,
const std::string &addr,
const std::string &value) const
{
if (memoryHint.hasAddress)
{
const uint32_t resolvedAddress = memoryHint.address;
const std::string resolvedAddressExpr = addressLiteral(resolvedAddress);
if (inst.isMmio || Ps2IsSpecialAddress(resolvedAddress))
{
return fmt::format("runtime->Store{}(rdram, ctx, {}, {})", width, resolvedAddressExpr, value);
}
return genFastWrite(width, resolvedAddress, value);
}
if (inst.isMmio)
{
return fmt::format("runtime->Store{}(rdram, ctx, {}, {})", width, addr, value);
}
return fmt::format("WRITE{}({}, {})", width, addr, value);
}
std::string InstructionTranslator::translate(const Instruction &inst, const MemoryAccessHint &memoryHint)
{
if (inst.isMMI)
{
return m_codeGenerator.translateMMIInstruction(inst);
}
const MemoryAccessHint effectiveMemoryHint = effectiveMemoryHintFor(inst, memoryHint);
auto genRead = [&](int width, const std::string &addr)
{
if (inst.isMmio)
{
return fmt::format("runtime->Load{}(rdram, ctx, {})", width, addr);
}
return fmt::format("READ{}({})", width, addr);
return translateMemoryRead(inst, effectiveMemoryHint, width, addr);
};
auto genWrite = [&](int width, const std::string &addr, const std::string &val)
{
if (inst.isMmio)
{
return fmt::format("runtime->Store{}(rdram, ctx, {}, {})", width, addr, val);
}
return fmt::format("WRITE{}({}, {})", width, addr, val);
return translateMemoryWrite(inst, effectiveMemoryHint, width, addr, val);
};
switch (inst.opcode)