Feature/agressive recompiler (#146)

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

* fix: fix test conflict

* feat: added debug sound driver logs

* feat: emmiter for return

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

* feat: staticc table insted of hashmap for runtime

* feat: back file to ignore

* feat: explode code across helpers and classes

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

* feat: fix link problem on linux

* feat: fix Segmentation fault

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

* feat: undo messup merge
This commit is contained in:
Ranieri
2026-07-07 10:14:25 -03:00
committed by GitHub
parent 61621b8313
commit 52edf07657
24 changed files with 1756 additions and 70 deletions
@@ -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;
@@ -3,6 +3,8 @@
#include <string>
#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;
};
}
@@ -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<Function> &functions, const std::map<uint32_t, std::string> &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<uint32_t, std::string> &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);
@@ -0,0 +1,62 @@
#ifndef PS2RECOMP_GIF_DMA_KICK_ANALYZER_H
#define PS2RECOMP_GIF_DMA_KICK_ANALYZER_H
#include <array>
#include <cstddef>
#include <cstdint>
#include <iosfwd>
#include <limits>
#include <string>
#include <string_view>
#include <unordered_set>
#include <vector>
#include "ps2recomp/types.h"
namespace ps2recomp
{
struct Instruction;
struct ConstantRegisterState
{
std::array<bool, 32> known{};
std::array<uint32_t, 32> 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<size_t, 4> storeIndices{};
std::array<std::string, 4> values{};
std::array<std::string, 4> captureExpressions{};
std::array<bool, 4> captures{};
size_t branchIndex = std::numeric_limits<size_t>::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<Instruction> &instructions,
size_t startIndex,
const ConstantRegisterState &constants,
const std::unordered_set<uint32_t> &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
+6
View File
@@ -80,6 +80,12 @@ namespace ps2recomp
}
};
struct MemoryAccessHint
{
bool hasAddress = false;
uint32_t address = 0;
};
// Function information
struct Function
{
+18 -1
View File
@@ -23,6 +23,7 @@
#include <algorithm>
#include <cstring>
#include <unordered_set>
#include <utility>
#include <unordered_map>
#include <iostream>
#include <cctype>
@@ -209,11 +210,27 @@ namespace ps2recomp
return emitter.emit();
}
std::string CodeGenerator::handleBranchDelaySlots(
const Instruction &branchInst,
const Instruction &delaySlot,
const Function &function,
const AnalysisResult &analysisResult,
std::string delaySlotOverride)
{
ControlFlowEmitter emitter(*this, branchInst, delaySlot, function, analysisResult, std::move(delaySlotOverride));
return emitter.emit();
}
CodeGenerator::~CodeGenerator() = default;
std::string CodeGenerator::translateInstruction(const Instruction &inst)
{
return InstructionTranslator(*this).translate(inst);
return InstructionTranslator(*this).translate(inst, {});
}
std::string CodeGenerator::translateInstruction(const Instruction &inst, const MemoryAccessHint &memoryHint)
{
return InstructionTranslator(*this).translate(inst, memoryHint);
}
std::string CodeGenerator::translateSpecialInstruction(const Instruction &inst)
+11 -3
View File
@@ -8,6 +8,7 @@
#include <algorithm>
#include <fmt/format.h>
#include <sstream>
#include <utility>
namespace ps2recomp
{
@@ -15,12 +16,14 @@ namespace ps2recomp
const Instruction &branchInst,
const Instruction &delaySlot,
const Function &function,
const CodeGenerator::AnalysisResult &analysisResult)
const CodeGenerator::AnalysisResult &analysisResult,
std::string delaySlotOverride)
: m_gen(generator),
m_branchInst(branchInst),
m_delaySlot(delaySlot),
m_function(function),
m_analysisResult(analysisResult)
m_analysisResult(analysisResult),
m_delaySlotOverride(std::move(delaySlotOverride))
{
}
@@ -41,7 +44,7 @@ namespace ps2recomp
bool ControlFlowEmitter::hasRealDelaySlot() const
{
return !isGuestNop(m_delaySlot);
return !m_delaySlotOverride.empty() || !isGuestNop(m_delaySlot);
}
bool ControlFlowEmitter::isCallLikeEdge() const
@@ -100,6 +103,11 @@ namespace ps2recomp
return {};
}
if (!m_delaySlotOverride.empty())
{
return m_delaySlotOverride;
}
std::string code;
if (m_gen.m_emitInstructionComments)
{
+48 -3
View File
@@ -1,5 +1,6 @@
#include "ps2recomp/Emitters/function_emitter.h"
#include "ps2recomp/code_generator.h"
#include "ps2recomp/gif_dma_kick_analyzer.h"
#include "ps2recomp/instructions.h"
#include "ps2recomp/r5900_decoder.h"
#include "ps2recomp/recompiler_reporter.h"
@@ -69,6 +70,8 @@ namespace ps2recomp
}
const std::unordered_set<uint32_t> &internalTargets = analysisResult.entryPoints;
ConstantRegisterState constantRegisters;
GifDmaKickPlan gifDmaKickPlan{};
ss << "// Function: " << function.name << "\n";
ss << "// Address: 0x" << std::hex << function.start << " - 0x" << function.end << std::dec << "\n";
@@ -106,6 +109,7 @@ namespace ps2recomp
if (internalTargets.contains(inst.address))
{
constantRegisters.clear();
ss << "label_" << std::hex << inst.address << std::dec << ":\n";
}
@@ -145,24 +149,65 @@ namespace ps2recomp
ss << "label_" << std::hex << delaySlot->address << std::dec << ":\n";
}
ss << cg.handleBranchDelaySlots(inst, *delaySlot, function, analysisResult);
if (gifDmaKickPlan.valid &&
gifDmaKickPlan.completesInDelaySlot &&
gifDmaKickPlan.branchIndex == i &&
hasDecodedDelaySlot)
{
ss << cg.handleBranchDelaySlots(
inst,
*delaySlot,
function,
analysisResult,
gifDmaDelaySlotOverride(*delaySlot, gifDmaKickPlan, cg.m_emitInstructionComments));
gifDmaKickPlan = {};
}
else
{
ss << cg.handleBranchDelaySlots(inst, *delaySlot, function, analysisResult);
}
if (hasDecodedDelaySlot)
{
++i; // Skip delay slot instruction (handled inside branch logic)
}
constantRegisters.clear();
}
else
{
if (!gifDmaKickPlan.valid)
{
gifDmaKickPlan = tryBuildGifDmaKickPlan(instructions, i, constantRegisters, internalTargets);
}
if (gifDmaKickPlan.suppresses(i))
{
const size_t slot = gifDmaKickPlan.slotFor(i);
emitGifDmaCapture(ss, gifDmaKickPlan, slot, " ");
if (gifDmaKickPlan.completesAt(i))
{
ss << " ctx->pc = 0x" << std::hex << inst.address << "u;\n"
<< std::dec;
ss << " " << gifDmaKickCall(gifDmaKickPlan) << "\n";
gifDmaKickPlan = {};
}
updateConstantRegisters(inst, constantRegisters);
continue;
}
ss << " ctx->pc = 0x" << std::hex << inst.address << "u;\n"
<< std::dec;
ss << " " << cg.translateInstruction(inst);
const MemoryAccessHint memoryHint = resolveMemoryAccessHint(inst, constantRegisters);
ss << " " << cg.translateInstruction(inst, memoryHint);
if (inst.isMmio)
{
ss << " // MMIO: 0x" << std::hex << inst.mmioAddress << std::dec;
}
ss << "\n";
updateConstantRegisters(inst, constantRegisters);
}
}
catch (const std::exception &e)
@@ -0,0 +1,374 @@
#include "ps2recomp/gif_dma_kick_analyzer.h"
#include "ps2recomp/instructions.h"
#include "ps2recomp/r5900_decoder.h"
#include <algorithm>
#include <ostream>
#include <sstream>
namespace ps2recomp
{
namespace
{
uint32_t add32(uint32_t lhs, uint32_t rhs)
{
return lhs + rhs;
}
std::string formatU32Literal(uint32_t value)
{
std::ostringstream ss;
ss << "0x" << std::hex << value << "u";
return ss.str();
}
std::string gprU32Expression(uint32_t reg)
{
std::ostringstream ss;
ss << "GPR_U32(ctx, " << std::dec << reg << ")";
return ss.str();
}
std::string gifDmaKickTempName(uint32_t address, size_t slot)
{
std::ostringstream ss;
ss << "gifDmaKickValue_" << std::hex << address << "_" << std::dec << slot;
return ss.str();
}
bool isReturnWithDelaySlot(const Instruction &inst)
{
return inst.opcode == OPCODE_SPECIAL &&
inst.function == SPECIAL_JR &&
inst.rs == 31u &&
inst.hasDelaySlot;
}
bool tryMatchGifDmaStore(size_t instructionIndex,
const Instruction &inst,
const MemoryAccessHint &hint,
const ConstantRegisterState &constants,
uint32_t target,
size_t slot,
GifDmaKickPlan &plan)
{
if (inst.opcode != OPCODE_SW || !hint.hasAddress || hint.address != target)
return false;
plan.storeIndices[slot] = instructionIndex;
uint32_t constantValue = 0u;
if (constants.read(inst.rt, constantValue))
{
plan.values[slot] = formatU32Literal(constantValue);
}
else
{
const std::string tempName = gifDmaKickTempName(inst.address, slot);
plan.values[slot] = tempName;
plan.captureExpressions[slot] = gprU32Expression(inst.rt);
plan.captures[slot] = true;
}
return true;
}
}
ConstantRegisterState::ConstantRegisterState()
{
clear();
}
void ConstantRegisterState::clear()
{
known.fill(false);
values.fill(0u);
known[0] = true;
}
bool ConstantRegisterState::read(uint32_t reg, uint32_t &value) const
{
if (reg >= known.size() || !known[reg])
return false;
value = values[reg];
return true;
}
void ConstantRegisterState::write(uint32_t reg, uint32_t value)
{
if (reg == 0 || reg >= known.size())
return;
known[reg] = true;
values[reg] = value;
}
void ConstantRegisterState::invalidate(uint32_t reg)
{
if (reg == 0 || reg >= known.size())
return;
known[reg] = false;
values[reg] = 0u;
}
bool GifDmaKickPlan::suppresses(size_t index) const
{
return valid && std::find(storeIndices.begin(), storeIndices.end(), index) != storeIndices.end();
}
bool GifDmaKickPlan::completesAt(size_t index) const
{
return valid && index == endIndex;
}
size_t GifDmaKickPlan::slotFor(size_t index) const
{
for (size_t i = 0; i < storeIndices.size(); ++i)
{
if (storeIndices[i] == index)
return i;
}
return storeIndices.size();
}
bool isDirectMemoryAccess(const Instruction &inst)
{
switch (inst.opcode)
{
case OPCODE_LB:
case OPCODE_LH:
case OPCODE_LW:
case OPCODE_LBU:
case OPCODE_LHU:
case OPCODE_LWU:
case OPCODE_LQ:
case OPCODE_LD:
case OPCODE_LWC1:
case OPCODE_LDC2:
case OPCODE_SB:
case OPCODE_SH:
case OPCODE_SW:
case OPCODE_SQ:
case OPCODE_SD:
case OPCODE_SWC1:
case OPCODE_SDC2:
return true;
default:
return false;
}
}
MemoryAccessHint resolveMemoryAccessHint(const Instruction &inst, const ConstantRegisterState &constants)
{
MemoryAccessHint hint{};
if (!isDirectMemoryAccess(inst))
return hint;
uint32_t base = 0u;
if (!constants.read(inst.rs, base))
return hint;
hint.hasAddress = true;
hint.address = add32(base, inst.simmediate);
return hint;
}
void updateConstantRegisters(const Instruction &inst, ConstantRegisterState &constants)
{
uint32_t lhs = 0u;
uint32_t rhs = 0u;
switch (inst.opcode)
{
case OPCODE_LUI:
constants.write(inst.rt, inst.immediate << 16);
return;
case OPCODE_ORI:
if (constants.read(inst.rs, lhs))
constants.write(inst.rt, lhs | (inst.immediate & 0xFFFFu));
else
constants.invalidate(inst.rt);
return;
case OPCODE_ADDIU:
if (constants.read(inst.rs, lhs))
constants.write(inst.rt, add32(lhs, inst.simmediate));
else
constants.invalidate(inst.rt);
return;
case OPCODE_ANDI:
if (constants.read(inst.rs, lhs))
constants.write(inst.rt, lhs & (inst.immediate & 0xFFFFu));
else
constants.invalidate(inst.rt);
return;
case OPCODE_XORI:
if (constants.read(inst.rs, lhs))
constants.write(inst.rt, lhs ^ (inst.immediate & 0xFFFFu));
else
constants.invalidate(inst.rt);
return;
case OPCODE_LB:
case OPCODE_LH:
case OPCODE_LWL:
case OPCODE_LW:
case OPCODE_LBU:
case OPCODE_LHU:
case OPCODE_LWR:
case OPCODE_LWU:
case OPCODE_LDL:
case OPCODE_LDR:
case OPCODE_LQ:
case OPCODE_LL:
case OPCODE_LD:
constants.invalidate(inst.rt);
return;
case OPCODE_SC:
constants.invalidate(inst.rt);
return;
case OPCODE_SPECIAL:
switch (inst.function)
{
case SPECIAL_ADDU:
case SPECIAL_DADDU:
if (constants.read(inst.rs, lhs) && constants.read(inst.rt, rhs))
constants.write(inst.rd, add32(lhs, rhs));
else
constants.invalidate(inst.rd);
return;
case SPECIAL_OR:
if (constants.read(inst.rs, lhs) && constants.read(inst.rt, rhs))
constants.write(inst.rd, lhs | rhs);
else
constants.invalidate(inst.rd);
return;
default:
break;
}
break;
default:
break;
}
if (inst.modificationInfo.modifiesGPR || inst.modificationInfo.modifiesControl)
constants.clear();
}
std::string gifDmaKickCall(const GifDmaKickPlan &plan)
{
std::ostringstream ss;
ss << "runtime->kickGifDmaChainFromMMIO(rdram, ctx, "
<< plan.values[0] << ", "
<< plan.values[1] << ", "
<< plan.values[2] << ", "
<< plan.values[3] << ");";
return ss.str();
}
void emitGifDmaCapture(std::ostream &out, const GifDmaKickPlan &plan, size_t slot, std::string_view indent)
{
if (slot >= plan.captures.size() || !plan.captures[slot])
return;
out << indent << "uint32_t " << plan.values[slot] << " = " << plan.captureExpressions[slot] << ";\n";
}
std::string gifDmaDelaySlotOverride(const Instruction &delaySlot, const GifDmaKickPlan &plan, bool emitComments)
{
std::ostringstream code;
const size_t slot = plan.slotFor(plan.endIndex);
if (emitComments)
{
code << "// 0x" << std::hex << delaySlot.address << ": 0x" << delaySlot.raw << std::dec;
std::string disassembly = R5900Decoder::disassembleInstruction(delaySlot);
if (!disassembly.empty())
code << " " << disassembly;
code << " (Delay Slot)\n";
}
if (slot < plan.captures.size() && plan.captures[slot])
code << "uint32_t " << plan.values[slot] << " = " << plan.captureExpressions[slot] << ";\n";
code << gifDmaKickCall(plan);
return code.str();
}
GifDmaKickPlan tryBuildGifDmaKickPlan(const std::vector<Instruction> &instructions,
size_t startIndex,
const ConstantRegisterState &constants,
const std::unordered_set<uint32_t> &internalTargets)
{
static constexpr std::array<uint32_t, 4> kTargets = {
0x1000E020u, // D_PCR
0x1000E010u, // D_STAT
0x1000A030u, // GIF TADR
0x1000A000u, // GIF CHCR
};
static constexpr size_t kMaxScanInstructions = 32u;
GifDmaKickPlan plan{};
if (startIndex >= instructions.size())
return plan;
ConstantRegisterState scanConstants = constants;
size_t matched = 0;
const size_t scanEnd = std::min(instructions.size(), startIndex + kMaxScanInstructions);
for (size_t j = startIndex; j < scanEnd; ++j)
{
const Instruction &inst = instructions[j];
if (j != startIndex && matched > 0 && internalTargets.contains(inst.address))
return {};
const MemoryAccessHint hint = resolveMemoryAccessHint(inst, scanConstants);
if (isDirectMemoryAccess(inst))
{
if (matched >= kTargets.size() ||
!tryMatchGifDmaStore(j, inst, hint, scanConstants, kTargets[matched], matched, plan))
{
return {};
}
++matched;
updateConstantRegisters(inst, scanConstants);
if (matched == kTargets.size())
{
plan.valid = true;
plan.endIndex = j;
return plan;
}
continue;
}
if (isReturnWithDelaySlot(inst))
{
const size_t delayIndex = j + 1u;
if (matched != kTargets.size() - 1u ||
delayIndex >= instructions.size() ||
instructions[delayIndex].address != inst.address + 4u ||
internalTargets.contains(instructions[delayIndex].address))
{
return {};
}
const Instruction &delayInst = instructions[delayIndex];
const MemoryAccessHint delayHint = resolveMemoryAccessHint(delayInst, scanConstants);
if (!tryMatchGifDmaStore(delayIndex, delayInst, delayHint, scanConstants, kTargets[matched], matched, plan))
return {};
plan.valid = true;
plan.completesInDelaySlot = true;
plan.branchIndex = j;
plan.endIndex = delayIndex;
return plan;
}
if (inst.hasDelaySlot || inst.isBranch || inst.isJump || inst.modificationInfo.modifiesControl)
return {};
updateConstantRegisters(inst, scanConstants);
}
return {};
}
}
+117 -14
View File
@@ -4,42 +4,145 @@
#include "ps2recomp/instructions.h"
#include "ps2recomp/types.h"
#include "ps2recomp/control_flow_utils.h"
#include "runtime/ps2_address.h"
#include <fmt/format.h>
#include <sstream>
#include <cmath>
namespace ps2recomp
{
namespace
{
std::string addressLiteral(uint32_t address)
{
return fmt::format("0x{:X}u", address);
}
uint32_t memoryAccessSize(int width)
{
return static_cast<uint32_t>(width / 8);
}
std::string memoryValueType(int width)
{
switch (width)
{
case 8:
return "uint8_t";
case 16:
return "uint16_t";
case 32:
return "uint32_t";
case 64:
return "uint64_t";
default:
return "";
}
}
std::string genFastWrite(int width, uint32_t address, const std::string &val)
{
const std::string addr = addressLiteral(address);
if (width == 128)
{
return fmt::format(
"do {{ __m128i _value = ({}); "
"const uint64_t _lo = static_cast<uint64_t>(PS2_EXTRACT_EPI64_0(_value)); "
"const uint64_t _hi = static_cast<uint64_t>(PS2_EXTRACT_EPI64_1(_value)); "
"ps2TraceGuestWrite(rdram, {}, 16u, _lo, _hi, \"WRITE128\", ctx); "
"FAST_WRITE128({}, _value); }} while (0)",
val, addr, addr);
}
const std::string valueType = memoryValueType(width);
return fmt::format(
"do {{ {} _value = static_cast<{}>({}); "
"ps2TraceGuestWrite(rdram, {}, {}u, _value, 0u, \"WRITE{}\", ctx); "
"FAST_WRITE{}({}, _value); }} while (0)",
valueType, valueType, val, addr, memoryAccessSize(width), width, width, addr);
}
}
InstructionTranslator::InstructionTranslator(CodeGenerator &codeGenerator)
: m_codeGenerator(codeGenerator)
{
}
std::string InstructionTranslator::translate(const Instruction &inst)
MemoryAccessHint InstructionTranslator::effectiveMemoryHintFor(const Instruction &inst, const MemoryAccessHint &memoryHint) const
{
MemoryAccessHint effectiveMemoryHint = memoryHint;
if (inst.isMmio)
{
effectiveMemoryHint.hasAddress = true;
effectiveMemoryHint.address = inst.mmioAddress;
}
return effectiveMemoryHint;
}
std::string InstructionTranslator::translateMemoryRead(const Instruction &inst,
const MemoryAccessHint &memoryHint,
int width,
const std::string &addr) const
{
if (memoryHint.hasAddress)
{
const uint32_t resolvedAddress = memoryHint.address;
const std::string resolvedAddressExpr = addressLiteral(resolvedAddress);
if (inst.isMmio || Ps2IsSpecialAddress(resolvedAddress))
{
return fmt::format("runtime->Load{}(rdram, ctx, {})", width, resolvedAddressExpr);
}
return fmt::format("FAST_READ{}({})", width, resolvedAddressExpr);
}
if (inst.isMmio)
{
return fmt::format("runtime->Load{}(rdram, ctx, {})", width, addr);
}
return fmt::format("READ{}({})", width, addr);
}
std::string InstructionTranslator::translateMemoryWrite(const Instruction &inst,
const MemoryAccessHint &memoryHint,
int width,
const std::string &addr,
const std::string &value) const
{
if (memoryHint.hasAddress)
{
const uint32_t resolvedAddress = memoryHint.address;
const std::string resolvedAddressExpr = addressLiteral(resolvedAddress);
if (inst.isMmio || Ps2IsSpecialAddress(resolvedAddress))
{
return fmt::format("runtime->Store{}(rdram, ctx, {}, {})", width, resolvedAddressExpr, value);
}
return genFastWrite(width, resolvedAddress, value);
}
if (inst.isMmio)
{
return fmt::format("runtime->Store{}(rdram, ctx, {}, {})", width, addr, value);
}
return fmt::format("WRITE{}({}, {})", width, addr, value);
}
std::string InstructionTranslator::translate(const Instruction &inst, const MemoryAccessHint &memoryHint)
{
if (inst.isMMI)
{
return m_codeGenerator.translateMMIInstruction(inst);
}
const MemoryAccessHint effectiveMemoryHint = effectiveMemoryHintFor(inst, memoryHint);
auto genRead = [&](int width, const std::string &addr)
{
if (inst.isMmio)
{
return fmt::format("runtime->Load{}(rdram, ctx, {})", width, addr);
}
return fmt::format("READ{}({})", width, addr);
return translateMemoryRead(inst, effectiveMemoryHint, width, addr);
};
auto genWrite = [&](int width, const std::string &addr, const std::string &val)
{
if (inst.isMmio)
{
return fmt::format("runtime->Store{}(rdram, ctx, {}, {})", width, addr, val);
}
return fmt::format("WRITE{}({}, {})", width, addr, val);
return translateMemoryWrite(inst, effectiveMemoryHint, width, addr, val);
};
switch (inst.opcode)
+8 -27
View File
@@ -23,6 +23,7 @@
#include <iomanip>
#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:
+61
View File
@@ -0,0 +1,61 @@
#ifndef PS2_ADDRESS_H
#define PS2_ADDRESS_H
#include <cstdint>
#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
@@ -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;
+12
View File
@@ -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<uint8_t> m_localToHostBuffer;
size_t m_localToHostReadPos = 0;
+4
View File
@@ -20,6 +20,8 @@
#include <smmintrin.h> // 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);
+5 -4
View File
@@ -1,6 +1,7 @@
#include "Common.h"
#include "SIF.h"
#include "../Syscalls/RPC.h"
#include "runtime/ps2_address.h"
#include <map>
@@ -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;
}
+190
View File
@@ -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 <typename Visitor>
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<uint8_t>((tag.lo >> 58u) & 0x3u);
if (flg != GIF_FMT_PACKED)
return false;
tag.nloop = static_cast<uint32_t>(tag.lo & 0x7FFFu);
tag.nreg = static_cast<uint32_t>((tag.lo >> 60u) & 0xFu);
if (tag.nreg == 0u)
tag.nreg = 16u;
const uint64_t payloadBytes64 =
static_cast<uint64_t>(tag.nloop) * static_cast<uint64_t>(tag.nreg) * 16ull;
if (payloadBytes64 > 0xFFFFFFFFull)
return false;
offset += 16u;
const uint32_t payloadBytes = static_cast<uint32_t>(payloadBytes64);
if (payloadBytes > sizeBytes - offset)
return false;
tag.payloadOffset = offset;
for (uint32_t i = 0u; i < tag.nreg; ++i)
tag.regs[i] = static_cast<uint8_t>((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<uint32_t>((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<std::recursive_mutex> 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<std::recursive_mutex> 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<uint32_t>(setupTagLo & 0x7FFFu);
const uint8_t setupFlg = static_cast<uint8_t>((setupTagLo >> 58u) & 0x3u);
uint32_t setupNreg = static_cast<uint32_t>((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<uint32_t>(regs[3] & 0x3ull);
const uint32_t rrw = static_cast<uint32_t>(regs[2] & 0xFFFull);
const uint32_t rrh = static_cast<uint32_t>((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<uint8_t>((imageTagLo >> 58u) & 0x3u);
const uint32_t imageNloop = static_cast<uint32_t>(imageTagLo & 0x7FFFu);
if (imageFlg != GIF_FMT_IMAGE || imageNloop == 0u)
return false;
offset += 16u;
const uint64_t imageBytes64 = static_cast<uint64_t>(imageNloop) * 16ull;
if (imageBytes64 > 0xFFFFFFFFull)
return false;
const uint32_t imageBytes = static_cast<uint32_t>(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)
+4 -2
View File
@@ -791,8 +791,10 @@ void GSRasterizer::drawSprite(GS *gs)
uint32_t texel = 0xFFFF00FFu;
if (gs->m_prim.fst)
{
const uint16_t sampleU = static_cast<uint16_t>(clampInt(static_cast<int>(std::lround(texUf * 16.0f)), 0, 0xFFFF));
const uint16_t sampleV = static_cast<uint16_t>(clampInt(static_cast<int>(std::lround(texVf * 16.0f)), 0, 0xFFFF));
const int fixedU = static_cast<int>((texUf * 16.0f) + 0.5f);
const int fixedV = static_cast<int>((texVf * 16.0f) + 0.5f);
const uint16_t sampleU = static_cast<uint16_t>(clampInt(fixedU, 0, 0xFFFF));
const uint16_t sampleV = static_cast<uint16_t>(clampInt(fixedV, 0, 0xFFFF));
texel = sampleTexture(gs, 0.0f, 0.0f, 1.0f, sampleU, sampleV);
}
else
+329 -14
View File
@@ -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 <atomic>
#include <chrono>
@@ -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<uint64_t>(duration_cast<nanoseconds>(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<uint16_t>(tag & 0xFFFFu);
out.id = static_cast<uint8_t>((tag >> 28u) & 0x7u);
out.irq = ((tag >> 31u) & 0x1ull) != 0ull;
out.addr = static_cast<uint32_t>((tag >> 32u) & 0x7FFFFFFFu);
out.upper = static_cast<uint32_t>((tag >> 16u) & 0xFFFFu);
return out;
}
inline uint32_t gifTagNloop(uint64_t tagLo)
{
return static_cast<uint32_t>(tagLo & 0x7FFFu);
}
inline uint8_t gifTagFlg(uint64_t tagLo)
{
return static_cast<uint8_t>((tagLo >> 58u) & 0x3u);
}
inline uint32_t gifTagNreg(uint64_t tagLo)
{
uint32_t nreg = static_cast<uint32_t>((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<uint16_t>(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<uint32_t>(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<uint64_t>(ptr, 0u, 16u, "native gif dma tag", guestAddr));
return true;
};
auto decodeSetupPayload = [&](const uint8_t *payload, uint64_t (&regs)[4]) -> bool
{
const uint64_t tagLo = loadScalar<uint64_t>(payload, 0u, 80u, "native gif setup tag", 0u);
const uint64_t tagHi = loadScalar<uint64_t>(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<uint64_t>(payload, offset, 80u, "native gif setup value", 0u);
const uint64_t reg = loadScalar<uint64_t>(payload, offset + 8u, 80u, "native gif setup register", 0u);
if ((reg & 0xFFu) != kExpectedRegs[i])
return false;
offset += 16u;
}
const uint32_t trxdirMode = static_cast<uint32_t>(regs[3] & 0x3ull);
const uint32_t rrw = static_cast<uint32_t>(regs[2] & 0xFFFull);
const uint32_t rrh = static_cast<uint32_t>((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<uint64_t>(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<uint64_t>(imageQwc) * 16ull;
if (imageBytes64 > 0xFFFFFFFFull)
return false;
const uint32_t imageBytes = static_cast<uint32_t>(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<uint64_t>(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<uint64_t>(tag.qwc) * 16ull;
if (payloadBytes64 > 0xFFFFFFFFull)
return false;
const uint32_t payloadBytes = static_cast<uint32_t>(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;
+34
View File
@@ -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);
+168
View File
@@ -35,6 +35,50 @@ static Instruction makeNop(uint32_t address)
return inst;
}
static uint32_t signExtend16(uint16_t value)
{
return static_cast<uint32_t>(static_cast<int32_t>(static_cast<int16_t>(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<uint32_t>(rs) << 21) |
(static_cast<uint32_t>(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<std::string> &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<Instruction> 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<Instruction> 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<Instruction> 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<Instruction> 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";
+67
View File
@@ -65,6 +65,12 @@ namespace
std::memcpy(dst.data() + pos, &value, sizeof(uint64_t));
}
void appendGifAd(std::vector<uint8_t> &dst, uint64_t value, uint64_t reg)
{
appendU64(dst, value);
appendU64(dst, reg);
}
template <typename Predicate>
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<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
GS gs;
gs.init(vram.data(), static_cast<uint32_t>(vram.size()), nullptr);
const uint64_t bitblt =
(static_cast<uint64_t>(0u) << 0) |
(static_cast<uint64_t>(1u) << 16) |
(static_cast<uint64_t>(0u) << 24) |
(static_cast<uint64_t>(0u) << 32) |
(static_cast<uint64_t>(1u) << 48) |
(static_cast<uint64_t>(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<uint8_t> 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<uint32_t>(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<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
+163
View File
@@ -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<uint64_t>(prim & 0x7FFu) << 47);
return tag;
}
uint64_t makeGsFrame(uint32_t fbp, uint32_t fbw, uint32_t psm, uint32_t mask = 0u)
{
return static_cast<uint64_t>(fbp & 0x1FFu) |
(static_cast<uint64_t>(fbw & 0x3Fu) << 16u) |
(static_cast<uint64_t>(psm & 0x3Fu) << 24u) |
(static_cast<uint64_t>(mask) << 32u);
}
uint64_t makeGsScissor(uint32_t x0, uint32_t x1, uint32_t y0, uint32_t y1)
{
return static_cast<uint64_t>(x0 & 0x7FFu) |
(static_cast<uint64_t>(x1 & 0x7FFu) << 16u) |
(static_cast<uint64_t>(y0 & 0x7FFu) << 32u) |
(static_cast<uint64_t>(y1 & 0x7FFu) << 48u);
}
void appendPackedRgbaq(std::vector<uint8_t> &packet, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
appendU64(packet, static_cast<uint64_t>(r) | (static_cast<uint64_t>(g) << 32u));
appendU64(packet, static_cast<uint64_t>(b) | (static_cast<uint64_t>(a) << 32u));
}
void appendPackedXyzf2(std::vector<uint8_t> &packet, uint32_t x, uint32_t y, uint32_t z)
{
appendU64(packet, static_cast<uint64_t>(x & 0xFFFFu) | (static_cast<uint64_t>(y & 0xFFFFu) << 32u));
appendU64(packet, static_cast<uint64_t>(z & 0xFFFFFFu) << 4u);
}
void appendPackedUv(std::vector<uint8_t> &packet, uint32_t u, uint32_t v)
{
appendU64(packet, static_cast<uint64_t>(u & 0x3FFFu) | (static_cast<uint64_t>(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<uint32_t>(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<uint8_t>(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<uint32_t>(PS2_GS_VRAM_SIZE), &mem.gs());
GSRegisters genericRegs{};
std::vector<uint8_t> genericVram(PS2_GS_VRAM_SIZE, 0u);
GS genericGs;
genericGs.init(genericVram.data(), static_cast<uint32_t>(genericVram.size()), &genericRegs);
std::vector<uint8_t> 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<uint16_t>(GS_PRIM_SPRITE);
appendU64(packet, makeGifTagPrim(2u, kSpritePrim, GIF_FMT_PACKED, 3u, true, true));
appendU64(packet, static_cast<uint64_t>(GS_REG_UV) |
(static_cast<uint64_t>(GS_REG_RGBAQ) << 4u) |
(static_cast<uint64_t>(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<uint32_t>(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<uint16_t>(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;
@@ -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();