Files
PS2Recomp/ps2xRecomp/src/lib/gif_dma_kick_analyzer.cpp
T
Ranieri 52edf07657 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
2026-07-07 10:14:25 -03:00

375 lines
12 KiB
C++

#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 {};
}
}