From 52edf0765736e8ea7417c4f68d8d4a3e559b49f2 Mon Sep 17 00:00:00 2001 From: Ranieri Date: Tue, 7 Jul 2026 10:14:25 -0300 Subject: [PATCH] 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 --- .../ps2recomp/Emitters/control_flow_emitter.h | 4 +- .../Translators/instruction_translator.h | 15 +- ps2xRecomp/include/ps2recomp/code_generator.h | 5 + .../include/ps2recomp/gif_dma_kick_analyzer.h | 62 +++ ps2xRecomp/include/ps2recomp/types.h | 6 + ps2xRecomp/src/lib/code_generator.cpp | 19 +- ps2xRecomp/src/lib/control_flow_emitter.cpp | 14 +- ps2xRecomp/src/lib/function_emitter.cpp | 51 ++- ps2xRecomp/src/lib/gif_dma_kick_analyzer.cpp | 374 ++++++++++++++++++ ps2xRecomp/src/lib/instruction_translator.cpp | 131 +++++- ps2xRuntime/include/ps2_runtime.h | 35 +- ps2xRuntime/include/runtime/ps2_address.h | 61 +++ ps2xRuntime/include/runtime/ps2_gif_arbiter.h | 1 + ps2xRuntime/include/runtime/ps2_gs_gpu.h | 12 + ps2xRuntime/include/runtime/ps2_memory.h | 4 + ps2xRuntime/src/lib/Kernel/Stubs/SIF.cpp | 9 +- ps2xRuntime/src/lib/ps2_gs_gpu.cpp | 190 +++++++++ ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp | 6 +- ps2xRuntime/src/lib/ps2_memory.cpp | 343 +++++++++++++++- ps2xRuntime/src/lib/ps2_runtime.cpp | 34 ++ ps2xTest/src/code_generator_tests.cpp | 168 ++++++++ ps2xTest/src/ps2_gs_tests.cpp | 67 ++++ ps2xTest/src/ps2_memory_tests.cpp | 163 ++++++++ ps2xTest/src/ps2_runtime_interrupt_tests.cpp | 52 +++ 24 files changed, 1756 insertions(+), 70 deletions(-) create mode 100644 ps2xRecomp/include/ps2recomp/gif_dma_kick_analyzer.h create mode 100644 ps2xRecomp/src/lib/gif_dma_kick_analyzer.cpp create mode 100644 ps2xRuntime/include/runtime/ps2_address.h diff --git a/ps2xRecomp/include/ps2recomp/Emitters/control_flow_emitter.h b/ps2xRecomp/include/ps2recomp/Emitters/control_flow_emitter.h index 6258519..a1ab9a0 100644 --- a/ps2xRecomp/include/ps2recomp/Emitters/control_flow_emitter.h +++ b/ps2xRecomp/include/ps2recomp/Emitters/control_flow_emitter.h @@ -19,7 +19,8 @@ namespace ps2recomp const Instruction &branchInst, const Instruction &delaySlot, const Function &function, - const CodeGenerator::AnalysisResult &analysisResult); + const CodeGenerator::AnalysisResult &analysisResult, + std::string delaySlotOverride = {}); std::string emit(); @@ -41,6 +42,7 @@ namespace ps2recomp const Instruction &m_delaySlot; const Function &m_function; const CodeGenerator::AnalysisResult &m_analysisResult; + std::string m_delaySlotOverride; std::stringstream m_ss; uint32_t branchPc() const; diff --git a/ps2xRecomp/include/ps2recomp/Translators/instruction_translator.h b/ps2xRecomp/include/ps2recomp/Translators/instruction_translator.h index ef0b274..c45cd07 100644 --- a/ps2xRecomp/include/ps2recomp/Translators/instruction_translator.h +++ b/ps2xRecomp/include/ps2recomp/Translators/instruction_translator.h @@ -3,6 +3,8 @@ #include +#include "ps2recomp/types.h" + namespace ps2recomp { struct Instruction; @@ -12,9 +14,20 @@ namespace ps2recomp { public: explicit InstructionTranslator(CodeGenerator &codeGenerator); - std::string translate(const Instruction &inst); + std::string translate(const Instruction &inst, const MemoryAccessHint &memoryHint); private: + MemoryAccessHint effectiveMemoryHintFor(const Instruction &inst, const MemoryAccessHint &memoryHint) const; + std::string translateMemoryRead(const Instruction &inst, + const MemoryAccessHint &memoryHint, + int width, + const std::string &addr) const; + std::string translateMemoryWrite(const Instruction &inst, + const MemoryAccessHint &memoryHint, + int width, + const std::string &addr, + const std::string &value) const; + CodeGenerator &m_codeGenerator; }; } diff --git a/ps2xRecomp/include/ps2recomp/code_generator.h b/ps2xRecomp/include/ps2recomp/code_generator.h index d2a3c8f..1bdf0f6 100644 --- a/ps2xRecomp/include/ps2recomp/code_generator.h +++ b/ps2xRecomp/include/ps2recomp/code_generator.h @@ -14,6 +14,7 @@ namespace ps2recomp struct JumpTableEntry; struct JumpTable; struct Instruction; + struct MemoryAccessHint; struct Function; struct Symbol; struct Section; @@ -43,6 +44,9 @@ namespace ps2recomp std::string generateFunctionRegistration(const std::vector &functions, const std::map &stubs); std::string handleBranchDelaySlots(const Instruction &branchInst, const Instruction &delaySlot, const Function &function, const AnalysisResult &analysisResult); + std::string handleBranchDelaySlots(const Instruction &branchInst, const Instruction &delaySlot, + const Function &function, const AnalysisResult &analysisResult, + std::string delaySlotOverride); void setRenamedFunctions(const std::unordered_map &renames); void setBootstrapInfo(const BootstrapInfo &info); @@ -69,6 +73,7 @@ namespace ps2recomp std::string m_currentFunctionName; std::string translateInstruction(const Instruction &inst); + std::string translateInstruction(const Instruction &inst, const MemoryAccessHint &memoryHint); std::string emitUnhandledInstruction(const Instruction &inst, const std::string &message); std::string translateMMIInstruction(const Instruction &inst); std::string translateVUInstruction(const Instruction &inst); diff --git a/ps2xRecomp/include/ps2recomp/gif_dma_kick_analyzer.h b/ps2xRecomp/include/ps2recomp/gif_dma_kick_analyzer.h new file mode 100644 index 0000000..f788ed2 --- /dev/null +++ b/ps2xRecomp/include/ps2recomp/gif_dma_kick_analyzer.h @@ -0,0 +1,62 @@ +#ifndef PS2RECOMP_GIF_DMA_KICK_ANALYZER_H +#define PS2RECOMP_GIF_DMA_KICK_ANALYZER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ps2recomp/types.h" + +namespace ps2recomp +{ + struct Instruction; + + struct ConstantRegisterState + { + std::array known{}; + std::array values{}; + + ConstantRegisterState(); + void clear(); + bool read(uint32_t reg, uint32_t &value) const; + void write(uint32_t reg, uint32_t value); + void invalidate(uint32_t reg); + }; + + struct GifDmaKickPlan + { + bool valid = false; + std::array storeIndices{}; + std::array values{}; + std::array captureExpressions{}; + std::array captures{}; + size_t branchIndex = std::numeric_limits::max(); + size_t endIndex = 0; + bool completesInDelaySlot = false; + + bool suppresses(size_t index) const; + bool completesAt(size_t index) const; + size_t slotFor(size_t index) const; + }; + + bool isDirectMemoryAccess(const Instruction &inst); + MemoryAccessHint resolveMemoryAccessHint(const Instruction &inst, const ConstantRegisterState &constants); + void updateConstantRegisters(const Instruction &inst, ConstantRegisterState &constants); + + GifDmaKickPlan tryBuildGifDmaKickPlan(const std::vector &instructions, + size_t startIndex, + const ConstantRegisterState &constants, + const std::unordered_set &internalTargets); + + std::string gifDmaKickCall(const GifDmaKickPlan &plan); + void emitGifDmaCapture(std::ostream &out, const GifDmaKickPlan &plan, size_t slot, std::string_view indent); + std::string gifDmaDelaySlotOverride(const Instruction &delaySlot, const GifDmaKickPlan &plan, bool emitComments); +} + +#endif // PS2RECOMP_GIF_DMA_KICK_ANALYZER_H diff --git a/ps2xRecomp/include/ps2recomp/types.h b/ps2xRecomp/include/ps2recomp/types.h index 7e03280..39adf9a 100644 --- a/ps2xRecomp/include/ps2recomp/types.h +++ b/ps2xRecomp/include/ps2recomp/types.h @@ -80,6 +80,12 @@ namespace ps2recomp } }; + struct MemoryAccessHint + { + bool hasAddress = false; + uint32_t address = 0; + }; + // Function information struct Function { diff --git a/ps2xRecomp/src/lib/code_generator.cpp b/ps2xRecomp/src/lib/code_generator.cpp index 96e5ce5..68b0984 100644 --- a/ps2xRecomp/src/lib/code_generator.cpp +++ b/ps2xRecomp/src/lib/code_generator.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -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) diff --git a/ps2xRecomp/src/lib/control_flow_emitter.cpp b/ps2xRecomp/src/lib/control_flow_emitter.cpp index bf22928..315b8d8 100644 --- a/ps2xRecomp/src/lib/control_flow_emitter.cpp +++ b/ps2xRecomp/src/lib/control_flow_emitter.cpp @@ -8,6 +8,7 @@ #include #include #include +#include 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) { diff --git a/ps2xRecomp/src/lib/function_emitter.cpp b/ps2xRecomp/src/lib/function_emitter.cpp index db57747..af98cda 100644 --- a/ps2xRecomp/src/lib/function_emitter.cpp +++ b/ps2xRecomp/src/lib/function_emitter.cpp @@ -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 &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) diff --git a/ps2xRecomp/src/lib/gif_dma_kick_analyzer.cpp b/ps2xRecomp/src/lib/gif_dma_kick_analyzer.cpp new file mode 100644 index 0000000..b7c17c6 --- /dev/null +++ b/ps2xRecomp/src/lib/gif_dma_kick_analyzer.cpp @@ -0,0 +1,374 @@ +#include "ps2recomp/gif_dma_kick_analyzer.h" + +#include "ps2recomp/instructions.h" +#include "ps2recomp/r5900_decoder.h" + +#include +#include +#include + +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 &instructions, + size_t startIndex, + const ConstantRegisterState &constants, + const std::unordered_set &internalTargets) + { + static constexpr std::array 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 {}; + } +} diff --git a/ps2xRecomp/src/lib/instruction_translator.cpp b/ps2xRecomp/src/lib/instruction_translator.cpp index f4a31c8..ae38ffa 100644 --- a/ps2xRecomp/src/lib/instruction_translator.cpp +++ b/ps2xRecomp/src/lib/instruction_translator.cpp @@ -4,42 +4,145 @@ #include "ps2recomp/instructions.h" #include "ps2recomp/types.h" #include "ps2recomp/control_flow_utils.h" +#include "runtime/ps2_address.h" #include -#include -#include - namespace ps2recomp { + namespace + { + std::string addressLiteral(uint32_t address) + { + return fmt::format("0x{:X}u", address); + } + + uint32_t memoryAccessSize(int width) + { + return static_cast(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(PS2_EXTRACT_EPI64_0(_value)); " + "const uint64_t _hi = static_cast(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) diff --git a/ps2xRuntime/include/ps2_runtime.h b/ps2xRuntime/include/ps2_runtime.h index 924e074..b58ddb8 100644 --- a/ps2xRuntime/include/ps2_runtime.h +++ b/ps2xRuntime/include/ps2_runtime.h @@ -23,6 +23,7 @@ #include #include "ps2_log.h" +#include "runtime/ps2_address.h" #include "runtime/ps2_gif_arbiter.h" #include "runtime/ps2_memory.h" #include "runtime/ps2_gs_gpu.h" @@ -480,36 +481,16 @@ public: void Store32(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, uint32_t value); void Store64(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, uint64_t value); void Store128(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, __m128i value); + void kickGifDmaChainFromMMIO(uint8_t *rdram, + R5900Context *ctx, + uint32_t dPcrValue, + uint32_t dStatValue, + uint32_t tadr, + uint32_t chcr); static inline bool isSpecialAddress(uint32_t addr) { - auto inRange = [](uint32_t value, uint32_t base, uint32_t size) -> bool - { - return (value - base) < size; - }; - - auto isPhysicalSpecial = [&](uint32_t physAddr) -> bool - { - if (inRange(physAddr, PS2_BIOS_BASE, PS2_BIOS_SIZE)) - return true; - if (inRange(physAddr, PS2_SCRATCHPAD_BASE, PS2_SCRATCHPAD_SIZE)) - return true; - if (inRange(physAddr, PS2_IO_BASE, PS2_IO_SIZE)) - return true; - if (inRange(physAddr, PS2_GS_PRIV_REG_BASE, PS2_GS_PRIV_REG_SIZE)) - return true; - if (physAddr >= PS2_VU0_DATA_BASE && physAddr < (PS2_VU1_CODE_BASE + PS2_VU1_CODE_SIZE)) - return true; - return false; - }; - - // KSEG2/KSEG3 (TLB mapped) - if (addr >= 0xC0000000u) - return true; - - // KSEG0/KSEG1 aliases → physical - const uint32_t physAddr = (addr >= 0x80000000u) ? (addr & 0x1FFFFFFFu) : addr; - return isPhysicalSpecial(physAddr); + return Ps2IsSpecialAddress(addr); } public: diff --git a/ps2xRuntime/include/runtime/ps2_address.h b/ps2xRuntime/include/runtime/ps2_address.h new file mode 100644 index 0000000..f7689e0 --- /dev/null +++ b/ps2xRuntime/include/runtime/ps2_address.h @@ -0,0 +1,61 @@ +#ifndef PS2_ADDRESS_H +#define PS2_ADDRESS_H + +#include + +#include "runtime/ps2_memory.h" + +static inline constexpr uint32_t PS2_EE_UNCACHED_RAM_MIRROR_BASE = 0x20000000u; +static inline constexpr uint32_t PS2_EE_UNCACHED_RAM_MIRROR_SIZE = 0x20000000u; +static inline constexpr uint32_t PS2_KSEG0_BASE = 0x80000000u; +static inline constexpr uint32_t PS2_KSEG0_KSEG1_SIZE = 0x40000000u; +static inline constexpr uint32_t PS2_KSEG2_BASE = 0xC0000000u; + +static inline constexpr bool Ps2AddressInRange(uint32_t value, uint32_t base, uint32_t size) +{ + return (value - base) < size; +} + +static inline constexpr bool Ps2IsUncachedRamMirrorAddress(uint32_t addr) +{ + return Ps2AddressInRange(addr, PS2_EE_UNCACHED_RAM_MIRROR_BASE, PS2_EE_UNCACHED_RAM_MIRROR_SIZE); +} + +static inline constexpr bool Ps2IsKseg01Address(uint32_t addr) +{ + return Ps2AddressInRange(addr, PS2_KSEG0_BASE, PS2_KSEG0_KSEG1_SIZE); +} + +static inline constexpr bool Ps2IsKseg23Address(uint32_t addr) +{ + return addr >= PS2_KSEG2_BASE; +} + +static inline constexpr uint32_t Ps2DirectMappedPhysicalAddress(uint32_t addr) +{ + return addr & 0x1FFFFFFFu; +} + +static inline constexpr uint32_t Ps2PhysicalAddress(uint32_t addr) +{ + return (addr >= PS2_KSEG0_BASE) ? Ps2DirectMappedPhysicalAddress(addr) : addr; +} + +static inline constexpr bool Ps2IsPhysicalSpecialAddress(uint32_t physAddr) +{ + return Ps2AddressInRange(physAddr, PS2_BIOS_BASE, PS2_BIOS_SIZE) || + Ps2AddressInRange(physAddr, PS2_SCRATCHPAD_BASE, PS2_SCRATCHPAD_SIZE) || + Ps2AddressInRange(physAddr, PS2_IO_BASE, PS2_IO_SIZE) || + Ps2AddressInRange(physAddr, PS2_GS_PRIV_REG_BASE, PS2_GS_PRIV_REG_SIZE) || + (physAddr >= PS2_VU0_DATA_BASE && physAddr < (PS2_VU1_CODE_BASE + PS2_VU1_CODE_SIZE)); +} + +static inline constexpr bool Ps2IsSpecialAddress(uint32_t addr) +{ + if (Ps2IsKseg23Address(addr)) + return true; + + return Ps2IsPhysicalSpecialAddress(Ps2PhysicalAddress(addr)); +} + +#endif // PS2_ADDRESS_H diff --git a/ps2xRuntime/include/runtime/ps2_gif_arbiter.h b/ps2xRuntime/include/runtime/ps2_gif_arbiter.h index 600076b..2012083 100644 --- a/ps2xRuntime/include/runtime/ps2_gif_arbiter.h +++ b/ps2xRuntime/include/runtime/ps2_gif_arbiter.h @@ -33,6 +33,7 @@ public: void submit(GifPathId pathId, const uint8_t *data, uint32_t sizeBytes, bool path2DirectHl = false); void drain(); + bool empty() const { return m_queue.empty(); } private: ProcessPacketFn m_processFn; diff --git a/ps2xRuntime/include/runtime/ps2_gs_gpu.h b/ps2xRuntime/include/runtime/ps2_gs_gpu.h index 6991524..40b9459 100644 --- a/ps2xRuntime/include/runtime/ps2_gs_gpu.h +++ b/ps2xRuntime/include/runtime/ps2_gs_gpu.h @@ -321,6 +321,13 @@ public: void reset(); void processGIFPacket(const uint8_t *data, uint32_t sizeBytes); + bool processNativePackedGIFPacket(const uint8_t *data, uint32_t sizeBytes); + void uploadImageNative(uint64_t bitbltbuf, + uint64_t trxpos, + uint64_t trxreg, + uint64_t trxdir, + const uint8_t *data, + uint32_t sizeBytes); void writeRegister(uint8_t regAddr, uint64_t value); const uint8_t *lockDisplaySnapshot(uint32_t &outSize); @@ -346,6 +353,8 @@ public: bool *outUsedPreferred = nullptr) const; bool clearFramebufferContext(uint32_t contextIndex, uint32_t rgba); bool clearActiveFramebuffer(uint32_t rgba); + uint64_t nativeImageUploadCount() const { return m_nativeImageUploadCount; } + uint64_t nativePackedGIFPacketCount() const { return m_nativePackedGIFPacketCount; } uint32_t consumeLocalToHostBytes(uint8_t *dst, uint32_t maxBytes); @@ -369,6 +378,7 @@ private: void recordPresentDebugEventUnlocked(uint32_t displayFbp, uint32_t sourceFbp, uint32_t width, uint32_t height, bool usedPreferred); void processImageData(const uint8_t *data, uint32_t sizeBytes); + bool tryProcessNativeImageUploadPacket(const uint8_t *data, uint32_t sizeBytes); void performLocalToLocalTransfer(); void performLocalToHostToBuffer(); bool copyFrameToHostRgbaUnlocked(const GSFrameReg &frame, @@ -433,6 +443,8 @@ private: uint32_t m_hostPresentationSourceFbp = 0; bool m_hostPresentationUsedPreferred = false; bool m_hasHostPresentationFrame = false; + uint64_t m_nativeImageUploadCount = 0; + uint64_t m_nativePackedGIFPacketCount = 0; std::vector m_localToHostBuffer; size_t m_localToHostReadPos = 0; diff --git a/ps2xRuntime/include/runtime/ps2_memory.h b/ps2xRuntime/include/runtime/ps2_memory.h index e5fdfb3..e6eefde 100644 --- a/ps2xRuntime/include/runtime/ps2_memory.h +++ b/ps2xRuntime/include/runtime/ps2_memory.h @@ -20,6 +20,8 @@ #include // For SSE4.1 instructions #endif +class GS; + constexpr uint32_t PS2_RAM_SIZE = 32u * 1024u * 1024u; // 32MB constexpr uint32_t PS2_RAM_MASK = PS2_RAM_SIZE - 1u; // Mask for 32MB alignment constexpr uint32_t PS2_RAM_BASE = 0x00000000; // Physical base of RDRAM @@ -332,6 +334,8 @@ public: void submitGifPacket(GifPathId pathId, const uint8_t *data, uint32_t sizeBytes, bool drainImmediately = true, bool path2DirectHl = false); void processGIFPacket(uint32_t srcPhysAddr, uint32_t qwCount); void processGIFPacket(const uint8_t *data, uint32_t sizeBytes); + bool tryProcessNativeGifImageUploadChain(GS &gs, uint32_t tadr, uint32_t chcr); + bool tryProcessNativeGifPackedChain(GS &gs, uint32_t tadr, uint32_t chcr); void processVIF0Data(uint32_t srcPhysAddr, uint32_t sizeBytes); void processVIF0Data(const uint8_t *data, uint32_t sizeBytes); void processVIF1Data(uint32_t srcPhysAddr, uint32_t sizeBytes); diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/SIF.cpp b/ps2xRuntime/src/lib/Kernel/Stubs/SIF.cpp index 6402190..feb6e8b 100644 --- a/ps2xRuntime/src/lib/Kernel/Stubs/SIF.cpp +++ b/ps2xRuntime/src/lib/Kernel/Stubs/SIF.cpp @@ -1,6 +1,7 @@ #include "Common.h" #include "SIF.h" #include "../Syscalls/RPC.h" +#include "runtime/ps2_address.h" #include @@ -186,22 +187,22 @@ namespace ps2_stubs bool isCopyableGuestAddress(uint32_t addr) { - if (addr >= PS2_SCRATCHPAD_BASE && addr < (PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE)) + if (Ps2AddressInRange(addr, PS2_SCRATCHPAD_BASE, PS2_SCRATCHPAD_SIZE)) { return true; } - if (addr < 0x20000000u) + if (addr < PS2_EE_UNCACHED_RAM_MIRROR_BASE) { return true; } - if (addr >= 0x20000000u && addr < 0x40000000u) + if (Ps2IsUncachedRamMirrorAddress(addr)) { return true; } - if (addr >= 0x80000000u && addr < 0xC0000000u) + if (Ps2IsKseg01Address(addr)) { return true; } diff --git a/ps2xRuntime/src/lib/ps2_gs_gpu.cpp b/ps2xRuntime/src/lib/ps2_gs_gpu.cpp index 6c76c98..d97e564 100644 --- a/ps2xRuntime/src/lib/ps2_gs_gpu.cpp +++ b/ps2xRuntime/src/lib/ps2_gs_gpu.cpp @@ -55,6 +55,63 @@ namespace return v; } + struct PackedGifPacketTag + { + uint64_t lo = 0u; + uint64_t hi = 0u; + uint32_t payloadOffset = 0u; + uint32_t nloop = 0u; + uint32_t nreg = 0u; + uint8_t regs[16]{}; + }; + + template + bool visitPackedGifPacket(const uint8_t *data, uint32_t sizeBytes, Visitor &&visitor) + { + uint32_t offset = 0u; + while (offset + 16u <= sizeBytes) + { + PackedGifPacketTag tag{}; + tag.lo = loadLE64(data + offset); + tag.hi = loadLE64(data + offset + 8u); + + const uint8_t flg = static_cast((tag.lo >> 58u) & 0x3u); + if (flg != GIF_FMT_PACKED) + return false; + + tag.nloop = static_cast(tag.lo & 0x7FFFu); + tag.nreg = static_cast((tag.lo >> 60u) & 0xFu); + if (tag.nreg == 0u) + tag.nreg = 16u; + + const uint64_t payloadBytes64 = + static_cast(tag.nloop) * static_cast(tag.nreg) * 16ull; + if (payloadBytes64 > 0xFFFFFFFFull) + return false; + + offset += 16u; + const uint32_t payloadBytes = static_cast(payloadBytes64); + if (payloadBytes > sizeBytes - offset) + return false; + + tag.payloadOffset = offset; + for (uint32_t i = 0u; i < tag.nreg; ++i) + tag.regs[i] = static_cast((tag.hi >> (i * 4u)) & 0xFu); + + if (!visitor(tag)) + return false; + + offset += payloadBytes; + } + + return offset == sizeBytes; + } + + bool validatePackedGifPacket(const uint8_t *data, uint32_t sizeBytes) + { + return visitPackedGifPacket(data, sizeBytes, [](const PackedGifPacketTag &) { return true; }); + } + void decodeDisplaySize(uint64_t display64, uint32_t &outWidth, uint32_t &outHeight) { const uint32_t dx = static_cast((display64 >> 0) & 0x0FFFu); @@ -1221,6 +1278,9 @@ void GS::processGIFPacket(const uint8_t *data, uint32_t sizeBytes) if (!data || sizeBytes < 16 || !m_vram) return; + if (tryProcessNativeImageUploadPacket(data, sizeBytes)) + return; + PS2_IF_AGRESSIVE_LOGS({ const uint32_t packetIndex = s_debugGifPacketCount.fetch_add(1, std::memory_order_relaxed); if (packetIndex < 48u) @@ -1311,6 +1371,136 @@ void GS::processGIFPacket(const uint8_t *data, uint32_t sizeBytes) } } +bool GS::processNativePackedGIFPacket(const uint8_t *data, uint32_t sizeBytes) +{ + std::lock_guard lock(m_stateMutex); + if (!data || sizeBytes < 16u || !m_vram) + return false; + + if (!validatePackedGifPacket(data, sizeBytes)) + return false; + + const bool processed = visitPackedGifPacket(data, sizeBytes, [&](const PackedGifPacketTag &tag) + { + m_curQ = 1.0f; + + recordGifTagDebugEventUnlocked(sizeBytes, tag.nloop, GIF_FMT_PACKED, tag.nreg); + + const bool pre = ((tag.lo >> 46u) & 1u) != 0u; + if (pre) + writeRegister(GS_REG_PRIM, (tag.lo >> 47u) & 0x7FFu); + + uint32_t offset = tag.payloadOffset; + for (uint32_t loop = 0u; loop < tag.nloop; ++loop) + { + for (uint32_t r = 0u; r < tag.nreg; ++r) + { + const uint64_t lo = loadLE64(data + offset); + const uint64_t hi = loadLE64(data + offset + 8u); + offset += 16u; + writeRegisterPacked(tag.regs[r], lo, hi); + } + } + + return true; + }); + + if (!processed) + return false; + + ++m_nativePackedGIFPacketCount; + return true; +} + +void GS::uploadImageNative(uint64_t bitbltbuf, + uint64_t trxpos, + uint64_t trxreg, + uint64_t trxdir, + const uint8_t *data, + uint32_t sizeBytes) +{ + std::lock_guard lock(m_stateMutex); + if (!data || sizeBytes == 0 || !m_vram) + return; + + writeRegister(GS_REG_BITBLTBUF, bitbltbuf); + writeRegister(GS_REG_TRXPOS, trxpos); + writeRegister(GS_REG_TRXREG, trxreg); + writeRegister(GS_REG_TRXDIR, trxdir); + processImageData(data, sizeBytes); + ++m_nativeImageUploadCount; +} + +bool GS::tryProcessNativeImageUploadPacket(const uint8_t *data, uint32_t sizeBytes) +{ + constexpr uint32_t kSetupRegisters = 4u; + constexpr uint32_t kPackedAdPayloadBytes = kSetupRegisters * 16u; + constexpr uint64_t kPackedAdDescriptor = 0x0Eull; + + if (!data || sizeBytes < 16u + kPackedAdPayloadBytes + 16u) + return false; + + const uint64_t setupTagLo = loadLE64(data); + const uint64_t setupTagHi = loadLE64(data + 8u); + const uint32_t setupNloop = static_cast(setupTagLo & 0x7FFFu); + const uint8_t setupFlg = static_cast((setupTagLo >> 58u) & 0x3u); + uint32_t setupNreg = static_cast((setupTagLo >> 60u) & 0xFu); + if (setupNreg == 0u) + setupNreg = 16u; + + if (setupNloop != kSetupRegisters || + setupFlg != GIF_FMT_PACKED || + setupNreg != 1u || + (setupTagHi & 0xFull) != kPackedAdDescriptor) + { + return false; + } + + uint64_t regs[kSetupRegisters] = {}; + uint32_t offset = 16u; + constexpr uint8_t expectedRegs[kSetupRegisters] = { + GS_REG_BITBLTBUF, + GS_REG_TRXPOS, + GS_REG_TRXREG, + GS_REG_TRXDIR, + }; + + for (uint32_t i = 0; i < kSetupRegisters; ++i) + { + regs[i] = loadLE64(data + offset); + const uint64_t reg = loadLE64(data + offset + 8u); + if ((reg & 0xFFu) != expectedRegs[i]) + return false; + offset += 16u; + } + + const uint32_t trxdirMode = static_cast(regs[3] & 0x3ull); + const uint32_t rrw = static_cast(regs[2] & 0xFFFull); + const uint32_t rrh = static_cast((regs[2] >> 32u) & 0xFFFull); + if (trxdirMode != 0u || rrw == 0u || rrh == 0u) + return false; + + if (offset + 16u > sizeBytes) + return false; + + const uint64_t imageTagLo = loadLE64(data + offset); + const uint8_t imageFlg = static_cast((imageTagLo >> 58u) & 0x3u); + const uint32_t imageNloop = static_cast(imageTagLo & 0x7FFFu); + if (imageFlg != GIF_FMT_IMAGE || imageNloop == 0u) + return false; + + offset += 16u; + const uint64_t imageBytes64 = static_cast(imageNloop) * 16ull; + if (imageBytes64 > 0xFFFFFFFFull) + return false; + const uint32_t imageBytes = static_cast(imageBytes64); + if (offset + imageBytes != sizeBytes) + return false; + + uploadImageNative(regs[0], regs[1], regs[2], regs[3], data + offset, imageBytes); + return true; +} + void GS::writeRegisterPacked(uint8_t regDesc, uint64_t lo, uint64_t hi) { switch (regDesc) diff --git a/ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp b/ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp index d3210c3..9c14355 100644 --- a/ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp +++ b/ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp @@ -791,8 +791,10 @@ void GSRasterizer::drawSprite(GS *gs) uint32_t texel = 0xFFFF00FFu; if (gs->m_prim.fst) { - const uint16_t sampleU = static_cast(clampInt(static_cast(std::lround(texUf * 16.0f)), 0, 0xFFFF)); - const uint16_t sampleV = static_cast(clampInt(static_cast(std::lround(texVf * 16.0f)), 0, 0xFFFF)); + const int fixedU = static_cast((texUf * 16.0f) + 0.5f); + const int fixedV = static_cast((texVf * 16.0f) + 0.5f); + const uint16_t sampleU = static_cast(clampInt(fixedU, 0, 0xFFFF)); + const uint16_t sampleV = static_cast(clampInt(fixedV, 0, 0xFFFF)); texel = sampleTexture(gs, 0.0f, 0.0f, 1.0f, sampleU, sampleV); } else diff --git a/ps2xRuntime/src/lib/ps2_memory.cpp b/ps2xRuntime/src/lib/ps2_memory.cpp index a40505b..bfd2163 100644 --- a/ps2xRuntime/src/lib/ps2_memory.cpp +++ b/ps2xRuntime/src/lib/ps2_memory.cpp @@ -1,4 +1,6 @@ #include "runtime/ps2_memory.h" +#include "runtime/ps2_address.h" +#include "runtime/ps2_gs_gpu.h" #include "ps2_log.h" #include #include @@ -36,7 +38,12 @@ namespace inline bool isGsPrivReg(uint32_t addr) { - return addr >= PS2_GS_PRIV_REG_BASE && addr < PS2_GS_PRIV_REG_BASE + PS2_GS_PRIV_REG_SIZE; + return Ps2AddressInRange(addr, PS2_GS_PRIV_REG_BASE, PS2_GS_PRIV_REG_SIZE); + } + + inline bool isIoRegister(uint32_t addr) + { + return Ps2AddressInRange(addr, PS2_IO_BASE, PS2_IO_SIZE); } inline uint64_t *gsRegPtr(GSRegisters &gs, uint32_t addr) @@ -156,6 +163,42 @@ namespace return static_cast(duration_cast(steady_clock::now().time_since_epoch()).count()); } + struct DmaTagView + { + uint16_t qwc = 0; + uint8_t id = 0; + bool irq = false; + uint32_t addr = 0; + uint32_t upper = 0; + }; + + inline DmaTagView decodeDmaTag(uint64_t tag) + { + DmaTagView out{}; + out.qwc = static_cast(tag & 0xFFFFu); + out.id = static_cast((tag >> 28u) & 0x7u); + out.irq = ((tag >> 31u) & 0x1ull) != 0ull; + out.addr = static_cast((tag >> 32u) & 0x7FFFFFFFu); + out.upper = static_cast((tag >> 16u) & 0xFFFFu); + return out; + } + + inline uint32_t gifTagNloop(uint64_t tagLo) + { + return static_cast(tagLo & 0x7FFFu); + } + + inline uint8_t gifTagFlg(uint64_t tagLo) + { + return static_cast((tagLo >> 58u) & 0x3u); + } + + inline uint32_t gifTagNreg(uint64_t tagLo) + { + uint32_t nreg = static_cast((tagLo >> 60u) & 0xFu); + return nreg == 0u ? 16u : nreg; + } + } // Helpers for GS VRAM addressing (PSMCT32 path). @@ -412,15 +455,15 @@ uint32_t PS2Memory::translateAddress(uint32_t virtualAddress) // EE uncached aliases of main RAM (per PS2 memory map): // 0x20000000-0x3FFFFFFF -> 32MB mirror of RDRAM // This includes the accelerated window rooted at 0x30100000. - if (virtualAddress >= 0x20000000u && virtualAddress < 0x40000000u) + if (Ps2IsUncachedRamMirrorAddress(virtualAddress)) { return virtualAddress & PS2_RAM_MASK; } // KSEG0/KSEG1 direct-mapped window. - if (virtualAddress >= 0x80000000 && virtualAddress < 0xC0000000) + if (Ps2IsKseg01Address(virtualAddress)) { - return virtualAddress & 0x1FFFFFFF; + return Ps2DirectMappedPhysicalAddress(virtualAddress); } // In this runtime, low segments are treated as physical-style addresses already. @@ -430,7 +473,7 @@ uint32_t PS2Memory::translateAddress(uint32_t virtualAddress) } // KSEG2/KSEG3 are TLB mapped. - if (virtualAddress >= 0xC0000000) + if (Ps2IsKseg23Address(virtualAddress)) { for (const auto &entry : m_tlbEntries) { @@ -526,7 +569,7 @@ uint8_t PS2Memory::read8(uint32_t address) (void)vuLimit; return vuMem[vuOffset]; } - else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) + else if (isIoRegister(physAddr)) { uint32_t regAddr = physAddr & ~0x3; uint32_t value = readIORegister(regAddr); @@ -561,7 +604,7 @@ uint16_t PS2Memory::read16(uint32_t address) { return loadScalar(vuMem, vuOffset, vuLimit, "read16 vu", address); } - else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) + else if (isIoRegister(physAddr)) { uint32_t regAddr = physAddr & ~0x3; uint32_t value = readIORegister(regAddr); @@ -612,7 +655,7 @@ uint32_t PS2Memory::read32(uint32_t address) { return loadScalar(vuMem, vuOffset, vuLimit, "read32 vu", address); } - else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) + else if (isIoRegister(physAddr)) { return readIORegister(physAddr); } @@ -658,7 +701,7 @@ uint64_t PS2Memory::read64(uint32_t address) // 64-bit IO read: compose from the two adjacent 32-bit IO register slots // to avoid any side-effects from read32 handlers. - if (address >= PS2_IO_BASE && address < (PS2_IO_BASE + PS2_IO_SIZE)) + if (isIoRegister(address)) { uint32_t lo = m_ioRegisters.count(address) ? m_ioRegisters[address] : 0u; uint32_t hi = m_ioRegisters.count(address + 4) ? m_ioRegisters[address + 4] : 0u; @@ -724,7 +767,7 @@ void PS2Memory::write8(uint32_t address, uint8_t value) return; } } - if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) + if (isIoRegister(physAddr)) { // IO registers - handle byte writes by modifying the appropriate byte in the word uint32_t regAddr = physAddr & ~0x3; @@ -763,7 +806,7 @@ void PS2Memory::write16(uint32_t address, uint16_t value) return; } } - if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) + if (isIoRegister(physAddr)) { uint32_t regAddr = physAddr & ~0x3; uint32_t shift = (physAddr & 2) * 8; @@ -823,7 +866,7 @@ void PS2Memory::write32(uint32_t address, uint32_t value) return; } } - if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) + if (isIoRegister(physAddr)) { writeIORegister(physAddr, value); } @@ -874,7 +917,7 @@ void PS2Memory::write64(uint32_t address, uint64_t value) return; } } - if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) + if (isIoRegister(physAddr)) { write32(address, (uint32_t)value); write32(address + 4, (uint32_t)(value >> 32)); @@ -913,7 +956,7 @@ void PS2Memory::write128(uint32_t address, __m128i value) return; } } - if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) + if (isIoRegister(physAddr)) { // Non-RAM 128-bit stores are modeled as two 64-bit stores. uint64_t lo = _mm_extract_epi64(value, 0); @@ -1693,6 +1736,278 @@ void PS2Memory::processGIFPacket(const uint8_t *data, uint32_t sizeBytes) m_gifPacketCallback(data, sizeBytes); } +bool PS2Memory::tryProcessNativeGifImageUploadChain(GS &gs, uint32_t tadr, uint32_t chcr) +{ + static constexpr uint32_t GIF_CHANNEL = 0x1000A000u; + static constexpr uint32_t D_STAT = 0x1000E010u; + static constexpr uint32_t D_CTRL = 0x1000E000u; + + if (!m_rdram || !m_gsVRAM || m_path3Masked) + return false; + if (m_gifArbiter && !m_gifArbiter->empty()) + return false; + if ((chcr & 0x100u) == 0u || ((chcr >> 2u) & 0x3u) != 1u) + return false; + if ((chcr & (1u << 7u)) != 0u || ((chcr >> 4u) & 0x3u) != 0u) + return false; + + const auto dctrlIt = m_ioRegisters.find(D_CTRL); + if (dctrlIt != m_ioRegisters.end() && ((dctrlIt->second & 0x1u) == 0u)) + return false; + + auto resolveContiguous = [&](uint32_t guestAddr, uint32_t bytes, const uint8_t *&out) -> bool + { + try + { + const bool scratch = isScratchpad(guestAddr); + const uint32_t phys = translateAddress(guestAddr); + const uint8_t *base = scratch ? m_scratchpad : m_rdram; + const uint32_t limit = scratch ? PS2_SCRATCHPAD_SIZE : PS2_RAM_SIZE; + if (!base || phys > limit || bytes > limit - phys) + return false; + out = base + phys; + return true; + } + catch (const std::exception &) + { + return false; + } + }; + + auto loadDmaTagAt = [&](uint32_t guestAddr, DmaTagView &out) -> bool + { + const uint8_t *ptr = nullptr; + if (!resolveContiguous(guestAddr, 16u, ptr)) + return false; + out = decodeDmaTag(loadScalar(ptr, 0u, 16u, "native gif dma tag", guestAddr)); + return true; + }; + + auto decodeSetupPayload = [&](const uint8_t *payload, uint64_t (®s)[4]) -> bool + { + const uint64_t tagLo = loadScalar(payload, 0u, 80u, "native gif setup tag", 0u); + const uint64_t tagHi = loadScalar(payload, 8u, 80u, "native gif setup regs", 0u); + if (gifTagNloop(tagLo) != 4u || + gifTagFlg(tagLo) != GIF_FMT_PACKED || + gifTagNreg(tagLo) != 1u || + (tagHi & 0xFull) != 0x0Eull) + { + return false; + } + + static constexpr uint8_t kExpectedRegs[4] = { + GS_REG_BITBLTBUF, + GS_REG_TRXPOS, + GS_REG_TRXREG, + GS_REG_TRXDIR, + }; + + uint32_t offset = 16u; + for (uint32_t i = 0; i < 4u; ++i) + { + regs[i] = loadScalar(payload, offset, 80u, "native gif setup value", 0u); + const uint64_t reg = loadScalar(payload, offset + 8u, 80u, "native gif setup register", 0u); + if ((reg & 0xFFu) != kExpectedRegs[i]) + return false; + offset += 16u; + } + + const uint32_t trxdirMode = static_cast(regs[3] & 0x3ull); + const uint32_t rrw = static_cast(regs[2] & 0xFFFull); + const uint32_t rrh = static_cast((regs[2] >> 32u) & 0xFFFull); + return trxdirMode == 0u && rrw != 0u && rrh != 0u; + }; + + DmaTagView setupTag{}; + if (!loadDmaTagAt(tadr, setupTag) || + setupTag.id != 1u || + setupTag.qwc != 5u || + setupTag.irq) + { + return false; + } + + const uint8_t *setupPayload = nullptr; + const uint32_t setupPayloadAddr = tadr + 16u; + if (!resolveContiguous(setupPayloadAddr, 5u * 16u, setupPayload)) + return false; + + uint64_t setupRegs[4] = {}; + if (!decodeSetupPayload(setupPayload, setupRegs)) + return false; + + uint32_t imageTagDmaAddr = setupPayloadAddr + 5u * 16u; + DmaTagView imageTagDma{}; + if (!loadDmaTagAt(imageTagDmaAddr, imageTagDma) || + imageTagDma.id != 1u || + imageTagDma.qwc != 1u || + imageTagDma.irq) + { + return false; + } + + const uint8_t *imageGifTag = nullptr; + if (!resolveContiguous(imageTagDmaAddr + 16u, 16u, imageGifTag)) + return false; + + const uint64_t imageTagLo = loadScalar(imageGifTag, 0u, 16u, "native gif image tag", imageTagDmaAddr + 16u); + if (gifTagFlg(imageTagLo) != GIF_FMT_IMAGE) + return false; + + const uint32_t imageQwc = gifTagNloop(imageTagLo); + if (imageQwc == 0u) + return false; + + const uint64_t imageBytes64 = static_cast(imageQwc) * 16ull; + if (imageBytes64 > 0xFFFFFFFFull) + return false; + const uint32_t imageBytes = static_cast(imageBytes64); + + const uint32_t payloadTagAddr = imageTagDmaAddr + 32u; + DmaTagView payloadTag{}; + if (!loadDmaTagAt(payloadTagAddr, payloadTag) || + payloadTag.qwc != imageQwc || + payloadTag.irq) + { + return false; + } + + uint32_t imageDataAddr = 0u; + uint32_t finalTadr = payloadTagAddr; + uint32_t lastTagUpper = payloadTag.upper; + if (payloadTag.id == 3u || payloadTag.id == 4u) + { + imageDataAddr = payloadTag.addr; + const uint32_t terminalTagAddr = payloadTagAddr + 16u; + DmaTagView terminalTag{}; + if (!loadDmaTagAt(terminalTagAddr, terminalTag) || + terminalTag.qwc != 0u || + terminalTag.irq || + (terminalTag.id != 0u && terminalTag.id != 7u)) + { + return false; + } + finalTadr = (terminalTag.id == 0u) ? (terminalTagAddr + 16u) : terminalTagAddr; + lastTagUpper = terminalTag.upper; + } + else if (payloadTag.id == 7u) + { + imageDataAddr = payloadTagAddr + 16u; + finalTadr = payloadTagAddr; + } + else + { + return false; + } + + const uint8_t *imageData = nullptr; + if (!resolveContiguous(imageDataAddr, imageBytes, imageData)) + return false; + + m_dmaStartCount.fetch_add(1, std::memory_order_relaxed); + m_seenGifCopy = true; + m_gifCopyCount.fetch_add(1, std::memory_order_relaxed); + gs.uploadImageNative(setupRegs[0], setupRegs[1], setupRegs[2], setupRegs[3], imageData, imageBytes); + + m_ioRegisters[GIF_CHANNEL + 0x30u] = finalTadr; + m_ioRegisters[GIF_CHANNEL + 0x40u] = 0u; + m_ioRegisters[GIF_CHANNEL + 0x50u] = 0u; + m_ioRegisters[GIF_CHANNEL + 0x00u] = ((chcr & 0x0000FFFFu) | (lastTagUpper << 16u)) & ~0x100u; + m_ioRegisters[GIF_CHANNEL + 0x20u] = 0u; + + uint32_t dstat = m_ioRegisters.count(D_STAT) ? m_ioRegisters[D_STAT] : 0u; + dstat |= (1u << 2u); + const uint32_t status = dstat & 0x3FFu; + const uint32_t mask = (dstat >> 16u) & 0x3FFu; + if ((status & mask) != 0u) + dstat |= (1u << 31u); + else + dstat &= ~(1u << 31u); + m_ioRegisters[D_STAT] = dstat; + queueCompletedDmacCause(2u); + return true; +} + +bool PS2Memory::tryProcessNativeGifPackedChain(GS &gs, uint32_t tadr, uint32_t chcr) +{ + static constexpr uint32_t GIF_CHANNEL = 0x1000A000u; + static constexpr uint32_t D_STAT = 0x1000E010u; + static constexpr uint32_t D_CTRL = 0x1000E000u; + + if (!m_rdram || !m_gsVRAM || m_path3Masked) + return false; + if (m_gifArbiter && !m_gifArbiter->empty()) + return false; + if ((chcr & 0x100u) == 0u || ((chcr >> 2u) & 0x3u) != 1u) + return false; + if ((chcr & (1u << 7u)) != 0u || ((chcr >> 4u) & 0x3u) != 0u) + return false; + + const auto dctrlIt = m_ioRegisters.find(D_CTRL); + if (dctrlIt != m_ioRegisters.end() && ((dctrlIt->second & 0x1u) == 0u)) + return false; + + auto resolveContiguous = [&](uint32_t guestAddr, uint32_t bytes, const uint8_t *&out) -> bool + { + try + { + const bool scratch = isScratchpad(guestAddr); + const uint32_t phys = translateAddress(guestAddr); + const uint8_t *base = scratch ? m_scratchpad : m_rdram; + const uint32_t limit = scratch ? PS2_SCRATCHPAD_SIZE : PS2_RAM_SIZE; + if (!base || phys > limit || bytes > limit - phys) + return false; + out = base + phys; + return true; + } + catch (const std::exception &) + { + return false; + } + }; + + const uint8_t *tagPtr = nullptr; + if (!resolveContiguous(tadr, 16u, tagPtr)) + return false; + + const DmaTagView tag = decodeDmaTag(loadScalar(tagPtr, 0u, 16u, "native packed gif dma tag", tadr)); + if (tag.id != 7u || tag.qwc == 0u || tag.irq) + return false; + + const uint64_t payloadBytes64 = static_cast(tag.qwc) * 16ull; + if (payloadBytes64 > 0xFFFFFFFFull) + return false; + const uint32_t payloadBytes = static_cast(payloadBytes64); + + const uint8_t *payload = nullptr; + if (!resolveContiguous(tadr + 16u, payloadBytes, payload)) + return false; + if (!gs.processNativePackedGIFPacket(payload, payloadBytes)) + return false; + + m_dmaStartCount.fetch_add(1, std::memory_order_relaxed); + m_seenGifCopy = true; + m_gifCopyCount.fetch_add(1, std::memory_order_relaxed); + + m_ioRegisters[GIF_CHANNEL + 0x30u] = tadr; + m_ioRegisters[GIF_CHANNEL + 0x40u] = 0u; + m_ioRegisters[GIF_CHANNEL + 0x50u] = 0u; + m_ioRegisters[GIF_CHANNEL + 0x00u] = ((chcr & 0x0000FFFFu) | (tag.upper << 16u)) & ~0x100u; + m_ioRegisters[GIF_CHANNEL + 0x20u] = 0u; + + uint32_t dstat = m_ioRegisters.count(D_STAT) ? m_ioRegisters[D_STAT] : 0u; + dstat |= (1u << 2u); + const uint32_t status = dstat & 0x3FFu; + const uint32_t mask = (dstat >> 16u) & 0x3FFu; + if ((status & mask) != 0u) + dstat |= (1u << 31u); + else + dstat &= ~(1u << 31u); + m_ioRegisters[D_STAT] = dstat; + queueCompletedDmacCause(2u); + return true; +} + int PS2Memory::pollDmaRegisters() { return 0; diff --git a/ps2xRuntime/src/lib/ps2_runtime.cpp b/ps2xRuntime/src/lib/ps2_runtime.cpp index 6f62997..667a6d3 100644 --- a/ps2xRuntime/src/lib/ps2_runtime.cpp +++ b/ps2xRuntime/src/lib/ps2_runtime.cpp @@ -2114,6 +2114,40 @@ void PS2Runtime::Store128(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, __m } } +void PS2Runtime::kickGifDmaChainFromMMIO(uint8_t *rdram, + R5900Context *ctx, + uint32_t dPcrValue, + uint32_t dStatValue, + uint32_t tadr, + uint32_t chcr) +{ + constexpr uint32_t D_PCR = 0x1000E020u; + constexpr uint32_t D_STAT = 0x1000E010u; + constexpr uint32_t GIF_TADR = 0x1000A030u; + constexpr uint32_t GIF_CHCR = 0x1000A000u; + + ps2TraceGuestWrite(rdram, D_PCR, 4u, dPcrValue, 0u, "WRITE32", ctx); + m_memory.writeIORegister(D_PCR, dPcrValue); + ps2TraceGuestWrite(rdram, D_STAT, 4u, dStatValue, 0u, "WRITE32", ctx); + m_memory.writeIORegister(D_STAT, dStatValue); + ps2TraceGuestWrite(rdram, GIF_TADR, 4u, tadr, 0u, "WRITE32", ctx); + m_memory.writeIORegister(GIF_TADR, tadr); + ps2TraceGuestWrite(rdram, GIF_CHCR, 4u, chcr, 0u, "WRITE32", ctx); + if (m_memory.tryProcessNativeGifImageUploadChain(m_gs, tadr, chcr)) + { + drainCompletedDmacHandlers(rdram); + return; + } + if (m_memory.tryProcessNativeGifPackedChain(m_gs, tadr, chcr)) + { + drainCompletedDmacHandlers(rdram); + return; + } + m_memory.writeIORegister(GIF_CHCR, chcr); + m_memory.processPendingTransfers(); + drainCompletedDmacHandlers(rdram); +} + void PS2Runtime::requestStop() { m_stopRequested.store(true, std::memory_order_relaxed); diff --git a/ps2xTest/src/code_generator_tests.cpp b/ps2xTest/src/code_generator_tests.cpp index 29ecb85..b1e8e0c 100644 --- a/ps2xTest/src/code_generator_tests.cpp +++ b/ps2xTest/src/code_generator_tests.cpp @@ -35,6 +35,50 @@ static Instruction makeNop(uint32_t address) return inst; } +static uint32_t signExtend16(uint16_t value) +{ + return static_cast(static_cast(static_cast(value))); +} + +static Instruction makeIType(uint32_t address, uint32_t opcode, uint8_t rs, uint8_t rt, uint16_t immediate) +{ + Instruction inst{}; + inst.address = address; + inst.opcode = opcode; + inst.rs = rs; + inst.rt = rt; + inst.immediate = immediate; + inst.simmediate = signExtend16(immediate); + inst.raw = (opcode << 26) | (static_cast(rs) << 21) | + (static_cast(rt) << 16) | immediate; + return inst; +} + +static Instruction makeLui(uint32_t address, uint8_t rt, uint16_t immediate) +{ + return makeIType(address, OPCODE_LUI, 0, rt, immediate); +} + +static Instruction makeOri(uint32_t address, uint8_t rt, uint8_t rs, uint16_t immediate) +{ + return makeIType(address, OPCODE_ORI, rs, rt, immediate); +} + +static Instruction makeAddiu(uint32_t address, uint8_t rt, uint8_t rs, uint16_t immediate) +{ + return makeIType(address, OPCODE_ADDIU, rs, rt, immediate); +} + +static Instruction makeLw(uint32_t address, uint8_t rt, uint8_t rs, uint16_t immediate) +{ + return makeIType(address, OPCODE_LW, rs, rt, immediate); +} + +static Instruction makeSw(uint32_t address, uint8_t rt, uint8_t rs, uint16_t immediate) +{ + return makeIType(address, OPCODE_SW, rs, rt, immediate); +} + static std::string readFileFromCandidates(const std::vector &candidates) { for (const auto &path : candidates) @@ -152,6 +196,130 @@ void register_code_generator_tests() "MULT1 should write low product to rd on R5900"); }); + tc.Run("constant MMIO store emits direct runtime store", [](TestCase &t) { + Function func; + func.name = "mmio_store"; + func.start = 0x1000; + func.end = 0x1010; + func.isRecompiled = true; + + std::vector instructions; + instructions.push_back(makeLui(0x1000, 1, 0x1000)); + instructions.push_back(makeOri(0x1004, 1, 1, 0xE020)); + instructions.push_back(makeSw(0x1008, 2, 1, 0)); + + CodeGenerator gen({}, {}); + std::string generated = gen.generateFunction(func, instructions, false); + printGeneratedCode("constant MMIO store emits direct runtime store", generated); + + t.IsTrue(generated.find("runtime->Store32(rdram, ctx, 0x1000E020u, GPR_U32(ctx, 2));") != std::string::npos, + "constant MMIO SW should emit a direct runtime Store32"); + t.IsTrue(generated.find("WRITE32(ADD32(GPR_U32(ctx, 1)") == std::string::npos, + "constant MMIO SW should not go through WRITE32 address classification"); + }); + + tc.Run("constant RDRAM load and store emit fast memory access", [](TestCase &t) { + Function func; + func.name = "rdram_access"; + func.start = 0x2000; + func.end = 0x2014; + func.isRecompiled = true; + + std::vector instructions; + instructions.push_back(makeLui(0x2000, 1, 0x0012)); + instructions.push_back(makeOri(0x2004, 1, 1, 0x3450)); + instructions.push_back(makeLw(0x2008, 3, 1, 0x0010)); + instructions.push_back(makeSw(0x200C, 4, 1, 0x0014)); + + CodeGenerator gen({}, {}); + std::string generated = gen.generateFunction(func, instructions, false); + printGeneratedCode("constant RDRAM load and store emit fast memory access", generated); + + t.IsTrue(generated.find("SET_GPR_S32(ctx, 3, (int32_t)FAST_READ32(0x123460u));") != std::string::npos, + "constant RDRAM LW should emit FAST_READ32 with the resolved address"); + t.IsTrue(generated.find("FAST_WRITE32(0x123464u, _value);") != std::string::npos, + "constant RDRAM SW should emit FAST_WRITE32 with the resolved address"); + t.IsTrue(generated.find("READ32(ADD32(GPR_U32(ctx, 1)") == std::string::npos, + "constant RDRAM LW should not go through READ32 address classification"); + t.IsTrue(generated.find("WRITE32(ADD32(GPR_U32(ctx, 1)") == std::string::npos, + "constant RDRAM SW should not go through WRITE32 address classification"); + }); + + tc.Run("known GIF DMA MMIO sequence emits native kick helper", [](TestCase &t) { + Function func; + func.name = "gif_dma_kick"; + func.start = 0x3000; + func.end = 0x3030; + func.isRecompiled = true; + + std::vector instructions; + instructions.push_back(makeAddiu(0x3000, 2, 0, 4)); + instructions.push_back(makeLui(0x3004, 1, 0x1000)); + instructions.push_back(makeOri(0x3008, 1, 1, 0xE020)); + instructions.push_back(makeSw(0x300C, 2, 1, 0)); + instructions.push_back(makeLui(0x3010, 1, 0x1000)); + instructions.push_back(makeOri(0x3014, 1, 1, 0xE010)); + instructions.push_back(makeSw(0x3018, 2, 1, 0)); + instructions.push_back(makeLui(0x301C, 1, 0x1000)); + instructions.push_back(makeOri(0x3020, 1, 1, 0xA030)); + instructions.push_back(makeSw(0x3024, 4, 1, 0)); + instructions.push_back(makeAddiu(0x3028, 5, 0, 0x0105)); + instructions.push_back(makeAddiu(0x302C, 1, 1, 0xFFD0)); + instructions.push_back(makeSw(0x3030, 5, 1, 0)); + + CodeGenerator gen({}, {}); + std::string generated = gen.generateFunction(func, instructions, false); + printGeneratedCode("known GIF DMA MMIO sequence emits native kick helper", generated); + + t.IsTrue(generated.find("uint32_t gifDmaKickValue_3024_2 = GPR_U32(ctx, 4);") != std::string::npos, + "dynamic GIF TADR source should be captured when the store is coalesced"); + t.IsTrue(generated.find("runtime->kickGifDmaChainFromMMIO(rdram, ctx, 0x4u, 0x4u, gifDmaKickValue_3024_2, 0x105u);") != std::string::npos, + "known GIF DMA MMIO stores should coalesce into the native kick helper"); + t.IsTrue(generated.find("runtime->Store32(rdram, ctx, 0x1000E020u") == std::string::npos, + "coalesced D_PCR store should not remain as an individual Store32"); + t.IsTrue(generated.find("runtime->Store32(rdram, ctx, 0x1000A000u") == std::string::npos, + "coalesced GIF CHCR store should not remain as an individual Store32"); + }); + + tc.Run("GIF DMA kick coalesces when CHCR store is a return delay slot", [](TestCase &t) { + Function func; + func.name = "loadImage_like"; + func.start = 0x2E7C90; + func.end = 0x2E7CC8; + func.isRecompiled = true; + + std::vector instructions; + instructions.push_back(makeBranch(0x2E7C90, 2)); + instructions.push_back(makeLui(0x2E7C94, 5, 0x1000)); + instructions.push_back(makeLui(0x2E7C98, 5, 0x1000)); + instructions.push_back(makeAddiu(0x2E7C9C, 6, 0, 4)); + instructions.push_back(makeOri(0x2E7CA0, 3, 5, 0xE020)); + instructions.push_back(makeSw(0x2E7CA4, 6, 3, 0)); + instructions.push_back(makeOri(0x2E7CA8, 3, 5, 0xE010)); + instructions.push_back(makeSw(0x2E7CAC, 6, 3, 0)); + instructions.push_back(makeOri(0x2E7CB0, 3, 5, 0xA030)); + instructions.push_back(makeSw(0x2E7CB4, 4, 3, 0)); + instructions.push_back(makeAddiu(0x2E7CB8, 4, 0, 0x0105)); + instructions.push_back(makeOri(0x2E7CBC, 3, 5, 0xA000)); + instructions.push_back(makeJr(0x2E7CC0, 31)); + instructions.push_back(makeSw(0x2E7CC4, 4, 3, 0)); + + CodeGenerator gen({}, {}); + std::string generated = gen.generateFunction(func, instructions, false); + printGeneratedCode("GIF DMA kick coalesces when CHCR store is a return delay slot", generated); + + t.IsTrue(generated.find("label_2e7c9c:") != std::string::npos, + "test should cover a branch target inside the GIF DMA setup"); + t.IsTrue(generated.find("uint32_t gifDmaKickValue_2e7cb4_2 = GPR_U32(ctx, 4);") != std::string::npos, + "TADR value should be captured before a0 is reused for CHCR"); + t.IsTrue(generated.find("ctx->in_delay_slot = true;") != std::string::npos, + "coalesced helper should still run as the return delay slot"); + t.IsTrue(generated.find("runtime->kickGifDmaChainFromMMIO(rdram, ctx, 0x4u, 0x4u, gifDmaKickValue_2e7cb4_2, 0x105u);") != std::string::npos, + "loadImage-like GIF DMA stores should coalesce into the native kick helper"); + t.IsTrue(generated.find("WRITE32(ADD32(GPR_U32(ctx, 3), 0), GPR_U32(ctx, 4));") == std::string::npos, + "coalesced delay-slot CHCR store should not remain as an individual WRITE32"); + }); + tc.Run("emits labels and gotos for internal branches", [](TestCase &t) { Function func; func.name = "test_func"; diff --git a/ps2xTest/src/ps2_gs_tests.cpp b/ps2xTest/src/ps2_gs_tests.cpp index 4b0edc0..d35cf7b 100644 --- a/ps2xTest/src/ps2_gs_tests.cpp +++ b/ps2xTest/src/ps2_gs_tests.cpp @@ -65,6 +65,12 @@ namespace std::memcpy(dst.data() + pos, &value, sizeof(uint64_t)); } + void appendGifAd(std::vector &dst, uint64_t value, uint64_t reg) + { + appendU64(dst, value); + appendU64(dst, reg); + } + template bool waitUntil(Predicate pred, std::chrono::milliseconds timeout) { @@ -1917,6 +1923,67 @@ void register_ps2_gs_tests() t.IsTrue(same, "GIF IMAGE transfer should write payload bytes into GS VRAM"); }); + tc.Run("GIF load-image packet uses native upload fast path", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); + + const uint64_t bitblt = + (static_cast(0u) << 0) | + (static_cast(1u) << 16) | + (static_cast(0u) << 24) | + (static_cast(0u) << 32) | + (static_cast(1u) << 48) | + (static_cast(0u) << 56); + const uint64_t trxpos = 0ull; + const uint64_t trxreg = (2ull << 0) | (2ull << 32); + const uint64_t trxdir = 0ull; + + const uint8_t payload[16] = { + 0x10u, 0x11u, 0x12u, 0x13u, + 0x20u, 0x21u, 0x22u, 0x23u, + 0x30u, 0x31u, 0x32u, 0x33u, + 0x40u, 0x41u, 0x42u, 0x43u, + }; + + std::vector packet; + appendU64(packet, makeGifTag(4u, GIF_FMT_PACKED, 1u, false)); + appendU64(packet, 0x0Eull); + appendGifAd(packet, bitblt, GS_REG_BITBLTBUF); + appendGifAd(packet, trxpos, GS_REG_TRXPOS); + appendGifAd(packet, trxreg, GS_REG_TRXREG); + appendGifAd(packet, trxdir, GS_REG_TRXDIR); + appendU64(packet, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true)); + appendU64(packet, 0ull); + packet.insert(packet.end(), payload, payload + sizeof(payload)); + + gs.processGIFPacket(packet.data(), static_cast(packet.size())); + + t.Equals(gs.nativeImageUploadCount(), 1ull, "load-image packet should use the native image upload fast path"); + + bool same = true; + for (uint32_t y = 0; y < 2u && same; ++y) + { + for (uint32_t x = 0; x < 2u; ++x) + { + const uint32_t pixelIndex = y * 2u + x; + const uint32_t off = referenceAddrPSMCT32(0u, 1u, x, y); + for (uint32_t c = 0; c < 4u; ++c) + { + if (vram[off + c] != payload[pixelIndex * 4u + c]) + { + same = false; + break; + } + } + if (!same) + break; + } + } + t.IsTrue(same, "native load-image upload should preserve pixel payload"); + }); + tc.Run("GS local-to-host transfer supports partial incremental reads", [](TestCase &t) { std::vector vram(PS2_GS_VRAM_SIZE, 0u); diff --git a/ps2xTest/src/ps2_memory_tests.cpp b/ps2xTest/src/ps2_memory_tests.cpp index 9e94d89..0eab823 100644 --- a/ps2xTest/src/ps2_memory_tests.cpp +++ b/ps2xTest/src/ps2_memory_tests.cpp @@ -113,6 +113,49 @@ namespace return tag; } + uint64_t makeGifTagPrim(uint16_t nloop, uint16_t prim, uint8_t flg, uint8_t nreg, bool eop = true, bool pre = true) + { + uint64_t tag = makeGifTag(nloop, flg, nreg, eop); + if (pre) + tag |= (1ull << 46); + tag |= (static_cast(prim & 0x7FFu) << 47); + return tag; + } + + uint64_t makeGsFrame(uint32_t fbp, uint32_t fbw, uint32_t psm, uint32_t mask = 0u) + { + return static_cast(fbp & 0x1FFu) | + (static_cast(fbw & 0x3Fu) << 16u) | + (static_cast(psm & 0x3Fu) << 24u) | + (static_cast(mask) << 32u); + } + + uint64_t makeGsScissor(uint32_t x0, uint32_t x1, uint32_t y0, uint32_t y1) + { + return static_cast(x0 & 0x7FFu) | + (static_cast(x1 & 0x7FFu) << 16u) | + (static_cast(y0 & 0x7FFu) << 32u) | + (static_cast(y1 & 0x7FFu) << 48u); + } + + void appendPackedRgbaq(std::vector &packet, uint8_t r, uint8_t g, uint8_t b, uint8_t a) + { + appendU64(packet, static_cast(r) | (static_cast(g) << 32u)); + appendU64(packet, static_cast(b) | (static_cast(a) << 32u)); + } + + void appendPackedXyzf2(std::vector &packet, uint32_t x, uint32_t y, uint32_t z) + { + appendU64(packet, static_cast(x & 0xFFFFu) | (static_cast(y & 0xFFFFu) << 32u)); + appendU64(packet, static_cast(z & 0xFFFFFFu) << 4u); + } + + void appendPackedUv(std::vector &packet, uint32_t u, uint32_t v) + { + appendU64(packet, static_cast(u & 0x3FFFu) | (static_cast(v & 0x3FFFu) << 32u)); + appendU64(packet, 0u); + } + uint32_t makeVuLowerSpecial(uint8_t specialOp, uint8_t is, uint8_t it = 0u, uint8_t id = 0u, uint8_t dest = 0u) { return (0x40u << 25) | @@ -970,6 +1013,126 @@ void register_ps2_memory_tests() t.IsTrue(contentOk, "scratchpad alias chain payload should match scratchpad bytes"); }); + tc.Run("native GIF image upload recognizes canonical load-image chain", [](TestCase &t) + { + PS2Memory mem; + t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed"); + + GS gs; + gs.init(mem.getGSVRAM(), static_cast(PS2_GS_VRAM_SIZE), &mem.gs()); + + constexpr uint32_t kGifCh = 0x1000A000u; + constexpr uint32_t kDStat = 0x1000E010u; + constexpr uint32_t kChain = 0x00028000u; + constexpr uint32_t kPixels = 0x00029000u; + constexpr uint32_t kQwc = 1u; + + uint8_t *rdram = mem.getRDRAM(); + for (uint32_t i = 0; i < kQwc * 16u; ++i) + { + rdram[kPixels + i] = static_cast(0x40u + i); + } + + uint32_t chain = kChain; + chain = writeTextureUploadSetup(rdram, chain, 0u, GS_PSM_CT32); + chain = writeTextureImageRef(rdram, chain, kQwc, kPixels); + writeDmaTag(rdram, chain, makeDmaTag(0u, 7u, 0u, false)); // END. + + t.IsTrue(mem.writeIORegister(kGifCh + 0x30u, kChain), "write GIF TADR should succeed"); + t.IsTrue(mem.tryProcessNativeGifImageUploadChain(gs, kChain, 0x105u), + "canonical load-image chain should use the native upload path"); + + t.Equals(gs.nativeImageUploadCount(), 1ull, "native GIF DMA chain should upload through GS fast path"); + t.Equals(mem.gifCopyCount(), 1ull, "native GIF DMA chain should still count as a GIF DMA copy"); + t.IsTrue((mem.readIORegister(kDStat) & (1u << 2u)) != 0u, + "native GIF DMA chain should raise D_STAT GIF completion"); + t.Equals(mem.readIORegister(kGifCh + 0x20u), 0u, "native GIF DMA chain should clear GIF QWC"); + t.Equals(mem.readIORegister(kGifCh + 0x00u) & 0x100u, 0u, + "native GIF DMA chain should clear GIF STR"); + t.Equals(mem.readIORegister(kGifCh + 0x00u) & 0x70000000u, 0x70000000u, + "native GIF DMA chain should latch the terminal END tag id"); + + bool pixelsOk = true; + for (uint32_t x = 0; x < 4u && pixelsOk; ++x) + { + const uint32_t dstOff = GSPSMCT32::addrPSMCT32(0u, 1u, x, 0u); + const uint32_t srcOff = kPixels + x * 4u; + for (uint32_t c = 0; c < 4u; ++c) + { + if (mem.getGSVRAM()[dstOff + c] != rdram[srcOff + c]) + { + pixelsOk = false; + break; + } + } + } + t.IsTrue(pixelsOk, "native GIF DMA chain should upload image payload into GS VRAM"); + }); + + tc.Run("native GIF packed chain matches generic packed primitive packet", [](TestCase &t) + { + PS2Memory mem; + t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed"); + + GS nativeGs; + nativeGs.init(mem.getGSVRAM(), static_cast(PS2_GS_VRAM_SIZE), &mem.gs()); + + GSRegisters genericRegs{}; + std::vector genericVram(PS2_GS_VRAM_SIZE, 0u); + GS genericGs; + genericGs.init(genericVram.data(), static_cast(genericVram.size()), &genericRegs); + + std::vector packet; + appendU64(packet, makeGifTag(4u, GIF_FMT_PACKED, 1u, false)); + appendU64(packet, 0x0Eull); + appendU64(packet, makeGsFrame(0u, 1u, GS_PSM_CT32)); + appendU64(packet, GS_REG_FRAME_1); + appendU64(packet, makeGsScissor(0u, 7u, 0u, 7u)); + appendU64(packet, GS_REG_SCISSOR_1); + appendU64(packet, 1ull << 17u); // ZTST always. + appendU64(packet, GS_REG_TEST_1); + appendU64(packet, 1ull << 32u); // Mask Z writes so the test framebuffer remains visible. + appendU64(packet, GS_REG_ZBUF_1); + + constexpr uint16_t kSpritePrim = static_cast(GS_PRIM_SPRITE); + appendU64(packet, makeGifTagPrim(2u, kSpritePrim, GIF_FMT_PACKED, 3u, true, true)); + appendU64(packet, static_cast(GS_REG_UV) | + (static_cast(GS_REG_RGBAQ) << 4u) | + (static_cast(GS_REG_XYZF2) << 8u)); + appendPackedUv(packet, 0u, 0u); + appendPackedRgbaq(packet, 0x20u, 0x40u, 0x80u, 0x80u); + appendPackedXyzf2(packet, 0u, 0u, 0u); + appendPackedUv(packet, 0u, 0u); + appendPackedRgbaq(packet, 0xE0u, 0x30u, 0x10u, 0x80u); + appendPackedXyzf2(packet, 64u, 64u, 0u); + + genericGs.processGIFPacket(packet.data(), static_cast(packet.size())); + + constexpr uint32_t kGifCh = 0x1000A000u; + constexpr uint32_t kDStat = 0x1000E010u; + constexpr uint32_t kScratchTag = 0xF0000000u; + uint8_t *scratch = mem.getScratchpad(); + writeDmaTag(scratch, 0u, makeDmaTag(static_cast(packet.size() / 16u), 7u, 0u, false)); + std::memcpy(scratch + 16u, packet.data(), packet.size()); + + t.IsTrue(mem.writeIORegister(kGifCh + 0x30u, kScratchTag), "write GIF TADR scratchpad alias should succeed"); + t.IsTrue(mem.tryProcessNativeGifPackedChain(nativeGs, kScratchTag, 0x105u), + "packed primitive chain should use the native packed GIF path"); + + t.Equals(nativeGs.nativePackedGIFPacketCount(), 1ull, "native packed GIF packet counter should increment"); + t.Equals(mem.gifCopyCount(), 1ull, "native packed GIF chain should still count as a GIF DMA copy"); + t.IsTrue((mem.readIORegister(kDStat) & (1u << 2u)) != 0u, + "native packed GIF chain should raise D_STAT GIF completion"); + t.Equals(mem.readIORegister(kGifCh + 0x20u), 0u, "native packed GIF chain should clear GIF QWC"); + t.Equals(mem.readIORegister(kGifCh + 0x00u) & 0x100u, 0u, + "native packed GIF chain should clear GIF STR"); + + const uint32_t nativePixel = nativeGs.ReadVram(GS_PSM_CT32, 0u, 1u, 1u, 1u); + const uint32_t genericPixel = genericGs.ReadVram(GS_PSM_CT32, 0u, 1u, 1u, 1u); + t.IsTrue(genericPixel != 0u, "generic packed primitive packet should draw a test pixel"); + t.Equals(nativePixel, genericPixel, "native packed GIF chain should match generic GS packet output"); + }); + tc.Run("GIF DMA chain REF keeps CT32 image data after paletted upload", [](TestCase &t) { PS2Memory mem; diff --git a/ps2xTest/src/ps2_runtime_interrupt_tests.cpp b/ps2xTest/src/ps2_runtime_interrupt_tests.cpp index 7254973..3204e66 100644 --- a/ps2xTest/src/ps2_runtime_interrupt_tests.cpp +++ b/ps2xTest/src/ps2_runtime_interrupt_tests.cpp @@ -554,6 +554,58 @@ void register_ps2_runtime_interrupt_tests() cleanupRuntime(env); }); + tc.Run("native GIF DMA MMIO kick dispatches completed DMAC handler", [](TestCase &t) + { + notifyRuntimeStop(); + TestEnv env; + t.IsTrue(env.runtime.memory().initialize(), "runtime memory initialize should succeed"); + + constexpr uint32_t kHandlerAddr = 0x00ABD1C0u; + constexpr uint32_t kDStat = 0x1000E010u; + constexpr uint32_t kDPcr = 0x1000E020u; + constexpr uint32_t kTag0 = 0x00028400u; + + uint8_t *rdram = env.runtime.memory().getRDRAM(); + writeDmaTag(rdram, kTag0, makeDmaTag(1u, 7u, 0u, false)); // END + writeGuestU64(rdram, kTag0 + 0x10u, 0x1122334455667788ull); + writeGuestU64(rdram, kTag0 + 0x18u, 0x99AABBCCDDEEFF00ull); + + g_dmacSendHits.store(0u, std::memory_order_relaxed); + g_dmacSendLastCause.store(0u, std::memory_order_relaxed); + g_dmacSendLastChcr.store(0u, std::memory_order_relaxed); + env.runtime.registerFunction(kHandlerAddr, &testDmacSendHandler); + + R5900Context addCtx{}; + setRegU32(addCtx, 4, 2u); + setRegU32(addCtx, 5, kHandlerAddr); + setRegU32(addCtx, 6, 0u); + setRegU32(addCtx, 7, 0u); + ps2_syscalls::AddDmacHandler(rdram, &addCtx, &env.runtime); + t.IsTrue(getRegS32(addCtx, 2) > 0, "AddDmacHandler should register GIF handler"); + + R5900Context enableCtx{}; + setRegU32(enableCtx, 4, 2u); + ps2_syscalls::EnableDmac(rdram, &enableCtx, &env.runtime); + t.Equals(getRegS32(enableCtx, 2), KE_OK, "EnableDmac should enable GIF cause"); + + R5900Context kickCtx{}; + env.runtime.kickGifDmaChainFromMMIO(rdram, &kickCtx, 4u, 4u, kTag0, 0x105u); + + t.Equals(env.runtime.memory().readIORegister(kDPcr), 4u, "native GIF kick should preserve D_PCR write"); + t.IsTrue((env.runtime.memory().readIORegister(kDStat) & (1u << 2)) != 0u, + "native GIF kick should raise D_STAT GIF completion status"); + t.Equals(g_dmacSendHits.load(std::memory_order_relaxed), 1u, + "native GIF kick should dispatch the GIF DMAC handler"); + t.Equals(g_dmacSendLastCause.load(std::memory_order_relaxed), 2u, + "DMAC handler should observe GIF cause"); + t.Equals(g_dmacSendLastChcr.load(std::memory_order_relaxed) & 0x100u, 0u, + "handler should see GIF STR cleared"); + t.Equals(g_dmacSendLastChcr.load(std::memory_order_relaxed) & 0x70000000u, 0x70000000u, + "handler should see the latched END tag id"); + + cleanupRuntime(env); + }); + tc.Run("negative interrupt-safe EE syscall ids dispatch", [](TestCase &t) { notifyRuntimeStop();