mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-26 08:51:05 -04:00
a293fa433a
refactor: codegen to catch callbacks on mips code feat: added a lot of entries or IOP emulator
240 lines
9.0 KiB
C++
240 lines
9.0 KiB
C++
#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"
|
|
#include "ps2recomp/types.h"
|
|
|
|
#include <algorithm>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <unordered_set>
|
|
|
|
namespace ps2recomp
|
|
{
|
|
namespace
|
|
{
|
|
Instruction makeSyntheticDelaySlot(uint32_t address)
|
|
{
|
|
Instruction inst{};
|
|
inst.address = address;
|
|
inst.raw = 0;
|
|
inst.opcode = OPCODE_SPECIAL;
|
|
inst.function = SPECIAL_SLL;
|
|
return inst;
|
|
}
|
|
}
|
|
|
|
FunctionEmitter::FunctionEmitter(CodeGenerator &codeGenerator)
|
|
: m_codeGenerator(codeGenerator)
|
|
{
|
|
}
|
|
|
|
std::string FunctionEmitter::emit(
|
|
const Function &function,
|
|
const std::vector<Instruction> &instructions,
|
|
bool useHeaders)
|
|
{
|
|
CodeGenerator &cg = m_codeGenerator;
|
|
std::stringstream ss;
|
|
cg.m_currentFunctionName = function.name;
|
|
|
|
if (useHeaders)
|
|
{
|
|
ss << "#include <stdexcept>\n";
|
|
ss << "#include \"ps2_runtime_macros.h\"\n";
|
|
ss << "#include \"ps2_runtime.h\"\n";
|
|
ss << "#include <ps2_recompiled_functions.h>\n";
|
|
ss << "#include <ps2_recompiled_stubs.h>\n\n";
|
|
ss << "#include \"ps2_syscalls.h\"\n";
|
|
ss << "#include \"ps2_stubs.h\"\n\n";
|
|
ss << "#ifdef PS2_FUNCTION_LOG_TRACKER\n";
|
|
ss << "#include \"ps2_log.h\"\n";
|
|
ss << "#endif\n\n";
|
|
}
|
|
|
|
CodeGenerator::AnalysisResult analysisResult = cg.collectInternalBranchTargets(function, instructions);
|
|
std::vector<uint32_t> resumeTargets(analysisResult.resumeEntryPoints.begin(),
|
|
analysisResult.resumeEntryPoints.end());
|
|
auto resumeIt = cg.m_resumeEntryTargetsByOwner.find(function.start);
|
|
if (resumeIt != cg.m_resumeEntryTargetsByOwner.end())
|
|
{
|
|
resumeTargets.insert(resumeTargets.end(), resumeIt->second.begin(), resumeIt->second.end());
|
|
}
|
|
std::sort(resumeTargets.begin(), resumeTargets.end());
|
|
resumeTargets.erase(std::unique(resumeTargets.begin(), resumeTargets.end()), resumeTargets.end());
|
|
for (uint32_t target : resumeTargets)
|
|
{
|
|
analysisResult.entryPoints.insert(target);
|
|
}
|
|
|
|
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";
|
|
|
|
std::string sanitizedName = cg.getFunctionName(function.start);
|
|
if (sanitizedName.empty())
|
|
{
|
|
std::stringstream nameBuilder;
|
|
nameBuilder << "Errorfunc_" << std::hex << function.start;
|
|
sanitizedName = nameBuilder.str();
|
|
}
|
|
|
|
ss << "void " << sanitizedName << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) {\n";
|
|
ss << "#ifdef PS2_FUNCTION_LOG_TRACKER\n";
|
|
ss << " PS_LOG_ENTRY(\"" << sanitizedName << "\");\n";
|
|
ss << "#endif\n";
|
|
ss << "\n";
|
|
if (!resumeTargets.empty())
|
|
{
|
|
ss << " switch (ctx->pc) {\n";
|
|
for (uint32_t target : resumeTargets)
|
|
{
|
|
ss << " case 0x" << std::hex << target << "u: goto label_" << target << ";\n"
|
|
<< std::dec;
|
|
}
|
|
ss << " default: break;\n";
|
|
ss << " }\n\n";
|
|
}
|
|
ss << " ctx->pc = 0x" << std::hex << function.start << "u;\n"
|
|
<< std::dec;
|
|
ss << "\n";
|
|
|
|
bool lastInstructionWasControlFlow = false;
|
|
|
|
for (size_t i = 0; i < instructions.size(); ++i)
|
|
{
|
|
const Instruction &inst = instructions[i];
|
|
lastInstructionWasControlFlow = inst.hasDelaySlot;
|
|
|
|
if (internalTargets.contains(inst.address))
|
|
{
|
|
constantRegisters.clear();
|
|
ss << "label_" << std::hex << inst.address << std::dec << ":\n";
|
|
}
|
|
|
|
if (cg.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";
|
|
}
|
|
|
|
try
|
|
{
|
|
if (inst.hasDelaySlot)
|
|
{
|
|
const bool hasDecodedDelaySlot =
|
|
i + 1 < instructions.size() &&
|
|
instructions[i + 1].address == inst.address + 4u;
|
|
|
|
Instruction syntheticDelaySlot{};
|
|
const Instruction *delaySlot = nullptr;
|
|
if (hasDecodedDelaySlot)
|
|
{
|
|
delaySlot = &instructions[i + 1];
|
|
}
|
|
else
|
|
{
|
|
syntheticDelaySlot = makeSyntheticDelaySlot(inst.address + 4u);
|
|
delaySlot = &syntheticDelaySlot;
|
|
}
|
|
|
|
if (hasDecodedDelaySlot && internalTargets.contains(delaySlot->address))
|
|
{
|
|
ss << "label_" << std::hex << delaySlot->address << std::dec << ":\n";
|
|
}
|
|
|
|
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;
|
|
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)
|
|
{
|
|
if (cg.m_reporter)
|
|
{
|
|
std::ostringstream msg;
|
|
msg << "translation failed: " << e.what() << " raw=0x" << std::hex << inst.raw;
|
|
cg.m_reporter->errorAt("codegen", function.name, inst.address, msg.str());
|
|
}
|
|
|
|
throw;
|
|
}
|
|
}
|
|
|
|
// Fallthrough with no terminating branch: publish the next PC so the EE dispatcher does not re-enter this function.
|
|
if (!instructions.empty() && !lastInstructionWasControlFlow)
|
|
{
|
|
ss << " ctx->pc = 0x" << std::hex << function.end << "u;\n"
|
|
<< std::dec;
|
|
}
|
|
|
|
ss << "}\n";
|
|
return ss.str();
|
|
}
|
|
}
|