mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-26 08:51:05 -04:00
Refactor runtime for move speed and better code style (#140)
* 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
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
#ifndef PS2RECOMP_CONTROL_FLOW_EMITTER_H
|
||||
#define PS2RECOMP_CONTROL_FLOW_EMITTER_H
|
||||
|
||||
#include "ps2recomp/code_generator.h"
|
||||
#include "ps2recomp/types.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
class ControlFlowEmitter
|
||||
{
|
||||
public:
|
||||
ControlFlowEmitter(CodeGenerator &generator,
|
||||
const Instruction &branchInst,
|
||||
const Instruction &delaySlot,
|
||||
const Function &function,
|
||||
const CodeGenerator::AnalysisResult &analysisResult);
|
||||
|
||||
std::string emit();
|
||||
|
||||
private:
|
||||
enum class StaticBranchKind
|
||||
{
|
||||
Jump,
|
||||
Call,
|
||||
};
|
||||
|
||||
enum class RegisterBranchKind
|
||||
{
|
||||
Jump,
|
||||
Call,
|
||||
};
|
||||
|
||||
CodeGenerator &m_gen;
|
||||
const Instruction &m_branchInst;
|
||||
const Instruction &m_delaySlot;
|
||||
const Function &m_function;
|
||||
const CodeGenerator::AnalysisResult &m_analysisResult;
|
||||
std::stringstream m_ss;
|
||||
|
||||
uint32_t branchPc() const;
|
||||
uint32_t delayPc() const;
|
||||
uint32_t fallthroughPc() const;
|
||||
bool hasRealDelaySlot() const;
|
||||
bool isLikelyBranch() const;
|
||||
bool isCallLikeEdge() const;
|
||||
bool isInternalTarget(uint32_t target) const;
|
||||
std::vector<uint32_t> resolvedLocalIndirectTargets() const;
|
||||
|
||||
std::string delaySlotCode() const;
|
||||
void emitDelaySlot(std::string_view indent);
|
||||
void emitResumeFromDelaySlotEntry();
|
||||
void emitInternalTarget(uint32_t target, uint32_t sourcePc, std::string_view indent);
|
||||
void emitFallthroughLabelIfNeeded();
|
||||
void emitFinalFallthrough();
|
||||
|
||||
void emitStaticJump(StaticBranchKind kind);
|
||||
void emitRegisterJump(RegisterBranchKind kind);
|
||||
void emitConditionalBranch();
|
||||
void emitFallbackInstruction();
|
||||
|
||||
bool emitDirectFunctionJumpIfAvailable(uint32_t target, StaticBranchKind kind, std::string_view indent);
|
||||
void emitExternalJumpDispatch(uint32_t target, StaticBranchKind kind, std::string_view indent);
|
||||
void emitExternalRegisterCallDispatch(std::string_view jumpTargetExpression, std::string_view indent);
|
||||
void emitExternalRegisterJumpDispatch(std::string_view jumpTargetExpression, RegisterBranchKind kind, uint8_t rsReg, std::string_view indent);
|
||||
void emitRuntimeBranchDispatch(std::string_view targetExpression,
|
||||
uint32_t sourcePc,
|
||||
uint32_t returnPc,
|
||||
std::string_view runtimeKind,
|
||||
std::string_view debugName,
|
||||
std::string_view indent,
|
||||
bool returnOnTransfer);
|
||||
bool emitRelocationCallIfAvailable(StaticBranchKind kind, std::string_view indent);
|
||||
std::string conditionalBranchExpression() const;
|
||||
uint32_t conditionalBranchTarget() const;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // PS2RECOMP_CONTROL_FLOW_EMITTER_H
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef PS2RECOMP_FUNCTION_EMITTER_H
|
||||
#define PS2RECOMP_FUNCTION_EMITTER_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
struct Function;
|
||||
struct Instruction;
|
||||
class CodeGenerator;
|
||||
|
||||
class FunctionEmitter
|
||||
{
|
||||
public:
|
||||
explicit FunctionEmitter(CodeGenerator &codeGenerator);
|
||||
|
||||
std::string emit(const Function &function, const std::vector<Instruction> &instructions, bool useHeaders);
|
||||
|
||||
private:
|
||||
CodeGenerator &m_codeGenerator;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // PS2RECOMP_FUNCTION_EMITTER_H
|
||||
@@ -0,0 +1,26 @@
|
||||
#ifndef PS2RECOMP_FUNCTION_TABLE_EMITTER_H
|
||||
#define PS2RECOMP_FUNCTION_TABLE_EMITTER_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
struct Function;
|
||||
class CodeGenerator;
|
||||
|
||||
class FunctionTableEmitter
|
||||
{
|
||||
public:
|
||||
explicit FunctionTableEmitter(CodeGenerator &codeGenerator);
|
||||
|
||||
std::string emit(const std::vector<Function> &functions, const std::map<uint32_t, std::string> &stubs);
|
||||
|
||||
private:
|
||||
CodeGenerator &m_codeGenerator;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // PS2RECOMP_FUNCTION_TABLE_EMITTER_H
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef PS2RECOMP_COP0_TRANSLATOR_H
|
||||
#define PS2RECOMP_COP0_TRANSLATOR_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
struct Instruction;
|
||||
class CodeGenerator;
|
||||
|
||||
class Cop0Translator
|
||||
{
|
||||
public:
|
||||
explicit Cop0Translator(CodeGenerator &codeGenerator);
|
||||
std::string translate(const Instruction &inst);
|
||||
|
||||
private:
|
||||
CodeGenerator &m_codeGenerator;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // PS2RECOMP_COP0_TRANSLATOR_H
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef PS2RECOMP_FPU_TRANSLATOR_H
|
||||
#define PS2RECOMP_FPU_TRANSLATOR_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
struct Instruction;
|
||||
class CodeGenerator;
|
||||
|
||||
class FpuTranslator
|
||||
{
|
||||
public:
|
||||
explicit FpuTranslator(CodeGenerator &codeGenerator);
|
||||
std::string translate(const Instruction &inst);
|
||||
|
||||
private:
|
||||
CodeGenerator &m_codeGenerator;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // PS2RECOMP_FPU_TRANSLATOR_H
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef PS2RECOMP_INSTRUCTION_TRANSLATOR_H
|
||||
#define PS2RECOMP_INSTRUCTION_TRANSLATOR_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
struct Instruction;
|
||||
class CodeGenerator;
|
||||
|
||||
class InstructionTranslator
|
||||
{
|
||||
public:
|
||||
explicit InstructionTranslator(CodeGenerator &codeGenerator);
|
||||
std::string translate(const Instruction &inst);
|
||||
|
||||
private:
|
||||
CodeGenerator &m_codeGenerator;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // PS2RECOMP_INSTRUCTION_TRANSLATOR_H
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef PS2RECOMP_MMI_TRANSLATOR_H
|
||||
#define PS2RECOMP_MMI_TRANSLATOR_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
struct Instruction;
|
||||
class CodeGenerator;
|
||||
|
||||
class MmiTranslator
|
||||
{
|
||||
public:
|
||||
explicit MmiTranslator(CodeGenerator &codeGenerator);
|
||||
std::string translate(const Instruction &inst);
|
||||
|
||||
private:
|
||||
CodeGenerator &m_codeGenerator;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // PS2RECOMP_MMI_TRANSLATOR_H
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef PS2RECOMP_REGIMM_TRANSLATOR_H
|
||||
#define PS2RECOMP_REGIMM_TRANSLATOR_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
struct Instruction;
|
||||
class CodeGenerator;
|
||||
|
||||
class RegimmTranslator
|
||||
{
|
||||
public:
|
||||
explicit RegimmTranslator(CodeGenerator &codeGenerator);
|
||||
std::string translate(const Instruction &inst);
|
||||
|
||||
private:
|
||||
CodeGenerator &m_codeGenerator;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // PS2RECOMP_REGIMM_TRANSLATOR_H
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef PS2RECOMP_SPECIAL_TRANSLATOR_H
|
||||
#define PS2RECOMP_SPECIAL_TRANSLATOR_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
struct Instruction;
|
||||
class CodeGenerator;
|
||||
|
||||
class SpecialTranslator
|
||||
{
|
||||
public:
|
||||
explicit SpecialTranslator(CodeGenerator &codeGenerator);
|
||||
std::string translate(const Instruction &inst);
|
||||
|
||||
private:
|
||||
CodeGenerator &m_codeGenerator;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // PS2RECOMP_SPECIAL_TRANSLATOR_H
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef PS2RECOMP_VU_TRANSLATOR_H
|
||||
#define PS2RECOMP_VU_TRANSLATOR_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
struct Instruction;
|
||||
class CodeGenerator;
|
||||
|
||||
class VuTranslator
|
||||
{
|
||||
public:
|
||||
explicit VuTranslator(CodeGenerator &codeGenerator);
|
||||
std::string translate(const Instruction &inst);
|
||||
|
||||
private:
|
||||
CodeGenerator &m_codeGenerator;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // PS2RECOMP_VU_TRANSLATOR_H
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include "ps2recomp/control_flow_analyzer.h"
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
@@ -16,6 +17,7 @@ namespace ps2recomp
|
||||
struct Function;
|
||||
struct Symbol;
|
||||
struct Section;
|
||||
class RecompilerReporter;
|
||||
|
||||
extern const std::unordered_set<std::string> kKeywords;
|
||||
|
||||
@@ -35,13 +37,7 @@ namespace ps2recomp
|
||||
std::string entryName;
|
||||
};
|
||||
|
||||
struct AnalysisResult {
|
||||
std::unordered_set<uint32_t> entryPoints;
|
||||
std::unordered_set<uint32_t> externalEntryPoints;
|
||||
std::unordered_set<uint32_t> resumeEntryPoints;
|
||||
std::unordered_set<uint32_t> indirectFallbackEntryPoints;
|
||||
std::unordered_map<uint32_t, std::vector<uint32_t>> jumpTableTargets;
|
||||
};
|
||||
using AnalysisResult = ControlFlowAnalysisResult;
|
||||
|
||||
std::string generateFunction(const Function &function, const std::vector<Instruction> &instructions, const bool &useHeaders);
|
||||
std::string generateFunctionRegistration(const std::vector<Function> &functions, const std::map<uint32_t, std::string> &stubs);
|
||||
@@ -54,6 +50,7 @@ namespace ps2recomp
|
||||
void setConfiguredJumpTables(const std::vector<JumpTable> &jumpTables);
|
||||
void setResumeEntryTargets(const std::unordered_map<uint32_t, std::vector<uint32_t>> &resumeTargetsByOwner);
|
||||
void setEmitInstructionComments(bool emitInstructionComments);
|
||||
void setReporter(RecompilerReporter *reporter);
|
||||
|
||||
AnalysisResult collectInternalBranchTargets(const Function &function,
|
||||
const std::vector<Instruction> &instructions,
|
||||
@@ -68,8 +65,11 @@ namespace ps2recomp
|
||||
const std::vector<Section>& m_sections;
|
||||
BootstrapInfo m_bootstrapInfo;
|
||||
bool m_emitInstructionComments = true;
|
||||
RecompilerReporter *m_reporter = nullptr;
|
||||
std::string m_currentFunctionName;
|
||||
|
||||
std::string translateInstruction(const Instruction &inst);
|
||||
std::string emitUnhandledInstruction(const Instruction &inst, const std::string &message);
|
||||
std::string translateMMIInstruction(const Instruction &inst);
|
||||
std::string translateVUInstruction(const Instruction &inst);
|
||||
std::string translateFPUInstruction(const Instruction &inst);
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#ifndef PS2RECOMP_CODEGEN_HELPERS_H
|
||||
#define PS2RECOMP_CODEGEN_HELPERS_H
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace ps2recomp::codegen
|
||||
{
|
||||
inline std::string formatFloatLiteral(float value)
|
||||
{
|
||||
if (!std::isfinite(value))
|
||||
{
|
||||
return (value < 0.0f) ? "-INFINITY" : "INFINITY";
|
||||
}
|
||||
|
||||
std::string literal = fmt::format("{:.9g}", value);
|
||||
if (literal.find_first_of(".eE") == std::string::npos)
|
||||
{
|
||||
literal += ".0";
|
||||
}
|
||||
literal += 'f';
|
||||
return literal;
|
||||
}
|
||||
|
||||
inline std::string vuMaskExpr(uint8_t dest_mask)
|
||||
{
|
||||
return fmt::format("_mm_castsi128_ps(_mm_set_epi32({}, {}, {}, {}))",
|
||||
(dest_mask & 0x1) ? -1 : 0,
|
||||
(dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0,
|
||||
(dest_mask & 0x8) ? -1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // PS2RECOMP_CODEGEN_HELPERS_H
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
class RecompilerReporter;
|
||||
|
||||
class ConfigManager
|
||||
{
|
||||
public:
|
||||
@@ -14,9 +16,11 @@ namespace ps2recomp
|
||||
|
||||
RecompilerConfig loadConfig() const;
|
||||
void saveConfig(const RecompilerConfig &config) const;
|
||||
void setReporter(RecompilerReporter *reporter);
|
||||
|
||||
private:
|
||||
std::string m_configPath;
|
||||
RecompilerReporter *m_reporter = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#ifndef PS2RECOMP_CONTROL_FLOW_ANALYZER_H
|
||||
#define PS2RECOMP_CONTROL_FLOW_ANALYZER_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
struct Function;
|
||||
struct Instruction;
|
||||
struct Section;
|
||||
class RecompilerReporter;
|
||||
|
||||
struct ControlFlowAnalysisResult
|
||||
{
|
||||
std::unordered_set<uint32_t> entryPoints;
|
||||
std::unordered_set<uint32_t> externalEntryPoints;
|
||||
std::unordered_set<uint32_t> resumeEntryPoints;
|
||||
std::unordered_set<uint32_t> indirectFallbackEntryPoints;
|
||||
std::unordered_map<uint32_t, std::vector<uint32_t>> jumpTableTargets;
|
||||
};
|
||||
|
||||
class ControlFlowAnalyzer
|
||||
{
|
||||
public:
|
||||
ControlFlowAnalyzer(const std::vector<Section> §ions,
|
||||
const std::unordered_map<uint32_t, std::vector<uint32_t>> &configuredJumpTableTargetsByAddress,
|
||||
RecompilerReporter *reporter);
|
||||
|
||||
ControlFlowAnalysisResult analyze(const Function &function,
|
||||
const std::vector<Instruction> &instructions,
|
||||
const std::vector<Function> *allFunctions = nullptr) const;
|
||||
|
||||
private:
|
||||
const std::vector<Section> &m_sections;
|
||||
const std::unordered_map<uint32_t, std::vector<uint32_t>> &m_configJumpTableTargetsByAddress;
|
||||
RecompilerReporter *m_reporter = nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // PS2RECOMP_CONTROL_FLOW_ANALYZER_H
|
||||
@@ -0,0 +1,35 @@
|
||||
#ifndef PS2RECOMP_CONTROL_FLOW_UTILS_H
|
||||
#define PS2RECOMP_CONTROL_FLOW_UTILS_H
|
||||
|
||||
#include "ps2recomp/instructions.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
inline uint32_t buildAbsoluteJumpTarget(uint32_t address, uint32_t target) noexcept
|
||||
{
|
||||
return ((address + 4u) & 0xF0000000u) | (target << 2);
|
||||
}
|
||||
|
||||
inline bool isGuestNop(const Instruction &inst) noexcept
|
||||
{
|
||||
const bool sllZero =
|
||||
inst.opcode == OPCODE_SPECIAL &&
|
||||
inst.function == SPECIAL_SLL &&
|
||||
inst.rd == 0u &&
|
||||
inst.rt == 0u &&
|
||||
inst.sa == 0u;
|
||||
|
||||
// Some tests and decoded streams represent a no-op as addiu $zero, $zero, 0.
|
||||
const bool addiuZero =
|
||||
inst.opcode == OPCODE_ADDIU &&
|
||||
inst.rs == 0u &&
|
||||
inst.rt == 0u &&
|
||||
static_cast<int16_t>(inst.simmediate) == 0;
|
||||
|
||||
return sllZero || addiuZero;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // PS2RECOMP_CONTROL_FLOW_UTILS_H
|
||||
@@ -13,6 +13,7 @@ namespace ps2recomp
|
||||
struct Section;
|
||||
struct Function;
|
||||
struct Symbol;
|
||||
class RecompilerReporter;
|
||||
|
||||
class ElfParser
|
||||
{
|
||||
@@ -36,6 +37,7 @@ namespace ps2recomp
|
||||
uint32_t getSectionAddress(const std::string §ionName) const;
|
||||
uint32_t getSectionSize(const std::string §ionName) const;
|
||||
uint32_t getEntryPoint() const;
|
||||
void setReporter(RecompilerReporter *reporter);
|
||||
void debugAddress(uint32_t address) const;
|
||||
|
||||
private:
|
||||
@@ -47,6 +49,7 @@ namespace ps2recomp
|
||||
std::vector<Relocation> m_relocations;
|
||||
std::vector<Function> m_extraFunctions;
|
||||
bool m_hasLoadedGhidraMap = false;
|
||||
RecompilerReporter *m_reporter = nullptr;
|
||||
std::unordered_set<uint32_t> m_ghidraMapStarts;
|
||||
|
||||
void loadSections();
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
|
||||
#include "code_generator.h"
|
||||
#include "config_manager.h"
|
||||
#include "recompiler_reporter.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
@@ -30,6 +33,7 @@ namespace ps2recomp
|
||||
bool initialize();
|
||||
bool recompile();
|
||||
void generateOutput();
|
||||
void printReport() const;
|
||||
|
||||
static StubTarget resolveStubTarget(const std::string& name);
|
||||
static size_t DiscoverAdditionalEntryPoints(
|
||||
@@ -48,6 +52,7 @@ namespace ps2recomp
|
||||
std::unique_ptr<R5900Decoder> m_decoder;
|
||||
std::unique_ptr<CodeGenerator> m_codeGenerator;
|
||||
RecompilerConfig m_config;
|
||||
RecompilerReporter m_reporter;
|
||||
|
||||
std::vector<Function> m_functions;
|
||||
std::vector<Symbol> m_symbols;
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
#ifndef PS2RECOMP_RECOMPILER_REPORTER_H
|
||||
#define PS2RECOMP_RECOMPILER_REPORTER_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iosfwd>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
class RecompilerReporter
|
||||
{
|
||||
public:
|
||||
enum class Severity
|
||||
{
|
||||
Info,
|
||||
Warning,
|
||||
Error
|
||||
};
|
||||
|
||||
struct Event
|
||||
{
|
||||
Severity severity = Severity::Info;
|
||||
std::string category;
|
||||
std::string message;
|
||||
std::string functionName;
|
||||
uint32_t address = 0;
|
||||
bool hasAddress = false;
|
||||
};
|
||||
|
||||
struct Counters
|
||||
{
|
||||
size_t functionsDiscovered = 0;
|
||||
size_t symbolsDiscovered = 0;
|
||||
size_t sectionsDiscovered = 0;
|
||||
size_t relocationsDiscovered = 0;
|
||||
size_t functionsProcessed = 0;
|
||||
size_t functionsRecompiled = 0;
|
||||
size_t functionsStubbed = 0;
|
||||
size_t functionsSkipped = 0;
|
||||
size_t decodeFailures = 0;
|
||||
size_t additionalEntryPoints = 0;
|
||||
size_t generatedFunctions = 0;
|
||||
size_t unhandledInstructions = 0;
|
||||
size_t indirectFallbackPromotions = 0;
|
||||
size_t indirectFallbackEntries = 0;
|
||||
};
|
||||
|
||||
void progress(const std::string &message);
|
||||
void info(const std::string &category, const std::string &message);
|
||||
void warning(const std::string &category, const std::string &message);
|
||||
void error(const std::string &category, const std::string &message);
|
||||
void warningAt(const std::string &category, const std::string &functionName, uint32_t address, const std::string &message);
|
||||
void errorAt(const std::string &category, const std::string &functionName, uint32_t address, const std::string &message);
|
||||
|
||||
void recordDiscovered(size_t functions, size_t symbols, size_t sections, size_t relocations);
|
||||
void recordFunctionProcessed();
|
||||
void recordFunctionRecompiled();
|
||||
void recordFunctionStubbed();
|
||||
void recordFunctionSkipped();
|
||||
void recordDecodeFailure();
|
||||
void recordAdditionalEntryPoints(size_t count);
|
||||
void recordGeneratedFunctions(size_t count);
|
||||
void recordIndirectFallbackPromotion(const std::string &functionName,
|
||||
const std::vector<uint32_t> &jumpAddresses,
|
||||
size_t promotedEntryCount);
|
||||
void recordUnhandledInstruction(const std::string &functionName,
|
||||
uint32_t address,
|
||||
uint32_t raw,
|
||||
const std::string &message);
|
||||
|
||||
const Counters &counters() const;
|
||||
bool hasErrors() const;
|
||||
bool hasWarnings() const;
|
||||
void printSummary(std::ostream &os) const;
|
||||
|
||||
private:
|
||||
void addEvent(Severity severity,
|
||||
const std::string &category,
|
||||
const std::string &message,
|
||||
const std::string &functionName = {},
|
||||
uint32_t address = 0,
|
||||
bool hasAddress = false);
|
||||
|
||||
mutable std::mutex m_mutex;
|
||||
Counters m_counters;
|
||||
std::vector<Event> m_events;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
#include "ps2recomp/config_manager.h"
|
||||
#include "ps2recomp/recompiler_reporter.h"
|
||||
#include <toml.hpp>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
@@ -18,13 +19,21 @@ namespace ps2recomp
|
||||
|
||||
ConfigManager::~ConfigManager() = default;
|
||||
|
||||
void ConfigManager::setReporter(RecompilerReporter *reporter)
|
||||
{
|
||||
m_reporter = reporter;
|
||||
}
|
||||
|
||||
RecompilerConfig ConfigManager::loadConfig() const
|
||||
{
|
||||
RecompilerConfig config;
|
||||
|
||||
try
|
||||
{
|
||||
std::cout << "Parsing toml file: " << m_configPath << std::endl;
|
||||
if (m_reporter)
|
||||
{
|
||||
m_reporter->info("config", "Parsing toml file: " + m_configPath);
|
||||
}
|
||||
auto data = toml::parse(m_configPath);
|
||||
const auto &general = toml::find(data, "general");
|
||||
|
||||
@@ -43,8 +52,13 @@ namespace ps2recomp
|
||||
std::thread::hardware_concurrency() * 2);
|
||||
if (configuredOutputWorkers != clampedOutputWorkers)
|
||||
{
|
||||
std::cerr << "Warning: output_worker_threads value " << configuredOutputWorkers
|
||||
<< " is out of range; clamped to " << clampedOutputWorkers << "." << std::endl;
|
||||
if (m_reporter)
|
||||
{
|
||||
std::ostringstream msg;
|
||||
msg << "output_worker_threads value " << configuredOutputWorkers
|
||||
<< " is out of range; clamped to " << clampedOutputWorkers << ".";
|
||||
m_reporter->warning("config", msg.str());
|
||||
}
|
||||
}
|
||||
config.outputWorkerThreads = static_cast<uint32_t>(clampedOutputWorkers);
|
||||
config.patchSyscalls = toml::find_or<bool>(general, "patch_syscalls", config.patchSyscalls);
|
||||
@@ -236,7 +250,10 @@ namespace ps2recomp
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::cerr << "Error parsing configuration file: " << e.what() << std::endl;
|
||||
if (m_reporter)
|
||||
{
|
||||
m_reporter->error("config", std::string("Error parsing configuration file: ") + e.what());
|
||||
}
|
||||
throw;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,423 @@
|
||||
#include "ps2recomp/control_flow_analyzer.h"
|
||||
#include "ps2recomp/types.h"
|
||||
#include "ps2recomp/instructions.h"
|
||||
#include "ps2recomp/control_flow_utils.h"
|
||||
#include "ps2recomp/recompiler_reporter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
ControlFlowAnalyzer::ControlFlowAnalyzer(
|
||||
const std::vector<Section> §ions,
|
||||
const std::unordered_map<uint32_t, std::vector<uint32_t>> &configuredJumpTableTargetsByAddress,
|
||||
RecompilerReporter *reporter)
|
||||
: m_sections(sections),
|
||||
m_configJumpTableTargetsByAddress(configuredJumpTableTargetsByAddress),
|
||||
m_reporter(reporter)
|
||||
{
|
||||
}
|
||||
|
||||
ControlFlowAnalysisResult ControlFlowAnalyzer::analyze(
|
||||
const Function &function,
|
||||
const std::vector<Instruction> &instructions,
|
||||
const std::vector<Function> *allFunctions) const
|
||||
{
|
||||
ControlFlowAnalysisResult result;
|
||||
std::unordered_set<uint32_t> instructionAddresses;
|
||||
instructionAddresses.reserve(instructions.size());
|
||||
bool hasIndirectRegisterJump = false;
|
||||
std::vector<const Instruction *> indirectJumps;
|
||||
|
||||
auto isExecutableAddress = [&](uint32_t address) -> bool
|
||||
{
|
||||
for (const auto §ion : m_sections)
|
||||
{
|
||||
if (!section.isCode)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (address >= section.address && address < (section.address + section.size))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto findContainingExternalFunction = [&](uint32_t address) -> const Function *
|
||||
{
|
||||
if (!allFunctions || !isExecutableAddress(address))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const Function *best = nullptr;
|
||||
for (const auto &candidateFn : *allFunctions)
|
||||
{
|
||||
if (!candidateFn.isRecompiled || candidateFn.isStub || candidateFn.isSkipped)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (candidateFn.name.rfind("entry_", 0) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (address < candidateFn.start || address >= candidateFn.end)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!best || candidateFn.start > best->start)
|
||||
{
|
||||
best = &candidateFn;
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
};
|
||||
|
||||
auto queueExternalEntryTarget = [&](uint32_t target)
|
||||
{
|
||||
const Function *containingFn = findContainingExternalFunction(target);
|
||||
if (!containingFn)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (containingFn->start == function.start)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (target == containingFn->start)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
result.externalEntryPoints.insert(target);
|
||||
};
|
||||
|
||||
auto queueResumeEntryTarget = [&](uint32_t resumeAddr)
|
||||
{
|
||||
if (resumeAddr >= function.start && resumeAddr < function.end &&
|
||||
instructionAddresses.contains(resumeAddr))
|
||||
{
|
||||
result.entryPoints.insert(resumeAddr);
|
||||
result.resumeEntryPoints.insert(resumeAddr);
|
||||
}
|
||||
};
|
||||
|
||||
auto queueLoopResumeEntryTarget = [&](uint32_t target, uint32_t sourcePc)
|
||||
{
|
||||
if (target > sourcePc || target == function.start)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
queueResumeEntryTarget(target);
|
||||
};
|
||||
|
||||
for (const auto &inst : instructions)
|
||||
{
|
||||
instructionAddresses.insert(inst.address);
|
||||
if (inst.opcode == OPCODE_SPECIAL &&
|
||||
((inst.function == SPECIAL_JR && inst.rs != 31) ||
|
||||
inst.function == SPECIAL_JALR))
|
||||
{
|
||||
hasIndirectRegisterJump = true;
|
||||
indirectJumps.push_back(&inst);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &inst : instructions)
|
||||
{
|
||||
bool isStaticJump = (inst.opcode == OPCODE_J || inst.opcode == OPCODE_JAL);
|
||||
if (inst.isBranch && inst.opcode != OPCODE_J && inst.opcode != OPCODE_JAL)
|
||||
{
|
||||
const int32_t offsetBytes = (static_cast<int32_t>(static_cast<int16_t>(inst.simmediate)) << 2);
|
||||
const uint32_t target = static_cast<uint32_t>(
|
||||
static_cast<int64_t>(inst.address + 4u) + static_cast<int64_t>(offsetBytes));
|
||||
|
||||
if (target >= function.start && target < function.end &&
|
||||
instructionAddresses.contains(target))
|
||||
{
|
||||
result.entryPoints.insert(target);
|
||||
queueLoopResumeEntryTarget(target, inst.address);
|
||||
}
|
||||
else
|
||||
{
|
||||
queueExternalEntryTarget(target);
|
||||
}
|
||||
}
|
||||
else if (isStaticJump)
|
||||
{
|
||||
uint32_t target = buildAbsoluteJumpTarget(inst.address, inst.target);
|
||||
if (target >= function.start && target < function.end &&
|
||||
instructionAddresses.contains(target))
|
||||
{
|
||||
result.entryPoints.insert(target);
|
||||
queueLoopResumeEntryTarget(target, inst.address);
|
||||
|
||||
if (inst.opcode == OPCODE_JAL)
|
||||
{
|
||||
queueResumeEntryTarget(inst.address + 8u);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
queueExternalEntryTarget(target);
|
||||
|
||||
if (inst.opcode == OPCODE_JAL)
|
||||
{
|
||||
queueResumeEntryTarget(inst.address + 8u);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasIndirectRegisterJump)
|
||||
{
|
||||
bool needsIndirectFallback = false;
|
||||
for (const Instruction *jrInst : indirectJumps)
|
||||
{
|
||||
if (jrInst->function == SPECIAL_JALR)
|
||||
{
|
||||
queueResumeEntryTarget(jrInst->address + 8u);
|
||||
}
|
||||
|
||||
bool foundTable = false;
|
||||
|
||||
uint32_t jrReg = jrInst->rs;
|
||||
|
||||
int lwIndex = -1;
|
||||
uint32_t baseReg = 0;
|
||||
int32_t lwOffset = 0;
|
||||
|
||||
auto it = std::find_if(instructions.begin(), instructions.end(), [&](const Instruction &inst)
|
||||
{ return inst.address == jrInst->address; });
|
||||
if (it != instructions.end())
|
||||
{
|
||||
int jrIndex = std::distance(instructions.begin(), it);
|
||||
for (int i = jrIndex - 1; i >= 0 && i >= jrIndex - 20; --i)
|
||||
{
|
||||
const auto &inst = instructions[i];
|
||||
if ((inst.opcode == OPCODE_LW || inst.opcode == OPCODE_LWU) && inst.rt == jrReg)
|
||||
{
|
||||
lwIndex = i;
|
||||
baseReg = inst.rs;
|
||||
lwOffset = inst.simmediate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lwIndex != -1)
|
||||
{
|
||||
int adduIndex = -1;
|
||||
uint32_t tableBaseReg = 0;
|
||||
uint32_t indexReg = 0;
|
||||
for (int i = lwIndex - 1; i >= 0 && i >= lwIndex - 10; --i)
|
||||
{
|
||||
const auto &inst = instructions[i];
|
||||
if (inst.opcode == OPCODE_SPECIAL && inst.function == SPECIAL_ADDU && inst.rd == baseReg)
|
||||
{
|
||||
adduIndex = i;
|
||||
tableBaseReg = inst.rs;
|
||||
indexReg = inst.rt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t tableAddress = 0;
|
||||
bool foundTableAddress = false;
|
||||
|
||||
if (adduIndex != -1)
|
||||
{
|
||||
for (int i = adduIndex - 1; i >= 0 && i >= adduIndex - 20; --i)
|
||||
{
|
||||
const auto &inst = instructions[i];
|
||||
if (inst.opcode == OPCODE_LUI)
|
||||
{
|
||||
if (inst.rt == tableBaseReg || inst.rt == indexReg)
|
||||
{
|
||||
uint32_t high = inst.immediate << 16;
|
||||
uint32_t low = 0;
|
||||
for (int j = i + 1; j < adduIndex; ++j)
|
||||
{
|
||||
const auto &lowInst = instructions[j];
|
||||
if (lowInst.rs == inst.rt && lowInst.rt == inst.rt)
|
||||
{
|
||||
if (lowInst.opcode == OPCODE_ADDIU)
|
||||
{
|
||||
low = (uint32_t)lowInst.simmediate;
|
||||
}
|
||||
else if (lowInst.opcode == OPCODE_ORI)
|
||||
{
|
||||
low = lowInst.immediate;
|
||||
}
|
||||
}
|
||||
}
|
||||
tableAddress = high + low;
|
||||
foundTableAddress = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (foundTableAddress)
|
||||
{
|
||||
tableAddress += lwOffset;
|
||||
|
||||
const auto configuredTableIt = m_configJumpTableTargetsByAddress.find(tableAddress);
|
||||
if (configuredTableIt != m_configJumpTableTargetsByAddress.end())
|
||||
{
|
||||
std::vector<uint32_t> jrTargets;
|
||||
jrTargets.reserve(configuredTableIt->second.size());
|
||||
for (uint32_t target : configuredTableIt->second)
|
||||
{
|
||||
if (target >= function.start && target < function.end &&
|
||||
instructionAddresses.contains(target))
|
||||
{
|
||||
jrTargets.push_back(target);
|
||||
}
|
||||
else
|
||||
{
|
||||
queueExternalEntryTarget(target);
|
||||
}
|
||||
}
|
||||
|
||||
if (!jrTargets.empty())
|
||||
{
|
||||
std::sort(jrTargets.begin(), jrTargets.end());
|
||||
jrTargets.erase(std::unique(jrTargets.begin(), jrTargets.end()), jrTargets.end());
|
||||
result.jumpTableTargets[jrInst->address] = jrTargets;
|
||||
for (uint32_t target : jrTargets)
|
||||
{
|
||||
result.entryPoints.insert(target);
|
||||
}
|
||||
foundTable = true;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t unshiftedIndexReg = 0;
|
||||
for (int i = adduIndex - 1; i >= 0 && i >= adduIndex - 10; --i)
|
||||
{
|
||||
const auto &inst = instructions[i];
|
||||
if (inst.opcode == OPCODE_SPECIAL && inst.function == SPECIAL_SLL && (inst.rd == tableBaseReg || inst.rd == indexReg))
|
||||
{
|
||||
unshiftedIndexReg = inst.rt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t numCases = 0;
|
||||
if (unshiftedIndexReg != 0)
|
||||
{
|
||||
for (int i = adduIndex - 1; i >= 0 && i >= adduIndex - 30; --i)
|
||||
{
|
||||
const auto &inst = instructions[i];
|
||||
if ((inst.opcode == OPCODE_SLTIU || inst.opcode == OPCODE_SLTI) && inst.rs == unshiftedIndexReg)
|
||||
{
|
||||
numCases = inst.immediate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundTable && numCases > 0 && numCases <= 1000)
|
||||
{
|
||||
const Section *rodata = nullptr;
|
||||
for (const auto &sec : m_sections)
|
||||
{
|
||||
if (tableAddress >= sec.address && tableAddress < sec.address + sec.size)
|
||||
{
|
||||
rodata = &sec;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (rodata && rodata->data)
|
||||
{
|
||||
std::vector<uint32_t> jrTargets;
|
||||
bool validJumpTable = true;
|
||||
std::unordered_set<uint32_t> uniqueTargets;
|
||||
for (uint32_t i = 0; i < numCases; ++i)
|
||||
{
|
||||
uint32_t addr = tableAddress + i * 4;
|
||||
if (addr >= rodata->address && addr + 4 <= rodata->address + rodata->size)
|
||||
{
|
||||
uint32_t target = 0;
|
||||
std::memcpy(&target, rodata->data + (addr - rodata->address), 4);
|
||||
if (target >= function.start && target < function.end && instructionAddresses.contains(target))
|
||||
{
|
||||
if (!uniqueTargets.contains(target))
|
||||
{
|
||||
jrTargets.push_back(target);
|
||||
uniqueTargets.insert(target);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
queueExternalEntryTarget(target);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
validJumpTable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (validJumpTable && !jrTargets.empty())
|
||||
{
|
||||
result.jumpTableTargets[jrInst->address] = jrTargets;
|
||||
for (uint32_t t : jrTargets)
|
||||
{
|
||||
result.entryPoints.insert(t);
|
||||
}
|
||||
foundTable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!foundTable)
|
||||
{
|
||||
needsIndirectFallback = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsIndirectFallback)
|
||||
{
|
||||
if (m_reporter)
|
||||
{
|
||||
std::vector<uint32_t> jumpAddresses;
|
||||
jumpAddresses.reserve(indirectJumps.size());
|
||||
for (const Instruction *jrInst : indirectJumps)
|
||||
{
|
||||
jumpAddresses.push_back(jrInst->address);
|
||||
}
|
||||
m_reporter->recordIndirectFallbackPromotion(function.name, jumpAddresses, instructionAddresses.size());
|
||||
}
|
||||
|
||||
for (uint32_t addr : instructionAddresses)
|
||||
{
|
||||
if (addr >= function.start && addr < function.end)
|
||||
{
|
||||
result.entryPoints.insert(addr);
|
||||
// Keep labels and runtime registration for unresolved JR/JALR targets
|
||||
// without emitting a local switch over every possible target.
|
||||
result.indirectFallbackEntryPoints.insert(addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,567 @@
|
||||
#include "ps2recomp/Emitters/control_flow_emitter.h"
|
||||
|
||||
#include "ps2recomp/control_flow_utils.h"
|
||||
#include "ps2recomp/instructions.h"
|
||||
#include "ps2recomp/r5900_decoder.h"
|
||||
#include "ps2_runtime_calls.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fmt/format.h>
|
||||
#include <sstream>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
ControlFlowEmitter::ControlFlowEmitter(CodeGenerator &generator,
|
||||
const Instruction &branchInst,
|
||||
const Instruction &delaySlot,
|
||||
const Function &function,
|
||||
const CodeGenerator::AnalysisResult &analysisResult)
|
||||
: m_gen(generator),
|
||||
m_branchInst(branchInst),
|
||||
m_delaySlot(delaySlot),
|
||||
m_function(function),
|
||||
m_analysisResult(analysisResult)
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t ControlFlowEmitter::branchPc() const
|
||||
{
|
||||
return m_branchInst.address;
|
||||
}
|
||||
|
||||
uint32_t ControlFlowEmitter::delayPc() const
|
||||
{
|
||||
return m_branchInst.address + 4u;
|
||||
}
|
||||
|
||||
uint32_t ControlFlowEmitter::fallthroughPc() const
|
||||
{
|
||||
return m_branchInst.address + 8u;
|
||||
}
|
||||
|
||||
bool ControlFlowEmitter::hasRealDelaySlot() const
|
||||
{
|
||||
return !isGuestNop(m_delaySlot);
|
||||
}
|
||||
|
||||
bool ControlFlowEmitter::isCallLikeEdge() const
|
||||
{
|
||||
return (m_branchInst.opcode == OPCODE_JAL) ||
|
||||
(m_branchInst.opcode == OPCODE_SPECIAL && m_branchInst.function == SPECIAL_JALR);
|
||||
}
|
||||
|
||||
bool ControlFlowEmitter::isInternalTarget(uint32_t target) const
|
||||
{
|
||||
return m_analysisResult.entryPoints.contains(target);
|
||||
}
|
||||
|
||||
bool ControlFlowEmitter::isLikelyBranch() const
|
||||
{
|
||||
return (m_branchInst.opcode == OPCODE_BEQL || m_branchInst.opcode == OPCODE_BNEL ||
|
||||
m_branchInst.opcode == OPCODE_BLEZL || m_branchInst.opcode == OPCODE_BGTZL ||
|
||||
(m_branchInst.opcode == OPCODE_REGIMM &&
|
||||
(m_branchInst.rt == REGIMM_BLTZL || m_branchInst.rt == REGIMM_BGEZL ||
|
||||
m_branchInst.rt == REGIMM_BLTZALL || m_branchInst.rt == REGIMM_BGEZALL)) ||
|
||||
(m_branchInst.opcode == OPCODE_COP1 && m_branchInst.rs == COP1_BC &&
|
||||
(m_branchInst.rt == COP1_BC_BCFL || m_branchInst.rt == COP1_BC_BCTL)) ||
|
||||
(m_branchInst.opcode == OPCODE_COP2 && m_branchInst.rs == COP2_BC &&
|
||||
(m_branchInst.rt == COP2_BC_BCFL || m_branchInst.rt == COP2_BC_BCTL)));
|
||||
}
|
||||
|
||||
std::vector<uint32_t> ControlFlowEmitter::resolvedLocalIndirectTargets() const
|
||||
{
|
||||
if (m_branchInst.opcode != OPCODE_SPECIAL)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!((m_branchInst.function == SPECIAL_JR && m_branchInst.rs != 31u) ||
|
||||
m_branchInst.function == SPECIAL_JALR))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
auto jtIt = m_analysisResult.jumpTableTargets.find(m_branchInst.address);
|
||||
if (jtIt == m_analysisResult.jumpTableTargets.end())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<uint32_t> targets = jtIt->second;
|
||||
std::sort(targets.begin(), targets.end());
|
||||
targets.erase(std::unique(targets.begin(), targets.end()), targets.end());
|
||||
return targets;
|
||||
}
|
||||
|
||||
std::string ControlFlowEmitter::delaySlotCode() const
|
||||
{
|
||||
if (!hasRealDelaySlot())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string code;
|
||||
if (m_gen.m_emitInstructionComments)
|
||||
{
|
||||
code = "// 0x" + fmt::format("{:x}", m_delaySlot.address) + ": 0x" + fmt::format("{:x}", m_delaySlot.raw);
|
||||
std::string disassembly = R5900Decoder::disassembleInstruction(m_delaySlot);
|
||||
if (!disassembly.empty())
|
||||
{
|
||||
code += " " + disassembly;
|
||||
}
|
||||
code += " (Delay Slot)\n";
|
||||
}
|
||||
|
||||
code += m_gen.translateInstruction(m_delaySlot);
|
||||
return code;
|
||||
}
|
||||
|
||||
void ControlFlowEmitter::emitDelaySlot(std::string_view indent)
|
||||
{
|
||||
if (!hasRealDelaySlot())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_ss << fmt::format("{}ctx->pc = 0x{:X}u;\n", indent, delayPc());
|
||||
m_ss << fmt::format("{}ctx->in_delay_slot = true;\n", indent);
|
||||
m_ss << fmt::format("{}ctx->branch_pc = 0x{:X}u;\n", indent, branchPc());
|
||||
|
||||
const std::string code = delaySlotCode();
|
||||
std::istringstream lines(code);
|
||||
std::string line;
|
||||
while (std::getline(lines, line))
|
||||
{
|
||||
if (!line.empty())
|
||||
{
|
||||
m_ss << indent << line << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
m_ss << fmt::format("{}ctx->in_delay_slot = false;\n", indent);
|
||||
}
|
||||
|
||||
void ControlFlowEmitter::emitResumeFromDelaySlotEntry()
|
||||
{
|
||||
if (!isInternalTarget(delayPc()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_ss << fmt::format(" if (ctx->pc == 0x{:X}u) {{\n", delayPc());
|
||||
emitDelaySlot(" ");
|
||||
m_ss << fmt::format(" ctx->pc = 0x{:X}u;\n", fallthroughPc());
|
||||
|
||||
if (isInternalTarget(fallthroughPc()))
|
||||
{
|
||||
m_ss << fmt::format(" goto label_{:x};\n", fallthroughPc());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ss << fmt::format(" goto label_fallthrough_0x{:x};\n", branchPc());
|
||||
}
|
||||
|
||||
m_ss << " }\n";
|
||||
}
|
||||
|
||||
void ControlFlowEmitter::emitInternalTarget(uint32_t target, uint32_t sourcePc, std::string_view indent)
|
||||
{
|
||||
m_ss << fmt::format("{}ctx->pc = 0x{:X}u;\n", indent, target);
|
||||
if (target <= sourcePc && !isCallLikeEdge())
|
||||
{
|
||||
m_ss << fmt::format("{}if (runtime->shouldPreemptGuestExecution()) {{\n", indent);
|
||||
m_ss << fmt::format("{} return;\n", indent);
|
||||
m_ss << fmt::format("{}}}\n", indent);
|
||||
}
|
||||
m_ss << fmt::format("{}goto label_{:x};\n", indent, target);
|
||||
}
|
||||
|
||||
void ControlFlowEmitter::emitRuntimeBranchDispatch(std::string_view targetExpression,
|
||||
uint32_t sourcePc,
|
||||
uint32_t returnPc,
|
||||
std::string_view runtimeKind,
|
||||
std::string_view debugName,
|
||||
std::string_view indent,
|
||||
bool returnOnTransfer)
|
||||
{
|
||||
m_ss << fmt::format(
|
||||
"{}if (!runtime->dispatchGuestBranch(rdram, ctx, {}, 0x{:X}u, 0x{:X}u, PS2Runtime::GuestBranchKind::{}, \"{}\")) {{\n",
|
||||
indent,
|
||||
targetExpression,
|
||||
sourcePc,
|
||||
returnPc,
|
||||
runtimeKind,
|
||||
debugName);
|
||||
if (returnOnTransfer)
|
||||
{
|
||||
m_ss << fmt::format("{} return;\n", indent);
|
||||
}
|
||||
m_ss << fmt::format("{}}}\n", indent);
|
||||
}
|
||||
|
||||
bool ControlFlowEmitter::emitDirectFunctionJumpIfAvailable(uint32_t target, StaticBranchKind kind, std::string_view indent)
|
||||
{
|
||||
if (kind != StaticBranchKind::Jump)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string functionName = m_gen.getFunctionName(target);
|
||||
if (functionName.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_ss << indent << functionName << "(rdram, ctx, runtime); return;\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
void ControlFlowEmitter::emitExternalJumpDispatch(uint32_t target, StaticBranchKind kind, std::string_view indent)
|
||||
{
|
||||
const bool isCall = kind == StaticBranchKind::Call;
|
||||
emitRuntimeBranchDispatch(fmt::format("0x{:X}u", target),
|
||||
branchPc(),
|
||||
isCall ? fallthroughPc() : 0u,
|
||||
isCall ? "DirectCall" : "DirectJump",
|
||||
isCall ? "JAL" : "J",
|
||||
indent,
|
||||
true);
|
||||
}
|
||||
|
||||
void ControlFlowEmitter::emitExternalRegisterCallDispatch(std::string_view jumpTargetExpression, std::string_view indent)
|
||||
{
|
||||
emitRuntimeBranchDispatch(jumpTargetExpression,
|
||||
branchPc(),
|
||||
fallthroughPc(),
|
||||
"IndirectCall",
|
||||
"JALR",
|
||||
indent,
|
||||
true);
|
||||
}
|
||||
|
||||
void ControlFlowEmitter::emitExternalRegisterJumpDispatch(std::string_view jumpTargetExpression,
|
||||
RegisterBranchKind kind,
|
||||
uint8_t rsReg,
|
||||
std::string_view indent)
|
||||
{
|
||||
const bool isReturn = kind == RegisterBranchKind::Jump && rsReg == 31u;
|
||||
|
||||
if (isReturn)
|
||||
{
|
||||
m_ss << indent << "#if defined(PS2X_STRICT_RETURN_DIAGNOSTICS) && PS2X_STRICT_RETURN_DIAGNOSTICS\n";
|
||||
m_ss << indent << "(void)runtime->dispatchGuestBranch(rdram, ctx, " << jumpTargetExpression
|
||||
<< ", 0x" << fmt::format("{:X}", branchPc())
|
||||
<< "u, 0u, PS2Runtime::GuestBranchKind::Return, \"JR $ra\");\n";
|
||||
m_ss << indent << "return;\n";
|
||||
m_ss << indent << "#else\n";
|
||||
m_ss << indent << "ctx->pc = " << jumpTargetExpression << ";\n";
|
||||
m_ss << indent << "return;\n";
|
||||
m_ss << indent << "#endif\n";
|
||||
return;
|
||||
}
|
||||
|
||||
emitRuntimeBranchDispatch(jumpTargetExpression,
|
||||
branchPc(),
|
||||
0u,
|
||||
"IndirectJump",
|
||||
"JR",
|
||||
indent,
|
||||
true);
|
||||
}
|
||||
|
||||
bool ControlFlowEmitter::emitRelocationCallIfAvailable(StaticBranchKind kind, std::string_view indent)
|
||||
{
|
||||
const auto relocIt = m_gen.m_relocationCallNames.find(m_branchInst.address);
|
||||
if (relocIt == m_gen.m_relocationCallNames.end() || relocIt->second.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string_view resolvedSyscallName = ps2_runtime_calls::resolveSyscallName(relocIt->second);
|
||||
const std::string_view resolvedStubName = ps2_runtime_calls::resolveStubName(relocIt->second);
|
||||
if (resolvedSyscallName.empty() && resolvedStubName.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool isSyscall = !resolvedSyscallName.empty();
|
||||
const std::string_view handlerName = isSyscall ? resolvedSyscallName : resolvedStubName;
|
||||
|
||||
m_ss << indent << "{\n";
|
||||
m_ss << indent << " const uint32_t __entryPc = ctx->pc;\n";
|
||||
m_ss << indent << " " << (isSyscall ? "ps2_syscalls::" : "ps2_stubs::")
|
||||
<< handlerName << "(rdram, ctx, runtime);\n";
|
||||
m_ss << indent << " if (ctx->pc == __entryPc) { ctx->pc = getRegU32(ctx, 31); }\n";
|
||||
m_ss << indent << "}\n";
|
||||
|
||||
if (kind == StaticBranchKind::Jump)
|
||||
{
|
||||
m_ss << indent << "return;\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ss << fmt::format("{}if (ctx->pc != 0x{:X}u) {{ return; }}\n", indent, fallthroughPc());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ControlFlowEmitter::emitStaticJump(StaticBranchKind kind)
|
||||
{
|
||||
if (kind == StaticBranchKind::Call)
|
||||
{
|
||||
m_ss << fmt::format(" SET_GPR_U32(ctx, 31, 0x{:X}u);\n", fallthroughPc());
|
||||
}
|
||||
|
||||
emitDelaySlot(" ");
|
||||
|
||||
const uint32_t target = buildAbsoluteJumpTarget(m_branchInst.address, m_branchInst.target);
|
||||
if (isInternalTarget(target))
|
||||
{
|
||||
emitInternalTarget(target, branchPc(), " ");
|
||||
return;
|
||||
}
|
||||
|
||||
m_ss << fmt::format(" ctx->pc = 0x{:X}u;\n", target);
|
||||
|
||||
if (kind == StaticBranchKind::Call && emitRelocationCallIfAvailable(kind, " "))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (emitDirectFunctionJumpIfAvailable(target, kind, " "))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
emitExternalJumpDispatch(target, kind, " ");
|
||||
}
|
||||
|
||||
void ControlFlowEmitter::emitRegisterJump(RegisterBranchKind kind)
|
||||
{
|
||||
const uint8_t rsReg = static_cast<uint8_t>(m_branchInst.rs);
|
||||
const uint8_t rdReg = static_cast<uint8_t>(m_branchInst.rd);
|
||||
const std::vector<uint32_t> sortedInternalTargets = resolvedLocalIndirectTargets();
|
||||
|
||||
m_ss << " {\n";
|
||||
m_ss << " const uint32_t jumpTarget = GPR_U32(ctx, " << static_cast<int>(rsReg) << ");\n";
|
||||
|
||||
if (kind == RegisterBranchKind::Call && rdReg != 0u)
|
||||
{
|
||||
m_ss << fmt::format(" SET_GPR_U32(ctx, {}, 0x{:X}u);\n", rdReg, fallthroughPc());
|
||||
}
|
||||
|
||||
emitDelaySlot(" ");
|
||||
m_ss << " ctx->pc = jumpTarget;\n";
|
||||
|
||||
if (!sortedInternalTargets.empty())
|
||||
{
|
||||
m_ss << " switch (jumpTarget) {\n";
|
||||
for (uint32_t target : sortedInternalTargets)
|
||||
{
|
||||
m_ss << fmt::format(" case 0x{:X}u: goto label_{:x};\n", target, target);
|
||||
}
|
||||
m_ss << " default: break;\n";
|
||||
m_ss << " }\n";
|
||||
}
|
||||
|
||||
if (kind == RegisterBranchKind::Jump)
|
||||
{
|
||||
emitExternalRegisterJumpDispatch("jumpTarget", kind, rsReg, " ");
|
||||
}
|
||||
else
|
||||
{
|
||||
emitExternalRegisterCallDispatch("jumpTarget", " ");
|
||||
}
|
||||
|
||||
m_ss << " }\n";
|
||||
}
|
||||
|
||||
std::string ControlFlowEmitter::conditionalBranchExpression() const
|
||||
{
|
||||
const uint8_t rsReg = static_cast<uint8_t>(m_branchInst.rs);
|
||||
const uint8_t rtReg = static_cast<uint8_t>(m_branchInst.rt);
|
||||
|
||||
switch (m_branchInst.opcode)
|
||||
{
|
||||
case OPCODE_BEQ:
|
||||
case OPCODE_BEQL:
|
||||
return fmt::format("GPR_U64(ctx, {}) == GPR_U64(ctx, {})", rsReg, rtReg);
|
||||
case OPCODE_BNE:
|
||||
case OPCODE_BNEL:
|
||||
return fmt::format("GPR_U64(ctx, {}) != GPR_U64(ctx, {})", rsReg, rtReg);
|
||||
case OPCODE_BLEZ:
|
||||
case OPCODE_BLEZL:
|
||||
return fmt::format("GPR_S32(ctx, {}) <= 0", rsReg);
|
||||
case OPCODE_BGTZ:
|
||||
case OPCODE_BGTZL:
|
||||
return fmt::format("GPR_S32(ctx, {}) > 0", rsReg);
|
||||
case OPCODE_REGIMM:
|
||||
switch (m_branchInst.rt)
|
||||
{
|
||||
case REGIMM_BLTZ:
|
||||
case REGIMM_BLTZL:
|
||||
case REGIMM_BLTZAL:
|
||||
case REGIMM_BLTZALL:
|
||||
return fmt::format("GPR_S32(ctx, {}) < 0", rsReg);
|
||||
case REGIMM_BGEZ:
|
||||
case REGIMM_BGEZL:
|
||||
case REGIMM_BGEZAL:
|
||||
case REGIMM_BGEZALL:
|
||||
return fmt::format("GPR_S32(ctx, {}) >= 0", rsReg);
|
||||
default:
|
||||
return "false";
|
||||
}
|
||||
case OPCODE_COP1:
|
||||
if (m_branchInst.rs == COP1_BC)
|
||||
{
|
||||
const uint8_t bcCond = static_cast<uint8_t>(m_branchInst.rt);
|
||||
return (bcCond == COP1_BC_BCF || bcCond == COP1_BC_BCFL)
|
||||
? "!(ctx->fcr31 & 0x800000)"
|
||||
: "(ctx->fcr31 & 0x800000)";
|
||||
}
|
||||
break;
|
||||
case OPCODE_COP2:
|
||||
if (m_branchInst.rs == COP2_BC)
|
||||
{
|
||||
const uint8_t bcCond = static_cast<uint8_t>(m_branchInst.rt);
|
||||
return (bcCond == COP2_BC_BCF || bcCond == COP2_BC_BCFL)
|
||||
? "!(ctx->vu0_status & 0x1)"
|
||||
: "(ctx->vu0_status & 0x1)";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return "false";
|
||||
}
|
||||
|
||||
uint32_t ControlFlowEmitter::conditionalBranchTarget() const
|
||||
{
|
||||
const int32_t offsetBytes = static_cast<int32_t>(static_cast<int16_t>(m_branchInst.simmediate)) << 2;
|
||||
return static_cast<uint32_t>(static_cast<int64_t>(m_branchInst.address + 4u) +
|
||||
static_cast<int64_t>(offsetBytes));
|
||||
}
|
||||
|
||||
void ControlFlowEmitter::emitConditionalBranch()
|
||||
{
|
||||
const uint32_t target = conditionalBranchTarget();
|
||||
const bool likely = isLikelyBranch();
|
||||
const std::string branchTakenVar = fmt::format("branch_taken_0x{:x}", m_branchInst.address);
|
||||
std::string unconditionalLinkCode;
|
||||
std::string conditionalLinkCode;
|
||||
|
||||
if (m_branchInst.opcode == OPCODE_REGIMM)
|
||||
{
|
||||
if (m_branchInst.rt == REGIMM_BLTZAL || m_branchInst.rt == REGIMM_BGEZAL)
|
||||
{
|
||||
unconditionalLinkCode = fmt::format("SET_GPR_U32(ctx, 31, 0x{:X}u);", fallthroughPc());
|
||||
}
|
||||
else if (m_branchInst.rt == REGIMM_BLTZALL || m_branchInst.rt == REGIMM_BGEZALL)
|
||||
{
|
||||
conditionalLinkCode = fmt::format("SET_GPR_U32(ctx, 31, 0x{:X}u);", fallthroughPc());
|
||||
}
|
||||
}
|
||||
|
||||
m_ss << " {\n";
|
||||
m_ss << " const bool " << branchTakenVar << " = (" << conditionalBranchExpression() << ");\n";
|
||||
|
||||
if (!unconditionalLinkCode.empty())
|
||||
{
|
||||
m_ss << " " << unconditionalLinkCode << "\n";
|
||||
}
|
||||
|
||||
if (likely)
|
||||
{
|
||||
m_ss << " if (" << branchTakenVar << ") {\n";
|
||||
if (!conditionalLinkCode.empty())
|
||||
{
|
||||
m_ss << " " << conditionalLinkCode << "\n";
|
||||
}
|
||||
emitDelaySlot(" ");
|
||||
|
||||
if (isInternalTarget(target))
|
||||
{
|
||||
emitInternalTarget(target, branchPc(), " ");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ss << fmt::format(" ctx->pc = 0x{:X}u;\n", target);
|
||||
m_ss << " return;\n";
|
||||
}
|
||||
m_ss << " }\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!conditionalLinkCode.empty())
|
||||
{
|
||||
m_ss << " if (" << branchTakenVar << ") { " << conditionalLinkCode << " }\n";
|
||||
}
|
||||
|
||||
emitDelaySlot(" ");
|
||||
|
||||
m_ss << " if (" << branchTakenVar << ") {\n";
|
||||
if (isInternalTarget(target))
|
||||
{
|
||||
emitInternalTarget(target, branchPc(), " ");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ss << fmt::format(" ctx->pc = 0x{:X}u;\n", target);
|
||||
m_ss << " return;\n";
|
||||
}
|
||||
m_ss << " }\n";
|
||||
}
|
||||
|
||||
m_ss << " }\n";
|
||||
}
|
||||
|
||||
void ControlFlowEmitter::emitFallbackInstruction()
|
||||
{
|
||||
m_ss << " " << m_gen.translateInstruction(m_branchInst) << "\n";
|
||||
emitDelaySlot(" ");
|
||||
}
|
||||
|
||||
void ControlFlowEmitter::emitFallthroughLabelIfNeeded()
|
||||
{
|
||||
if (isInternalTarget(delayPc()) && !isInternalTarget(fallthroughPc()))
|
||||
{
|
||||
m_ss << fmt::format("label_fallthrough_0x{:x}:\n", branchPc());
|
||||
}
|
||||
}
|
||||
|
||||
void ControlFlowEmitter::emitFinalFallthrough()
|
||||
{
|
||||
m_ss << fmt::format(" ctx->pc = 0x{:X}u;\n", fallthroughPc());
|
||||
}
|
||||
|
||||
std::string ControlFlowEmitter::emit()
|
||||
{
|
||||
(void)m_function;
|
||||
emitResumeFromDelaySlotEntry();
|
||||
m_ss << fmt::format(" ctx->pc = 0x{:X}u;\n", branchPc());
|
||||
|
||||
if (m_branchInst.opcode == OPCODE_J || m_branchInst.opcode == OPCODE_JAL)
|
||||
{
|
||||
emitStaticJump(m_branchInst.opcode == OPCODE_JAL ? StaticBranchKind::Call : StaticBranchKind::Jump);
|
||||
}
|
||||
else if (m_branchInst.opcode == OPCODE_SPECIAL &&
|
||||
(m_branchInst.function == SPECIAL_JR || m_branchInst.function == SPECIAL_JALR))
|
||||
{
|
||||
emitRegisterJump(m_branchInst.function == SPECIAL_JALR ? RegisterBranchKind::Call : RegisterBranchKind::Jump);
|
||||
}
|
||||
else if (m_branchInst.isBranch)
|
||||
{
|
||||
emitConditionalBranch();
|
||||
}
|
||||
else
|
||||
{
|
||||
emitFallbackInstruction();
|
||||
}
|
||||
|
||||
emitFallthroughLabelIfNeeded();
|
||||
emitFinalFallthrough();
|
||||
return m_ss.str();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
#include "ps2recomp/Translators/cop0_translator.h"
|
||||
#include "ps2recomp/code_generator.h"
|
||||
#include "ps2recomp/codegen_helpers.h"
|
||||
#include "ps2recomp/instructions.h"
|
||||
#include "ps2recomp/types.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <sstream>
|
||||
#include <cmath>
|
||||
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
Cop0Translator::Cop0Translator(CodeGenerator &codeGenerator)
|
||||
: m_codeGenerator(codeGenerator)
|
||||
{
|
||||
}
|
||||
|
||||
std::string Cop0Translator::translate(const Instruction &inst)
|
||||
{
|
||||
uint32_t format = inst.rs; // Format field
|
||||
uint32_t rt = inst.rt; // GPR register
|
||||
uint32_t rd = inst.rd; // COP0 register
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case COP0_MF:
|
||||
switch (rd)
|
||||
{
|
||||
case COP0_REG_INDEX:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_index);", rt);
|
||||
case COP0_REG_RANDOM:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_random);", rt);
|
||||
case COP0_REG_ENTRYLO0:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_entrylo0);", rt);
|
||||
case COP0_REG_ENTRYLO1:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_entrylo1);", rt);
|
||||
case COP0_REG_CONTEXT:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_context);", rt);
|
||||
case COP0_REG_PAGEMASK:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_pagemask);", rt);
|
||||
case COP0_REG_WIRED:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_wired);", rt);
|
||||
case COP0_REG_BADVADDR:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_badvaddr);", rt);
|
||||
case COP0_REG_COUNT:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_count);", rt);
|
||||
case COP0_REG_ENTRYHI:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_entryhi);", rt);
|
||||
case COP0_REG_COMPARE:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_compare);", rt);
|
||||
case COP0_REG_STATUS:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_status);", rt);
|
||||
case COP0_REG_CAUSE:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_cause);", rt);
|
||||
case COP0_REG_EPC:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_epc);", rt);
|
||||
case COP0_REG_PRID:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_prid);", rt);
|
||||
case COP0_REG_CONFIG:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_config);", rt);
|
||||
case COP0_REG_BADPADDR:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_badpaddr);", rt);
|
||||
case COP0_REG_DEBUG:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_debug);", rt);
|
||||
case COP0_REG_PERF:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_perf);", rt);
|
||||
case COP0_REG_TAGLO:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_taglo);", rt);
|
||||
case COP0_REG_TAGHI:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_taghi);", rt);
|
||||
case COP0_REG_ERROREPC:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ctx->cop0_errorepc);", rt);
|
||||
default:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, 0); // Unimplemented COP0 register {}", rt, rd);
|
||||
}
|
||||
case COP0_MT:
|
||||
switch (rd)
|
||||
{
|
||||
case COP0_REG_INDEX:
|
||||
return fmt::format("ctx->cop0_index = GPR_U32(ctx, {}) & 0x3F;", rt);
|
||||
case COP0_REG_RANDOM:
|
||||
return "// MTC0 to RANDOM register ignored (read-only)";
|
||||
case COP0_REG_ENTRYLO0:
|
||||
return fmt::format("ctx->cop0_entrylo0 = GPR_U32(ctx, {}) & 0x3FFFFFFF;", rt);
|
||||
case COP0_REG_ENTRYLO1:
|
||||
return fmt::format("ctx->cop0_entrylo1 = GPR_U32(ctx, {}) & 0x3FFFFFFF;", rt);
|
||||
case COP0_REG_CONTEXT:
|
||||
return fmt::format("ctx->cop0_context = (ctx->cop0_context & 0xFF800000) | (GPR_U32(ctx, {}) & 0x7FFFFF);", rt);
|
||||
case COP0_REG_PAGEMASK:
|
||||
return fmt::format("ctx->cop0_pagemask = GPR_U32(ctx, {}) & 0x01FFE000;", rt);
|
||||
case COP0_REG_WIRED:
|
||||
return fmt::format("ctx->cop0_wired = GPR_U32(ctx, {}) & 0x3F; ctx->cop0_random = 47;", rt);
|
||||
case COP0_REG_BADVADDR:
|
||||
return "// MTC0 to BADVADDR register ignored (read-only)";
|
||||
case COP0_REG_COUNT:
|
||||
return fmt::format("ctx->cop0_count = GPR_U32(ctx, {});", rt);
|
||||
case COP0_REG_ENTRYHI:
|
||||
return fmt::format("ctx->cop0_entryhi = GPR_U32(ctx, {}) & 0xC00000FF;", rt);
|
||||
case COP0_REG_COMPARE:
|
||||
return fmt::format("ctx->cop0_compare = GPR_U32(ctx, {}); ctx->cop0_cause &= ~0x8000;", rt);
|
||||
case COP0_REG_STATUS:
|
||||
return fmt::format("ctx->cop0_status = GPR_U32(ctx, {}) & 0xFF57FFFF;", rt);
|
||||
case COP0_REG_CAUSE:
|
||||
return fmt::format("ctx->cop0_cause = (ctx->cop0_cause & ~0x00000300) | (GPR_U32(ctx, {}) & 0x00000300);", rt);
|
||||
case COP0_REG_EPC:
|
||||
return fmt::format("ctx->cop0_epc = GPR_U32(ctx, {});", rt);
|
||||
case COP0_REG_PRID:
|
||||
return "// MTC0 to PRID register ignored (read-only)";
|
||||
case COP0_REG_CONFIG:
|
||||
return fmt::format("ctx->cop0_config = (ctx->cop0_config & ~0x7) | (GPR_U32(ctx, {}) & 0x7);", rt);
|
||||
case COP0_REG_BADPADDR:
|
||||
return "// MTC0 to BADPADDR register ignored (read-only)";
|
||||
case COP0_REG_DEBUG:
|
||||
return fmt::format("ctx->cop0_debug = GPR_U32(ctx, {});", rt);
|
||||
case COP0_REG_PERF:
|
||||
return fmt::format("ctx->cop0_perf = GPR_U32(ctx, {});", rt);
|
||||
case COP0_REG_TAGLO:
|
||||
return fmt::format("ctx->cop0_taglo = GPR_U32(ctx, {});", rt);
|
||||
case COP0_REG_TAGHI:
|
||||
return fmt::format("ctx->cop0_taghi = GPR_U32(ctx, {});", rt);
|
||||
case COP0_REG_ERROREPC:
|
||||
return fmt::format("ctx->cop0_errorepc = GPR_U32(ctx, {});", rt);
|
||||
default:
|
||||
return fmt::format("// Unimplemented MTC0 to COP0 {}", rd);
|
||||
}
|
||||
case COP0_BC:
|
||||
return fmt::format("// BC0 (Condition: 0x{:X}) - Handled by branch logic", rt);
|
||||
case COP0_CO:
|
||||
{
|
||||
uint8_t function = FUNCTION(inst.raw);
|
||||
switch (function)
|
||||
{
|
||||
case COP0_CO_TLBR:
|
||||
return fmt::format("runtime->handleTLBR(rdram, ctx);");
|
||||
case COP0_CO_TLBWI:
|
||||
return fmt::format("runtime->handleTLBWI(rdram, ctx);");
|
||||
case COP0_CO_TLBWR:
|
||||
return fmt::format("runtime->handleTLBWR(rdram, ctx);");
|
||||
case COP0_CO_TLBP:
|
||||
return fmt::format("runtime->handleTLBP(rdram, ctx);");
|
||||
case COP0_CO_ERET:
|
||||
return fmt::format(
|
||||
"if (ctx->cop0_status & 0x4) {{ \n" // Check ERL bit (bit 2)
|
||||
" ctx->pc = ctx->cop0_errorepc; \n"
|
||||
" ctx->cop0_status &= ~0x4; \n" // Clear ERL bit
|
||||
"}} else {{ \n" // If ERL is not set, use EPC and clear EXL (bit 1)
|
||||
" ctx->pc = ctx->cop0_epc; \n" // Note: If neither ERL/EXL set, behavior is undefined; using EPC is common.
|
||||
" ctx->cop0_status &= ~0x2; \n" // Clear EXL bit
|
||||
"}} \n"
|
||||
"runtime->clearLLBit(ctx); \n" // Essential: Clear Load-Linked bit
|
||||
"return;" // Stop execution in this recompiled block
|
||||
);
|
||||
case COP0_CO_EI:
|
||||
return fmt::format("ctx->cop0_status |= 0x10000; // Enable interrupts");
|
||||
case COP0_CO_DI:
|
||||
return fmt::format("ctx->cop0_status &= ~0x10000; // Disable interrupts");
|
||||
default:
|
||||
return m_codeGenerator.emitUnhandledInstruction(inst, fmt::format("Unhandled COP0 CO-OP: 0x{:X}", function));
|
||||
}
|
||||
}
|
||||
default:
|
||||
return m_codeGenerator.emitUnhandledInstruction(inst, fmt::format("Unhandled COP0 instruction format: 0x{:X}", format));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "ps2recomp/elf_parser.h"
|
||||
#include "ps2recomp/recompiler_reporter.h"
|
||||
#include "ps2recomp/types.h"
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
@@ -950,6 +951,11 @@ namespace ps2recomp
|
||||
return static_cast<uint32_t>(m_elf->get_entry());
|
||||
}
|
||||
|
||||
void ElfParser::setReporter(RecompilerReporter *reporter)
|
||||
{
|
||||
m_reporter = reporter;
|
||||
}
|
||||
|
||||
bool ElfParser::loadGhidraFunctionMap(const std::string &mapPath)
|
||||
{
|
||||
if (mapPath.empty())
|
||||
@@ -963,7 +969,10 @@ namespace ps2recomp
|
||||
std::ifstream file(mapPath);
|
||||
if (!file.is_open())
|
||||
{
|
||||
std::cerr << "Warning: Could not open Ghidra function map: " << mapPath << std::endl;
|
||||
if (m_reporter)
|
||||
{
|
||||
m_reporter->warning("ghidra-map", "Could not open Ghidra function map: " + mapPath);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1034,16 +1043,23 @@ namespace ps2recomp
|
||||
{
|
||||
m_hasLoadedGhidraMap = true;
|
||||
m_ghidraMapStarts = mapStarts;
|
||||
std::cout << "Loaded " << count << " functions from Ghidra map" << std::endl;
|
||||
if (m_reporter)
|
||||
{
|
||||
m_reporter->info("ghidra-map", "Loaded " + std::to_string(count) + " functions from Ghidra map");
|
||||
}
|
||||
if (skippedNonExecutable > 0)
|
||||
{
|
||||
std::cout << "Ignored " << skippedNonExecutable
|
||||
<< " Ghidra function(s) outside executable sections." << std::endl;
|
||||
if (m_reporter)
|
||||
{
|
||||
m_reporter->warning("ghidra-map", "Ignored " + std::to_string(skippedNonExecutable) + " Ghidra function(s) outside executable sections.");
|
||||
}
|
||||
}
|
||||
if (skippedInvalidRange > 0)
|
||||
{
|
||||
std::cout << "Ignored " << skippedInvalidRange
|
||||
<< " Ghidra function(s) with invalid ranges after section clamping." << std::endl;
|
||||
if (m_reporter)
|
||||
{
|
||||
m_reporter->warning("ghidra-map", "Ignored " + std::to_string(skippedInvalidRange) + " Ghidra function(s) with invalid ranges after section clamping.");
|
||||
}
|
||||
}
|
||||
|
||||
m_extraFunctions.erase(
|
||||
@@ -1095,9 +1111,12 @@ namespace ps2recomp
|
||||
|
||||
if (skippedNonExecutable > 0 || skippedInvalidRange > 0)
|
||||
{
|
||||
std::cout << "Loaded 0 functions from Ghidra map after filtering ("
|
||||
<< skippedNonExecutable << " non-executable, "
|
||||
<< skippedInvalidRange << " invalid range)." << std::endl;
|
||||
if (m_reporter)
|
||||
{
|
||||
m_reporter->warning("ghidra-map", "Loaded 0 functions from Ghidra map after filtering (" +
|
||||
std::to_string(skippedNonExecutable) + " non-executable, " +
|
||||
std::to_string(skippedInvalidRange) + " invalid range).");
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -1109,14 +1128,20 @@ namespace ps2recomp
|
||||
{
|
||||
if (!m_elf->load(m_filePath))
|
||||
{
|
||||
std::cerr << "Error: Could not load ELF file: " << m_filePath << std::endl;
|
||||
if (m_reporter)
|
||||
{
|
||||
m_reporter->error("elf", "Could not load ELF file: " + m_filePath);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if this is a PS2 ELF (MIPS R5900)
|
||||
if (m_elf->get_machine() != ELFIO::EM_MIPS)
|
||||
{
|
||||
std::cerr << "Error: Not a MIPS ELF file" << std::endl;
|
||||
if (m_reporter)
|
||||
{
|
||||
m_reporter->error("elf", "Not a MIPS ELF file");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1165,8 +1190,11 @@ namespace ps2recomp
|
||||
AppendLoadSegmentsAsSections(*m_elf, m_sections);
|
||||
if (!m_sections.empty())
|
||||
{
|
||||
std::cout << "Info: ELF has no section headers; using loadable segments as sections ("
|
||||
<< m_sections.size() << " entries)." << std::endl;
|
||||
if (m_reporter)
|
||||
{
|
||||
m_reporter->info("elf", "ELF has no section headers; using loadable segments as sections (" +
|
||||
std::to_string(m_sections.size()) + " entries).");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1183,7 +1211,10 @@ namespace ps2recomp
|
||||
{
|
||||
if (psec->get_link() >= m_elf->sections.size())
|
||||
{
|
||||
std::cerr << "Warning: Symbol section link out of bounds: " << psec->get_link() << std::endl;
|
||||
if (m_reporter)
|
||||
{
|
||||
m_reporter->warning("elf", "Symbol section link out of bounds: " + std::to_string(psec->get_link()));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1238,7 +1269,10 @@ namespace ps2recomp
|
||||
{
|
||||
if (psec->get_link() >= m_elf->sections.size())
|
||||
{
|
||||
std::cout << "Warning: Relocation section link out of bounds: " << psec->get_link() << std::endl;
|
||||
if (m_reporter)
|
||||
{
|
||||
m_reporter->warning("elf", "Relocation section link out of bounds: " + std::to_string(psec->get_link()));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1248,7 +1282,10 @@ namespace ps2recomp
|
||||
|
||||
if (symSec->get_link() >= m_elf->sections.size())
|
||||
{
|
||||
std::cout << "Warning: Symbol section link out of bounds (in relocation): " << symSec->get_link() << std::endl;
|
||||
if (m_reporter)
|
||||
{
|
||||
m_reporter->warning("elf", "Symbol section link out of bounds (in relocation): " + std::to_string(symSec->get_link()));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
#include "ps2recomp/Translators/fpu_translator.h"
|
||||
#include "ps2recomp/code_generator.h"
|
||||
#include "ps2recomp/codegen_helpers.h"
|
||||
#include "ps2recomp/instructions.h"
|
||||
#include "ps2recomp/types.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <sstream>
|
||||
#include <cmath>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
FpuTranslator::FpuTranslator(CodeGenerator &codeGenerator)
|
||||
: m_codeGenerator(codeGenerator)
|
||||
{
|
||||
}
|
||||
|
||||
std::string FpuTranslator::translate(const Instruction &inst)
|
||||
{
|
||||
uint8_t format = inst.rs; // Format field
|
||||
uint32_t ft = inst.rt; // FPU source register
|
||||
uint32_t fs = inst.rd; // FPU source register
|
||||
uint32_t fd = inst.sa; // FPU destination register
|
||||
uint32_t function = inst.function;
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case COP1_MF:
|
||||
return fmt::format("{{ uint32_t bits; std::memcpy(&bits, &ctx->f[{}], sizeof(bits)); SET_GPR_U32(ctx, {}, bits); }}", fs, ft);
|
||||
case COP1_MT:
|
||||
return fmt::format("{{ uint32_t bits = GPR_U32(ctx, {}); std::memcpy(&ctx->f[{}], &bits, sizeof(bits)); }}", ft, fs);
|
||||
case COP1_CF:
|
||||
if (fs == 31)
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, ctx->fcr31);", ft); // FCR31 contains status/control
|
||||
if (fs == 0)
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, 0x00000000);", ft); // FCR0 is the FPU implementation register
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, 0); // Unimplemented FCR{}", ft, fs);
|
||||
case COP1_CT:
|
||||
if (fs == 31)
|
||||
return fmt::format("ctx->fcr31 = GPR_U32(ctx, {}) & 0x0183FFFF;", ft);
|
||||
else
|
||||
return fmt::format("// CTC1 to FCR{} ignored", fs);
|
||||
return "";
|
||||
case COP1_BC:
|
||||
return "// FPU branch instruction - handled elsewhere";
|
||||
case COP1_S:
|
||||
switch (function)
|
||||
{
|
||||
case COP1_S_ADD:
|
||||
return fmt::format("ctx->f[{}] = FPU_ADD_S(ctx->f[{}], ctx->f[{}]);", fd, fs, ft);
|
||||
case COP1_S_SUB:
|
||||
return fmt::format("ctx->f[{}] = FPU_SUB_S(ctx->f[{}], ctx->f[{}]);", fd, fs, ft);
|
||||
case COP1_S_MUL:
|
||||
return fmt::format("ctx->f[{}] = FPU_MUL_S(ctx->f[{}], ctx->f[{}]);", fd, fs, ft);
|
||||
case COP1_S_DIV:
|
||||
return fmt::format("if (ctx->f[{}] == 0.0f) {{ ctx->fcr31 |= 0x100000; /* DZ flag */ "
|
||||
"ctx->f[{}] = copysignf(INFINITY, ctx->f[{}] * 0.0f); }} "
|
||||
"else ctx->f[{}] = ctx->f[{}] / ctx->f[{}];",
|
||||
ft, fd, fs, fd, fs, ft);
|
||||
case COP1_S_SQRT:
|
||||
return fmt::format("ctx->f[{}] = FPU_SQRT_S(ctx->f[{}]);", fd, fs);
|
||||
case COP1_S_ABS:
|
||||
return fmt::format("ctx->f[{}] = FPU_ABS_S(ctx->f[{}]);", fd, fs);
|
||||
case COP1_S_MOV:
|
||||
return fmt::format("ctx->f[{}] = FPU_MOV_S(ctx->f[{}]);", fd, fs);
|
||||
case COP1_S_NEG:
|
||||
return fmt::format("ctx->f[{}] = FPU_NEG_S(ctx->f[{}]);", fd, fs);
|
||||
case COP1_S_ROUND_W:
|
||||
return fmt::format("{{ int32_t tmp = FPU_ROUND_W_S(ctx->f[{}]); std::memcpy(&ctx->f[{}], &tmp, sizeof(tmp)); }}", fs, fd);
|
||||
case COP1_S_TRUNC_W:
|
||||
return fmt::format("{{ int32_t tmp = FPU_TRUNC_W_S(ctx->f[{}]); std::memcpy(&ctx->f[{}], &tmp, sizeof(tmp)); }}", fs, fd);
|
||||
case COP1_S_CEIL_W:
|
||||
return fmt::format("{{ int32_t tmp = FPU_CEIL_W_S(ctx->f[{}]); std::memcpy(&ctx->f[{}], &tmp, sizeof(tmp)); }}", fs, fd);
|
||||
case COP1_S_FLOOR_W:
|
||||
return fmt::format("{{ int32_t tmp = FPU_FLOOR_W_S(ctx->f[{}]); std::memcpy(&ctx->f[{}], &tmp, sizeof(tmp)); }}", fs, fd);
|
||||
case COP1_S_CVT_W:
|
||||
return fmt::format("{{ int32_t tmp = FPU_CVT_W_S(ctx->f[{}]); std::memcpy(&ctx->f[{}], &tmp, sizeof(tmp)); }}", fs, fd);
|
||||
case COP1_S_RSQRT:
|
||||
return fmt::format("ctx->f[{}] = 1.0f / sqrtf(ctx->f[{}]);", fd, fs);
|
||||
case COP1_S_ADDA:
|
||||
return fmt::format("FPU_SET_ACC(ctx, FPU_ADD_S(ctx->f[{}], ctx->f[{}]));", fs, ft);
|
||||
case COP1_S_SUBA:
|
||||
return fmt::format("FPU_SET_ACC(ctx, FPU_SUB_S(ctx->f[{}], ctx->f[{}]));", fs, ft);
|
||||
case COP1_S_MULA:
|
||||
return fmt::format("FPU_SET_ACC(ctx, FPU_MUL_S(ctx->f[{}], ctx->f[{}]));", fs, ft);
|
||||
case COP1_S_MADD:
|
||||
return fmt::format("ctx->f[{}] = FPU_ADD_S(ctx->f_acc, FPU_MUL_S(ctx->f[{}], ctx->f[{}]));", fd, fs, ft);
|
||||
case COP1_S_MSUB:
|
||||
return fmt::format("ctx->f[{}] = FPU_SUB_S(ctx->f_acc, FPU_MUL_S(ctx->f[{}], ctx->f[{}]));", fd, fs, ft);
|
||||
case COP1_S_MADDA:
|
||||
return fmt::format("FPU_SET_ACC(ctx, FPU_ADD_S(ctx->f_acc, FPU_MUL_S(ctx->f[{}], ctx->f[{}])));", fs, ft);
|
||||
case COP1_S_MSUBA:
|
||||
return fmt::format("FPU_SET_ACC(ctx, FPU_SUB_S(ctx->f_acc, FPU_MUL_S(ctx->f[{}], ctx->f[{}])));", fs, ft);
|
||||
case COP1_S_MAX:
|
||||
return fmt::format("ctx->f[{}] = std::max(ctx->f[{}], ctx->f[{}]);", fd, fs, ft);
|
||||
case COP1_S_MIN:
|
||||
return fmt::format("ctx->f[{}] = std::min(ctx->f[{}], ctx->f[{}]);", fd, fs, ft);
|
||||
case COP1_S_C_F:
|
||||
return fmt::format("ctx->fcr31 &= ~0x800000;");
|
||||
case COP1_S_C_UN:
|
||||
return fmt::format("ctx->fcr31 = (FPU_C_UN_S(ctx->f[{}], ctx->f[{}])) ? (ctx->fcr31 | 0x800000) : (ctx->fcr31 & ~0x800000);", fs, ft);
|
||||
case COP1_S_C_EQ:
|
||||
return fmt::format("ctx->fcr31 = (FPU_C_EQ_S(ctx->f[{}], ctx->f[{}])) ? (ctx->fcr31 | 0x800000) : (ctx->fcr31 & ~0x800000);", fs, ft);
|
||||
case COP1_S_C_UEQ:
|
||||
return fmt::format("ctx->fcr31 = (FPU_C_UEQ_S(ctx->f[{}], ctx->f[{}])) ? (ctx->fcr31 | 0x800000) : (ctx->fcr31 & ~0x800000);", fs, ft);
|
||||
case COP1_S_C_OLT:
|
||||
return fmt::format("ctx->fcr31 = (FPU_C_OLT_S(ctx->f[{}], ctx->f[{}])) ? (ctx->fcr31 | 0x800000) : (ctx->fcr31 & ~0x800000);", fs, ft);
|
||||
case COP1_S_C_ULT:
|
||||
return fmt::format("ctx->fcr31 = (FPU_C_ULT_S(ctx->f[{}], ctx->f[{}])) ? (ctx->fcr31 | 0x800000) : (ctx->fcr31 & ~0x800000);", fs, ft);
|
||||
case COP1_S_C_OLE:
|
||||
return fmt::format("ctx->fcr31 = (FPU_C_OLE_S(ctx->f[{}], ctx->f[{}])) ? (ctx->fcr31 | 0x800000) : (ctx->fcr31 & ~0x800000);", fs, ft);
|
||||
case COP1_S_C_ULE:
|
||||
return fmt::format("ctx->fcr31 = (FPU_C_ULE_S(ctx->f[{}], ctx->f[{}])) ? (ctx->fcr31 | 0x800000) : (ctx->fcr31 & ~0x800000);", fs, ft);
|
||||
case COP1_S_C_SF:
|
||||
return fmt::format("ctx->fcr31 &= ~0x800000;");
|
||||
case COP1_S_C_NGLE:
|
||||
return fmt::format("ctx->fcr31 = (FPU_C_NGLE_S(ctx->f[{}], ctx->f[{}])) ? (ctx->fcr31 | 0x800000) : (ctx->fcr31 & ~0x800000);", fs, ft);
|
||||
case COP1_S_C_SEQ:
|
||||
return fmt::format("ctx->fcr31 = (FPU_C_SEQ_S(ctx->f[{}], ctx->f[{}])) ? (ctx->fcr31 | 0x800000) : (ctx->fcr31 & ~0x800000);", fs, ft);
|
||||
case COP1_S_C_NGL:
|
||||
return fmt::format("ctx->fcr31 = (FPU_C_NGL_S(ctx->f[{}], ctx->f[{}])) ? (ctx->fcr31 | 0x800000) : (ctx->fcr31 & ~0x800000);", fs, ft);
|
||||
case COP1_S_C_LT:
|
||||
return fmt::format("ctx->fcr31 = (FPU_C_LT_S(ctx->f[{}], ctx->f[{}])) ? (ctx->fcr31 | 0x800000) : (ctx->fcr31 & ~0x800000);", fs, ft);
|
||||
case COP1_S_C_NGE:
|
||||
return fmt::format("ctx->fcr31 = (FPU_C_NGE_S(ctx->f[{}], ctx->f[{}])) ? (ctx->fcr31 | 0x800000) : (ctx->fcr31 & ~0x800000);", fs, ft);
|
||||
case COP1_S_C_LE:
|
||||
return fmt::format("ctx->fcr31 = (FPU_C_LE_S(ctx->f[{}], ctx->f[{}])) ? (ctx->fcr31 | 0x800000) : (ctx->fcr31 & ~0x800000);", fs, ft);
|
||||
case COP1_S_C_NGT:
|
||||
return fmt::format("ctx->fcr31 = (FPU_C_NGT_S(ctx->f[{}], ctx->f[{}])) ? (ctx->fcr31 | 0x800000) : (ctx->fcr31 & ~0x800000);", fs, ft);
|
||||
default:
|
||||
return m_codeGenerator.emitUnhandledInstruction(inst, fmt::format("Unhandled FPU.S instruction: function 0x{:X}", function));
|
||||
}
|
||||
case COP1_W:
|
||||
switch (function)
|
||||
{
|
||||
case COP1_W_CVT_S:
|
||||
return fmt::format("{{ int32_t tmp; std::memcpy(&tmp, &ctx->f[{}], sizeof(tmp)); ctx->f[{}] = FPU_CVT_S_W(tmp); }}", fs, fd);
|
||||
default:
|
||||
return m_codeGenerator.emitUnhandledInstruction(inst, fmt::format("Unhandled FPU.W instruction: function 0x{:X}", function));
|
||||
}
|
||||
default:
|
||||
return m_codeGenerator.emitUnhandledInstruction(inst, fmt::format("Unhandled FPU instruction: format 0x{:X}, function 0x{:X}", format, function));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
#include "ps2recomp/Emitters/function_emitter.h"
|
||||
#include "ps2recomp/code_generator.h"
|
||||
#include "ps2recomp/instructions.h"
|
||||
#include "ps2recomp/r5900_decoder.h"
|
||||
#include "ps2recomp/recompiler_reporter.h"
|
||||
#include "ps2recomp/types.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
namespace
|
||||
{
|
||||
Instruction makeSyntheticDelaySlot(uint32_t address)
|
||||
{
|
||||
Instruction inst{};
|
||||
inst.address = address;
|
||||
inst.raw = 0;
|
||||
inst.opcode = OPCODE_SPECIAL;
|
||||
inst.function = SPECIAL_SLL;
|
||||
return inst;
|
||||
}
|
||||
}
|
||||
|
||||
FunctionEmitter::FunctionEmitter(CodeGenerator &codeGenerator)
|
||||
: m_codeGenerator(codeGenerator)
|
||||
{
|
||||
}
|
||||
|
||||
std::string FunctionEmitter::emit(
|
||||
const Function &function,
|
||||
const std::vector<Instruction> &instructions,
|
||||
bool useHeaders)
|
||||
{
|
||||
CodeGenerator &cg = m_codeGenerator;
|
||||
std::stringstream ss;
|
||||
cg.m_currentFunctionName = function.name;
|
||||
|
||||
if (useHeaders)
|
||||
{
|
||||
ss << "#include <stdexcept>\n";
|
||||
ss << "#include \"ps2_runtime_macros.h\"\n";
|
||||
ss << "#include \"ps2_runtime.h\"\n";
|
||||
ss << "#include \"ps2_recompiled_functions.h\"\n";
|
||||
ss << "#include \"ps2_recompiled_stubs.h\"\n\n";
|
||||
ss << "#include \"ps2_syscalls.h\"\n";
|
||||
ss << "#include \"ps2_stubs.h\"\n\n";
|
||||
ss << "#ifdef PS2_FUNCTION_LOG_TRACKER\n";
|
||||
ss << "#include \"ps2_log.h\"\n";
|
||||
ss << "#endif\n\n";
|
||||
}
|
||||
|
||||
CodeGenerator::AnalysisResult analysisResult = cg.collectInternalBranchTargets(function, instructions);
|
||||
std::vector<uint32_t> resumeTargets(analysisResult.resumeEntryPoints.begin(),
|
||||
analysisResult.resumeEntryPoints.end());
|
||||
auto resumeIt = cg.m_resumeEntryTargetsByOwner.find(function.start);
|
||||
if (resumeIt != cg.m_resumeEntryTargetsByOwner.end())
|
||||
{
|
||||
resumeTargets.insert(resumeTargets.end(), resumeIt->second.begin(), resumeIt->second.end());
|
||||
}
|
||||
std::sort(resumeTargets.begin(), resumeTargets.end());
|
||||
resumeTargets.erase(std::unique(resumeTargets.begin(), resumeTargets.end()), resumeTargets.end());
|
||||
for (uint32_t target : resumeTargets)
|
||||
{
|
||||
analysisResult.entryPoints.insert(target);
|
||||
}
|
||||
|
||||
const std::unordered_set<uint32_t> &internalTargets = analysisResult.entryPoints;
|
||||
ss << "// Function: " << function.name << "\n";
|
||||
ss << "// Address: 0x" << std::hex << function.start << " - 0x" << function.end << std::dec << "\n";
|
||||
|
||||
std::string sanitizedName = cg.getFunctionName(function.start);
|
||||
if (sanitizedName.empty())
|
||||
{
|
||||
std::stringstream nameBuilder;
|
||||
nameBuilder << "Errorfunc_" << std::hex << function.start;
|
||||
sanitizedName = nameBuilder.str();
|
||||
}
|
||||
|
||||
ss << "void " << sanitizedName << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) {\n";
|
||||
ss << "#ifdef PS2_FUNCTION_LOG_TRACKER\n";
|
||||
ss << " PS_LOG_ENTRY(\"" << sanitizedName << "\");\n";
|
||||
ss << "#endif\n";
|
||||
ss << "\n";
|
||||
if (!resumeTargets.empty())
|
||||
{
|
||||
ss << " switch (ctx->pc) {\n";
|
||||
for (uint32_t target : resumeTargets)
|
||||
{
|
||||
ss << " case 0x" << std::hex << target << "u: goto label_" << target << ";\n"
|
||||
<< std::dec;
|
||||
}
|
||||
ss << " default: break;\n";
|
||||
ss << " }\n\n";
|
||||
}
|
||||
ss << " ctx->pc = 0x" << std::hex << function.start << "u;\n"
|
||||
<< std::dec;
|
||||
ss << "\n";
|
||||
|
||||
for (size_t i = 0; i < instructions.size(); ++i)
|
||||
{
|
||||
const Instruction &inst = instructions[i];
|
||||
|
||||
if (internalTargets.contains(inst.address))
|
||||
{
|
||||
ss << "label_" << std::hex << inst.address << std::dec << ":\n";
|
||||
}
|
||||
|
||||
if (cg.m_emitInstructionComments)
|
||||
{
|
||||
ss << " // 0x" << std::hex << inst.address << ": 0x" << inst.raw << std::dec;
|
||||
std::string disassembly = R5900Decoder::disassembleInstruction(inst);
|
||||
if (!disassembly.empty())
|
||||
{
|
||||
ss << " " << disassembly;
|
||||
}
|
||||
ss << "\n";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (inst.hasDelaySlot)
|
||||
{
|
||||
const bool hasDecodedDelaySlot =
|
||||
i + 1 < instructions.size() &&
|
||||
instructions[i + 1].address == inst.address + 4u;
|
||||
|
||||
Instruction syntheticDelaySlot{};
|
||||
const Instruction *delaySlot = nullptr;
|
||||
if (hasDecodedDelaySlot)
|
||||
{
|
||||
delaySlot = &instructions[i + 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
syntheticDelaySlot = makeSyntheticDelaySlot(inst.address + 4u);
|
||||
delaySlot = &syntheticDelaySlot;
|
||||
}
|
||||
|
||||
if (hasDecodedDelaySlot && internalTargets.contains(delaySlot->address))
|
||||
{
|
||||
ss << "label_" << std::hex << delaySlot->address << std::dec << ":\n";
|
||||
}
|
||||
|
||||
ss << cg.handleBranchDelaySlots(inst, *delaySlot, function, analysisResult);
|
||||
|
||||
if (hasDecodedDelaySlot)
|
||||
{
|
||||
++i; // Skip delay slot instruction (handled inside branch logic)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << " ctx->pc = 0x" << std::hex << inst.address << "u;\n"
|
||||
<< std::dec;
|
||||
|
||||
ss << " " << cg.translateInstruction(inst);
|
||||
if (inst.isMmio)
|
||||
{
|
||||
ss << " // MMIO: 0x" << std::hex << inst.mmioAddress << std::dec;
|
||||
}
|
||||
ss << "\n";
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
if (cg.m_reporter)
|
||||
{
|
||||
std::ostringstream msg;
|
||||
msg << "translation failed: " << e.what() << " raw=0x" << std::hex << inst.raw;
|
||||
cg.m_reporter->errorAt("codegen", function.name, inst.address, msg.str());
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
ss << "}\n";
|
||||
return ss.str();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
#include "ps2recomp/Emitters/function_table_emitter.h"
|
||||
#include "ps2recomp/code_generator.h"
|
||||
#include "ps2recomp/ps2_recompiler.h"
|
||||
#include "ps2recomp/recompiler_reporter.h"
|
||||
#include "ps2recomp/types.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
FunctionTableEmitter::FunctionTableEmitter(CodeGenerator &codeGenerator)
|
||||
: m_codeGenerator(codeGenerator)
|
||||
{
|
||||
}
|
||||
|
||||
std::string FunctionTableEmitter::emit(const std::vector<Function> &functions, const std::map<uint32_t, std::string> &stubs)
|
||||
{
|
||||
(void)stubs;
|
||||
|
||||
CodeGenerator &cg = m_codeGenerator;
|
||||
std::vector<std::pair<uint32_t, std::string>> entries;
|
||||
std::unordered_set<uint32_t> registeredAddresses;
|
||||
|
||||
auto addEntry = [&](uint32_t address, const std::string &name)
|
||||
{
|
||||
if (name.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if ((address & 3u) != 0u)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "Unaligned function table entry for " << name << " at 0x" << std::hex << address;
|
||||
|
||||
if (cg.m_reporter)
|
||||
{
|
||||
cg.m_reporter->errorAt("function-table", name, address, oss.str());
|
||||
}
|
||||
|
||||
throw std::runtime_error(oss.str());
|
||||
}
|
||||
if (!registeredAddresses.insert(address).second)
|
||||
{
|
||||
return;
|
||||
}
|
||||
entries.emplace_back(address, name);
|
||||
};
|
||||
|
||||
std::vector<std::pair<uint32_t, std::string>> normalFunctions;
|
||||
std::vector<std::pair<uint32_t, std::string>> stubFunctions;
|
||||
std::vector<std::pair<uint32_t, std::string>> systemCallFunctions;
|
||||
std::vector<std::pair<uint32_t, std::string>> libraryFunctions;
|
||||
|
||||
for (const auto &function : functions)
|
||||
{
|
||||
if (!function.isRecompiled && !function.isStub && !function.isSkipped)
|
||||
continue;
|
||||
|
||||
std::string generatedName = cg.getFunctionName(function.start);
|
||||
|
||||
if (function.isSkipped)
|
||||
{
|
||||
libraryFunctions.emplace_back(function.start, generatedName);
|
||||
}
|
||||
else if (function.isStub)
|
||||
{
|
||||
const auto target = PS2Recompiler::resolveStubTarget(function.name);
|
||||
if (target == StubTarget::Syscall)
|
||||
{
|
||||
systemCallFunctions.emplace_back(function.start, generatedName);
|
||||
}
|
||||
else
|
||||
{
|
||||
stubFunctions.emplace_back(function.start, generatedName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
normalFunctions.emplace_back(function.start, generatedName);
|
||||
}
|
||||
}
|
||||
|
||||
if (cg.m_bootstrapInfo.valid)
|
||||
{
|
||||
std::string entryTarget = cg.m_bootstrapInfo.entryName;
|
||||
if (entryTarget.empty())
|
||||
{
|
||||
entryTarget = cg.getFunctionName(cg.m_bootstrapInfo.entry);
|
||||
}
|
||||
if (entryTarget.empty())
|
||||
{
|
||||
throw std::runtime_error("No entry function name available for registration.");
|
||||
}
|
||||
addEntry(cg.m_bootstrapInfo.entry, entryTarget);
|
||||
}
|
||||
|
||||
for (const auto &[address, name] : normalFunctions)
|
||||
{
|
||||
addEntry(address, name);
|
||||
}
|
||||
|
||||
for (const auto &[ownerStart, targets] : cg.m_resumeEntryTargetsByOwner)
|
||||
{
|
||||
const std::string ownerName = cg.getFunctionName(ownerStart);
|
||||
if (ownerName.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (uint32_t target : targets)
|
||||
{
|
||||
addEntry(target, ownerName);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &[address, name] : stubFunctions)
|
||||
{
|
||||
addEntry(address, name);
|
||||
}
|
||||
for (const auto &[address, name] : systemCallFunctions)
|
||||
{
|
||||
addEntry(address, name);
|
||||
}
|
||||
for (const auto &[address, name] : libraryFunctions)
|
||||
{
|
||||
addEntry(address, name);
|
||||
}
|
||||
|
||||
std::sort(entries.begin(), entries.end(), [](const auto &a, const auto &b)
|
||||
{ return a.first < b.first; });
|
||||
|
||||
uint32_t tableBase = 0u;
|
||||
uint32_t tableEnd = 0u;
|
||||
uint32_t slotCount = 0u;
|
||||
if (!entries.empty())
|
||||
{
|
||||
tableBase = entries.front().first & ~3u;
|
||||
tableEnd = (entries.back().first + 4u + 3u) & ~3u;
|
||||
slotCount = (tableEnd - tableBase) >> 2;
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "#include \"ps2_runtime.h\"\n";
|
||||
ss << "#include \"ps2_recompiled_functions.h\"\n";
|
||||
ss << "#include \"ps2_stubs.h\"\n";
|
||||
ss << "#include \"ps2_recompiled_stubs.h\"//this will give duplicated erros because runtime maybe has it define already, just delete the TODOS ones\n";
|
||||
ss << "#include \"ps2_syscalls.h\"\n\n";
|
||||
|
||||
ss << "extern const uint32_t g_ps2RecompiledFunctionTableBase = 0x" << std::hex << tableBase << "u;\n";
|
||||
ss << "extern const uint32_t g_ps2RecompiledFunctionTableEnd = 0x" << std::hex << tableEnd << "u;\n";
|
||||
ss << "extern const uint32_t g_ps2RecompiledFunctionTableSlotCount = " << std::dec << slotCount << "u;\n";
|
||||
ss << "PS2Runtime::RecompiledFunction g_ps2RecompiledFunctionTable[" << std::dec << (slotCount == 0u ? 1u : slotCount) << "u] = {};\n\n";
|
||||
|
||||
ss << "namespace {\n";
|
||||
ss << "struct GeneratedFunctionTableInitializer {\n";
|
||||
ss << " GeneratedFunctionTableInitializer() {\n";
|
||||
for (const auto &[address, name] : entries)
|
||||
{
|
||||
const uint32_t slot = (address - tableBase) >> 2;
|
||||
ss << " g_ps2RecompiledFunctionTable[" << std::dec << slot << "] = " << name
|
||||
<< "; // 0x" << std::hex << address << std::dec << "\n";
|
||||
}
|
||||
ss << " }\n";
|
||||
ss << "};\n";
|
||||
ss << "static const GeneratedFunctionTableInitializer g_generatedFunctionTableInitializer;\n";
|
||||
ss << "}\n";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
#include "ps2recomp/Translators/instruction_translator.h"
|
||||
#include "ps2recomp/code_generator.h"
|
||||
#include "ps2recomp/codegen_helpers.h"
|
||||
#include "ps2recomp/instructions.h"
|
||||
#include "ps2recomp/types.h"
|
||||
#include "ps2recomp/control_flow_utils.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <sstream>
|
||||
#include <cmath>
|
||||
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
InstructionTranslator::InstructionTranslator(CodeGenerator &codeGenerator)
|
||||
: m_codeGenerator(codeGenerator)
|
||||
{
|
||||
}
|
||||
|
||||
std::string InstructionTranslator::translate(const Instruction &inst)
|
||||
{
|
||||
if (inst.isMMI)
|
||||
{
|
||||
return m_codeGenerator.translateMMIInstruction(inst);
|
||||
}
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
switch (inst.opcode)
|
||||
{
|
||||
case OPCODE_SPECIAL:
|
||||
return m_codeGenerator.translateSpecialInstruction(inst);
|
||||
case OPCODE_REGIMM:
|
||||
return m_codeGenerator.translateRegimmInstruction(inst);
|
||||
case OPCODE_COP0:
|
||||
return m_codeGenerator.translateCOP0Instruction(inst);
|
||||
case OPCODE_COP1:
|
||||
return m_codeGenerator.translateFPUInstruction(inst);
|
||||
case OPCODE_COP2:
|
||||
return m_codeGenerator.translateVUInstruction(inst);
|
||||
case OPCODE_ADDI:
|
||||
if (inst.rt == 0)
|
||||
return "// NOP (addi to $zero)";
|
||||
return fmt::format(
|
||||
"{{ uint32_t tmp; bool ov; "
|
||||
"ADD32_OV(GPR_U32(ctx, {}), (int32_t){}, tmp, ov); "
|
||||
"if (ov) runtime->SignalException(ctx, EXCEPTION_INTEGER_OVERFLOW); "
|
||||
"else SET_GPR_S32(ctx, {}, (int32_t)tmp); }}",
|
||||
inst.rs, inst.simmediate, inst.rt);
|
||||
|
||||
case OPCODE_ADDIU:
|
||||
if (inst.rt == 0)
|
||||
return "// NOP (addiu $zero, ...)";
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ADD32(GPR_U32(ctx, {}), {}));", inst.rt, inst.rs, inst.simmediate);
|
||||
case OPCODE_SLTI:
|
||||
return fmt::format("SET_GPR_U64(ctx, {}, ((int64_t)GPR_S64(ctx, {}) < (int64_t)(int32_t){}) ? 1 : 0);", inst.rt, inst.rs, inst.simmediate);
|
||||
case OPCODE_SLTIU:
|
||||
return fmt::format("SET_GPR_U64(ctx, {}, ((uint64_t)GPR_U64(ctx, {}) < (uint64_t)(int64_t)(int32_t){}) ? 1 : 0);", inst.rt, inst.rs, inst.simmediate);
|
||||
case OPCODE_ANDI:
|
||||
return fmt::format("SET_GPR_U64(ctx, {}, GPR_U64(ctx, {}) & (uint64_t)(uint16_t){});", inst.rt, inst.rs, inst.immediate);
|
||||
case OPCODE_ORI:
|
||||
return fmt::format("SET_GPR_U64(ctx, {}, GPR_U64(ctx, {}) | (uint64_t)(uint16_t){});", inst.rt, inst.rs, inst.immediate);
|
||||
case OPCODE_XORI:
|
||||
return fmt::format("SET_GPR_U64(ctx, {}, GPR_U64(ctx, {}) ^ (uint64_t)(uint16_t){});", inst.rt, inst.rs, inst.immediate);
|
||||
case OPCODE_LUI:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)((uint32_t){} << 16));", inst.rt, inst.immediate);
|
||||
case OPCODE_LB:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int8_t){});", inst.rt, genRead(8, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate)));
|
||||
case OPCODE_LH:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int16_t){});", inst.rt, genRead(16, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate)));
|
||||
case OPCODE_LW:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t){});", inst.rt, genRead(32, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate)));
|
||||
case OPCODE_LBU:
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, (uint8_t){});", inst.rt, genRead(8, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate)));
|
||||
case OPCODE_LHU:
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, (uint16_t){});", inst.rt, genRead(16, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate)));
|
||||
case OPCODE_LWU:
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, {});", inst.rt, genRead(32, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate)));
|
||||
case OPCODE_SB:
|
||||
return genWrite(8, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate), fmt::format("(uint8_t)GPR_U32(ctx, {})", inst.rt)) + ";";
|
||||
case OPCODE_SH:
|
||||
return genWrite(16, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate), fmt::format("(uint16_t)GPR_U32(ctx, {})", inst.rt)) + ";";
|
||||
case OPCODE_SW:
|
||||
return genWrite(32, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate), fmt::format("GPR_U32(ctx, {})", inst.rt)) + ";";
|
||||
case OPCODE_LQ:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, {});", inst.rt, genRead(128, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate)));
|
||||
case OPCODE_SQ:
|
||||
return genWrite(128, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate), fmt::format("GPR_VEC(ctx, {})", inst.rt)) + ";";
|
||||
case OPCODE_LD:
|
||||
return fmt::format("SET_GPR_U64(ctx, {}, {});", inst.rt, genRead(64, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate)));
|
||||
case OPCODE_SD:
|
||||
return genWrite(64, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate), fmt::format("GPR_U64(ctx, {})", inst.rt)) + ";";
|
||||
case OPCODE_LWC1:
|
||||
return fmt::format("{{ uint32_t bits = {}; float f; std::memcpy(&f, &bits, sizeof(f)); ctx->f[{}] = f; }}", genRead(32, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate)), inst.rt);
|
||||
case OPCODE_SWC1:
|
||||
return fmt::format(
|
||||
"{{ float f = ctx->f[{}]; uint32_t bits; std::memcpy(&bits, &f, sizeof(bits)); {}; }}",
|
||||
inst.rt,
|
||||
genWrite(32, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate), "bits"));
|
||||
case OPCODE_LDC2:
|
||||
return fmt::format("ctx->vu0_vf[{}] = _mm_castsi128_ps({});", inst.rt, genRead(128, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate)));
|
||||
case OPCODE_SDC2:
|
||||
return genWrite(128, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate), fmt::format("_mm_castps_si128(ctx->vu0_vf[{}])", inst.rt)) + ";";
|
||||
case OPCODE_DADDI:
|
||||
return fmt::format(
|
||||
"{{ int64_t src = (int64_t)GPR_S64(ctx, {}); "
|
||||
"int64_t imm = (int64_t)(int32_t){}; "
|
||||
"int64_t res = src + imm; "
|
||||
"if (((src ^ imm) >= 0) && ((src ^ res) < 0)) "
|
||||
" runtime->SignalException(ctx, EXCEPTION_INTEGER_OVERFLOW); "
|
||||
"else SET_GPR_S64(ctx, {}, res); }}",
|
||||
inst.rs, inst.simmediate, inst.rt);
|
||||
case OPCODE_DADDIU:
|
||||
return fmt::format(
|
||||
"SET_GPR_S64(ctx, {}, (int64_t)GPR_S64(ctx, {}) + (int64_t)(int32_t){});",
|
||||
inst.rt, inst.rs, inst.simmediate);
|
||||
case OPCODE_J:
|
||||
return fmt::format("// J 0x{:X} - Handled by branch logic", buildAbsoluteJumpTarget(inst.address, inst.target));
|
||||
case OPCODE_JAL:
|
||||
return fmt::format("// JAL 0x{:X} - Handled by branch logic", buildAbsoluteJumpTarget(inst.address, inst.target));
|
||||
case OPCODE_BEQ:
|
||||
case OPCODE_BNE:
|
||||
case OPCODE_BLEZ:
|
||||
case OPCODE_BGTZ:
|
||||
case OPCODE_BEQL:
|
||||
case OPCODE_BNEL:
|
||||
case OPCODE_BLEZL:
|
||||
case OPCODE_BGTZL:
|
||||
return fmt::format("// Likely branch instruction at 0x{:X} - Handled by branch logic", inst.address);
|
||||
|
||||
case OPCODE_LDL:
|
||||
return fmt::format("{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); "
|
||||
"uint32_t aligned_addr = addr & ~7u; "
|
||||
"uint32_t offset = addr & 7u; "
|
||||
"uint64_t mem = {}; "
|
||||
"uint32_t shift = (7u - offset) << 3; "
|
||||
"uint64_t keepMask = (shift == 0) ? 0ull : ((1ull << shift) - 1ull); "
|
||||
"SET_GPR_U64(ctx, {}, (GPR_U64(ctx, {}) & keepMask) | (mem << shift)); }}",
|
||||
inst.rs, inst.simmediate, genRead(64, "aligned_addr"), inst.rt, inst.rt);
|
||||
|
||||
case OPCODE_LDR:
|
||||
return fmt::format("{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); "
|
||||
"uint32_t aligned_addr = addr & ~7u; "
|
||||
"uint32_t offset = addr & 7u; "
|
||||
"uint64_t mem = {}; "
|
||||
"uint32_t shift = offset << 3; "
|
||||
"uint64_t keepMask = (offset == 0) ? 0ull : (0xFFFFFFFFFFFFFFFFull << ((8u - offset) << 3)); "
|
||||
"SET_GPR_U64(ctx, {}, (GPR_U64(ctx, {}) & keepMask) | (mem >> shift)); }}",
|
||||
inst.rs, inst.simmediate, genRead(64, "aligned_addr"), inst.rt, inst.rt);
|
||||
|
||||
case OPCODE_LWL:
|
||||
return fmt::format("{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); "
|
||||
"uint32_t aligned_addr = addr & ~3u; "
|
||||
"uint32_t offset = addr & 3u; "
|
||||
"uint32_t mem = {}; "
|
||||
"uint32_t shift = (3u - offset) << 3; "
|
||||
"uint32_t keepMask = (shift == 0) ? 0u : ((1u << shift) - 1u); "
|
||||
"uint32_t merged = (GPR_U32(ctx, {}) & keepMask) | (mem << shift); "
|
||||
"SET_GPR_S32(ctx, {}, (int32_t)merged); }}",
|
||||
inst.rs, inst.simmediate, genRead(32, "aligned_addr"), inst.rt, inst.rt);
|
||||
|
||||
case OPCODE_LWR:
|
||||
return fmt::format("{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); "
|
||||
"uint32_t aligned_addr = addr & ~3u; "
|
||||
"uint32_t offset = addr & 3u; "
|
||||
"uint32_t mem = {}; "
|
||||
"uint32_t shift = offset << 3; "
|
||||
"uint32_t keepMask = (offset == 0) ? 0u : (0xFFFFFFFFu << ((4u - offset) << 3)); "
|
||||
"uint32_t merged32 = (GPR_U32(ctx, {}) & keepMask) | (mem >> shift); "
|
||||
"uint64_t merged64 = (GPR_U64(ctx, {}) & 0xFFFFFFFF00000000ull) | (uint64_t)merged32; "
|
||||
"if (offset == 0) merged64 = (uint64_t)(int64_t)(int32_t)merged32; "
|
||||
"SET_GPR_U64(ctx, {}, merged64); }}",
|
||||
inst.rs, inst.simmediate, genRead(32, "aligned_addr"),
|
||||
inst.rt, inst.rt, inst.rt);
|
||||
|
||||
case OPCODE_SWL:
|
||||
return fmt::format("{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); "
|
||||
"uint32_t aligned_addr = addr & ~3u; "
|
||||
"uint32_t offset = addr & 3u; "
|
||||
"uint32_t shift = (3u - offset) << 3; "
|
||||
"uint32_t mask = 0xFFFFFFFFu >> shift; "
|
||||
"uint32_t old_data = {}; "
|
||||
"uint32_t val = GPR_U32(ctx, {}); "
|
||||
"uint32_t new_data = (old_data & ~mask) | ((val >> shift) & mask); "
|
||||
"{}; }}",
|
||||
inst.rs, inst.simmediate, genRead(32, "aligned_addr"), inst.rt, genWrite(32, "aligned_addr", "new_data"));
|
||||
|
||||
case OPCODE_SWR:
|
||||
return fmt::format("{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); "
|
||||
"uint32_t aligned_addr = addr & ~3u; "
|
||||
"uint32_t offset = addr & 3u; "
|
||||
"uint32_t shift = offset << 3; "
|
||||
"uint32_t mask = 0xFFFFFFFFu << shift; "
|
||||
"uint32_t old_data = {}; "
|
||||
"uint32_t val = GPR_U32(ctx, {}); "
|
||||
"uint32_t new_data = (old_data & ~mask) | ((val << shift) & mask); "
|
||||
"{}; }}",
|
||||
inst.rs, inst.simmediate, genRead(32, "aligned_addr"), inst.rt, genWrite(32, "aligned_addr", "new_data"));
|
||||
|
||||
case OPCODE_SDL:
|
||||
return fmt::format("{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); "
|
||||
"uint32_t aligned_addr = addr & ~7u; "
|
||||
"uint32_t offset = addr & 7u; "
|
||||
"uint32_t shift = (7u - offset) << 3; "
|
||||
"uint64_t mask = 0xFFFFFFFFFFFFFFFFull >> shift; "
|
||||
"uint64_t old_data = {}; "
|
||||
"uint64_t val = GPR_U64(ctx, {}); "
|
||||
"uint64_t new_data = (old_data & ~mask) | ((val >> shift) & mask); "
|
||||
"{}; }}",
|
||||
inst.rs, inst.simmediate, genRead(64, "aligned_addr"), inst.rt, genWrite(64, "aligned_addr", "new_data"));
|
||||
|
||||
case OPCODE_SDR:
|
||||
return fmt::format("{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); "
|
||||
"uint32_t aligned_addr = addr & ~7u; "
|
||||
"uint32_t offset = addr & 7u; "
|
||||
"uint32_t shift = offset << 3; "
|
||||
"uint64_t mask = 0xFFFFFFFFFFFFFFFFull << shift; "
|
||||
"uint64_t old_data = {}; "
|
||||
"uint64_t val = GPR_U64(ctx, {}); "
|
||||
"uint64_t new_data = (old_data & ~mask) | ((val << shift) & mask); "
|
||||
"{}; }}",
|
||||
inst.rs, inst.simmediate, genRead(64, "aligned_addr"), inst.rt, genWrite(64, "aligned_addr", "new_data"));
|
||||
case OPCODE_CACHE:
|
||||
return "// CACHE instruction (ignored)";
|
||||
case OPCODE_PREF:
|
||||
return "// PREF instruction (ignored)";
|
||||
case OPCODE_LL:
|
||||
return fmt::format(
|
||||
"{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); "
|
||||
"SET_GPR_S32(ctx, {}, (int32_t)READ32(addr)); "
|
||||
"ctx->llbit = 1; ctx->lladdr = addr; }}",
|
||||
inst.rs, inst.simmediate, inst.rt);
|
||||
case OPCODE_SC:
|
||||
return fmt::format(
|
||||
"{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); "
|
||||
"if (ctx->llbit && ctx->lladdr == addr) {{ WRITE32(addr, GPR_U32(ctx, {})); "
|
||||
"SET_GPR_S32(ctx, {}, 1); }} "
|
||||
"else {{ SET_GPR_S32(ctx, {}, 0); }} "
|
||||
"ctx->llbit = 0; ctx->lladdr = 0; }}",
|
||||
inst.rs, inst.simmediate, inst.rt, inst.rt, inst.rt);
|
||||
default:
|
||||
return m_codeGenerator.emitUnhandledInstruction(inst, fmt::format("Unhandled opcode: 0x{:X}", inst.opcode));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#include "ps2recomp/code_generator.h"
|
||||
#include "ps2recomp/codegen_helpers.h"
|
||||
#include "ps2recomp/instructions.h"
|
||||
#include "ps2recomp/types.h"
|
||||
#include <fmt/format.h>
|
||||
#include <sstream>
|
||||
#include <cmath>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
std::string CodeGenerator::generateJumpTableSwitch(const Instruction &inst, uint32_t tableAddress,
|
||||
const std::vector<JumpTableEntry> &entries)
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
uint32_t indexReg = inst.rs;
|
||||
|
||||
ss << "switch (GPR_U32(ctx, " << indexReg << ")) {\n";
|
||||
|
||||
for (const auto &[index, target] : entries)
|
||||
{
|
||||
ss << " case " << index << ": {\n";
|
||||
|
||||
std::string funcName = getFunctionName(target);
|
||||
if (!funcName.empty())
|
||||
{
|
||||
ss << " " << funcName << "(rdram, ctx, runtime);\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << " func_" << std::hex << target << std::dec << "(rdram, ctx, runtime);\n";
|
||||
}
|
||||
|
||||
ss << " return;\n";
|
||||
ss << " }\n";
|
||||
}
|
||||
|
||||
ss << " default:\n";
|
||||
ss << " // Unknown jump table target\n";
|
||||
ss << " return;\n";
|
||||
ss << "}\n";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,608 @@
|
||||
#include "ps2recomp/code_generator.h"
|
||||
#include "ps2recomp/codegen_helpers.h"
|
||||
#include "ps2recomp/instructions.h"
|
||||
#include "ps2recomp/types.h"
|
||||
#include <fmt/format.h>
|
||||
#include <sstream>
|
||||
#include <cmath>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
std::string CodeGenerator::translateMMI0Instruction(const Instruction &inst)
|
||||
{
|
||||
uint8_t subfunc = inst.sa;
|
||||
uint8_t rs = inst.rs;
|
||||
uint8_t rt = inst.rt;
|
||||
uint8_t rd = inst.rd;
|
||||
switch (subfunc)
|
||||
{
|
||||
case MMI0_PADDW:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PADDW(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI0_PSUBW:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PSUBW(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI0_PCGTW:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PCGTW(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI0_PMAXW:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PMAXW(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI0_PADDH:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PADDH(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI0_PSUBH:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PSUBH(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI0_PCGTH:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PCGTH(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI0_PMAXH:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PMAXH(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI0_PADDB:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PADDB(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI0_PSUBB:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PSUBB(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI0_PCGTB:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PCGTB(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI0_PADDSW:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PADDSW(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI0_PSUBSW:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PSUBSW(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI0_PEXTLW:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PEXTLW(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI0_PPACW:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PPACW(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI0_PADDSH:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_adds_epi16(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI0_PSUBSH:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_subs_epi16(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI0_PEXTLH:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PEXTLH(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI0_PPACH:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PPACH(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI0_PADDSB:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_adds_epi8(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI0_PSUBSB:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_subs_epi8(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI0_PEXTLB:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PEXTLB(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI0_PPACB:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PPACB(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI0_PEXT5:
|
||||
return translatePEXT5(inst);
|
||||
case MMI0_PPAC5:
|
||||
return translatePPAC5(inst);
|
||||
default:
|
||||
return emitUnhandledInstruction(inst, fmt::format("Unhandled MMI0 instruction: function 0x{:X}", subfunc));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translateMMI1Instruction(const Instruction &inst)
|
||||
{
|
||||
uint8_t subfunc = inst.sa;
|
||||
uint8_t rs = inst.rs;
|
||||
uint8_t rt = inst.rt;
|
||||
uint8_t rd = inst.rd;
|
||||
switch (subfunc)
|
||||
{
|
||||
case MMI1_PABSW:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PABSW(GPR_VEC(ctx, {})));", rd, rs);
|
||||
case MMI1_PCEQW:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PCEQW(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI1_PMINW:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PMINW(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI1_PADSBH:
|
||||
return translatePADSBH(inst);
|
||||
case MMI1_PABSH:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PABSH(GPR_VEC(ctx, {})));", rd, rs);
|
||||
case MMI1_PCEQH:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PCEQH(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI1_PMINH:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PMINH(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI1_PCEQB:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PCEQB(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI1_PADDUW:
|
||||
return fmt::format(
|
||||
"SET_GPR_VEC(ctx, {}, ps2_paddu32(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI1_PSUBUW:
|
||||
return fmt::format(
|
||||
"SET_GPR_VEC(ctx, {}, ps2_psubu32(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI1_PEXTUW:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PEXTUW(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI1_PADDUH:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_add_epi16(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI1_PSUBUH:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_sub_epi16(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI1_PEXTUH:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PEXTUH(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI1_PADDUB:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_adds_epu8(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI1_PSUBUB:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_subs_epu8(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI1_PEXTUB:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PEXTUB(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI1_QFSRV:
|
||||
return translateQFSRV(inst);
|
||||
default:
|
||||
return emitUnhandledInstruction(inst, fmt::format("Unhandled MMI1 instruction: function 0x{:X}", subfunc));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translateMMI2Instruction(const Instruction &inst)
|
||||
{
|
||||
uint8_t subfunc = inst.sa;
|
||||
uint8_t rs = inst.rs;
|
||||
uint8_t rt = inst.rt;
|
||||
uint8_t rd = inst.rd;
|
||||
switch (subfunc)
|
||||
{
|
||||
case MMI2_PMADDW:
|
||||
return translatePMADDW(inst);
|
||||
case MMI2_PSLLVW:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PSLLVW(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI2_PSRLVW:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PSRLVW(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI2_PMSUBW:
|
||||
return translatePMSUBW(inst);
|
||||
case MMI2_PMFHI:
|
||||
return fmt::format("SET_GPR_U64(ctx, {}, ctx->hi);", rd);
|
||||
case MMI2_PMFLO:
|
||||
return fmt::format("SET_GPR_U64(ctx, {}, ctx->lo);", rd);
|
||||
case MMI2_PINTH:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PINTH(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI2_PMULTW:
|
||||
return translatePMULTW(inst);
|
||||
case MMI2_PDIVW:
|
||||
return translatePDIVW(inst);
|
||||
case MMI2_PCPYLD:
|
||||
return translatePCPYLD(inst);
|
||||
case MMI2_PAND:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PAND(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI2_PXOR:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PXOR(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI2_PMADDH:
|
||||
return translatePMADDH(inst);
|
||||
case MMI2_PHMADH:
|
||||
return translatePHMADH(inst);
|
||||
case MMI2_PMSUBH:
|
||||
return translatePMSUBH(inst);
|
||||
case MMI2_PHMSBH:
|
||||
return translatePHMSBH(inst);
|
||||
case MMI2_PEXEH:
|
||||
return translatePEXEH(inst);
|
||||
case MMI2_PREVH:
|
||||
return translatePREVH(inst);
|
||||
case MMI2_PMULTH:
|
||||
return translatePMULTH(inst);
|
||||
case MMI2_PDIVBW:
|
||||
return translatePDIVBW(inst);
|
||||
case MMI2_PEXEW:
|
||||
return translatePEXEW(inst);
|
||||
case MMI2_PROT3W:
|
||||
return translatePROT3W(inst);
|
||||
default:
|
||||
return emitUnhandledInstruction(inst, fmt::format("Unhandled MMI2 instruction: function 0x{:X}", subfunc));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translateMMI3Instruction(const Instruction &inst)
|
||||
{
|
||||
uint8_t subfunc = inst.sa;
|
||||
uint8_t rs = inst.rs;
|
||||
uint8_t rt = inst.rt;
|
||||
uint8_t rd = inst.rd;
|
||||
switch (subfunc)
|
||||
{
|
||||
case MMI3_PMADDUW:
|
||||
return translatePMADDUW(inst);
|
||||
case MMI3_PSRAVW:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PSRAVW(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI3_PMTHI:
|
||||
return translatePMTHI(inst);
|
||||
case MMI3_PMTLO:
|
||||
return translatePMTLO(inst);
|
||||
case MMI3_PINTEH:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PINTEH(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI3_PMULTUW:
|
||||
return translatePMULTUW(inst);
|
||||
case MMI3_PDIVUW:
|
||||
return translatePDIVUW(inst);
|
||||
case MMI3_PCPYUD:
|
||||
return translatePCPYUD(inst);
|
||||
case MMI3_POR:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_POR(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI3_PNOR:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PNOR(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI3_PEXCH:
|
||||
return translatePEXCH(inst);
|
||||
case MMI3_PCPYH:
|
||||
return translatePCPYH(inst);
|
||||
case MMI3_PEXCW:
|
||||
return translatePEXCW(inst);
|
||||
default:
|
||||
return emitUnhandledInstruction(inst, fmt::format("Unhandled MMI3 instruction: function 0x{:X}", subfunc));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePMFHLInstruction(const Instruction &inst)
|
||||
{
|
||||
uint8_t subfunc = inst.sa;
|
||||
switch (subfunc)
|
||||
{
|
||||
case PMFHL_LW:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PMFHL_LW(ctx->hi, ctx->lo));", inst.rd);
|
||||
case PMFHL_UW:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PMFHL_UW(ctx->hi, ctx->lo));", inst.rd);
|
||||
case PMFHL_SLW:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PMFHL_SLW(ctx->hi, ctx->lo));", inst.rd);
|
||||
case PMFHL_LH:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PMFHL_LH(ctx->hi, ctx->lo));", inst.rd);
|
||||
case PMFHL_SH:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PMFHL_SH(ctx->hi, ctx->lo));", inst.rd);
|
||||
default:
|
||||
return emitUnhandledInstruction(inst, fmt::format("Unhandled PMFHL instruction: function 0x{:X}", subfunc));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePMTHLInstruction(const Instruction &inst)
|
||||
{
|
||||
uint8_t subfunc = inst.sa;
|
||||
switch (subfunc)
|
||||
{
|
||||
case PMFHL_LW:
|
||||
return fmt::format("{{ __m128i val = GPR_VEC(ctx, {}); ctx->lo = _mm_extract_epi32(val, 0); ctx->hi = _mm_extract_epi32(val, 1); }}", inst.rs);
|
||||
default:
|
||||
return emitUnhandledInstruction(inst, fmt::format("Unhandled PMTHL instruction: function 0x{:X}", subfunc));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePEXT5(const Instruction &inst)
|
||||
{
|
||||
return fmt::format(
|
||||
"{{ __m128i rt = GPR_VEC(ctx, {}); \n"
|
||||
" __m128i m1 = _mm_set1_epi32(0x0000001F); \n"
|
||||
" __m128i m2 = _mm_set1_epi32(0x000003E0); \n"
|
||||
" __m128i m3 = _mm_set1_epi32(0x00007C00); \n"
|
||||
" __m128i m4 = _mm_set1_epi32(0x00008000); \n"
|
||||
" __m128i a1 = _mm_slli_epi32(_mm_and_si128(rt, m1), 3); \n"
|
||||
" __m128i a2 = _mm_slli_epi32(_mm_and_si128(rt, m2), 6); \n"
|
||||
" __m128i a3 = _mm_slli_epi32(_mm_and_si128(rt, m3), 9); \n"
|
||||
" __m128i a4 = _mm_slli_epi32(_mm_and_si128(rt, m4), 16); \n"
|
||||
" SET_GPR_VEC(ctx, {}, _mm_or_si128(_mm_or_si128(a1, a2), _mm_or_si128(a3, a4))); }}",
|
||||
inst.rt, inst.rd);
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePPAC5(const Instruction &inst)
|
||||
{
|
||||
return fmt::format(
|
||||
"{{ __m128i rt = GPR_VEC(ctx, {}); \n"
|
||||
" __m128i m1 = _mm_set1_epi32(0x0000001F); \n"
|
||||
" __m128i m2 = _mm_set1_epi32(0x000003E0); \n"
|
||||
" __m128i m3 = _mm_set1_epi32(0x00007C00); \n"
|
||||
" __m128i m4 = _mm_set1_epi32(0x00008000); \n"
|
||||
" __m128i a1 = _mm_and_si128(_mm_srli_epi32(rt, 3), m1); \n"
|
||||
" __m128i a2 = _mm_and_si128(_mm_srli_epi32(rt, 6), m2); \n"
|
||||
" __m128i a3 = _mm_and_si128(_mm_srli_epi32(rt, 9), m3); \n"
|
||||
" __m128i a4 = _mm_and_si128(_mm_srli_epi32(rt, 16), m4); \n"
|
||||
" SET_GPR_VEC(ctx, {}, _mm_or_si128(_mm_or_si128(a1, a2), _mm_or_si128(a3, a4))); }}",
|
||||
inst.rt, inst.rd);
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePADSBH(const Instruction &inst)
|
||||
{
|
||||
return fmt::format(
|
||||
"{{ __m128i rs = GPR_VEC(ctx, {}); __m128i rt = GPR_VEC(ctx, {}); \n"
|
||||
" __m128i sub = _mm_sub_epi16(rs, rt); \n"
|
||||
" __m128i add = _mm_add_epi16(rs, rt); \n"
|
||||
" SET_GPR_VEC(ctx, {}, _mm_unpacklo_epi64(sub, _mm_unpackhi_epi64(add, add))); }}",
|
||||
inst.rs, inst.rt, inst.rd);
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePMADDW(const Instruction &inst)
|
||||
{
|
||||
return fmt::format("{{ __m128i p01 = _mm_mul_epu32(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})); \n"
|
||||
" __m128i p23 = _mm_mul_epu32(_mm_srli_si128(GPR_VEC(ctx, {}), 8), _mm_srli_si128(GPR_VEC(ctx, {}), 8)); \n"
|
||||
" uint64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); \n"
|
||||
" acc += _mm_cvtsi128_si64(p01); \n"
|
||||
" acc += _mm_cvtsi128_si64(_mm_srli_si128(p01, 8)); \n"
|
||||
" acc += _mm_cvtsi128_si64(p23); \n"
|
||||
" acc += _mm_cvtsi128_si64(_mm_srli_si128(p23, 8)); \n"
|
||||
" ctx->lo = (uint32_t)acc; ctx->hi = (uint32_t)(acc >> 32); \n"
|
||||
" SET_GPR_U64(ctx, {}, acc); }}",
|
||||
inst.rs, inst.rt, inst.rs, inst.rt, inst.rd);
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePMSUBW(const Instruction &inst)
|
||||
{
|
||||
return fmt::format("{{ __m128i p01 = _mm_mul_epu32(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})); \n"
|
||||
" __m128i p23 = _mm_mul_epu32(_mm_srli_si128(GPR_VEC(ctx, {}), 8), _mm_srli_si128(GPR_VEC(ctx, {}), 8)); \n"
|
||||
" uint64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); \n"
|
||||
" acc -= _mm_cvtsi128_si64(p01); \n"
|
||||
" acc -= _mm_cvtsi128_si64(_mm_srli_si128(p01, 8)); \n"
|
||||
" acc -= _mm_cvtsi128_si64(p23); \n"
|
||||
" acc -= _mm_cvtsi128_si64(_mm_srli_si128(p23, 8)); \n"
|
||||
" ctx->lo = (uint32_t)acc; ctx->hi = (uint32_t)(acc >> 32); \n"
|
||||
" SET_GPR_U64(ctx, {}, acc); }}",
|
||||
inst.rs, inst.rt, inst.rs, inst.rt, inst.rd);
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePMULTW(const Instruction &inst)
|
||||
{
|
||||
return fmt::format("{{ __m128i p01 = _mm_mul_epu32(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})); \n"
|
||||
" __m128i p23 = _mm_mul_epu32(_mm_srli_si128(GPR_VEC(ctx, {}), 8), _mm_srli_si128(GPR_VEC(ctx, {}), 8)); \n"
|
||||
" uint64_t acc = 0; \n"
|
||||
" acc += _mm_cvtsi128_si64(p01); \n"
|
||||
" acc += _mm_cvtsi128_si64(_mm_srli_si128(p01, 8)); \n"
|
||||
" acc += _mm_cvtsi128_si64(p23); \n"
|
||||
" acc += _mm_cvtsi128_si64(_mm_srli_si128(p23, 8)); \n"
|
||||
" ctx->lo = (uint32_t)acc; ctx->hi = (uint32_t)(acc >> 32); \n"
|
||||
" SET_GPR_U64(ctx, {}, acc); }}",
|
||||
inst.rs, inst.rt, inst.rs, inst.rt, inst.rd);
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePMADDUW(const Instruction &inst)
|
||||
{
|
||||
return fmt::format("{{ __m128i p01 = _mm_mul_epu32(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})); \n"
|
||||
" __m128i p23 = _mm_mul_epu32(_mm_srli_si128(GPR_VEC(ctx, {}), 8), _mm_srli_si128(GPR_VEC(ctx, {}), 8)); \n"
|
||||
" uint64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); \n"
|
||||
" acc += _mm_cvtsi128_si64(p01); \n"
|
||||
" acc += _mm_cvtsi128_si64(_mm_srli_si128(p01, 8)); \n"
|
||||
" acc += _mm_cvtsi128_si64(p23); \n"
|
||||
" acc += _mm_cvtsi128_si64(_mm_srli_si128(p23, 8)); \n"
|
||||
" ctx->lo = (uint32_t)acc; ctx->hi = (uint32_t)(acc >> 32); \n"
|
||||
" SET_GPR_U64(ctx, {}, acc); }}",
|
||||
inst.rs, inst.rt, inst.rs, inst.rt, inst.rd);
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePDIVW(const Instruction &inst)
|
||||
{
|
||||
// Only divides the first word element rs[0] / rt[0]
|
||||
return fmt::format("{{ int32_t rs0 = GPR_S32(ctx, {}); int32_t rt0 = GPR_S32(ctx, {}); \n"
|
||||
" if (rt0 != 0) {{ \n"
|
||||
" if (rt0 == -1 && rs0 == INT32_MIN) {{ ctx->lo = (uint32_t)INT32_MIN; ctx->hi = 0; }} \n"
|
||||
" else {{ ctx->lo = (uint32_t)(rs0 / rt0); ctx->hi = (uint32_t)(rs0 % rt0); }} \n"
|
||||
" }} else {{ ctx->lo = (rs0 < 0) ? 1 : -1; ctx->hi = (uint32_t)rs0; }} \n"
|
||||
" SET_GPR_U32(ctx, {}, ctx->lo); }}",
|
||||
inst.rs, inst.rt, inst.rd);
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePCPYLD(const Instruction &inst)
|
||||
{
|
||||
// PCPYLD uses rs as the upper source and rt as the lower source.
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PCPYLD(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));",
|
||||
inst.rd, inst.rs, inst.rt);
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePMADDH(const Instruction &inst)
|
||||
{
|
||||
// Parallel multiply add halfword -> results to HI/LO and rd
|
||||
return fmt::format("{{ __m128i prod = _mm_madd_epi16(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})); \n" // Packed multiply and add adjacent pairs
|
||||
" int32_t p0 = _mm_cvtsi128_si32(prod); \n"
|
||||
" int32_t p1 = _mm_cvtsi128_si32(_mm_srli_si128(prod, 4)); \n"
|
||||
" int32_t p2 = _mm_cvtsi128_si32(_mm_srli_si128(prod, 8)); \n"
|
||||
" int32_t p3 = _mm_cvtsi128_si32(_mm_srli_si128(prod, 12)); \n"
|
||||
" int64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); \n"
|
||||
" acc += (int64_t)p0 + (int64_t)p1 + (int64_t)p2 + (int64_t)p3; \n"
|
||||
" ctx->lo = (uint32_t)acc; ctx->hi = (uint32_t)(acc >> 32); \n"
|
||||
" SET_GPR_U64(ctx, {}, acc); }}",
|
||||
inst.rs, inst.rt, inst.rd);
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePHMADH(const Instruction &inst)
|
||||
{
|
||||
// Parallel Horizontal Multiply Add Halfword -> results to HI/LO and rd
|
||||
return fmt::format("{{ __m128i evens = _mm_shuffle_epi32(GPR_VEC(ctx, {}), _MM_SHUFFLE(2,0,2,0)); \n" // Select even halfwords
|
||||
" __m128i odds = _mm_shuffle_epi32(GPR_VEC(ctx, {}), _MM_SHUFFLE(3,1,3,1)); \n" // Select odd halfwords
|
||||
" __m128i prod_ev = _mm_mullo_epi16(evens, _mm_shuffle_epi32(GPR_VEC(ctx, {}), _MM_SHUFFLE(2,0,2,0))); \n"
|
||||
" __m128i prod_od = _mm_mullo_epi16(odds, _mm_shuffle_epi32(GPR_VEC(ctx, {}), _MM_SHUFFLE(3,1,3,1))); \n"
|
||||
" __m128i sum_pairs = _mm_add_epi16(prod_ev, prod_od); \n" // Add rs[0]*rt[0] + rs[1]*rt[1], etc.
|
||||
" int32_t h0 = _mm_extract_epi16(sum_pairs, 0) + _mm_extract_epi16(sum_pairs, 1); \n" // Horizontal add within low 32b
|
||||
" int32_t h1 = _mm_extract_epi16(sum_pairs, 2) + _mm_extract_epi16(sum_pairs, 3); \n" // Horizontal add within next 32b
|
||||
" int32_t h2 = _mm_extract_epi16(sum_pairs, 4) + _mm_extract_epi16(sum_pairs, 5); \n"
|
||||
" int32_t h3 = _mm_extract_epi16(sum_pairs, 6) + _mm_extract_epi16(sum_pairs, 7); \n"
|
||||
" int64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); \n"
|
||||
" acc += (int64_t)h0 + (int64_t)h1 + (int64_t)h2 + (int64_t)h3; \n"
|
||||
" ctx->lo = (uint32_t)acc; ctx->hi = (uint32_t)(acc >> 32); \n"
|
||||
" SET_GPR_U64(ctx, {}, acc); }}",
|
||||
inst.rs, inst.rt, inst.rs, inst.rt, inst.rd);
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePMSUBH(const Instruction &inst)
|
||||
{
|
||||
return fmt::format("{{ __m128i prod = _mm_madd_epi16(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})); \n"
|
||||
" int32_t p0 = _mm_cvtsi128_si32(prod); \n"
|
||||
" int32_t p1 = _mm_cvtsi128_si32(_mm_srli_si128(prod, 4)); \n"
|
||||
" int32_t p2 = _mm_cvtsi128_si32(_mm_srli_si128(prod, 8)); \n"
|
||||
" int32_t p3 = _mm_cvtsi128_si32(_mm_srli_si128(prod, 12)); \n"
|
||||
" int64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); \n"
|
||||
" acc -= (int64_t)p0 + (int64_t)p1 + (int64_t)p2 + (int64_t)p3; \n"
|
||||
" ctx->lo = (uint32_t)acc; ctx->hi = (uint32_t)(acc >> 32); \n"
|
||||
" SET_GPR_U64(ctx, {}, acc); }}",
|
||||
inst.rs, inst.rt, inst.rd);
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePHMSBH(const Instruction &inst)
|
||||
{
|
||||
return fmt::format("{{ __m128i evens = _mm_shuffle_epi32(GPR_VEC(ctx, {}), _MM_SHUFFLE(2,0,2,0)); \n"
|
||||
" __m128i odds = _mm_shuffle_epi32(GPR_VEC(ctx, {}), _MM_SHUFFLE(3,1,3,1)); \n"
|
||||
" __m128i prod_ev = _mm_mullo_epi16(evens, _mm_shuffle_epi32(GPR_VEC(ctx, {}), _MM_SHUFFLE(2,0,2,0))); \n"
|
||||
" __m128i prod_od = _mm_mullo_epi16(odds, _mm_shuffle_epi32(GPR_VEC(ctx, {}), _MM_SHUFFLE(3,1,3,1))); \n"
|
||||
" __m128i sub_pairs = _mm_sub_epi16(prod_od, prod_ev); \n"
|
||||
" int32_t h0 = _mm_extract_epi16(sub_pairs, 0) + _mm_extract_epi16(sub_pairs, 1); \n"
|
||||
" int32_t h1 = _mm_extract_epi16(sub_pairs, 2) + _mm_extract_epi16(sub_pairs, 3); \n"
|
||||
" int32_t h2 = _mm_extract_epi16(sub_pairs, 4) + _mm_extract_epi16(sub_pairs, 5); \n"
|
||||
" int32_t h3 = _mm_extract_epi16(sub_pairs, 6) + _mm_extract_epi16(sub_pairs, 7); \n"
|
||||
" int64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); \n"
|
||||
" acc += (int64_t)h0 + (int64_t)h1 + (int64_t)h2 + (int64_t)h3; \n"
|
||||
" ctx->lo = (uint32_t)acc; ctx->hi = (uint32_t)(acc >> 32); \n"
|
||||
" SET_GPR_U64(ctx, {}, acc); }}",
|
||||
inst.rs, inst.rt, inst.rs, inst.rt, inst.rd);
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePEXEH(const Instruction &inst)
|
||||
{
|
||||
// Swaps halfwords 1<->3 and 5<->7 within the 128-bit register
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_shufflelo_epi16(_mm_shufflehi_epi16(GPR_VEC(ctx, {}), _MM_SHUFFLE(2,3,0,1)), _MM_SHUFFLE(2,3,0,1)));",
|
||||
inst.rd, inst.rs);
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePREVH(const Instruction &inst)
|
||||
{
|
||||
// Reverses the order of the 8 halfwords
|
||||
return fmt::format("{{ __m128i mask = _mm_setr_epi8(14,15, 12,13, 10,11, 8,9, 6,7, 4,5, 2,3, 0,1); "
|
||||
"SET_GPR_VEC(ctx, {}, PS2_SHUFFLE_EPI8(GPR_VEC(ctx, {}), mask)); }}",
|
||||
inst.rd, inst.rs);
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePMULTH(const Instruction &inst)
|
||||
{
|
||||
// Parallel multiply halfword, results sum to HI/LO and rd
|
||||
return fmt::format("{{ __m128i prod = _mm_madd_epi16(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})); \n"
|
||||
" int32_t p0 = _mm_cvtsi128_si32(prod); \n"
|
||||
" int32_t p1 = _mm_cvtsi128_si32(_mm_srli_si128(prod, 4)); \n"
|
||||
" int32_t p2 = _mm_cvtsi128_si32(_mm_srli_si128(prod, 8)); \n"
|
||||
" int32_t p3 = _mm_cvtsi128_si32(_mm_srli_si128(prod, 12)); \n"
|
||||
" int64_t result = (int64_t)p0 + (int64_t)p1 + (int64_t)p2 + (int64_t)p3; \n"
|
||||
" ctx->lo = (uint32_t)result; ctx->hi = (uint32_t)(result >> 32); \n"
|
||||
" SET_GPR_U64(ctx, {}, result); }}",
|
||||
inst.rs, inst.rt, inst.rd);
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePDIVBW(const Instruction &inst)
|
||||
{
|
||||
return fmt::format(
|
||||
"{{\n"
|
||||
" __m128i rsVec = GPR_VEC(ctx, {});\n"
|
||||
" __m128i rtVec = GPR_VEC(ctx, {});\n"
|
||||
" alignas(16) int32_t rsWords[4];\n"
|
||||
" alignas(16) int32_t rtWords[4];\n"
|
||||
" _mm_store_si128((__m128i*)rsWords, rsVec);\n"
|
||||
" _mm_store_si128((__m128i*)rtWords, rtVec);\n"
|
||||
" int32_t div = rtWords[0];\n"
|
||||
" int32_t q0 = 0, q1 = 0, q2 = 0, q3 = 0;\n"
|
||||
" if (div != 0) {{\n"
|
||||
" q0 = rsWords[0] / div; ctx->lo = (uint32_t)q0; ctx->hi = (uint32_t)(rsWords[0] % div);\n"
|
||||
" q1 = rsWords[1] / div;\n"
|
||||
" q2 = rsWords[2] / div;\n"
|
||||
" q3 = rsWords[3] / div;\n"
|
||||
" }} else {{\n"
|
||||
" ctx->lo = (rsWords[0] < 0) ? 1 : -1;\n"
|
||||
" ctx->hi = (uint32_t)rsWords[0];\n"
|
||||
" }}\n"
|
||||
" SET_GPR_VEC(ctx, {}, _mm_set_epi32(q3, q2, q1, q0));\n"
|
||||
"}}",
|
||||
inst.rs, inst.rt, inst.rd);
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePEXEW(const Instruction &inst)
|
||||
{
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PEXEW(GPR_VEC(ctx, {})));",
|
||||
inst.rd, inst.rs);
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePROT3W(const Instruction &inst)
|
||||
{
|
||||
// Rotates words left by 3: [d,c,b,a] -> [a,d,c,b]
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_shuffle_epi32(GPR_VEC(ctx, {}), _MM_SHUFFLE(0,3,2,1)));",
|
||||
inst.rd, inst.rs);
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePMULTUW(const Instruction &inst)
|
||||
{
|
||||
// Parallel multiply unsigned word -> results to HI/LO and rd (lower 32 bits)
|
||||
return fmt::format("{{ __m128i p01 = _mm_mul_epu32(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})); \n"
|
||||
" __m128i p23 = _mm_mul_epu32(_mm_srli_si128(GPR_VEC(ctx, {}), 8), _mm_srli_si128(GPR_VEC(ctx, {}), 8)); \n"
|
||||
" uint64_t res0 = _mm_cvtsi128_si64(p01); uint64_t res1 = _mm_cvtsi128_si64(_mm_srli_si128(p01, 8)); \n"
|
||||
" uint64_t res2 = _mm_cvtsi128_si64(p23); uint64_t res3 = _mm_cvtsi128_si64(_mm_srli_si128(p23, 8)); \n"
|
||||
" ctx->lo = (uint32_t)res0; ctx->hi = (uint32_t)(res0 >> 32); \n" // HI/LO from first product only
|
||||
" SET_GPR_VEC(ctx, {}, _mm_set_epi32((uint32_t)res3, (uint32_t)res2, (uint32_t)res1, (uint32_t)res0)); }}",
|
||||
inst.rs, inst.rt, inst.rs, inst.rt, inst.rd);
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePDIVUW(const Instruction &inst)
|
||||
{
|
||||
// Parallel divide unsigned word (only first element) -> results to HI/LO and rd (quotient)
|
||||
return fmt::format("{{ uint32_t rs0 = GPR_U32(ctx, {}); uint32_t rt0 = GPR_U32(ctx, {}); \n"
|
||||
" if (rt0 != 0) {{ ctx->lo = rs0 / rt0; ctx->hi = rs0 % rt0; }} \n"
|
||||
" else {{ ctx->lo = 0xFFFFFFFF; ctx->hi = rs0; }} \n" // Div by zero behavior
|
||||
" SET_GPR_U32(ctx, {}, ctx->lo); }}",
|
||||
inst.rs, inst.rt, inst.rd);
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePCPYUD(const Instruction &inst)
|
||||
{
|
||||
// Copies upper 64 of rs to lower 64 of rd, upper 64 of rt to upper 64 of rd
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_unpackhi_epi64(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));",
|
||||
inst.rd, inst.rs, inst.rt); // Order matters
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePEXCH(const Instruction &inst)
|
||||
{
|
||||
// Parallel Exchange Center Halfword (same as MMI2 PEXEH)
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_shufflelo_epi16(_mm_shufflehi_epi16(GPR_VEC(ctx, {}), _MM_SHUFFLE(2,3,0,1)), _MM_SHUFFLE(2,3,0,1)));",
|
||||
inst.rd, inst.rs);
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePCPYH(const Instruction &inst)
|
||||
{
|
||||
// Parallel Copy Halfword (Broadcast lower 16 bits of each 64-bit half)
|
||||
return fmt::format("{{ __m128i src = GPR_VEC(ctx, {}); uint16_t l = _mm_extract_epi16(src, 0); uint16_t h = _mm_extract_epi16(src, 4); \n"
|
||||
" SET_GPR_VEC(ctx, {}, _mm_set_epi16(h,h,h,h, l,l,l,l)); }}",
|
||||
inst.rs, inst.rd);
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePEXCW(const Instruction &inst)
|
||||
{
|
||||
// Parallel Exchange Center Word (Swaps words 0<>2, 1<>3)
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_shuffle_epi32(GPR_VEC(ctx, {}), _MM_SHUFFLE(1,0,3,2)));",
|
||||
inst.rd, inst.rs);
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePMTHI(const Instruction &inst)
|
||||
{
|
||||
return fmt::format("ctx->hi = GPR_U32(ctx, {});", inst.rs); // PMTHI uses standard HI/LO
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translatePMTLO(const Instruction &inst)
|
||||
{
|
||||
return fmt::format("ctx->lo = GPR_U32(ctx, {});", inst.rs); // PMTLO uses standard HI/LO
|
||||
}
|
||||
|
||||
|
||||
std::string CodeGenerator::translateQFSRV(const Instruction &inst)
|
||||
{
|
||||
uint8_t rd = inst.rd;
|
||||
uint8_t rs = inst.rs;
|
||||
uint8_t rt = inst.rt;
|
||||
// QFSRV semantics are centralized in runtime macro helpers.
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_QFSRV(GPR_VEC(ctx, {}), GPR_VEC(ctx, {}), ctx->sa & 0x7F));",
|
||||
rd, rs, rt);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
#include "ps2recomp/Translators/mmi_translator.h"
|
||||
#include "ps2recomp/code_generator.h"
|
||||
#include "ps2recomp/codegen_helpers.h"
|
||||
#include "ps2recomp/instructions.h"
|
||||
#include "ps2recomp/types.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <sstream>
|
||||
#include <cmath>
|
||||
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
MmiTranslator::MmiTranslator(CodeGenerator &codeGenerator)
|
||||
: m_codeGenerator(codeGenerator)
|
||||
{
|
||||
}
|
||||
|
||||
std::string MmiTranslator::translate(const Instruction &inst)
|
||||
{
|
||||
uint32_t function = inst.function;
|
||||
uint8_t rs = inst.rs;
|
||||
uint8_t rt = inst.rt;
|
||||
uint8_t rd = inst.rd;
|
||||
uint8_t sa = inst.sa;
|
||||
switch (function)
|
||||
{
|
||||
case MMI_MFHI1:
|
||||
return fmt::format("SET_GPR_U64(ctx, {}, ctx->hi1);", rd);
|
||||
case MMI_MTHI1:
|
||||
return fmt::format("ctx->hi1 = GPR_U64(ctx, {});", rs);
|
||||
case MMI_MFLO1:
|
||||
return fmt::format("SET_GPR_U64(ctx, {}, ctx->lo1);", rd);
|
||||
case MMI_MTLO1:
|
||||
return fmt::format("ctx->lo1 = GPR_U64(ctx, {});", rs);
|
||||
case MMI_MULT1:
|
||||
if (rd != 0)
|
||||
{
|
||||
return fmt::format("{{ int64_t result = (int64_t)GPR_S32(ctx, {}) * (int64_t)GPR_S32(ctx, {}); ctx->lo1 = (uint64_t)(int64_t)(int32_t)result; ctx->hi1 = (uint64_t)(int64_t)(int32_t)(result >> 32); SET_GPR_S32(ctx, {}, (int32_t)result); }}", rs, rt, rd);
|
||||
}
|
||||
return fmt::format("{{ int64_t result = (int64_t)GPR_S32(ctx, {}) * (int64_t)GPR_S32(ctx, {}); ctx->lo1 = (uint64_t)(int64_t)(int32_t)result; ctx->hi1 = (uint64_t)(int64_t)(int32_t)(result >> 32); }}", rs, rt);
|
||||
case MMI_MULTU1:
|
||||
if (rd != 0)
|
||||
{
|
||||
return fmt::format("{{ uint64_t result = (uint64_t)GPR_U32(ctx, {}) * (uint64_t)GPR_U32(ctx, {}); ctx->lo1 = (uint64_t)(int64_t)(int32_t)result; ctx->hi1 = (uint64_t)(int64_t)(int32_t)(result >> 32); SET_GPR_S32(ctx, {}, (int32_t)result); }}", rs, rt, rd);
|
||||
}
|
||||
return fmt::format("{{ uint64_t result = (uint64_t)GPR_U32(ctx, {}) * (uint64_t)GPR_U32(ctx, {}); ctx->lo1 = (uint64_t)(int64_t)(int32_t)result; ctx->hi1 = (uint64_t)(int64_t)(int32_t)(result >> 32); }}", rs, rt);
|
||||
case MMI_DIV1:
|
||||
return fmt::format("{{ int32_t divisor = GPR_S32(ctx, {}); "
|
||||
"int32_t dividend = GPR_S32(ctx, {}); "
|
||||
"if (divisor != 0) {{ "
|
||||
" if (divisor == -1 && dividend == INT32_MIN) {{ "
|
||||
" ctx->lo1 = (uint64_t)(int64_t)INT32_MIN; ctx->hi1 = 0; "
|
||||
" }} else {{ "
|
||||
" ctx->lo1 = (uint64_t)(int64_t)(dividend / divisor); "
|
||||
" ctx->hi1 = (uint64_t)(int64_t)(dividend % divisor); "
|
||||
" }} "
|
||||
"}} else {{ "
|
||||
" ctx->lo1 = (dividend < 0) ? 1ull : 0xFFFFFFFFFFFFFFFFull; ctx->hi1 = (uint64_t)(int64_t)dividend; "
|
||||
"}} }}",
|
||||
inst.rt, inst.rs);
|
||||
case MMI_DIVU1:
|
||||
return fmt::format("{{ uint32_t divisor = GPR_U32(ctx, {}); if (divisor != 0) {{ ctx->lo1 = (uint64_t)(int64_t)(int32_t)(GPR_U32(ctx, {}) / divisor); ctx->hi1 = (uint64_t)(int64_t)(int32_t)(GPR_U32(ctx, {}) % divisor); }} else {{ ctx->lo1=0xFFFFFFFFFFFFFFFFull; ctx->hi1=(uint64_t)(int64_t)(int32_t)GPR_U32(ctx,{}); }} }}", rt, rs, rs, rs);
|
||||
case MMI_MADD:
|
||||
if (rd != 0)
|
||||
{
|
||||
return fmt::format("{{ uint64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); int64_t prod = (int64_t)GPR_S32(ctx, {}) * (int64_t)GPR_S32(ctx, {}); int64_t result = acc + prod; ctx->lo = Ps2SignExt32ToU64((uint32_t)result); ctx->hi = Ps2SignExt32ToU64((uint32_t)(result >> 32)); SET_GPR_S32(ctx, {}, (int32_t)result); }}", rs, rt, rd);
|
||||
}
|
||||
return fmt::format("{{ uint64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); int64_t prod = (int64_t)GPR_S32(ctx, {}) * (int64_t)GPR_S32(ctx, {}); int64_t result = acc + prod; ctx->lo = Ps2SignExt32ToU64((uint32_t)result); ctx->hi = Ps2SignExt32ToU64((uint32_t)(result >> 32)); }}", rs, rt);
|
||||
case MMI_MADDU:
|
||||
if (rd != 0)
|
||||
{
|
||||
return fmt::format("{{ uint64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); uint64_t prod = (uint64_t)GPR_U32(ctx, {}) * (uint64_t)GPR_U32(ctx, {}); uint64_t result = acc + prod; ctx->lo = Ps2SignExt32ToU64((uint32_t)result); ctx->hi = Ps2SignExt32ToU64((uint32_t)(result >> 32)); SET_GPR_S32(ctx, {}, (int32_t)result); }}", rs, rt, rd);
|
||||
}
|
||||
return fmt::format("{{ uint64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); uint64_t prod = (uint64_t)GPR_U32(ctx, {}) * (uint64_t)GPR_U32(ctx, {}); uint64_t result = acc + prod; ctx->lo = Ps2SignExt32ToU64((uint32_t)result); ctx->hi = Ps2SignExt32ToU64((uint32_t)(result >> 32)); }}", rs, rt);
|
||||
case MMI_MSUB:
|
||||
if (rd != 0)
|
||||
{
|
||||
return fmt::format("{{ uint64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); int64_t prod = (int64_t)GPR_S32(ctx, {}) * (int64_t)GPR_S32(ctx, {}); int64_t result = acc - prod; ctx->lo = Ps2SignExt32ToU64((uint32_t)result); ctx->hi = Ps2SignExt32ToU64((uint32_t)(result >> 32)); SET_GPR_S32(ctx, {}, (int32_t)result); }}", rs, rt, rd);
|
||||
}
|
||||
return fmt::format("{{ uint64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); int64_t prod = (int64_t)GPR_S32(ctx, {}) * (int64_t)GPR_S32(ctx, {}); int64_t result = acc - prod; ctx->lo = Ps2SignExt32ToU64((uint32_t)result); ctx->hi = Ps2SignExt32ToU64((uint32_t)(result >> 32)); }}", rs, rt);
|
||||
case MMI_MSUBU:
|
||||
if (rd != 0)
|
||||
{
|
||||
return fmt::format("{{ uint64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); uint64_t prod = (uint64_t)GPR_U32(ctx, {}) * (uint64_t)GPR_U32(ctx, {}); uint64_t result = acc - prod; ctx->lo = Ps2SignExt32ToU64((uint32_t)result); ctx->hi = Ps2SignExt32ToU64((uint32_t)(result >> 32)); SET_GPR_S32(ctx, {}, (int32_t)result); }}", rs, rt, rd);
|
||||
}
|
||||
return fmt::format("{{ uint64_t acc = Ps2HiLoToU64(ctx->hi, ctx->lo); uint64_t prod = (uint64_t)GPR_U32(ctx, {}) * (uint64_t)GPR_U32(ctx, {}); uint64_t result = acc - prod; ctx->lo = Ps2SignExt32ToU64((uint32_t)result); ctx->hi = Ps2SignExt32ToU64((uint32_t)(result >> 32)); }}", rs, rt);
|
||||
case MMI_MADD1:
|
||||
if (rd != 0)
|
||||
{
|
||||
return fmt::format("{{ uint64_t acc = Ps2HiLoToU64(ctx->hi1, ctx->lo1); int64_t prod = (int64_t)GPR_S32(ctx, {}) * (int64_t)GPR_S32(ctx, {}); int64_t result = acc + prod; ctx->lo1 = Ps2SignExt32ToU64((uint32_t)result); ctx->hi1 = Ps2SignExt32ToU64((uint32_t)(result >> 32)); SET_GPR_S32(ctx, {}, (int32_t)result); }}", rs, rt, rd);
|
||||
}
|
||||
return fmt::format("{{ uint64_t acc = Ps2HiLoToU64(ctx->hi1, ctx->lo1); int64_t prod = (int64_t)GPR_S32(ctx, {}) * (int64_t)GPR_S32(ctx, {}); int64_t result = acc + prod; ctx->lo1 = Ps2SignExt32ToU64((uint32_t)result); ctx->hi1 = Ps2SignExt32ToU64((uint32_t)(result >> 32)); }}", rs, rt);
|
||||
case MMI_MADDU1:
|
||||
if (rd != 0)
|
||||
{
|
||||
return fmt::format("{{ uint64_t acc = Ps2HiLoToU64(ctx->hi1, ctx->lo1); uint64_t prod = (uint64_t)GPR_U32(ctx, {}) * (uint64_t)GPR_U32(ctx, {}); uint64_t result = acc + prod; ctx->lo1 = Ps2SignExt32ToU64((uint32_t)result); ctx->hi1 = Ps2SignExt32ToU64((uint32_t)(result >> 32)); SET_GPR_S32(ctx, {}, (int32_t)result); }}", rs, rt, rd);
|
||||
}
|
||||
return fmt::format("{{ uint64_t acc = Ps2HiLoToU64(ctx->hi1, ctx->lo1); uint64_t prod = (uint64_t)GPR_U32(ctx, {}) * (uint64_t)GPR_U32(ctx, {}); uint64_t result = acc + prod; ctx->lo1 = Ps2SignExt32ToU64((uint32_t)result); ctx->hi1 = Ps2SignExt32ToU64((uint32_t)(result >> 32)); }}", rs, rt);
|
||||
case MMI_PLZCW:
|
||||
return fmt::format(
|
||||
"{{ "
|
||||
"uint64_t v = GPR_U64(ctx, {}); "
|
||||
"uint32_t lo = (uint32_t)(v & 0xFFFFFFFFu); "
|
||||
"uint32_t hi = (uint32_t)(v >> 32); "
|
||||
"uint64_t out = ((uint64_t)ps2_plzcw32(hi) << 32) | (uint64_t)ps2_plzcw32(lo); "
|
||||
"SET_GPR_U64(ctx, {}, out); "
|
||||
"}}",
|
||||
rs, rd);
|
||||
case MMI_PSLLH:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_slli_epi16(GPR_VEC(ctx, {}), {}));", rd, rt, sa);
|
||||
case MMI_PSRLH:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_srli_epi16(GPR_VEC(ctx, {}), {}));", rd, rt, sa);
|
||||
case MMI_PSRAH:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_srai_epi16(GPR_VEC(ctx, {}), {}));", rd, rt, sa);
|
||||
case MMI_PSLLW:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_slli_epi32(GPR_VEC(ctx, {}), {}));", rd, rt, sa);
|
||||
case MMI_PSRLW:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_srli_epi32(GPR_VEC(ctx, {}), {}));", rd, rt, sa);
|
||||
case MMI_PSRAW:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_srai_epi32(GPR_VEC(ctx, {}), {}));", rd, rt, sa);
|
||||
case MMI_MMI0:
|
||||
return m_codeGenerator.translateMMI0Instruction(inst);
|
||||
case MMI_MMI1:
|
||||
return m_codeGenerator.translateMMI1Instruction(inst);
|
||||
case MMI_MMI2:
|
||||
return m_codeGenerator.translateMMI2Instruction(inst);
|
||||
case MMI_MMI3:
|
||||
return m_codeGenerator.translateMMI3Instruction(inst);
|
||||
case MMI_PMFHL:
|
||||
return m_codeGenerator.translatePMFHLInstruction(inst);
|
||||
case MMI_PMTHL:
|
||||
return m_codeGenerator.translatePMTHLInstruction(inst);
|
||||
default:
|
||||
return m_codeGenerator.emitUnhandledInstruction(inst, fmt::format("Unhandled MMI instruction: function 0x{:X}", function));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -100,6 +100,7 @@ namespace ps2recomp
|
||||
|
||||
void writeCombinedOutputPreamble(std::ostream &output)
|
||||
{
|
||||
output << "#include <stdexcept>\n";
|
||||
output << "#include \"ps2_recompiled_functions.h\"\n\n";
|
||||
output << "#include \"ps2_runtime_macros.h\"\n";
|
||||
output << "#include \"ps2_runtime.h\"\n";
|
||||
@@ -728,14 +729,21 @@ namespace ps2recomp
|
||||
PS2Recompiler::PS2Recompiler(const std::string &configPath)
|
||||
: m_configManager(configPath)
|
||||
{
|
||||
m_configManager.setReporter(&m_reporter);
|
||||
}
|
||||
|
||||
PS2Recompiler::~PS2Recompiler() = default;
|
||||
|
||||
void PS2Recompiler::printReport() const
|
||||
{
|
||||
m_reporter.printSummary(std::cout);
|
||||
}
|
||||
|
||||
bool PS2Recompiler::initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
m_reporter.progress("parsing config");
|
||||
m_config = m_configManager.loadConfig();
|
||||
m_skipFunctions.clear();
|
||||
m_skipFunctionStarts.clear();
|
||||
@@ -771,20 +779,24 @@ namespace ps2recomp
|
||||
if (bindingIt != m_stubHandlerBindingsByStart.end() &&
|
||||
bindingIt->second != selector.name)
|
||||
{
|
||||
std::cerr << "Warning: Multiple stub handler bindings for 0x"
|
||||
<< std::hex << *selector.start << std::dec
|
||||
<< " (keeping latest '" << selector.name
|
||||
<< "', previous '" << bindingIt->second << "')" << std::endl;
|
||||
std::ostringstream msg;
|
||||
msg << "Multiple stub handler bindings for 0x"
|
||||
<< std::hex << *selector.start
|
||||
<< " (keeping latest '" << selector.name
|
||||
<< "', previous '" << bindingIt->second << "')";
|
||||
m_reporter.warning("config", msg.str());
|
||||
}
|
||||
m_stubHandlerBindingsByStart[*selector.start] = selector.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_reporter.progress("parsing ELF");
|
||||
m_elfParser = std::make_unique<ElfParser>(m_config.inputPath);
|
||||
m_elfParser->setReporter(&m_reporter);
|
||||
if (!m_elfParser->parse())
|
||||
{
|
||||
std::cerr << "Failed to parse ELF file: " << m_config.inputPath << std::endl;
|
||||
m_reporter.error("elf", "Failed to parse ELF file: " + m_config.inputPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -800,14 +812,18 @@ namespace ps2recomp
|
||||
|
||||
if (m_functions.empty())
|
||||
{
|
||||
std::cerr << "No functions found in ELF file." << std::endl;
|
||||
m_reporter.error("elf", "No functions found in ELF file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
m_bootstrapInfo = {};
|
||||
uint32_t entry = m_elfParser->getEntryPoint();
|
||||
std::cout << "ELF entry point: 0x" << std::hex << entry << std::dec << std::endl;
|
||||
{
|
||||
std::ostringstream msg;
|
||||
msg << "ELF entry point: 0x" << std::hex << entry;
|
||||
m_reporter.info("elf", msg.str());
|
||||
}
|
||||
uint32_t bssStart = std::numeric_limits<uint32_t>::max();
|
||||
uint32_t bssEnd = 0;
|
||||
for (const auto &sec : m_sections)
|
||||
@@ -831,12 +847,16 @@ namespace ps2recomp
|
||||
|
||||
if (bssStart != std::numeric_limits<uint32_t>::max())
|
||||
{
|
||||
std::cout << "BSS range: 0x" << std::hex << bssStart << " - 0x" << bssEnd
|
||||
<< " (size 0x" << (bssEnd - bssStart) << "), gp=0x" << gp << std::dec << std::endl;
|
||||
std::ostringstream msg;
|
||||
msg << "BSS range: 0x" << std::hex << bssStart << " - 0x" << bssEnd
|
||||
<< " (size 0x" << (bssEnd - bssStart) << "), gp=0x" << gp;
|
||||
m_reporter.info("elf", msg.str());
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "No BSS found, gp=0x" << std::hex << gp << std::dec << std::endl;
|
||||
std::ostringstream msg;
|
||||
msg << "No BSS found, gp=0x" << std::hex << gp;
|
||||
m_reporter.info("elf", msg.str());
|
||||
}
|
||||
|
||||
if (entry != 0)
|
||||
@@ -857,13 +877,19 @@ namespace ps2recomp
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "Extracted " << m_functions.size() << " functions, "
|
||||
<< m_symbols.size() << " symbols, "
|
||||
<< m_sections.size() << " sections, "
|
||||
<< m_relocations.size() << " relocations." << std::endl;
|
||||
m_reporter.recordDiscovered(m_functions.size(), m_symbols.size(), m_sections.size(), m_relocations.size());
|
||||
{
|
||||
std::ostringstream msg;
|
||||
msg << "extracted " << m_functions.size() << " functions, "
|
||||
<< m_symbols.size() << " symbols, "
|
||||
<< m_sections.size() << " sections, "
|
||||
<< m_relocations.size() << " relocations";
|
||||
m_reporter.progress(msg.str());
|
||||
}
|
||||
|
||||
m_decoder = std::make_unique<R5900Decoder>();
|
||||
m_codeGenerator = std::make_unique<CodeGenerator>(m_symbols, m_sections);
|
||||
m_codeGenerator->setReporter(&m_reporter);
|
||||
std::unordered_map<uint32_t, std::string> relocationCallNames;
|
||||
relocationCallNames.reserve(m_relocations.size());
|
||||
for (const auto &reloc : m_relocations)
|
||||
@@ -876,10 +902,11 @@ namespace ps2recomp
|
||||
auto inserted = relocationCallNames.emplace(reloc.offset, reloc.symbolName);
|
||||
if (!inserted.second && inserted.first->second != reloc.symbolName)
|
||||
{
|
||||
std::cerr << "Warning: multiple relocation symbols at 0x"
|
||||
<< std::hex << reloc.offset << std::dec
|
||||
<< " (keeping '" << inserted.first->second
|
||||
<< "', ignoring '" << reloc.symbolName << "')" << std::endl;
|
||||
std::ostringstream msg;
|
||||
msg << "multiple relocation symbols at 0x" << std::hex << reloc.offset
|
||||
<< " (keeping '" << inserted.first->second
|
||||
<< "', ignoring '" << reloc.symbolName << "')";
|
||||
m_reporter.warning("relocation", msg.str());
|
||||
}
|
||||
}
|
||||
m_codeGenerator->setRelocationCallNames(relocationCallNames);
|
||||
@@ -893,7 +920,7 @@ namespace ps2recomp
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::cerr << "Error during initialization: " << e.what() << std::endl;
|
||||
m_reporter.error("initialize", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -902,43 +929,55 @@ namespace ps2recomp
|
||||
{
|
||||
try
|
||||
{
|
||||
std::cout << "Recompiling " << m_functions.size() << " functions..." << std::endl;
|
||||
{
|
||||
std::ostringstream msg;
|
||||
msg << "recompiling " << m_functions.size() << " functions";
|
||||
m_reporter.progress(msg.str());
|
||||
}
|
||||
|
||||
size_t processedCount = 0;
|
||||
size_t failedCount = 0;
|
||||
for (auto &function : m_functions)
|
||||
{
|
||||
std::cout << "processing function: " << function.name << std::endl;
|
||||
m_reporter.recordFunctionProcessed();
|
||||
|
||||
if (isStubFunction(function))
|
||||
{
|
||||
function.isStub = true;
|
||||
function.isSkipped = false;
|
||||
m_reporter.recordFunctionStubbed();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (shouldSkipFunction(function))
|
||||
{
|
||||
std::cout << "Skipping function (runtime TODO wrapper): " << function.name << std::endl;
|
||||
function.isSkipped = true;
|
||||
function.isStub = false;
|
||||
m_reporter.recordFunctionSkipped();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!decodeFunction(function))
|
||||
{
|
||||
++failedCount;
|
||||
std::cerr << "Skipping function due decode failure: " << function.name << std::endl;
|
||||
m_reporter.recordDecodeFailure();
|
||||
m_reporter.recordFunctionSkipped();
|
||||
m_reporter.warningAt("decode", function.name, function.start, "Skipping function due decode failure");
|
||||
function.isSkipped = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
function.isRecompiled = true;
|
||||
m_reporter.recordFunctionRecompiled();
|
||||
#if _DEBUG
|
||||
processedCount++;
|
||||
if (processedCount % 100 == 0)
|
||||
{
|
||||
std::cout << "Processed " << processedCount << " functions." << std::endl;
|
||||
{
|
||||
std::ostringstream msg;
|
||||
msg << "processed " << processedCount << " functions";
|
||||
m_reporter.progress(msg.str());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -947,15 +986,17 @@ namespace ps2recomp
|
||||
|
||||
if (failedCount > 0)
|
||||
{
|
||||
std::cerr << "Recompile completed with " << failedCount << " function(s) skipped due decode issues." << std::endl;
|
||||
std::ostringstream msg;
|
||||
msg << "Recompile completed with " << failedCount << " function(s) skipped due decode issues.";
|
||||
m_reporter.warning("decode", msg.str());
|
||||
}
|
||||
|
||||
std::cout << "Recompilation completed successfully." << std::endl;
|
||||
m_reporter.progress("recompilation pass completed");
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::cerr << "Error during recompilation: " << e.what() << std::endl;
|
||||
m_reporter.error("recompile", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1084,7 +1125,10 @@ namespace ps2recomp
|
||||
}
|
||||
}
|
||||
|
||||
generateFunctionHeader();
|
||||
if (!generateFunctionHeader())
|
||||
{
|
||||
throw std::runtime_error("Failed to generate function header");
|
||||
}
|
||||
|
||||
std::vector<const Function *> outputFunctions;
|
||||
outputFunctions.reserve(m_functions.size());
|
||||
@@ -1096,10 +1140,16 @@ namespace ps2recomp
|
||||
}
|
||||
}
|
||||
|
||||
m_reporter.recordGeneratedFunctions(outputFunctions.size());
|
||||
|
||||
const size_t outputWorkerCount = m_config.lowMemoryMode ? 1 : resolveOutputWorkerCount(m_config.outputWorkerThreads);
|
||||
if (outputFunctions.size() > 1 && outputWorkerCount > 1)
|
||||
{
|
||||
std::cout << "Generating function output with " << outputWorkerCount << " worker(s)." << std::endl;
|
||||
{
|
||||
std::ostringstream msg;
|
||||
msg << "generating function output with " << outputWorkerCount << " worker(s)";
|
||||
m_reporter.progress(msg.str());
|
||||
}
|
||||
}
|
||||
|
||||
const auto &generatedStubs = m_generatedStubs;
|
||||
@@ -1133,10 +1183,11 @@ namespace ps2recomp
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::cerr << "Error generating code for function "
|
||||
<< function.name << " (start 0x"
|
||||
<< std::hex << function.start << std::dec << "): "
|
||||
<< e.what() << std::endl;
|
||||
{
|
||||
std::ostringstream msg;
|
||||
msg << "Error generating code: " << e.what();
|
||||
m_reporter.errorAt("codegen", function.name, function.start, msg.str());
|
||||
}
|
||||
throw;
|
||||
}
|
||||
};
|
||||
@@ -1347,7 +1398,11 @@ namespace ps2recomp
|
||||
throw std::runtime_error("Failed to finish combined output: " + outputPath.string());
|
||||
}
|
||||
|
||||
std::cout << "Wrote recompiled to combined output to: " << outputPath << std::endl;
|
||||
{
|
||||
std::ostringstream msg;
|
||||
msg << "wrote combined output to " << outputPath;
|
||||
m_reporter.progress(msg.str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1524,7 +1579,11 @@ namespace ps2recomp
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "Wrote individual function files to: " << m_config.outputPath << std::endl;
|
||||
{
|
||||
std::ostringstream msg;
|
||||
msg << "wrote individual function files to " << m_config.outputPath;
|
||||
m_reporter.progress(msg.str());
|
||||
}
|
||||
}
|
||||
|
||||
m_decodedFunctions.clear();
|
||||
@@ -1537,13 +1596,22 @@ namespace ps2recomp
|
||||
{
|
||||
throw std::runtime_error("Failed to write function registration file: " + registerPath.string());
|
||||
}
|
||||
std::cout << "Generated function registration file: " << registerPath << std::endl;
|
||||
{
|
||||
std::ostringstream msg;
|
||||
msg << "generated function registration file: " << registerPath;
|
||||
m_reporter.progress(msg.str());
|
||||
}
|
||||
|
||||
generateStubHeader();
|
||||
if (!generateStubHeader())
|
||||
{
|
||||
throw std::runtime_error("Failed to generate stub header");
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::cerr << "Error during output generation: " << e.what() << std::endl;
|
||||
m_reporter.error("output", e.what());
|
||||
m_reporter.printSummary(std::cout);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1588,12 +1656,16 @@ namespace ps2recomp
|
||||
fs::path headerPath = fs::path(m_config.outputPath) / "ps2_recompiled_stubs.h";
|
||||
writeToFile(headerPath.string(), ss.str());
|
||||
|
||||
std::cout << "Generated generating header file: " << headerPath << std::endl;
|
||||
{
|
||||
std::ostringstream msg;
|
||||
msg << "generated stub header file: " << headerPath;
|
||||
m_reporter.progress(msg.str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::cerr << "Error generating stub header: " << e.what() << std::endl;
|
||||
m_reporter.error("stub-header", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1628,12 +1700,16 @@ namespace ps2recomp
|
||||
fs::path headerPath = fs::path(m_config.outputPath) / "ps2_recompiled_functions.h";
|
||||
writeToFile(headerPath.string(), ss.str());
|
||||
|
||||
std::cout << "Generated function header file: " << headerPath << std::endl;
|
||||
{
|
||||
std::ostringstream msg;
|
||||
msg << "generated function header file: " << headerPath;
|
||||
m_reporter.progress(msg.str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::cerr << "Error generating function header: " << e.what() << std::endl;
|
||||
m_reporter.error("function-header", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1757,10 +1833,13 @@ namespace ps2recomp
|
||||
|
||||
if (totalTargets > 0u)
|
||||
{
|
||||
std::cout << "Collected " << totalTargets
|
||||
<< " resumable entry point(s) across "
|
||||
<< m_resumeEntryTargetsByOwner.size()
|
||||
<< " owner function(s)." << std::endl;
|
||||
m_reporter.recordAdditionalEntryPoints(totalTargets);
|
||||
std::ostringstream msg;
|
||||
msg << "collected " << totalTargets
|
||||
<< " resumable entry point(s) across "
|
||||
<< m_resumeEntryTargetsByOwner.size()
|
||||
<< " owner function(s)";
|
||||
m_reporter.progress(msg.str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1778,9 +1857,11 @@ namespace ps2recomp
|
||||
{
|
||||
if (!m_elfParser->isValidAddress(address))
|
||||
{
|
||||
std::cerr << "Invalid address: 0x" << std::hex << address << std::dec
|
||||
<< " in function: " << function.name
|
||||
<< " (truncating decode)" << std::endl;
|
||||
{
|
||||
std::ostringstream msg;
|
||||
msg << "Invalid address 0x" << std::hex << address << " (truncating decode)";
|
||||
m_reporter.warningAt("decode", function.name, address, msg.str());
|
||||
}
|
||||
truncated = true;
|
||||
break;
|
||||
}
|
||||
@@ -1797,13 +1878,21 @@ namespace ps2recomp
|
||||
try
|
||||
{
|
||||
rawInstruction = std::stoul(patchIt->second, nullptr, 0);
|
||||
std::cout << "Applied patch at 0x" << std::hex << address << std::dec << std::endl;
|
||||
{
|
||||
std::ostringstream msg;
|
||||
msg << "Applied patch at 0x" << std::hex << address;
|
||||
m_reporter.info("patch", msg.str());
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::cerr << "Invalid patch value at 0x" << std::hex << address << std::dec
|
||||
<< " (" << patchIt->second << "): " << e.what()
|
||||
<< ". Using original instruction." << std::endl;
|
||||
{
|
||||
std::ostringstream msg;
|
||||
msg << "Invalid patch value at 0x" << std::hex << address
|
||||
<< " (" << patchIt->second << "): " << e.what()
|
||||
<< ". Using original instruction.";
|
||||
m_reporter.warningAt("patch", function.name, address, msg.str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1821,9 +1910,11 @@ namespace ps2recomp
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::cerr << "Error decoding instruction at 0x" << std::hex << address << std::dec
|
||||
<< " in function: " << function.name << ": " << e.what()
|
||||
<< " (truncating decode)" << std::endl;
|
||||
{
|
||||
std::ostringstream msg;
|
||||
msg << "Error decoding instruction: " << e.what() << " (truncating decode)";
|
||||
m_reporter.warningAt("decode", function.name, address, msg.str());
|
||||
}
|
||||
truncated = true;
|
||||
break;
|
||||
}
|
||||
@@ -1831,8 +1922,11 @@ namespace ps2recomp
|
||||
|
||||
if (instructions.empty())
|
||||
{
|
||||
std::cerr << "No decodable instructions found for function: " << function.name
|
||||
<< " (0x" << std::hex << function.start << ")" << std::dec << std::endl;
|
||||
{
|
||||
std::ostringstream msg;
|
||||
msg << "No decodable instructions found at 0x" << std::hex << function.start;
|
||||
m_reporter.warningAt("decode", function.name, function.start, msg.str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1875,7 +1969,7 @@ namespace ps2recomp
|
||||
std::ofstream file(path);
|
||||
if (!file)
|
||||
{
|
||||
std::cerr << "Failed to open file for writing: " << path << std::endl;
|
||||
m_reporter.error("file", "Failed to open file for writing: " + path);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1934,7 +2028,7 @@ namespace ps2recomp
|
||||
{
|
||||
if (maxLength == 0)
|
||||
{
|
||||
std::cerr << "clampFilenameLength::maxLength must be greater than 0" << std::endl;
|
||||
// Keep this static helper side-effect free; callers validate arguments.
|
||||
//Better go over the limit than create files with an empty path
|
||||
return baseName + extension;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
#include "ps2recomp/recompiler_reporter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
namespace
|
||||
{
|
||||
const char *severityName(RecompilerReporter::Severity severity)
|
||||
{
|
||||
switch (severity)
|
||||
{
|
||||
case RecompilerReporter::Severity::Info:
|
||||
return "info";
|
||||
case RecompilerReporter::Severity::Warning:
|
||||
return "warning";
|
||||
case RecompilerReporter::Severity::Error:
|
||||
return "error";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
std::string hexAddress(uint32_t address)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << "0x" << std::hex << address;
|
||||
return ss.str();
|
||||
}
|
||||
}
|
||||
|
||||
void RecompilerReporter::progress(const std::string &message)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
std::cout << "[recompiler] " << message << std::endl;
|
||||
}
|
||||
|
||||
void RecompilerReporter::info(const std::string &category, const std::string &message)
|
||||
{
|
||||
addEvent(Severity::Info, category, message);
|
||||
}
|
||||
|
||||
void RecompilerReporter::warning(const std::string &category, const std::string &message)
|
||||
{
|
||||
addEvent(Severity::Warning, category, message);
|
||||
}
|
||||
|
||||
void RecompilerReporter::error(const std::string &category, const std::string &message)
|
||||
{
|
||||
addEvent(Severity::Error, category, message);
|
||||
}
|
||||
|
||||
void RecompilerReporter::warningAt(const std::string &category, const std::string &functionName, uint32_t address, const std::string &message)
|
||||
{
|
||||
addEvent(Severity::Warning, category, message, functionName, address, true);
|
||||
}
|
||||
|
||||
void RecompilerReporter::errorAt(const std::string &category, const std::string &functionName, uint32_t address, const std::string &message)
|
||||
{
|
||||
addEvent(Severity::Error, category, message, functionName, address, true);
|
||||
}
|
||||
|
||||
void RecompilerReporter::recordDiscovered(size_t functions, size_t symbols, size_t sections, size_t relocations)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_counters.functionsDiscovered = functions;
|
||||
m_counters.symbolsDiscovered = symbols;
|
||||
m_counters.sectionsDiscovered = sections;
|
||||
m_counters.relocationsDiscovered = relocations;
|
||||
}
|
||||
|
||||
void RecompilerReporter::recordFunctionProcessed()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
++m_counters.functionsProcessed;
|
||||
}
|
||||
|
||||
void RecompilerReporter::recordFunctionRecompiled()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
++m_counters.functionsRecompiled;
|
||||
}
|
||||
|
||||
void RecompilerReporter::recordFunctionStubbed()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
++m_counters.functionsStubbed;
|
||||
}
|
||||
|
||||
void RecompilerReporter::recordFunctionSkipped()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
++m_counters.functionsSkipped;
|
||||
}
|
||||
|
||||
void RecompilerReporter::recordDecodeFailure()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
++m_counters.decodeFailures;
|
||||
}
|
||||
|
||||
void RecompilerReporter::recordAdditionalEntryPoints(size_t count)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_counters.additionalEntryPoints += count;
|
||||
}
|
||||
|
||||
void RecompilerReporter::recordGeneratedFunctions(size_t count)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_counters.generatedFunctions += count;
|
||||
}
|
||||
|
||||
void RecompilerReporter::recordIndirectFallbackPromotion(const std::string &functionName,
|
||||
const std::vector<uint32_t> &jumpAddresses,
|
||||
size_t promotedEntryCount)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << "unresolved JR/JALR at";
|
||||
for (uint32_t address : jumpAddresses)
|
||||
{
|
||||
ss << ' ' << hexAddress(address);
|
||||
}
|
||||
ss << "; promoted " << promotedEntryCount << " fallback entr" << (promotedEntryCount == 1 ? "y" : "ies");
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
++m_counters.indirectFallbackPromotions;
|
||||
m_counters.indirectFallbackEntries += promotedEntryCount;
|
||||
m_events.push_back(Event{Severity::Warning, "control-flow", ss.str(), functionName, jumpAddresses.empty() ? 0u : jumpAddresses.front(), !jumpAddresses.empty()});
|
||||
}
|
||||
}
|
||||
|
||||
void RecompilerReporter::recordUnhandledInstruction(const std::string &functionName,
|
||||
uint32_t address,
|
||||
uint32_t raw,
|
||||
const std::string &message)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << message << " raw=0x" << std::hex << raw;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
++m_counters.unhandledInstructions;
|
||||
m_events.push_back(Event{Severity::Error, "unhandled-instruction", ss.str(), functionName, address, true});
|
||||
}
|
||||
}
|
||||
|
||||
const RecompilerReporter::Counters &RecompilerReporter::counters() const
|
||||
{
|
||||
return m_counters;
|
||||
}
|
||||
|
||||
bool RecompilerReporter::hasErrors() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return std::any_of(m_events.begin(), m_events.end(), [](const Event &event) { return event.severity == Severity::Error; });
|
||||
}
|
||||
|
||||
bool RecompilerReporter::hasWarnings() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return std::any_of(m_events.begin(), m_events.end(), [](const Event &event) { return event.severity == Severity::Warning; });
|
||||
}
|
||||
|
||||
void RecompilerReporter::printSummary(std::ostream &os) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
os << "\n========== PS2Recomp report ==========" << std::endl;
|
||||
os << "Functions discovered: " << m_counters.functionsDiscovered << std::endl;
|
||||
os << "Symbols: " << m_counters.symbolsDiscovered
|
||||
<< ", sections: " << m_counters.sectionsDiscovered
|
||||
<< ", relocations: " << m_counters.relocationsDiscovered << std::endl;
|
||||
os << "Functions processed: " << m_counters.functionsProcessed
|
||||
<< ", recompiled: " << m_counters.functionsRecompiled
|
||||
<< ", stubs: " << m_counters.functionsStubbed
|
||||
<< ", skipped: " << m_counters.functionsSkipped
|
||||
<< ", decode failures: " << m_counters.decodeFailures << std::endl;
|
||||
os << "Additional entrypoints: " << m_counters.additionalEntryPoints << std::endl;
|
||||
os << "Generated functions: " << m_counters.generatedFunctions << std::endl;
|
||||
os << "Indirect fallback promotions: " << m_counters.indirectFallbackPromotions
|
||||
<< " (" << m_counters.indirectFallbackEntries << " fallback entries)" << std::endl;
|
||||
os << "Unhandled instructions: " << m_counters.unhandledInstructions << std::endl;
|
||||
|
||||
size_t warnings = 0;
|
||||
size_t errors = 0;
|
||||
for (const Event &event : m_events)
|
||||
{
|
||||
warnings += event.severity == Severity::Warning ? 1u : 0u;
|
||||
errors += event.severity == Severity::Error ? 1u : 0u;
|
||||
}
|
||||
os << "Warnings: " << warnings << ", errors: " << errors << std::endl;
|
||||
|
||||
if (!m_events.empty())
|
||||
{
|
||||
os << "\nEvents:" << std::endl;
|
||||
const size_t count = m_events.size();
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
const Event &event = m_events[i];
|
||||
os << " [" << severityName(event.severity) << "] " << event.category;
|
||||
if (!event.functionName.empty())
|
||||
{
|
||||
os << " function=" << event.functionName;
|
||||
}
|
||||
if (event.hasAddress)
|
||||
{
|
||||
os << " addr=" << hexAddress(event.address);
|
||||
}
|
||||
os << " - " << event.message << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
os << "======================================" << std::endl;
|
||||
}
|
||||
|
||||
void RecompilerReporter::addEvent(Severity severity,
|
||||
const std::string &category,
|
||||
const std::string &message,
|
||||
const std::string &functionName,
|
||||
uint32_t address,
|
||||
bool hasAddress)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_events.push_back(Event{severity, category, message, functionName, address, hasAddress});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#include "ps2recomp/Translators/regimm_translator.h"
|
||||
#include "ps2recomp/code_generator.h"
|
||||
#include "ps2recomp/codegen_helpers.h"
|
||||
#include "ps2recomp/instructions.h"
|
||||
#include "ps2recomp/types.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <sstream>
|
||||
#include <cmath>
|
||||
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
RegimmTranslator::RegimmTranslator(CodeGenerator &codeGenerator)
|
||||
: m_codeGenerator(codeGenerator)
|
||||
{
|
||||
}
|
||||
|
||||
std::string RegimmTranslator::translate(const Instruction &inst)
|
||||
{
|
||||
switch (inst.rt)
|
||||
{
|
||||
case REGIMM_BLTZ:
|
||||
case REGIMM_BGEZ:
|
||||
case REGIMM_BLTZL:
|
||||
case REGIMM_BGEZL:
|
||||
case REGIMM_BLTZAL:
|
||||
case REGIMM_BGEZAL:
|
||||
case REGIMM_BLTZALL:
|
||||
case REGIMM_BGEZALL:
|
||||
{
|
||||
const int32_t offsetBytes = (static_cast<int32_t>(static_cast<int16_t>(inst.simmediate)) << 2);
|
||||
const uint32_t target = static_cast<uint32_t>(static_cast<int64_t>(inst.address + 4u) + static_cast<int64_t>(offsetBytes));
|
||||
return fmt::format("// REGIMM branch instruction to 0x{:X} - Handled by branch logic", target);
|
||||
}
|
||||
case REGIMM_MTSAB:
|
||||
return fmt::format("ctx->sa = ((GPR_U32(ctx, {}) ^ (uint32_t){}) & 0xF) << 3;", inst.rs, inst.simmediate);
|
||||
case REGIMM_MTSAH:
|
||||
return fmt::format("ctx->sa = ((GPR_U32(ctx, {}) ^ (uint32_t){}) & 0x7) << 4;", inst.rs, inst.simmediate);
|
||||
case REGIMM_TGEI:
|
||||
return fmt::format("if (GPR_S64(ctx, {}) >= (int64_t)(int32_t){}) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.simmediate);
|
||||
case REGIMM_TGEIU:
|
||||
return fmt::format("if (GPR_U64(ctx, {}) >= (uint64_t)(int64_t)(int32_t){}) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.simmediate);
|
||||
case REGIMM_TLTI:
|
||||
return fmt::format("if (GPR_S64(ctx, {}) < (int64_t)(int32_t){}) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.simmediate);
|
||||
case REGIMM_TLTIU:
|
||||
return fmt::format("if (GPR_U64(ctx, {}) < (uint64_t)(int64_t)(int32_t){}) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.simmediate);
|
||||
case REGIMM_TEQI:
|
||||
return fmt::format("if (GPR_S64(ctx, {}) == (int64_t)(int32_t){}) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.simmediate);
|
||||
case REGIMM_TNEI:
|
||||
return fmt::format("if (GPR_S64(ctx, {}) != (int64_t)(int32_t){}) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.simmediate);
|
||||
default:
|
||||
return m_codeGenerator.emitUnhandledInstruction(inst, fmt::format("Unhandled REGIMM instruction: 0x{:X}", inst.rt));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
#include "ps2recomp/Translators/special_translator.h"
|
||||
#include "ps2recomp/code_generator.h"
|
||||
#include "ps2recomp/codegen_helpers.h"
|
||||
#include "ps2recomp/instructions.h"
|
||||
#include "ps2recomp/types.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <sstream>
|
||||
#include <cmath>
|
||||
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
SpecialTranslator::SpecialTranslator(CodeGenerator &codeGenerator)
|
||||
: m_codeGenerator(codeGenerator)
|
||||
{
|
||||
}
|
||||
|
||||
std::string SpecialTranslator::translate(const Instruction &inst)
|
||||
{
|
||||
switch (inst.function)
|
||||
{
|
||||
case SPECIAL_SLL:
|
||||
if (inst.rd == 0 && inst.rt == 0 && inst.sa == 0)
|
||||
return "// NOP";
|
||||
if (inst.rd == 0)
|
||||
return "";
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)SLL32(GPR_U32(ctx, {}), {}));", inst.rd, inst.rt, inst.sa);
|
||||
case SPECIAL_SRL:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)SRL32(GPR_U32(ctx, {}), {}));", inst.rd, inst.rt, inst.sa);
|
||||
case SPECIAL_SRA:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, SRA32(GPR_S32(ctx, {}), {}));", inst.rd, inst.rt, inst.sa);
|
||||
case SPECIAL_SLLV:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)SLL32(GPR_U32(ctx, {}), GPR_U32(ctx, {}) & 0x1F));", inst.rd, inst.rt, inst.rs);
|
||||
case SPECIAL_SRLV:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)SRL32(GPR_U32(ctx, {}), GPR_U32(ctx, {}) & 0x1F));", inst.rd, inst.rt, inst.rs);
|
||||
case SPECIAL_SRAV:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, SRA32(GPR_S32(ctx, {}), GPR_U32(ctx, {}) & 0x1F));", inst.rd, inst.rt, inst.rs);
|
||||
case SPECIAL_JR:
|
||||
return fmt::format("// JR ${} - Handled by branch logic", inst.rs);
|
||||
case SPECIAL_JALR:
|
||||
return fmt::format("// JALR ${}, ${} - Handled by branch logic", inst.rd, inst.rs);
|
||||
case SPECIAL_SYSCALL:
|
||||
return fmt::format("runtime->handleSyscall(rdram, ctx, 0x{:X}u);", (inst.raw >> 6) & 0xFFFFFu);
|
||||
case SPECIAL_BREAK:
|
||||
return fmt::format("runtime->handleBreak(rdram, ctx);");
|
||||
case SPECIAL_SYNC:
|
||||
return "// SYNC instruction - memory barrier\n// In recompiled code, we don't need explicit memory barriers";
|
||||
case SPECIAL_MFHI:
|
||||
return fmt::format("SET_GPR_U64(ctx, {}, ctx->hi);", inst.rd);
|
||||
case SPECIAL_MTHI:
|
||||
return fmt::format("ctx->hi = GPR_U64(ctx, {});", inst.rs);
|
||||
case SPECIAL_MFLO:
|
||||
return fmt::format("SET_GPR_U64(ctx, {}, ctx->lo);", inst.rd);
|
||||
case SPECIAL_MTLO:
|
||||
return fmt::format("ctx->lo = GPR_U64(ctx, {});", inst.rs);
|
||||
case SPECIAL_MULT:
|
||||
if (inst.rd != 0)
|
||||
{
|
||||
return fmt::format("{{ int64_t result = (int64_t)GPR_S32(ctx, {}) * (int64_t)GPR_S32(ctx, {}); ctx->lo = (uint64_t)(int64_t)(int32_t)result; ctx->hi = (uint64_t)(int64_t)(int32_t)(result >> 32); SET_GPR_S32(ctx, {}, (int32_t)result); }}", inst.rs, inst.rt, inst.rd);
|
||||
}
|
||||
return fmt::format("{{ int64_t result = (int64_t)GPR_S32(ctx, {}) * (int64_t)GPR_S32(ctx, {}); ctx->lo = (uint64_t)(int64_t)(int32_t)result; ctx->hi = (uint64_t)(int64_t)(int32_t)(result >> 32); }}", inst.rs, inst.rt);
|
||||
case SPECIAL_MULTU:
|
||||
if (inst.rd != 0)
|
||||
{
|
||||
return fmt::format("{{ uint64_t result = (uint64_t)GPR_U32(ctx, {}) * (uint64_t)GPR_U32(ctx, {}); ctx->lo = (uint64_t)(int64_t)(int32_t)result; ctx->hi = (uint64_t)(int64_t)(int32_t)(result >> 32); SET_GPR_S32(ctx, {}, (int32_t)result); }}", inst.rs, inst.rt, inst.rd);
|
||||
}
|
||||
return fmt::format("{{ uint64_t result = (uint64_t)GPR_U32(ctx, {}) * (uint64_t)GPR_U32(ctx, {}); ctx->lo = (uint64_t)(int64_t)(int32_t)result; ctx->hi = (uint64_t)(int64_t)(int32_t)(result >> 32); }}", inst.rs, inst.rt);
|
||||
case SPECIAL_DIV:
|
||||
return fmt::format("{{ int32_t divisor = GPR_S32(ctx, {}); "
|
||||
" int32_t dividend = GPR_S32(ctx, {}); "
|
||||
" if (divisor != 0) {{ "
|
||||
" if (divisor == -1 && dividend == INT32_MIN) {{ "
|
||||
" ctx->lo = (uint64_t)(int64_t)INT32_MIN; ctx->hi = 0; "
|
||||
" }} else {{ "
|
||||
" ctx->lo = (uint64_t)(int64_t)(dividend / divisor); "
|
||||
" ctx->hi = (uint64_t)(int64_t)(dividend % divisor); "
|
||||
" }} "
|
||||
" }} else {{ "
|
||||
" ctx->lo = (dividend < 0) ? 1ull : 0xFFFFFFFFFFFFFFFFull; ctx->hi = (uint64_t)(int64_t)dividend; "
|
||||
" }} }}",
|
||||
inst.rt, inst.rs);
|
||||
case SPECIAL_DIVU:
|
||||
return fmt::format("{{ uint32_t divisor = GPR_U32(ctx, {}); if (divisor != 0) {{ ctx->lo = (uint64_t)(int64_t)(int32_t)(GPR_U32(ctx, {}) / divisor); ctx->hi = (uint64_t)(int64_t)(int32_t)(GPR_U32(ctx, {}) % divisor); }} else {{ ctx->lo = 0xFFFFFFFFFFFFFFFFull; ctx->hi = (uint64_t)(int64_t)(int32_t)GPR_U32(ctx,{}); }} }}", inst.rt, inst.rs, inst.rs, inst.rs);
|
||||
case SPECIAL_ADD:
|
||||
return fmt::format(
|
||||
"{{ "
|
||||
" int32_t rs_val = GPR_S32(ctx, {}); "
|
||||
" int32_t rt_val = GPR_S32(ctx, {}); "
|
||||
" int64_t result = (int64_t)rs_val + (int64_t)rt_val; "
|
||||
" if (result > INT32_MAX || result < INT32_MIN) {{ "
|
||||
" runtime->SignalException(ctx, EXCEPTION_INTEGER_OVERFLOW); "
|
||||
" }} else {{ "
|
||||
" SET_GPR_S32(ctx, {}, (int32_t)result); "
|
||||
" }} "
|
||||
"}}",
|
||||
inst.rs, inst.rt, inst.rd);
|
||||
case SPECIAL_ADDU:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)ADD32(GPR_U32(ctx, {}), GPR_U32(ctx, {})));", inst.rd, inst.rs, inst.rt);
|
||||
case SPECIAL_SUB:
|
||||
return fmt::format(
|
||||
"{{ uint32_t tmp; bool ov; "
|
||||
"SUB32_OV(GPR_U32(ctx, {}), GPR_U32(ctx, {}), tmp, ov); "
|
||||
"if (ov) runtime->SignalException(ctx, EXCEPTION_INTEGER_OVERFLOW); "
|
||||
"else SET_GPR_S32(ctx, {}, (int32_t)tmp); }}",
|
||||
inst.rs, inst.rt, inst.rd);
|
||||
case SPECIAL_SUBU:
|
||||
return fmt::format("SET_GPR_S32(ctx, {}, (int32_t)SUB32(GPR_U32(ctx, {}), GPR_U32(ctx, {})));", inst.rd, inst.rs, inst.rt);
|
||||
case SPECIAL_AND:
|
||||
return fmt::format("SET_GPR_U64(ctx, {}, GPR_U64(ctx, {}) & GPR_U64(ctx, {}));", inst.rd, inst.rs, inst.rt);
|
||||
case SPECIAL_OR:
|
||||
return fmt::format("SET_GPR_U64(ctx, {}, GPR_U64(ctx, {}) | GPR_U64(ctx, {}));", inst.rd, inst.rs, inst.rt);
|
||||
case SPECIAL_XOR:
|
||||
return fmt::format("SET_GPR_U64(ctx, {}, GPR_U64(ctx, {}) ^ GPR_U64(ctx, {}));", inst.rd, inst.rs, inst.rt);
|
||||
case SPECIAL_NOR:
|
||||
return fmt::format("SET_GPR_U64(ctx, {}, ~(GPR_U64(ctx, {}) | GPR_U64(ctx, {})));", inst.rd, inst.rs, inst.rt);
|
||||
case SPECIAL_SLT:
|
||||
return fmt::format("SET_GPR_U64(ctx, {}, ((int64_t)GPR_S64(ctx, {}) < (int64_t)GPR_S64(ctx, {})) ? 1 : 0);", inst.rd, inst.rs, inst.rt);
|
||||
case SPECIAL_SLTU:
|
||||
return fmt::format("SET_GPR_U64(ctx, {}, ((uint64_t)GPR_U64(ctx, {}) < (uint64_t)GPR_U64(ctx, {})) ? 1 : 0);", inst.rd, inst.rs, inst.rt);
|
||||
case SPECIAL_MOVZ:
|
||||
return fmt::format("if (GPR_U64(ctx, {}) == 0) SET_GPR_VEC(ctx, {}, GPR_VEC(ctx, {}));", inst.rt, inst.rd, inst.rs);
|
||||
case SPECIAL_MOVN:
|
||||
return fmt::format("if (GPR_U64(ctx, {}) != 0) SET_GPR_VEC(ctx, {}, GPR_VEC(ctx, {}));", inst.rt, inst.rd, inst.rs);
|
||||
case SPECIAL_MFSA:
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, ctx->sa);", inst.rd);
|
||||
case SPECIAL_MTSA:
|
||||
return fmt::format("ctx->sa = GPR_U32(ctx, {}) & 0x7F;", inst.rs);
|
||||
case SPECIAL_DADD:
|
||||
return fmt::format(
|
||||
"{{ int64_t a = (int64_t)GPR_S64(ctx, {}); "
|
||||
"int64_t b = (int64_t)GPR_S64(ctx, {}); "
|
||||
"int64_t r = a + b; "
|
||||
"if (((a ^ b) >= 0) && ((a ^ r) < 0)) runtime->SignalException(ctx, EXCEPTION_INTEGER_OVERFLOW); "
|
||||
"else SET_GPR_S64(ctx, {}, r); }}",
|
||||
inst.rs, inst.rt, inst.rd);
|
||||
case SPECIAL_DADDU:
|
||||
return fmt::format(
|
||||
"SET_GPR_U64(ctx, {}, (uint64_t)GPR_U64(ctx, {}) + (uint64_t)GPR_U64(ctx, {}));",
|
||||
inst.rd, inst.rs, inst.rt);
|
||||
case SPECIAL_DSUB:
|
||||
return fmt::format(
|
||||
"{{ int64_t a = (int64_t)GPR_S64(ctx, {}); "
|
||||
"int64_t b = (int64_t)GPR_S64(ctx, {}); "
|
||||
"int64_t r = a - b; "
|
||||
"if (((a ^ b) < 0) && ((a ^ r) < 0)) runtime->SignalException(ctx, EXCEPTION_INTEGER_OVERFLOW); "
|
||||
"else SET_GPR_S64(ctx, {}, r); }}",
|
||||
inst.rs, inst.rt, inst.rd);
|
||||
case SPECIAL_DSUBU:
|
||||
return fmt::format("SET_GPR_U64(ctx, {}, GPR_U64(ctx, {}) - GPR_U64(ctx, {}));", inst.rd, inst.rs, inst.rt);
|
||||
case SPECIAL_DSLL:
|
||||
return fmt::format("SET_GPR_U64(ctx, {}, GPR_U64(ctx, {}) << {});", inst.rd, inst.rt, inst.sa);
|
||||
case SPECIAL_DSRL:
|
||||
return fmt::format("SET_GPR_U64(ctx, {}, GPR_U64(ctx, {}) >> {});", inst.rd, inst.rt, inst.sa);
|
||||
case SPECIAL_DSRA:
|
||||
return fmt::format("SET_GPR_S64(ctx, {}, GPR_S64(ctx, {}) >> {});", inst.rd, inst.rt, inst.sa);
|
||||
case SPECIAL_DSLLV:
|
||||
return fmt::format("SET_GPR_U64(ctx, {}, GPR_U64(ctx, {}) << (GPR_U32(ctx, {}) & 0x3F));", inst.rd, inst.rt, inst.rs);
|
||||
case SPECIAL_DSRLV:
|
||||
return fmt::format("SET_GPR_U64(ctx, {}, GPR_U64(ctx, {}) >> (GPR_U32(ctx, {}) & 0x3F));", inst.rd, inst.rt, inst.rs);
|
||||
case SPECIAL_DSRAV:
|
||||
return fmt::format("SET_GPR_S64(ctx, {}, GPR_S64(ctx, {}) >> (GPR_U32(ctx, {}) & 0x3F));", inst.rd, inst.rt, inst.rs);
|
||||
case SPECIAL_DSLL32:
|
||||
return fmt::format("SET_GPR_U64(ctx, {}, GPR_U64(ctx, {}) << (32 + {}));", inst.rd, inst.rt, inst.sa);
|
||||
case SPECIAL_DSRL32:
|
||||
return fmt::format("SET_GPR_U64(ctx, {}, GPR_U64(ctx, {}) >> (32 + {}));", inst.rd, inst.rt, inst.sa);
|
||||
case SPECIAL_DSRA32:
|
||||
return fmt::format("SET_GPR_S64(ctx, {}, GPR_S64(ctx, {}) >> (32 + {}));", inst.rd, inst.rt, inst.sa);
|
||||
case SPECIAL_TGE:
|
||||
return fmt::format("if (GPR_S64(ctx, {}) >= GPR_S64(ctx, {})) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.rt);
|
||||
case SPECIAL_TGEU:
|
||||
return fmt::format("if (GPR_U64(ctx, {}) >= GPR_U64(ctx, {})) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.rt);
|
||||
case SPECIAL_TLT:
|
||||
return fmt::format("if (GPR_S64(ctx, {}) < GPR_S64(ctx, {})) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.rt);
|
||||
case SPECIAL_TLTU:
|
||||
return fmt::format("if (GPR_U64(ctx, {}) < GPR_U64(ctx, {})) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.rt);
|
||||
case SPECIAL_TEQ:
|
||||
return fmt::format("if (GPR_U64(ctx, {}) == GPR_U64(ctx, {})) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.rt);
|
||||
case SPECIAL_TNE:
|
||||
return fmt::format("if (GPR_U64(ctx, {}) != GPR_U64(ctx, {})) {{ runtime->handleTrap(rdram, ctx); }}", inst.rs, inst.rt);
|
||||
default:
|
||||
return m_codeGenerator.emitUnhandledInstruction(inst, fmt::format("Unhandled SPECIAL instruction: 0x{:X}", inst.function));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,890 @@
|
||||
#include "ps2recomp/code_generator.h"
|
||||
#include "ps2recomp/codegen_helpers.h"
|
||||
#include "ps2recomp/instructions.h"
|
||||
#include "ps2recomp/types.h"
|
||||
#include <fmt/format.h>
|
||||
#include <sstream>
|
||||
#include <cmath>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
std::string CodeGenerator::translateVU_VADD_Field(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
uint8_t field = inst.function & 0x3;
|
||||
std::string shuffle_pattern = fmt::format("_MM_SHUFFLE({},{},{},{})", field, field, field, field);
|
||||
return fmt::format("{{ __m128 res = PS2_VADD(ctx->vu0_vf[{}], _mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], {})); __m128i mask = _mm_set_epi32({}, {}, {}, {}); ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", vfs, vft, vft, shuffle_pattern, (dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0, vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VSUB_Field(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
uint8_t field = inst.function & 0x3;
|
||||
std::string shuffle_pattern = fmt::format("_MM_SHUFFLE({},{},{},{})", field, field, field, field);
|
||||
return fmt::format("{{ __m128 res = PS2_VSUB(ctx->vu0_vf[{}], _mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], {})); __m128i mask = _mm_set_epi32({}, {}, {}, {}); ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", vfs, vft, vft, shuffle_pattern, (dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0, vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMUL_Field(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
uint8_t field = inst.function & 0x3;
|
||||
std::string shuffle_pattern = fmt::format("_MM_SHUFFLE({},{},{},{})", field, field, field, field);
|
||||
return fmt::format("{{ __m128 res = PS2_VMUL(ctx->vu0_vf[{}], _mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], {})); __m128i mask = _mm_set_epi32({}, {}, {}, {}); ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", vfs, vft, vft, shuffle_pattern, (dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0, vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VADD(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 res = PS2_VADD(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); __m128i mask = _mm_set_epi32({}, {}, {}, {}); ctx->vu0_vf[{}] = PS2_VBLEND(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", vfs, vft, (dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0, vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VSUB(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 res = PS2_VSUB(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); __m128i mask = _mm_set_epi32({}, {}, {}, {}); ctx->vu0_vf[{}] = PS2_VBLEND(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", vfs, vft, (dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0, vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMUL(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 res = PS2_VMUL(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); __m128i mask = _mm_set_epi32({}, {}, {}, {}); ctx->vu0_vf[{}] = PS2_VBLEND(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", vfs, vft, (dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0, vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VDIV(const Instruction &inst)
|
||||
{
|
||||
uint8_t fsf = inst.vectorInfo.fsf;
|
||||
uint8_t ftf = inst.vectorInfo.ftf;
|
||||
uint8_t fs_reg = inst.rd;
|
||||
uint8_t ft_reg = inst.rt;
|
||||
|
||||
return fmt::format("{{ float fs = _mm_cvtss_f32(_mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], _MM_SHUFFLE(0,0,0,{}))); float ft = _mm_cvtss_f32(_mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], _MM_SHUFFLE(0,0,0,{}))); ctx->vu0_q = (ft != 0.0f) ? (fs / ft) : 0.0f; }}", fs_reg, fs_reg, fsf, ft_reg, ft_reg, ftf);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VSQRT(const Instruction &inst)
|
||||
{
|
||||
uint8_t ftf = inst.vectorInfo.ftf;
|
||||
uint8_t ft_reg = inst.rt;
|
||||
return fmt::format("{{ float ft = _mm_cvtss_f32(_mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], _MM_SHUFFLE(0,0,0,{}))); ctx->vu0_q = sqrtf(std::max(0.0f, ft)); }}", ft_reg, ft_reg, ftf);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VRSQRT(const Instruction &inst)
|
||||
{
|
||||
uint8_t ftf = inst.vectorInfo.ftf;
|
||||
uint8_t ft_reg = inst.rt;
|
||||
return fmt::format("{{ float ft = _mm_cvtss_f32(_mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], _MM_SHUFFLE(0,0,0,{}))); ctx->vu0_q = (ft > 0.0f) ? (1.0f / sqrtf(ft)) : 0.0f; }}", ft_reg, ft_reg, ftf);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMTIR(const Instruction &inst)
|
||||
{
|
||||
uint8_t fsf = inst.vectorInfo.fsf;
|
||||
return fmt::format("{{ uint32_t bits; float src = _mm_cvtss_f32(_mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], _MM_SHUFFLE(0,0,0,{}))); std::memcpy(&bits, &src, sizeof(bits)); ctx->vi[{}] = (uint16_t)(bits & 0xFFFF); }}", inst.rd, inst.rd, fsf, inst.rt);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMFIR(const Instruction &inst)
|
||||
{
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ uint32_t tmp = (uint32_t)(int32_t)(int16_t)ctx->vi[{}]; float val; std::memcpy(&val, &tmp, sizeof(val)); "
|
||||
"__m128 res = _mm_set1_ps(val); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
|
||||
inst.rd,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
inst.rt, inst.rt);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VILWR(const Instruction &inst)
|
||||
{
|
||||
return fmt::format("{{ uint32_t addr = (uint32_t)(ctx->vi[{}] << 2) & 0x3FFC; ctx->vi[{}] = static_cast<uint16_t>(READ32(addr)); }}", inst.rd, inst.rt); // VILWR.<f> vit, (vis)
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VISWR(const Instruction &inst)
|
||||
{
|
||||
return fmt::format("{{ uint32_t addr = (uint32_t)(ctx->vi[{}] << 2) & 0x3FFC; WRITE32(addr, (uint32_t)ctx->vi[{}]); }}", inst.rd, inst.rt); // VISWR.<f> vit, (vis)
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VIADD(const Instruction &inst)
|
||||
{
|
||||
return fmt::format("ctx->vi[{}] = ctx->vi[{}] + ctx->vi[{}];", inst.sa, inst.rd, inst.rt); // vid, vis, vit
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VISUB(const Instruction &inst)
|
||||
{
|
||||
return fmt::format("ctx->vi[{}] = ctx->vi[{}] - ctx->vi[{}];", inst.sa, inst.rd, inst.rt); // vid, vis, vit
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VIADDI(const Instruction &inst)
|
||||
{
|
||||
int32_t imm5 = (inst.sa & 0x10) ? static_cast<int32_t>(inst.sa | ~0x1F) : static_cast<int32_t>(inst.sa);
|
||||
return fmt::format("ctx->vi[{}] = ctx->vi[{}] + {};", inst.rt, inst.rd, imm5); // vit, vis, imm5
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VIAND(const Instruction &inst)
|
||||
{
|
||||
return fmt::format("ctx->vi[{}] = ctx->vi[{}] & ctx->vi[{}];", inst.sa, inst.rd, inst.rt); // vid, vis, vit
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VIOR(const Instruction &inst)
|
||||
{
|
||||
return fmt::format("ctx->vi[{}] = ctx->vi[{}] | ctx->vi[{}];", inst.sa, inst.rd, inst.rt); // vid, vis, vit
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VCALLMS(const Instruction &inst)
|
||||
{
|
||||
// VCALLMS calls a VU0 microprogram at the specified immediate address.
|
||||
// VU0 micro memory is 4KB = 512 instructions (8 bytes each). Index is 0-511.
|
||||
uint16_t instr_index = static_cast<uint16_t>((inst.raw >> 6) & 0x1FF); // imm15[8:0]
|
||||
uint32_t target_byte_addr = static_cast<uint32_t>(instr_index) << 3; // Convert instruction index to byte address
|
||||
|
||||
return fmt::format(
|
||||
"{{ "
|
||||
" ctx->vu0_tpc = 0x{:X}; " // Set target program counter
|
||||
" runtime->executeVU0Microprogram(rdram, ctx, 0x{:X}); "
|
||||
"}}",
|
||||
target_byte_addr, target_byte_addr);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VCALLMSR(const Instruction &inst)
|
||||
{
|
||||
// VCALLMSR calls a VU0 microprogram at address stored in integer register
|
||||
uint8_t vis_reg_idx = inst.rd; // Source integer register (vis)
|
||||
|
||||
return fmt::format(
|
||||
"{{ "
|
||||
" uint16_t instr_index = ctx->vi[{}] & 0x1FF; " // Get instruction index from VI[IS], mask to 9 bits
|
||||
" uint32_t target_byte_addr = (uint32_t)instr_index << 3; " // Convert to byte address
|
||||
" ctx->vu0_pc = target_byte_addr; "
|
||||
" runtime->vu0StartMicroProgram(rdram, ctx, target_byte_addr); "
|
||||
"}}",
|
||||
vis_reg_idx);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VRNEXT(const Instruction &inst)
|
||||
{
|
||||
return fmt::format(
|
||||
"{{\n"
|
||||
" uint32_t r_vals[4];\n"
|
||||
" _mm_storeu_si128((__m128i*)r_vals, _mm_castps_si128(ctx->vu0_r));\n"
|
||||
"\n"
|
||||
" // Simple LFSR-based random number generation (PS2-like behavior)\n"
|
||||
" uint32_t feedback = r_vals[0] ^ (r_vals[0] << 13) ^ (r_vals[1] >> 19) ^ (r_vals[2] << 7);\n"
|
||||
" r_vals[0] = r_vals[1];\n"
|
||||
" r_vals[1] = r_vals[2];\n"
|
||||
" r_vals[2] = r_vals[3];\n"
|
||||
" r_vals[3] = feedback;\n"
|
||||
"\n"
|
||||
" ctx->vu0_r = _mm_castsi128_ps(_mm_loadu_si128((__m128i*)r_vals));\n"
|
||||
"}}");
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMADD_Field(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
uint8_t field = inst.function & 0x3; // Extract field from function code
|
||||
|
||||
// Pre-construct the shuffle pattern to avoid format string issues
|
||||
std::string shuffle_pattern = fmt::format("_MM_SHUFFLE({},{},{},{})", field, field, field, field);
|
||||
|
||||
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], _mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], {})); "
|
||||
"__m128 res = PS2_VADD(ctx->vu0_acc, mul_res); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); "
|
||||
"ctx->vu0_acc = res; }}",
|
||||
vfs, vft, vft, shuffle_pattern,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMSUB_Field(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
uint8_t field = inst.function & 0x3; // Extract field from function code
|
||||
|
||||
std::string shuffle_pattern = fmt::format("_MM_SHUFFLE({},{},{},{})", field, field, field, field);
|
||||
|
||||
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], _mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], {})); "
|
||||
"__m128 res = PS2_VSUB(ctx->vu0_acc, mul_res); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); "
|
||||
"ctx->vu0_acc = res; }}",
|
||||
vfs, vft, vft, shuffle_pattern,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMINI_Field(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
uint8_t field = inst.function & 0x3;
|
||||
|
||||
std::string shuffle_pattern = fmt::format("_MM_SHUFFLE({},{},{},{})", field, field, field, field);
|
||||
|
||||
return fmt::format("{{ __m128 res = _mm_min_ps(ctx->vu0_vf[{}], _mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], {})); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
|
||||
vfs, vft, vft, shuffle_pattern,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMAX_Field(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
uint8_t field = inst.function & 0x3;
|
||||
|
||||
std::string shuffle_pattern = fmt::format("_MM_SHUFFLE({},{},{},{})", field, field, field, field);
|
||||
|
||||
return fmt::format("{{ __m128 res = _mm_max_ps(ctx->vu0_vf[{}], _mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], {})); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
|
||||
vfs, vft, vft, shuffle_pattern,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMADD(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); "
|
||||
"__m128 res = PS2_VADD(ctx->vu0_acc, mul_res); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); "
|
||||
"ctx->vu0_acc = res; }}",
|
||||
vfs, vft,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMADDq(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q)); "
|
||||
"__m128 res = PS2_VADD(ctx->vu0_acc, mul_res); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); "
|
||||
"ctx->vu0_acc = res; }}",
|
||||
vfs,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMADDi(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i)); "
|
||||
"__m128 res = PS2_VADD(ctx->vu0_acc, mul_res); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); "
|
||||
"ctx->vu0_acc = res; }}",
|
||||
vfs,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMAX(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 res = _mm_max_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
|
||||
vfs, vft,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMAXi(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 res = _mm_max_ps(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i)); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
|
||||
vfs,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMINIi(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 res = _mm_min_ps(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i)); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
|
||||
vfs,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMULi(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 res = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i)); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
|
||||
vfs,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMULq(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 res = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q)); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
|
||||
vfs,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VOPMSUB(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); "
|
||||
"__m128 res = PS2_VSUB(ctx->vu0_acc, mul_res); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); "
|
||||
"ctx->vu0_acc = res; }}",
|
||||
vfs, vft,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VADDq(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 res = PS2_VADD(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q)); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
|
||||
vfs,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VADDi(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 res = PS2_VADD(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i)); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
|
||||
vfs,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMSUB(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); "
|
||||
"__m128 res = PS2_VSUB(ctx->vu0_acc, mul_res); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); "
|
||||
"ctx->vu0_acc = res; }}",
|
||||
vfs, vft,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMINI(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 res = _mm_min_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
|
||||
vfs, vft,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VSUBi(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 res = PS2_VSUB(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i)); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
|
||||
vfs,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VSUBq(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 res = PS2_VSUB(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q)); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
|
||||
vfs,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMSUBq(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q)); "
|
||||
"__m128 res = PS2_VSUB(ctx->vu0_acc, mul_res); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); "
|
||||
"ctx->vu0_acc = res; }}",
|
||||
vfs,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMSUBi(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfd = inst.sa;
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i)); "
|
||||
"__m128 res = PS2_VSUB(ctx->vu0_acc, mul_res); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); "
|
||||
"ctx->vu0_acc = res; }}",
|
||||
vfs,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
vfd, vfd);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VADDA_Field(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
uint8_t field = inst.function & 0x3;
|
||||
std::string shuffle_pattern = fmt::format("_MM_SHUFFLE({},{},{},{})", field, field, field, field);
|
||||
|
||||
return fmt::format("{{ __m128 res = PS2_VADD(ctx->vu0_vf[{}], _mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], {})); "
|
||||
"ctx->vu0_acc = _mm_blendv_ps(ctx->vu0_acc, res, {}); }}",
|
||||
vfs, vft, vft, shuffle_pattern, codegen::vuMaskExpr(dest_mask));
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VSUBA_Field(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
uint8_t field = inst.function & 0x3;
|
||||
std::string shuffle_pattern = fmt::format("_MM_SHUFFLE({},{},{},{})", field, field, field, field);
|
||||
|
||||
return fmt::format("{{ __m128 res = PS2_VSUB(ctx->vu0_vf[{}], _mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], {})); "
|
||||
"ctx->vu0_acc = _mm_blendv_ps(ctx->vu0_acc, res, {}); }}",
|
||||
vfs, vft, vft, shuffle_pattern, codegen::vuMaskExpr(dest_mask));
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMADDA_Field(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
uint8_t field = inst.function & 0x3;
|
||||
std::string shuffle_pattern = fmt::format("_MM_SHUFFLE({},{},{},{})", field, field, field, field);
|
||||
|
||||
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], _mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], {})); "
|
||||
"__m128 res = PS2_VADD(ctx->vu0_acc, mul_res); "
|
||||
"ctx->vu0_acc = _mm_blendv_ps(ctx->vu0_acc, res, {}); }}",
|
||||
vfs, vft, vft, shuffle_pattern, codegen::vuMaskExpr(dest_mask));
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMSUBA_Field(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
uint8_t field = inst.function & 0x3;
|
||||
std::string shuffle_pattern = fmt::format("_MM_SHUFFLE({},{},{},{})", field, field, field, field);
|
||||
|
||||
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], _mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], {})); "
|
||||
"__m128 res = PS2_VSUB(ctx->vu0_acc, mul_res); "
|
||||
"ctx->vu0_acc = _mm_blendv_ps(ctx->vu0_acc, res, {}); }}",
|
||||
vfs, vft, vft, shuffle_pattern, codegen::vuMaskExpr(dest_mask));
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMULA_Field(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
uint8_t field = inst.function & 0x3;
|
||||
std::string shuffle_pattern = fmt::format("_MM_SHUFFLE({},{},{},{})", field, field, field, field);
|
||||
|
||||
return fmt::format("{{ __m128 res = PS2_VMUL(ctx->vu0_vf[{}], _mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], {})); "
|
||||
"ctx->vu0_acc = _mm_blendv_ps(ctx->vu0_acc, res, {}); }}",
|
||||
vfs, vft, vft, shuffle_pattern, codegen::vuMaskExpr(dest_mask));
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VADDA(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 res = PS2_VADD(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); ctx->vu0_acc = _mm_blendv_ps(ctx->vu0_acc, res, {}); }}",
|
||||
vfs, vft, codegen::vuMaskExpr(dest_mask));
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VADDAq(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 res = PS2_VADD(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q)); ctx->vu0_acc = _mm_blendv_ps(ctx->vu0_acc, res, {}); }}",
|
||||
vfs, codegen::vuMaskExpr(dest_mask));
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VADDAi(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 res = PS2_VADD(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i)); ctx->vu0_acc = _mm_blendv_ps(ctx->vu0_acc, res, {}); }}",
|
||||
vfs, codegen::vuMaskExpr(dest_mask));
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VSUBA(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 res = PS2_VSUB(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); ctx->vu0_acc = _mm_blendv_ps(ctx->vu0_acc, res, {}); }}",
|
||||
vfs, vft, codegen::vuMaskExpr(dest_mask));
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VSUBAq(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 res = PS2_VSUB(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q)); ctx->vu0_acc = _mm_blendv_ps(ctx->vu0_acc, res, {}); }}",
|
||||
vfs, codegen::vuMaskExpr(dest_mask));
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VSUBAi(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 res = PS2_VSUB(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i)); ctx->vu0_acc = _mm_blendv_ps(ctx->vu0_acc, res, {}); }}",
|
||||
vfs, codegen::vuMaskExpr(dest_mask));
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMADDA(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); __m128 res = PS2_VADD(ctx->vu0_acc, mul_res); ctx->vu0_acc = _mm_blendv_ps(ctx->vu0_acc, res, {}); }}",
|
||||
vfs, vft, codegen::vuMaskExpr(dest_mask));
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMADDAq(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q)); __m128 res = PS2_VADD(ctx->vu0_acc, mul_res); ctx->vu0_acc = _mm_blendv_ps(ctx->vu0_acc, res, {}); }}",
|
||||
vfs, codegen::vuMaskExpr(dest_mask));
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMADDAi(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i)); __m128 res = PS2_VADD(ctx->vu0_acc, mul_res); ctx->vu0_acc = _mm_blendv_ps(ctx->vu0_acc, res, {}); }}",
|
||||
vfs, codegen::vuMaskExpr(dest_mask));
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMSUBA(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); __m128 res = PS2_VSUB(ctx->vu0_acc, mul_res); ctx->vu0_acc = _mm_blendv_ps(ctx->vu0_acc, res, {}); }}",
|
||||
vfs, vft, codegen::vuMaskExpr(dest_mask));
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMSUBAq(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q)); __m128 res = PS2_VSUB(ctx->vu0_acc, mul_res); ctx->vu0_acc = _mm_blendv_ps(ctx->vu0_acc, res, {}); }}",
|
||||
vfs, codegen::vuMaskExpr(dest_mask));
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMSUBAi(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i)); __m128 res = PS2_VSUB(ctx->vu0_acc, mul_res); ctx->vu0_acc = _mm_blendv_ps(ctx->vu0_acc, res, {}); }}",
|
||||
vfs, codegen::vuMaskExpr(dest_mask));
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMULA(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 res = PS2_VMUL(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); ctx->vu0_acc = _mm_blendv_ps(ctx->vu0_acc, res, {}); }}",
|
||||
vfs, vft, codegen::vuMaskExpr(dest_mask));
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMULAq(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 res = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q)); ctx->vu0_acc = _mm_blendv_ps(ctx->vu0_acc, res, {}); }}",
|
||||
vfs, codegen::vuMaskExpr(dest_mask));
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VMULAi(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 res = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i)); ctx->vu0_acc = _mm_blendv_ps(ctx->vu0_acc, res, {}); }}",
|
||||
vfs, codegen::vuMaskExpr(dest_mask));
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VOPMULA(const Instruction &inst)
|
||||
{
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t vft = inst.rt;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 res = PS2_VMUL(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); ctx->vu0_acc = _mm_blendv_ps(ctx->vu0_acc, res, {}); }}",
|
||||
vfs, vft, codegen::vuMaskExpr(dest_mask));
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VITOF(const Instruction &inst, int shift)
|
||||
{
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
float scale = (shift == 0) ? 1.0f : (1.0f / static_cast<float>(1 << shift));
|
||||
|
||||
return fmt::format("{{ __m128i src = _mm_castps_si128(ctx->vu0_vf[{}]); "
|
||||
"__m128 res = _mm_cvtepi32_ps(src); "
|
||||
"res = _mm_mul_ps(res, _mm_set1_ps({})); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
|
||||
vfs, codegen::formatFloatLiteral(scale),
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
inst.rt, inst.rt);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VFTOI(const Instruction &inst, int shift)
|
||||
{
|
||||
uint8_t vfs = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
float scale = (shift == 0) ? 1.0f : static_cast<float>(1 << shift);
|
||||
|
||||
return fmt::format("{{ __m128 src = ctx->vu0_vf[{}]; "
|
||||
"src = _mm_mul_ps(src, _mm_set1_ps({})); "
|
||||
"__m128i res_i = _mm_cvttps_epi32(src); "
|
||||
"__m128 res = _mm_castsi128_ps(res_i); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
|
||||
vfs, codegen::formatFloatLiteral(scale),
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
inst.rt, inst.rt);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VLQI(const Instruction &inst)
|
||||
{
|
||||
uint8_t vis = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ uint32_t addr = ((uint32_t)(ctx->vi[{}] & 0x3FF)) << 4; "
|
||||
"__m128 res = _mm_castsi128_ps(READ128(addr)); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); "
|
||||
"ctx->vi[{}] = (ctx->vi[{}] + 1) & 0x3FF; }}",
|
||||
vis,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
inst.rt, inst.rt,
|
||||
vis, vis);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VSQI(const Instruction &inst)
|
||||
{
|
||||
uint8_t vis = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ uint32_t addr = ((uint32_t)(ctx->vi[{}] & 0x3FF)) << 4; "
|
||||
"__m128i old_val = READ128(addr); "
|
||||
"__m128 res = _mm_blendv_ps(_mm_castsi128_ps(old_val), ctx->vu0_vf[{}], _mm_castsi128_ps(_mm_set_epi32({}, {}, {}, {}))); "
|
||||
"WRITE128(addr, _mm_castps_si128(res)); "
|
||||
"ctx->vi[{}] = (ctx->vi[{}] + 1) & 0x3FF; }}",
|
||||
vis,
|
||||
inst.rt,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
vis, vis);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VLQD(const Instruction &inst)
|
||||
{
|
||||
uint8_t vis = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ ctx->vi[{}] = (ctx->vi[{}] - 1) & 0x3FF; "
|
||||
"uint32_t addr = ((uint32_t)(ctx->vi[{}] & 0x3FF)) << 4; "
|
||||
"__m128 res = _mm_castsi128_ps(READ128(addr)); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
|
||||
vis, vis,
|
||||
vis,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
inst.rt, inst.rt);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VSQD(const Instruction &inst)
|
||||
{
|
||||
uint8_t vis = inst.rd;
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ ctx->vi[{}] = (ctx->vi[{}] - 1) & 0x3FF; "
|
||||
"uint32_t addr = ((uint32_t)(ctx->vi[{}] & 0x3FF)) << 4; "
|
||||
"__m128i old_val = READ128(addr); "
|
||||
"__m128 res = _mm_blendv_ps(_mm_castsi128_ps(old_val), ctx->vu0_vf[{}], _mm_castsi128_ps(_mm_set_epi32({}, {}, {}, {}))); "
|
||||
"WRITE128(addr, _mm_castps_si128(res)); }}",
|
||||
vis, vis,
|
||||
vis,
|
||||
inst.rt,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VRGET(const Instruction &inst)
|
||||
{
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
uint8_t ft_reg = inst.rt;
|
||||
return fmt::format("{{ __m128 res = ctx->vu0_r; __m128i mask = _mm_set_epi32({}, {}, {}, {}); ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", (dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0, ft_reg, ft_reg);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VRINIT(const Instruction &inst)
|
||||
{
|
||||
uint8_t fs_reg = inst.rd;
|
||||
uint8_t fsf = inst.vectorInfo.fsf;
|
||||
|
||||
return fmt::format(
|
||||
"{{\n"
|
||||
" float src = _mm_cvtss_f32(_mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], _MM_SHUFFLE(0,0,0,{})));\n"
|
||||
" uint32_t seed; std::memcpy(&seed, &src, sizeof(seed));\n"
|
||||
"\n"
|
||||
" // PS2 uses a specific LFSR initialization pattern\n"
|
||||
" if (seed == 0) seed = 1;\n"
|
||||
"\n"
|
||||
" uint32_t r0 = seed;\n"
|
||||
" uint32_t r1 = seed * 0x41C64E6D + 0x3039;\n"
|
||||
" uint32_t r2 = r1 * 0x41C64E6D + 0x3039;\n"
|
||||
" uint32_t r3 = r2 * 0x41C64E6D + 0x3039;\n"
|
||||
"\n"
|
||||
" ctx->vu0_r = _mm_castsi128_ps(_mm_set_epi32(r3, r2, r1, r0));\n"
|
||||
"}}",
|
||||
fs_reg, fs_reg, fsf);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateVU_VRXOR(const Instruction &inst)
|
||||
{
|
||||
uint8_t fs_reg = inst.rd;
|
||||
uint8_t fsf = inst.vectorInfo.fsf;
|
||||
|
||||
return fmt::format(
|
||||
"{{\n"
|
||||
" float src = _mm_cvtss_f32(_mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], _MM_SHUFFLE(0,0,0,{})));\n"
|
||||
" uint32_t src_bits; std::memcpy(&src_bits, &src, sizeof(src_bits));\n"
|
||||
" __m128i r_current = _mm_castps_si128(ctx->vu0_r);\n"
|
||||
" __m128i fs_data = _mm_set1_epi32((int)src_bits);\n"
|
||||
"\n"
|
||||
" // XOR the current random value with the data from the VU vector register\n"
|
||||
" __m128i xored = _mm_xor_si128(r_current, fs_data);\n"
|
||||
"\n"
|
||||
" // Apply a simple mixing function similar to PS2's LFSR\n"
|
||||
" __m128i mixed = _mm_xor_si128(xored, _mm_slli_epi32(xored, 7));\n"
|
||||
" mixed = _mm_xor_si128(mixed, _mm_srli_epi32(mixed, 9));\n"
|
||||
"\n"
|
||||
" ctx->vu0_r = _mm_castsi128_ps(mixed);\n"
|
||||
"}}",
|
||||
fs_reg, fs_reg, fsf);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,446 @@
|
||||
#include "ps2recomp/Translators/vu_translator.h"
|
||||
#include "ps2recomp/code_generator.h"
|
||||
#include "ps2recomp/codegen_helpers.h"
|
||||
#include "ps2recomp/instructions.h"
|
||||
#include "ps2recomp/types.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <sstream>
|
||||
#include <cmath>
|
||||
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
VuTranslator::VuTranslator(CodeGenerator &codeGenerator)
|
||||
: m_codeGenerator(codeGenerator)
|
||||
{
|
||||
}
|
||||
|
||||
std::string VuTranslator::translate(const Instruction &inst)
|
||||
{
|
||||
uint8_t format = inst.rs; // Use parsed rs field for COP2 format
|
||||
uint8_t rt = inst.rt;
|
||||
uint8_t rd = inst.rd;
|
||||
uint8_t sa = inst.sa;
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case COP2_QMFC2:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_castps_si128(ctx->vu0_vf[{}]));", rt, rd);
|
||||
case COP2_CFC2:
|
||||
{
|
||||
switch (rd) // Control register number is in rd
|
||||
{
|
||||
case VU0_CR_STATUS:
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_status);", rt);
|
||||
case VU0_CR_MAC:
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_mac_flags);", rt);
|
||||
case VU0_CR_VPU_STAT:
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_vpu_stat);", rt);
|
||||
case VU0_CR_R:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_castps_si128(ctx->vu0_r));", rt);
|
||||
case VU0_CR_I:
|
||||
return fmt::format("{{ uint32_t bits; std::memcpy(&bits, &ctx->vu0_i, sizeof(bits)); SET_GPR_U32(ctx, {}, bits); }}", rt);
|
||||
case VU0_CR_CLIP:
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_clip_flags);", rt);
|
||||
case VU0_CR_TPC:
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_tpc);", rt);
|
||||
case VU0_CR_CMSAR0:
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_cmsar0);", rt);
|
||||
case VU0_CR_FBRST:
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_fbrst);", rt);
|
||||
case VU0_CR_VPU_STAT2:
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_vpu_stat2);", rt);
|
||||
case VU0_CR_TPC2:
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_tpc2);", rt);
|
||||
case VU0_CR_CMSAR1:
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_cmsar1);", rt);
|
||||
case VU0_CR_FBRST2:
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_fbrst2);", rt);
|
||||
case VU0_CR_VPU_STAT3:
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_vpu_stat3);", rt);
|
||||
case VU0_CR_CMSAR2:
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_cmsar2);", rt);
|
||||
case VU0_CR_FBRST3:
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_fbrst3);", rt);
|
||||
case VU0_CR_VPU_STAT4:
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_vpu_stat4);", rt);
|
||||
case VU0_CR_CMSAR3:
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_cmsar3);", rt);
|
||||
case VU0_CR_FBRST4:
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_fbrst4);", rt);
|
||||
case VU0_CR_ACC:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_castps_si128(ctx->vu0_acc));", rt);
|
||||
case VU0_CR_INFO: // I dd found on offical docs but ok
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_info);", rt);
|
||||
case VU0_CR_CLIP2:
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_clip_flags2);", rt);
|
||||
case VU0_CR_P:
|
||||
return fmt::format("{{ uint32_t bits; std::memcpy(&bits, &ctx->vu0_p, sizeof(bits)); SET_GPR_U32(ctx, {}, bits); }}", rt);
|
||||
case VU0_CR_XITOP: // Maybe this does not exist, maybe we handle to vu0_itop
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_xitop);", rt);
|
||||
case VU0_CR_ITOP:
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_itop);", rt);
|
||||
case VU0_CR_TOP:
|
||||
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_top);", rt);
|
||||
default:
|
||||
return fmt::format("// Unimplemented CFC2 VU CReg: {}", rt);
|
||||
}
|
||||
}
|
||||
case COP2_QMTC2:
|
||||
return fmt::format("ctx->vu0_vf[{}] = _mm_castsi128_ps(GPR_VEC(ctx, {}));", rd, rt);
|
||||
case COP2_CTC2:
|
||||
{
|
||||
switch (rd) // Control register number is in rd
|
||||
{
|
||||
case VU0_CR_STATUS:
|
||||
return fmt::format("ctx->vu0_status = GPR_U32(ctx, {}) & 0xFFFF;", rt);
|
||||
case VU0_CR_MAC:
|
||||
return fmt::format("ctx->vu0_mac_flags = GPR_U32(ctx, {});", rt);
|
||||
case VU0_CR_VPU_STAT:
|
||||
return fmt::format("ctx->vu0_vpu_stat = GPR_U32(ctx, {});", rt);
|
||||
case VU0_CR_CLIP:
|
||||
return fmt::format("ctx->vu0_clip_flags = GPR_U32(ctx, {});", rt);
|
||||
case VU0_CR_R:
|
||||
return fmt::format("ctx->vu0_r = _mm_castsi128_ps(GPR_VEC(ctx, {}));", rt);
|
||||
case VU0_CR_I:
|
||||
return fmt::format("{{ uint32_t tmp = GPR_U32(ctx, {}); std::memcpy(&ctx->vu0_i, &tmp, sizeof(tmp)); }}", rt);
|
||||
case VU0_CR_TPC:
|
||||
return fmt::format("ctx->vu0_tpc = GPR_U32(ctx, {});", rt);
|
||||
case VU0_CR_CMSAR0:
|
||||
return fmt::format("ctx->vu0_cmsar0 = GPR_U32(ctx, {});", rt);
|
||||
case VU0_CR_FBRST:
|
||||
return fmt::format("ctx->vu0_fbrst = GPR_U32(ctx, {});", rt);
|
||||
case VU0_CR_VPU_STAT2:
|
||||
return fmt::format("ctx->vu0_vpu_stat2 = GPR_U32(ctx, {});", rt);
|
||||
case VU0_CR_TPC2:
|
||||
return fmt::format("ctx->vu0_tpc2 = GPR_U32(ctx, {});", rt);
|
||||
case VU0_CR_CMSAR1:
|
||||
return fmt::format("ctx->vu0_cmsar1 = GPR_U32(ctx, {});", rt);
|
||||
case VU0_CR_FBRST2:
|
||||
return fmt::format("ctx->vu0_fbrst2 = GPR_U32(ctx, {});", rt);
|
||||
case VU0_CR_VPU_STAT3:
|
||||
return fmt::format("ctx->vu0_vpu_stat3 = GPR_U32(ctx, {});", rt);
|
||||
case VU0_CR_CMSAR2:
|
||||
return fmt::format("ctx->vu0_cmsar2 = GPR_U32(ctx, {});", rt);
|
||||
case VU0_CR_FBRST3:
|
||||
return fmt::format("ctx->vu0_fbrst3 = GPR_U32(ctx, {});", rt);
|
||||
case VU0_CR_VPU_STAT4:
|
||||
return fmt::format("ctx->vu0_vpu_stat4 = GPR_U32(ctx, {});", rt);
|
||||
case VU0_CR_CMSAR3:
|
||||
return fmt::format("ctx->vu0_cmsar3 = GPR_U32(ctx, {});", rt);
|
||||
case VU0_CR_FBRST4:
|
||||
return fmt::format("ctx->vu0_fbrst4 = GPR_U32(ctx, {});", rt);
|
||||
case VU0_CR_ACC:
|
||||
return fmt::format("ctx->vu0_acc = _mm_castsi128_ps(GPR_VEC(ctx, {}));", rt);
|
||||
case VU0_CR_INFO:
|
||||
return fmt::format("ctx->vu0_info = GPR_U32(ctx, {});", rt);
|
||||
case VU0_CR_CLIP2:
|
||||
return fmt::format("ctx->vu0_clip_flags2 = GPR_U32(ctx, {});", rt);
|
||||
case VU0_CR_P:
|
||||
return fmt::format("{{ uint32_t tmp = GPR_U32(ctx, {}); std::memcpy(&ctx->vu0_p, &tmp, sizeof(tmp)); }}", rt);
|
||||
case VU0_CR_XITOP:
|
||||
return fmt::format("ctx->vu0_xitop = GPR_U32(ctx, {}) & 0x3FF;", rt);
|
||||
case VU0_CR_ITOP:
|
||||
return fmt::format("ctx->vu0_itop = GPR_U32(ctx, {}) & 0x3FF;", rt);
|
||||
case VU0_CR_TOP:
|
||||
return fmt::format("ctx->vu0_top = GPR_U32(ctx, {}) & 0x3FF;", rt);
|
||||
default:
|
||||
return fmt::format("// Unimplemented CTC2 VU CReg: {}", rd);
|
||||
}
|
||||
}
|
||||
case COP2_BC:
|
||||
return fmt::format("// BC2 (Condition: 0x{:X}) - Handled by branch logic", rt);
|
||||
case COP2_CO:
|
||||
case COP2_CO + 1:
|
||||
case COP2_CO + 2:
|
||||
case COP2_CO + 3:
|
||||
case COP2_CO + 4:
|
||||
case COP2_CO + 5:
|
||||
case COP2_CO + 6:
|
||||
case COP2_CO + 7:
|
||||
case COP2_CO + 8:
|
||||
case COP2_CO + 9:
|
||||
case COP2_CO + 10:
|
||||
case COP2_CO + 11:
|
||||
case COP2_CO + 12:
|
||||
case COP2_CO + 13:
|
||||
case COP2_CO + 14:
|
||||
case COP2_CO + 15:
|
||||
{
|
||||
const uint8_t special1_func = static_cast<uint8_t>(inst.function & 0x3F);
|
||||
if (special1_func >= 0x3C) // Special2 Table
|
||||
{
|
||||
const uint8_t vu_func = static_cast<uint8_t>((((inst.raw >> 6) & 0x1F) << 2) | (inst.raw & 0x3));
|
||||
switch (vu_func)
|
||||
{
|
||||
case VU0_S2_VADDAx:
|
||||
case VU0_S2_VADDAy:
|
||||
case VU0_S2_VADDAz:
|
||||
case VU0_S2_VADDAw:
|
||||
return m_codeGenerator.translateVU_VADDA_Field(inst);
|
||||
case VU0_S2_VSUBAx:
|
||||
case VU0_S2_VSUBAy:
|
||||
case VU0_S2_VSUBAz:
|
||||
case VU0_S2_VSUBAw:
|
||||
return m_codeGenerator.translateVU_VSUBA_Field(inst);
|
||||
case VU0_S2_VMADDAx:
|
||||
case VU0_S2_VMADDAy:
|
||||
case VU0_S2_VMADDAz:
|
||||
case VU0_S2_VMADDAw:
|
||||
return m_codeGenerator.translateVU_VMADDA_Field(inst);
|
||||
case VU0_S2_VMSUBAx:
|
||||
case VU0_S2_VMSUBAy:
|
||||
case VU0_S2_VMSUBAz:
|
||||
case VU0_S2_VMSUBAw:
|
||||
return m_codeGenerator.translateVU_VMSUBA_Field(inst);
|
||||
case VU0_S2_VMULAx:
|
||||
case VU0_S2_VMULAy:
|
||||
case VU0_S2_VMULAz:
|
||||
case VU0_S2_VMULAw:
|
||||
return m_codeGenerator.translateVU_VMULA_Field(inst);
|
||||
case VU0_S2_VADDA:
|
||||
return m_codeGenerator.translateVU_VADDA(inst);
|
||||
case VU0_S2_VADDAq:
|
||||
return m_codeGenerator.translateVU_VADDAq(inst);
|
||||
case VU0_S2_VADDAi:
|
||||
return m_codeGenerator.translateVU_VADDAi(inst);
|
||||
case VU0_S2_VMADDA:
|
||||
return m_codeGenerator.translateVU_VMADDA(inst);
|
||||
case VU0_S2_VMADDAq:
|
||||
return m_codeGenerator.translateVU_VMADDAq(inst);
|
||||
case VU0_S2_VMADDAi:
|
||||
return m_codeGenerator.translateVU_VMADDAi(inst);
|
||||
case VU0_S2_VSUBA:
|
||||
return m_codeGenerator.translateVU_VSUBA(inst);
|
||||
case VU0_S2_VSUBAq:
|
||||
return m_codeGenerator.translateVU_VSUBAq(inst);
|
||||
case VU0_S2_VSUBAi:
|
||||
return m_codeGenerator.translateVU_VSUBAi(inst);
|
||||
case VU0_S2_VMSUBA:
|
||||
return m_codeGenerator.translateVU_VMSUBA(inst);
|
||||
case VU0_S2_VMSUBAq:
|
||||
return m_codeGenerator.translateVU_VMSUBAq(inst);
|
||||
case VU0_S2_VMSUBAi:
|
||||
return m_codeGenerator.translateVU_VMSUBAi(inst);
|
||||
case VU0_S2_VMULA:
|
||||
return m_codeGenerator.translateVU_VMULA(inst);
|
||||
case VU0_S2_VMULAq:
|
||||
return m_codeGenerator.translateVU_VMULAq(inst);
|
||||
case VU0_S2_VMULAi:
|
||||
return m_codeGenerator.translateVU_VMULAi(inst);
|
||||
case VU0_S2_VOPMULA:
|
||||
return m_codeGenerator.translateVU_VOPMULA(inst);
|
||||
case VU0_S2_VITOF0:
|
||||
return m_codeGenerator.translateVU_VITOF(inst, 0);
|
||||
case VU0_S2_VITOF4:
|
||||
return m_codeGenerator.translateVU_VITOF(inst, 4);
|
||||
case VU0_S2_VITOF12:
|
||||
return m_codeGenerator.translateVU_VITOF(inst, 12);
|
||||
case VU0_S2_VITOF15:
|
||||
return m_codeGenerator.translateVU_VITOF(inst, 15);
|
||||
case VU0_S2_VFTOI0:
|
||||
return m_codeGenerator.translateVU_VFTOI(inst, 0);
|
||||
case VU0_S2_VFTOI4:
|
||||
return m_codeGenerator.translateVU_VFTOI(inst, 4);
|
||||
case VU0_S2_VFTOI12:
|
||||
return m_codeGenerator.translateVU_VFTOI(inst, 12);
|
||||
case VU0_S2_VFTOI15:
|
||||
return m_codeGenerator.translateVU_VFTOI(inst, 15);
|
||||
case VU0_S2_VLQI:
|
||||
return m_codeGenerator.translateVU_VLQI(inst);
|
||||
case VU0_S2_VSQI:
|
||||
return m_codeGenerator.translateVU_VSQI(inst);
|
||||
case VU0_S2_VLQD:
|
||||
return m_codeGenerator.translateVU_VLQD(inst);
|
||||
case VU0_S2_VSQD:
|
||||
return m_codeGenerator.translateVU_VSQD(inst);
|
||||
case VU0_S2_VDIV:
|
||||
return m_codeGenerator.translateVU_VDIV(inst);
|
||||
case VU0_S2_VSQRT:
|
||||
return m_codeGenerator.translateVU_VSQRT(inst);
|
||||
case VU0_S2_VRSQRT:
|
||||
return m_codeGenerator.translateVU_VRSQRT(inst);
|
||||
case VU0_S2_VWAITQ:
|
||||
return fmt::format("// VWAITQ (Q already resolved in this runtime)");
|
||||
case VU0_S2_VMTIR:
|
||||
return m_codeGenerator.translateVU_VMTIR(inst);
|
||||
case VU0_S2_VMFIR:
|
||||
return m_codeGenerator.translateVU_VMFIR(inst);
|
||||
case VU0_S2_VILWR:
|
||||
return m_codeGenerator.translateVU_VILWR(inst);
|
||||
case VU0_S2_VISWR:
|
||||
return m_codeGenerator.translateVU_VISWR(inst);
|
||||
case VU0_S2_VABS:
|
||||
{
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format("{{ __m128 res = _mm_and_ps(ctx->vu0_vf[{}], _mm_castsi128_ps(_mm_set1_epi32(0x7FFFFFFF))); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
|
||||
inst.rd,
|
||||
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
|
||||
inst.rt, inst.rt);
|
||||
}
|
||||
case VU0_S2_VMOVE:
|
||||
{
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format(
|
||||
"{{ __m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], _mm_castsi128_ps(mask)); }}",
|
||||
(dest_mask & 0x1) ? -1 : 0,
|
||||
(dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0,
|
||||
(dest_mask & 0x8) ? -1 : 0,
|
||||
inst.rt, inst.rt, inst.rd);
|
||||
}
|
||||
case VU0_S2_VMR32:
|
||||
{
|
||||
uint8_t dest_mask = inst.vectorInfo.vectorField;
|
||||
return fmt::format(
|
||||
"{{ __m128 res = _mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], _MM_SHUFFLE(0,3,2,1)); "
|
||||
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
|
||||
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
|
||||
inst.rd, inst.rd,
|
||||
(dest_mask & 0x1) ? -1 : 0,
|
||||
(dest_mask & 0x2) ? -1 : 0,
|
||||
(dest_mask & 0x4) ? -1 : 0,
|
||||
(dest_mask & 0x8) ? -1 : 0,
|
||||
inst.rt, inst.rt);
|
||||
}
|
||||
case VU0_S2_VCLIPw:
|
||||
{
|
||||
uint8_t field = inst.function & 0x3;
|
||||
std::string shuffle_pattern = fmt::format("_MM_SHUFFLE({},{},{},{})", field, field, field, field);
|
||||
|
||||
return fmt::format(
|
||||
"{{ __m128 fs = ctx->vu0_vf[{}]; "
|
||||
"__m128 ft = _mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], {}); "
|
||||
"__m128 neg_ft = _mm_xor_ps(ft, _mm_castsi128_ps(_mm_set1_epi32(0x80000000))); "
|
||||
"__m128 gt = _mm_cmpgt_ps(fs, ft); "
|
||||
"__m128 lt = _mm_cmplt_ps(fs, neg_ft); "
|
||||
"uint32_t gt_mask = (uint32_t)_mm_movemask_ps(gt); "
|
||||
"uint32_t lt_mask = (uint32_t)_mm_movemask_ps(lt); "
|
||||
"uint32_t flags = ((lt_mask & 0x1) << 0) | ((gt_mask & 0x1) << 1) | "
|
||||
"((lt_mask & 0x2) << 1) | ((gt_mask & 0x2) << 2) | "
|
||||
"((lt_mask & 0x4) << 2) | ((gt_mask & 0x4) << 3); "
|
||||
"ctx->vu0_clip_flags = ((ctx->vu0_clip_flags << 6) | (flags & 0x3F)) & 0xFFFFFF; }}",
|
||||
inst.rd, inst.rt, inst.rt, shuffle_pattern);
|
||||
}
|
||||
case VU0_S2_VNOP:
|
||||
return fmt::format("// NOP operation, no action needed for VU0");
|
||||
case VU0_S2_VRNEXT:
|
||||
return m_codeGenerator.translateVU_VRNEXT(inst);
|
||||
case VU0_S2_VRGET:
|
||||
return m_codeGenerator.translateVU_VRGET(inst);
|
||||
case VU0_S2_VRINIT:
|
||||
return m_codeGenerator.translateVU_VRINIT(inst);
|
||||
case VU0_S2_VRXOR:
|
||||
return m_codeGenerator.translateVU_VRXOR(inst);
|
||||
default:
|
||||
return m_codeGenerator.emitUnhandledInstruction(inst, fmt::format("Unhandled VU0 Special2 function: 0x{:X}", vu_func));
|
||||
}
|
||||
}
|
||||
|
||||
// Special1 Table (function-based)
|
||||
switch (special1_func)
|
||||
{
|
||||
case VU0_S1_VADDx:
|
||||
case VU0_S1_VADDy:
|
||||
case VU0_S1_VADDz:
|
||||
case VU0_S1_VADDw:
|
||||
return m_codeGenerator.translateVU_VADD_Field(inst);
|
||||
case VU0_S1_VSUBx:
|
||||
case VU0_S1_VSUBy:
|
||||
case VU0_S1_VSUBz:
|
||||
case VU0_S1_VSUBw:
|
||||
return m_codeGenerator.translateVU_VSUB_Field(inst);
|
||||
case VU0_S1_VMULx:
|
||||
case VU0_S1_VMULy:
|
||||
case VU0_S1_VMULz:
|
||||
case VU0_S1_VMULw:
|
||||
return m_codeGenerator.translateVU_VMUL_Field(inst);
|
||||
case VU0_S1_VADD:
|
||||
return m_codeGenerator.translateVU_VADD(inst);
|
||||
case VU0_S1_VSUB:
|
||||
return m_codeGenerator.translateVU_VSUB(inst);
|
||||
case VU0_S1_VMUL:
|
||||
return m_codeGenerator.translateVU_VMUL(inst);
|
||||
case VU0_S1_VIADD:
|
||||
return m_codeGenerator.translateVU_VIADD(inst);
|
||||
case VU0_S1_VISUB:
|
||||
return m_codeGenerator.translateVU_VISUB(inst);
|
||||
case VU0_S1_VIADDI:
|
||||
return m_codeGenerator.translateVU_VIADDI(inst);
|
||||
case VU0_S1_VIAND:
|
||||
return m_codeGenerator.translateVU_VIAND(inst);
|
||||
case VU0_S1_VIOR:
|
||||
return m_codeGenerator.translateVU_VIOR(inst);
|
||||
case VU0_S1_VCALLMS:
|
||||
return m_codeGenerator.translateVU_VCALLMS(inst);
|
||||
case VU0_S1_VCALLMSR:
|
||||
return m_codeGenerator.translateVU_VCALLMSR(inst);
|
||||
case VU0_S1_VADDq:
|
||||
return m_codeGenerator.translateVU_VADDq(inst);
|
||||
case VU0_S1_VSUBq:
|
||||
return m_codeGenerator.translateVU_VSUBq(inst);
|
||||
case VU0_S1_VMULq:
|
||||
return m_codeGenerator.translateVU_VMULq(inst);
|
||||
case VU0_S1_VADDi:
|
||||
return m_codeGenerator.translateVU_VADDi(inst);
|
||||
case VU0_S1_VSUBi:
|
||||
return m_codeGenerator.translateVU_VSUBi(inst);
|
||||
case VU0_S1_VMULi:
|
||||
return m_codeGenerator.translateVU_VMULi(inst);
|
||||
case VU0_S1_VMADDx:
|
||||
case VU0_S1_VMADDy:
|
||||
case VU0_S1_VMADDz:
|
||||
case VU0_S1_VMADDw:
|
||||
return m_codeGenerator.translateVU_VMADD_Field(inst);
|
||||
case VU0_S1_VMSUBx:
|
||||
case VU0_S1_VMSUBy:
|
||||
case VU0_S1_VMSUBz:
|
||||
case VU0_S1_VMSUBw:
|
||||
return m_codeGenerator.translateVU_VMSUB_Field(inst);
|
||||
case VU0_S1_VMAXx:
|
||||
case VU0_S1_VMAXy:
|
||||
case VU0_S1_VMAXz:
|
||||
case VU0_S1_VMAXw:
|
||||
return m_codeGenerator.translateVU_VMAX_Field(inst);
|
||||
case VU0_S1_VMINIx:
|
||||
case VU0_S1_VMINIy:
|
||||
case VU0_S1_VMINIz:
|
||||
case VU0_S1_VMINIw:
|
||||
return m_codeGenerator.translateVU_VMINI_Field(inst);
|
||||
case VU0_S1_VMAXi:
|
||||
return m_codeGenerator.translateVU_VMAXi(inst);
|
||||
case VU0_S1_VMINIi:
|
||||
return m_codeGenerator.translateVU_VMINIi(inst);
|
||||
case VU0_S1_VMADD:
|
||||
return m_codeGenerator.translateVU_VMADD(inst);
|
||||
case VU0_S1_VMADDq:
|
||||
return m_codeGenerator.translateVU_VMADDq(inst);
|
||||
case VU0_S1_VMADDi:
|
||||
return m_codeGenerator.translateVU_VMADDi(inst);
|
||||
case VU0_S1_VMAX:
|
||||
return m_codeGenerator.translateVU_VMAX(inst);
|
||||
case VU0_S1_VOPMSUB:
|
||||
return m_codeGenerator.translateVU_VOPMSUB(inst);
|
||||
case VU0_S1_VMINI:
|
||||
return m_codeGenerator.translateVU_VMINI(inst);
|
||||
case VU0_S1_VMSUB:
|
||||
return m_codeGenerator.translateVU_VMSUB(inst);
|
||||
case VU0_S1_VMSUBq:
|
||||
return m_codeGenerator.translateVU_VMSUBq(inst);
|
||||
case VU0_S1_VMSUBi:
|
||||
return m_codeGenerator.translateVU_VMSUBi(inst);
|
||||
default:
|
||||
return m_codeGenerator.emitUnhandledInstruction(inst, fmt::format("Unhandled VU0 Special1 function: 0x{:X}", special1_func));
|
||||
}
|
||||
}
|
||||
default:
|
||||
return m_codeGenerator.emitUnhandledInstruction(inst, fmt::format("Unhandled COP2 format: 0x{:X}", format));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,16 +28,19 @@ int main(int argc, char *argv[])
|
||||
if (!recompiler.initialize())
|
||||
{
|
||||
std::cerr << "Failed to initialize recompiler\n";
|
||||
recompiler.printReport();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!recompiler.recompile())
|
||||
{
|
||||
std::cerr << "Recompilation failed\n";
|
||||
recompiler.printReport();
|
||||
return 1;
|
||||
}
|
||||
|
||||
recompiler.generateOutput();
|
||||
recompiler.printReport();
|
||||
|
||||
std::cout << "Recompilation completed successfully\n";
|
||||
return 0;
|
||||
|
||||
@@ -11,6 +11,7 @@ option(PS2X_ENABLE_SCCACHE "Use sccache as compiler launcher when available" ON)
|
||||
|
||||
option(PS2X_ENABLE_RUNTIME_LOGS "Enable PS2 runtime logs" OFF)
|
||||
option(PS2X_ENABLE_AGRESSIVE_LOGS "Enable very verbose/agressive PS2 runtime logs" OFF)
|
||||
option(PS2X_STRICT_RETURN_DIAGNOSTICS "Route generated JR $ra returns through runtime branch diagnostics" OFF)
|
||||
option(PS2X_SHOW_WINDOWS_CONSOLE "Show a console window for ps2EntryRunner on Windows release builds" ON)
|
||||
|
||||
if(PS2X_ENABLE_SCCACHE)
|
||||
@@ -373,6 +374,12 @@ if(PS2X_ENABLE_AGRESSIVE_LOGS)
|
||||
)
|
||||
endif()
|
||||
|
||||
if(PS2X_STRICT_RETURN_DIAGNOSTICS)
|
||||
target_compile_definitions(ps2_runtime PUBLIC
|
||||
PS2X_STRICT_RETURN_DIAGNOSTICS=1
|
||||
)
|
||||
endif()
|
||||
|
||||
file(GLOB_RECURSE KERNEL_SRC_FILES CONFIGURE_DEPENDS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/lib/Kernel/*.cpp"
|
||||
)
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#if defined(_MSC_VER)
|
||||
@@ -357,6 +356,30 @@ public:
|
||||
|
||||
using RecompiledFunction = void (*)(uint8_t *, R5900Context *, PS2Runtime *);
|
||||
|
||||
enum class GuestBranchKind
|
||||
{
|
||||
DirectJump,
|
||||
DirectCall,
|
||||
IndirectJump,
|
||||
IndirectCall,
|
||||
Return,
|
||||
};
|
||||
|
||||
enum class MissingFunctionPolicy : uint32_t
|
||||
{
|
||||
// Strict mode for tests/CI: log the bad target and request the runtime to stop.
|
||||
Stop = 0,
|
||||
|
||||
// Debug mode: log once, leave ctx->pc on the bad target, and let the caller unwind.
|
||||
ContinueToTarget = 1,
|
||||
|
||||
// Debug mode: same as ContinueToTarget, but triggers a debugger break once on MSVC.
|
||||
BreakOnce = 2,
|
||||
|
||||
// Escape hatch only: skip missing calls by returning to fallthrough (it can hide guest bugs)
|
||||
SkipCallDebug = 3,
|
||||
};
|
||||
|
||||
class GuestExecutionScope
|
||||
{
|
||||
public:
|
||||
@@ -384,9 +407,27 @@ public:
|
||||
uint32_t m_depth = 0u;
|
||||
};
|
||||
|
||||
void registerFunction(uint32_t address, RecompiledFunction func);
|
||||
bool replaceFunction(uint32_t address, RecompiledFunction func);
|
||||
// TODO remove this later need to update all tests
|
||||
bool registerFunction(uint32_t address, RecompiledFunction func);
|
||||
RecompiledFunction lookupFunction(uint32_t address);
|
||||
bool hasFunction(uint32_t address) const;
|
||||
bool dispatchGuestBranch(uint8_t *rdram,
|
||||
R5900Context *ctx,
|
||||
uint32_t targetPc,
|
||||
uint32_t sourcePc,
|
||||
uint32_t fallthroughPc,
|
||||
GuestBranchKind kind,
|
||||
const char *debugName);
|
||||
void reportMissingFunction(uint8_t *rdram,
|
||||
R5900Context *ctx,
|
||||
uint32_t targetPc,
|
||||
uint32_t sourcePc,
|
||||
GuestBranchKind kind,
|
||||
const char *debugName);
|
||||
void setMissingFunctionPolicy(MissingFunctionPolicy policy);
|
||||
MissingFunctionPolicy missingFunctionPolicy() const;
|
||||
void resetMissingFunctionReportOnce();
|
||||
|
||||
static const IoPaths &getIoPaths();
|
||||
static void setIoPaths(const IoPaths &paths);
|
||||
@@ -550,7 +591,8 @@ private:
|
||||
uint32_t m_asyncCallbackStackFloor = 0x01F00000u;
|
||||
uint32_t m_asyncCallbackStackTop = PS2_RAM_SIZE;
|
||||
|
||||
std::unordered_map<uint32_t, RecompiledFunction> m_functionTable;
|
||||
std::atomic<uint32_t> m_missingFunctionPolicy{static_cast<uint32_t>(MissingFunctionPolicy::ContinueToTarget)};
|
||||
std::atomic<bool> m_missingFunctionReported{false};
|
||||
std::atomic<bool> m_stopRequested{false};
|
||||
DebugUiCallback m_debugUiInitCallback = nullptr;
|
||||
DebugUiCallback m_debugUiDrawCallback = nullptr;
|
||||
@@ -578,4 +620,10 @@ private:
|
||||
uint8_t *m_boundGSVram = nullptr;
|
||||
};
|
||||
|
||||
// Generated by ps2xRecomp in ps2xRuntime/src/runner/register_functions.cpp.
|
||||
extern const uint32_t g_ps2RecompiledFunctionTableBase;
|
||||
extern const uint32_t g_ps2RecompiledFunctionTableEnd;
|
||||
extern const uint32_t g_ps2RecompiledFunctionTableSlotCount;
|
||||
extern PS2Runtime::RecompiledFunction g_ps2RecompiledFunctionTable[];
|
||||
|
||||
#endif // PS2_RUNTIME_H
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
#ifndef REGISTER_FUNCTIONS_H
|
||||
#define REGISTER_FUNCTIONS_H
|
||||
|
||||
#include "ps2_runtime.h"
|
||||
|
||||
void registerAllFunctions(PS2Runtime &runtime);
|
||||
|
||||
#endif // REGISTER_FUNCTIONS_H
|
||||
@@ -170,8 +170,7 @@ namespace ps2_game_overrides
|
||||
return false;
|
||||
}
|
||||
|
||||
runtime.registerFunction(address, resolved.value());
|
||||
return true;
|
||||
return runtime.replaceFunction(address, resolved.value());
|
||||
}
|
||||
|
||||
void applyMatching(PS2Runtime &runtime, const std::string &elfPath, uint32_t entry)
|
||||
|
||||
@@ -83,6 +83,10 @@ namespace
|
||||
return "LIBSD";
|
||||
case IOP_SID_FATAL_FRAME_SDRDRV:
|
||||
return "Fatal Frame SDRDRV";
|
||||
case IOP_SID_LOTR_CLFILE:
|
||||
return "LOTR CL";
|
||||
case IOP_SID_LOTR_SOUND:
|
||||
return "LOTR Sound";
|
||||
case 0x80001300u:
|
||||
return "DBCMAN";
|
||||
default:
|
||||
@@ -1085,6 +1089,8 @@ namespace
|
||||
{"SNDDRV state", IOP_SID_SNDDRV_STATE, false},
|
||||
{"LIBSD", IOP_SID_LIBSD, false},
|
||||
{"Fatal Frame SDRDRV", IOP_SID_FATAL_FRAME_SDRDRV, false},
|
||||
{"LOTR SOUND", IOP_SID_LOTR_SOUND, false},
|
||||
{"LOTR CLFILE", IOP_SID_LOTR_CLFILE, false},
|
||||
{"DBCMAN", 0x80001300u, false},
|
||||
{"DTX compat", dtxLayout.rpcSid, true},
|
||||
};
|
||||
|
||||
+277
-352
@@ -106,8 +106,6 @@ namespace
|
||||
|
||||
thread_local DispatchHistory g_dispatchHistory;
|
||||
thread_local std::unordered_map<PS2Runtime *, uint32_t> g_guestExecutionDepths;
|
||||
std::mutex g_functionStartsMutex;
|
||||
std::unordered_map<const PS2Runtime *, std::vector<uint32_t>> g_functionStartsByRuntime;
|
||||
|
||||
void pushDispatchPc(uint32_t pc)
|
||||
{
|
||||
@@ -144,41 +142,6 @@ namespace
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
uint32_t selectDispatchRecoveryPc(const PS2Runtime *runtime)
|
||||
{
|
||||
const DispatchHistory &h = g_dispatchHistory;
|
||||
const uint32_t count = h.wrapped ? static_cast<uint32_t>(h.pcs.size()) : h.next;
|
||||
if (count == 0u)
|
||||
{
|
||||
return 0u;
|
||||
}
|
||||
|
||||
uint32_t firstHigh = 0u;
|
||||
for (uint32_t step = 1u; step <= count; ++step)
|
||||
{
|
||||
const uint32_t idx = (h.next + h.pcs.size() - step) % static_cast<uint32_t>(h.pcs.size());
|
||||
const uint32_t pc = h.pcs[idx];
|
||||
if (pc < 0x00100000u)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (runtime && !runtime->hasFunction(pc))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (firstHigh == 0u)
|
||||
{
|
||||
firstHigh = pc;
|
||||
continue;
|
||||
}
|
||||
|
||||
return pc;
|
||||
}
|
||||
|
||||
return firstHigh;
|
||||
}
|
||||
|
||||
uint32_t selectExceptionVector(const R5900Context *ctx, bool tlbRefill)
|
||||
{
|
||||
if (ctx->cop0_status & COP0_STATUS_BEV)
|
||||
@@ -204,7 +167,6 @@ namespace
|
||||
ctx->vu0_vpu_stat2 = 0;
|
||||
}
|
||||
|
||||
|
||||
void copyVu0ContextToState(const R5900Context *ctx, VU1State &state)
|
||||
{
|
||||
std::memset(&state, 0, sizeof(state));
|
||||
@@ -331,98 +293,6 @@ namespace
|
||||
return paths;
|
||||
}
|
||||
|
||||
uint32_t readGuestU32Wrapped(const uint8_t *rdram, uint32_t addr)
|
||||
{
|
||||
if (!rdram)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t value = 0;
|
||||
value |= static_cast<uint32_t>(rdram[(addr + 0u) & PS2_RAM_MASK]) << 0;
|
||||
value |= static_cast<uint32_t>(rdram[(addr + 1u) & PS2_RAM_MASK]) << 8;
|
||||
value |= static_cast<uint32_t>(rdram[(addr + 2u) & PS2_RAM_MASK]) << 16;
|
||||
value |= static_cast<uint32_t>(rdram[(addr + 3u) & PS2_RAM_MASK]) << 24;
|
||||
return value;
|
||||
}
|
||||
|
||||
uint64_t readGuestU64Wrapped(const uint8_t *rdram, uint32_t addr)
|
||||
{
|
||||
const uint64_t lo = readGuestU32Wrapped(rdram, addr);
|
||||
const uint64_t hi = readGuestU32Wrapped(rdram, addr + 4u);
|
||||
return lo | (hi << 32);
|
||||
}
|
||||
|
||||
uint32_t selectStackRecoveryPc(const uint8_t *rdram, const R5900Context *ctx, const PS2Runtime *runtime)
|
||||
{
|
||||
if (!rdram || !ctx || !runtime)
|
||||
{
|
||||
return 0u;
|
||||
}
|
||||
|
||||
const uint32_t sp = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[29], 0));
|
||||
constexpr uint32_t kScanBytes = 0x200u;
|
||||
|
||||
for (uint32_t offset = 0u; offset < kScanBytes; offset += 8u)
|
||||
{
|
||||
const uint32_t slotAddr = sp + offset;
|
||||
const uint32_t ra32 = static_cast<uint32_t>(readGuestU64Wrapped(rdram, slotAddr));
|
||||
if (ra32 < 0x00100000u)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!runtime->hasFunction(ra32))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return ra32;
|
||||
}
|
||||
|
||||
for (uint32_t offset = 0u; offset < kScanBytes; offset += 4u)
|
||||
{
|
||||
const uint32_t slotAddr = sp + offset;
|
||||
const uint32_t ra32 = readGuestU32Wrapped(rdram, slotAddr);
|
||||
if (ra32 < 0x00100000u)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!runtime->hasFunction(ra32))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return ra32;
|
||||
}
|
||||
|
||||
return 0u;
|
||||
}
|
||||
|
||||
void clearFunctionStarts(const PS2Runtime *runtime)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_functionStartsMutex);
|
||||
g_functionStartsByRuntime.erase(runtime);
|
||||
}
|
||||
|
||||
std::vector<uint32_t> snapshotFunctionStarts(const PS2Runtime *runtime)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_functionStartsMutex);
|
||||
auto it = g_functionStartsByRuntime.find(runtime);
|
||||
if (it == g_functionStartsByRuntime.end())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void registerFunctionStart(const PS2Runtime *runtime, uint32_t address)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_functionStartsMutex);
|
||||
auto &starts = g_functionStartsByRuntime[runtime];
|
||||
auto insertPos = std::lower_bound(starts.begin(), starts.end(), address);
|
||||
if (insertPos == starts.end() || *insertPos != address)
|
||||
{
|
||||
starts.insert(insertPos, address);
|
||||
}
|
||||
}
|
||||
|
||||
std::string readGuestPrintableString(const uint8_t *rdram, uint32_t addr, size_t maxLen)
|
||||
{
|
||||
@@ -607,9 +477,6 @@ PS2Runtime::PS2Runtime()
|
||||
|
||||
// Stack pointer (SP) and global pointer (GP) will be set by the loaded ELF
|
||||
|
||||
m_functionTable.clear();
|
||||
clearFunctionStarts(this);
|
||||
|
||||
m_loadedModules.clear();
|
||||
m_guestHeapBlocks.clear();
|
||||
m_guestHeapBase = kGuestHeapDefaultBase;
|
||||
@@ -622,9 +489,9 @@ PS2Runtime::PS2Runtime()
|
||||
}
|
||||
|
||||
void PS2Runtime::setDebugUiCallbacks(DebugUiCallback initCallback,
|
||||
DebugUiCallback drawCallback,
|
||||
DebugUiCallback shutdownCallback,
|
||||
void *userData)
|
||||
DebugUiCallback drawCallback,
|
||||
DebugUiCallback shutdownCallback,
|
||||
void *userData)
|
||||
{
|
||||
if (m_debugUiInitialized && m_debugUiShutdownCallback)
|
||||
{
|
||||
@@ -667,8 +534,6 @@ PS2Runtime::~PS2Runtime()
|
||||
|
||||
m_loadedModules.clear();
|
||||
|
||||
m_functionTable.clear();
|
||||
clearFunctionStarts(this);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
@@ -930,7 +795,7 @@ bool PS2Runtime::loadELF(const std::string &elfPath)
|
||||
|
||||
if (ph.flags & 0x1u) // PF_X
|
||||
{
|
||||
const uint64_t execEnd = static_cast<uint64_t>(ph.vaddr) + static_cast<uint64_t>(ph.memsz);
|
||||
const uint64_t execEnd = static_cast<uint64_t>(ph.vaddr) + static_cast<uint64_t>(ph.filesz);
|
||||
if (execEnd <= std::numeric_limits<uint32_t>::max())
|
||||
{
|
||||
m_memory.registerCodeRegion(ph.vaddr, static_cast<uint32_t>(execEnd));
|
||||
@@ -1049,243 +914,305 @@ void PS2Runtime::configureIoPathsFromElf(const std::string &elfPath)
|
||||
setIoPaths(paths);
|
||||
}
|
||||
|
||||
void PS2Runtime::registerFunction(uint32_t address, RecompiledFunction func)
|
||||
namespace
|
||||
{
|
||||
registerFunctionStart(this, address);
|
||||
m_functionTable[address] = func;
|
||||
bool generatedFunctionTableSlot(uint32_t address, uint32_t &slot)
|
||||
{
|
||||
if ((address & 3u) != 0u || g_ps2RecompiledFunctionTableSlotCount == 0u)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (address < g_ps2RecompiledFunctionTableBase || address >= g_ps2RecompiledFunctionTableEnd)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t offset = address - g_ps2RecompiledFunctionTableBase;
|
||||
slot = offset >> 2;
|
||||
return slot < g_ps2RecompiledFunctionTableSlotCount;
|
||||
}
|
||||
}
|
||||
|
||||
bool PS2Runtime::replaceFunction(uint32_t address, RecompiledFunction func)
|
||||
{
|
||||
uint32_t slot = 0u;
|
||||
if (!generatedFunctionTableSlot(address, slot))
|
||||
{
|
||||
std::cerr << "[function-table] cannot replace guest PC 0x" << std::hex << address
|
||||
<< ": outside generated dense table [0x" << g_ps2RecompiledFunctionTableBase
|
||||
<< ", 0x" << g_ps2RecompiledFunctionTableEnd << ")"
|
||||
<< std::dec << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
g_ps2RecompiledFunctionTable[slot] = func;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PS2Runtime::registerFunction(uint32_t address, RecompiledFunction func)
|
||||
{
|
||||
return replaceFunction(address, func);
|
||||
}
|
||||
|
||||
bool PS2Runtime::hasFunction(uint32_t address) const
|
||||
{
|
||||
auto it = m_functionTable.find(address);
|
||||
if (it != m_functionTable.end())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
uint32_t slot = 0u;
|
||||
return generatedFunctionTableSlot(address, slot) && g_ps2RecompiledFunctionTable[slot] != nullptr;
|
||||
}
|
||||
|
||||
return false;
|
||||
const char *describeGuestBranchKind(PS2Runtime::GuestBranchKind kind)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case PS2Runtime::GuestBranchKind::DirectJump:
|
||||
return "DirectJump";
|
||||
case PS2Runtime::GuestBranchKind::DirectCall:
|
||||
return "DirectCall";
|
||||
case PS2Runtime::GuestBranchKind::IndirectJump:
|
||||
return "IndirectJump";
|
||||
case PS2Runtime::GuestBranchKind::IndirectCall:
|
||||
return "IndirectCall";
|
||||
case PS2Runtime::GuestBranchKind::Return:
|
||||
return "Return";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
PS2Runtime::RecompiledFunction PS2Runtime::lookupFunction(uint32_t address)
|
||||
{
|
||||
pushDispatchPc(address);
|
||||
|
||||
auto it = m_functionTable.find(address);
|
||||
if (it != m_functionTable.end())
|
||||
uint32_t slot = 0u;
|
||||
if (generatedFunctionTableSlot(address, slot))
|
||||
{
|
||||
return it->second;
|
||||
RecompiledFunction fn = g_ps2RecompiledFunctionTable[slot];
|
||||
if (fn != nullptr)
|
||||
{
|
||||
return fn;
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<uint32_t> functionStarts = snapshotFunctionStarts(this);
|
||||
auto aliasOwner = [&](uint32_t ownerAddress) -> RecompiledFunction
|
||||
std::cerr << "Error: No exact recompiled function for guest PC 0x" << std::hex << address
|
||||
<< " tableBase=0x" << g_ps2RecompiledFunctionTableBase
|
||||
<< " tableEnd=0x" << g_ps2RecompiledFunctionTableEnd
|
||||
<< " codeRegion=" << (m_memory.isCodeAddress(address) ? "yes" : "no")
|
||||
<< " trace=" << formatDispatchHistory()
|
||||
<< std::dec << std::endl;
|
||||
|
||||
static RecompiledFunction missingFunction = [](uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
auto owner = m_functionTable.find(ownerAddress);
|
||||
if (owner == m_functionTable.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto ownerStart = std::lower_bound(functionStarts.begin(), functionStarts.end(), ownerAddress);
|
||||
if (ownerStart == functionStarts.end() || *ownerStart != ownerAddress)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto nextStart = ownerStart;
|
||||
++nextStart;
|
||||
if (nextStart != functionStarts.end())
|
||||
{
|
||||
if (address >= *nextStart)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
else if (!m_memory.isCodeAddress(address))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return owner->second;
|
||||
const uint32_t badPc = ctx->pc;
|
||||
runtime->reportMissingFunction(rdram,
|
||||
ctx,
|
||||
badPc,
|
||||
0u,
|
||||
PS2Runtime::GuestBranchKind::IndirectJump,
|
||||
"dispatch");
|
||||
};
|
||||
|
||||
if (!functionStarts.empty())
|
||||
return missingFunction;
|
||||
}
|
||||
|
||||
void PS2Runtime::setMissingFunctionPolicy(MissingFunctionPolicy policy)
|
||||
{
|
||||
m_missingFunctionPolicy.store(static_cast<uint32_t>(policy), std::memory_order_release);
|
||||
}
|
||||
|
||||
PS2Runtime::MissingFunctionPolicy PS2Runtime::missingFunctionPolicy() const
|
||||
{
|
||||
return static_cast<MissingFunctionPolicy>(m_missingFunctionPolicy.load(std::memory_order_acquire));
|
||||
}
|
||||
|
||||
void PS2Runtime::resetMissingFunctionReportOnce()
|
||||
{
|
||||
m_missingFunctionReported.store(false, std::memory_order_release);
|
||||
}
|
||||
|
||||
void PS2Runtime::reportMissingFunction(uint8_t *rdram,
|
||||
R5900Context *ctx,
|
||||
uint32_t targetPc,
|
||||
uint32_t sourcePc,
|
||||
GuestBranchKind kind,
|
||||
const char *debugName)
|
||||
{
|
||||
const MissingFunctionPolicy policy = missingFunctionPolicy();
|
||||
const bool firstReport = !m_missingFunctionReported.exchange(true, std::memory_order_acq_rel);
|
||||
|
||||
const uint32_t pc = ctx->pc;
|
||||
const uint32_t ra = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[31], 0));
|
||||
const uint32_t sp = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[29], 0));
|
||||
const uint32_t gp = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[28], 0));
|
||||
const uint32_t a0 = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[4], 0));
|
||||
const uint32_t a1 = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[5], 0));
|
||||
const uint32_t v0 = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[2], 0));
|
||||
const uint32_t v1 = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[3], 0));
|
||||
|
||||
auto readGuestU32At = [rdram](uint32_t addr, uint32_t &out) -> bool
|
||||
{
|
||||
const DispatchHistory &history = g_dispatchHistory;
|
||||
const uint32_t count = history.wrapped ? static_cast<uint32_t>(history.pcs.size()) : history.next;
|
||||
for (uint32_t step = 1u; step <= count; ++step)
|
||||
// TODO this !rdram exist only because of test fix those test later
|
||||
if (!rdram || addr > PS2_RAM_SIZE - sizeof(uint32_t))
|
||||
{
|
||||
const uint32_t idx = (history.next + static_cast<uint32_t>(history.pcs.size()) - step) %
|
||||
static_cast<uint32_t>(history.pcs.size());
|
||||
const uint32_t previousPc = history.pcs[idx];
|
||||
if (previousPc == address)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (RecompiledFunction owner = aliasOwner(previousPc))
|
||||
{
|
||||
return owner;
|
||||
}
|
||||
out = 0u;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto nextStart = std::upper_bound(functionStarts.begin(), functionStarts.end(), address);
|
||||
if (nextStart != functionStarts.begin())
|
||||
{
|
||||
auto ownerStart = nextStart;
|
||||
--ownerStart;
|
||||
if (RecompiledFunction owner = aliasOwner(*ownerStart))
|
||||
{
|
||||
return owner;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::memcpy(&out, rdram + addr, sizeof(uint32_t));
|
||||
return true;
|
||||
};
|
||||
|
||||
std::cerr << "Warning: Function at address 0x" << std::hex << address;
|
||||
if (!functionStarts.empty())
|
||||
auto readGuestU32Offset = [&readGuestU32At](uint32_t base, uint32_t offset, uint32_t &out) -> bool
|
||||
{
|
||||
auto nextStart = std::upper_bound(functionStarts.begin(), functionStarts.end(), address);
|
||||
if (nextStart != functionStarts.begin())
|
||||
if (base > PS2_RAM_SIZE - sizeof(uint32_t) || offset > PS2_RAM_SIZE - sizeof(uint32_t) - base)
|
||||
{
|
||||
auto ownerStart = nextStart;
|
||||
--ownerStart;
|
||||
std::cerr << " nearestStart=0x" << *ownerStart;
|
||||
out = 0u;
|
||||
return false;
|
||||
}
|
||||
if (nextStart != functionStarts.end())
|
||||
{
|
||||
std::cerr << " nextStart=0x" << *nextStart;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << " nextStart=<end>";
|
||||
}
|
||||
}
|
||||
std::cerr << std::dec << " not found" << std::endl;
|
||||
|
||||
static RecompiledFunction defaultFunction = [](uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
return readGuestU32At(base + offset, out);
|
||||
};
|
||||
|
||||
uint32_t a0Word0 = 0u;
|
||||
uint32_t a0Word4 = 0u;
|
||||
uint32_t a0Word8 = 0u;
|
||||
uint32_t a0WordC = 0u;
|
||||
const bool a0Readable =
|
||||
readGuestU32Offset(a0, 0x00u, a0Word0) &&
|
||||
readGuestU32Offset(a0, 0x04u, a0Word4) &&
|
||||
readGuestU32Offset(a0, 0x08u, a0Word8) &&
|
||||
readGuestU32Offset(a0, 0x0cu, a0WordC);
|
||||
|
||||
uint32_t vtableSlot0 = 0u;
|
||||
uint32_t vtableSlot4 = 0u;
|
||||
uint32_t vtableSlot8 = 0u;
|
||||
uint32_t vtableSlotC = 0u;
|
||||
const bool vtableReadable =
|
||||
a0Readable && a0Word0 != 0u &&
|
||||
readGuestU32Offset(a0Word0, 0x00u, vtableSlot0) &&
|
||||
readGuestU32Offset(a0Word0, 0x04u, vtableSlot4) &&
|
||||
readGuestU32Offset(a0Word0, 0x08u, vtableSlot8) &&
|
||||
readGuestU32Offset(a0Word0, 0x0cu, vtableSlotC);
|
||||
|
||||
if (firstReport)
|
||||
{
|
||||
const uint32_t ra = ctx ? static_cast<uint32_t>(_mm_extract_epi32(ctx->r[31], 0)) : 0u;
|
||||
const uint32_t sp = ctx ? static_cast<uint32_t>(_mm_extract_epi32(ctx->r[29], 0)) : 0u;
|
||||
const uint32_t gp = ctx ? static_cast<uint32_t>(_mm_extract_epi32(ctx->r[28], 0)) : 0u;
|
||||
const uint32_t a0 = ctx ? static_cast<uint32_t>(_mm_extract_epi32(ctx->r[4], 0)) : 0u;
|
||||
const uint32_t a1 = ctx ? static_cast<uint32_t>(_mm_extract_epi32(ctx->r[5], 0)) : 0u;
|
||||
const uint32_t v0 = ctx ? static_cast<uint32_t>(_mm_extract_epi32(ctx->r[2], 0)) : 0u;
|
||||
const uint32_t v1 = ctx ? static_cast<uint32_t>(_mm_extract_epi32(ctx->r[3], 0)) : 0u;
|
||||
|
||||
if (ctx && runtime)
|
||||
{
|
||||
thread_local uint32_t s_recoverCount = 0u;
|
||||
thread_local bool s_loggedContext = false;
|
||||
const uint32_t pc = ctx->pc;
|
||||
const bool hasPcFunction = runtime->hasFunction(pc);
|
||||
|
||||
if (!hasPcFunction && s_recoverCount < 8192u)
|
||||
{
|
||||
if (!s_loggedContext)
|
||||
{
|
||||
std::ostringstream stackDump;
|
||||
if (rdram)
|
||||
{
|
||||
stackDump << " [stack]";
|
||||
for (uint32_t off = 0u; off < 0x40u; off += 4u)
|
||||
{
|
||||
const uint32_t slot = readGuestU32Wrapped(rdram, sp + off);
|
||||
stackDump << " +" << std::hex << off << "=0x" << slot;
|
||||
}
|
||||
}
|
||||
std::cerr << "[dispatch:first-bad-pc] bad=0x" << std::hex << pc
|
||||
<< " ra=0x" << ra
|
||||
<< " sp=0x" << sp
|
||||
<< " gp=0x" << gp
|
||||
<< " v0=0x" << v0
|
||||
<< " v1=0x" << v1
|
||||
<< " a0=0x" << a0
|
||||
<< " a1=0x" << a1
|
||||
<< " trace=" << formatDispatchHistory()
|
||||
<< stackDump.str()
|
||||
<< std::dec << std::endl;
|
||||
s_loggedContext = true;
|
||||
}
|
||||
|
||||
uint32_t recoveryPc = 0u;
|
||||
if (ra != 0u && runtime->hasFunction(ra))
|
||||
{
|
||||
recoveryPc = ra;
|
||||
}
|
||||
|
||||
if (recoveryPc == 0u)
|
||||
{
|
||||
recoveryPc = selectStackRecoveryPc(rdram, ctx, runtime);
|
||||
}
|
||||
|
||||
if (recoveryPc == 0u)
|
||||
{
|
||||
recoveryPc = selectDispatchRecoveryPc(runtime);
|
||||
}
|
||||
|
||||
if (recoveryPc != 0u && recoveryPc != pc)
|
||||
{
|
||||
if (s_recoverCount < 256u)
|
||||
{
|
||||
std::cerr << "[dispatch:recover-pc] bad=0x" << std::hex << pc
|
||||
<< " ra=0x" << ra
|
||||
<< " fallback=0x" << recoveryPc
|
||||
<< " sp=0x" << sp
|
||||
<< std::dec << std::endl;
|
||||
}
|
||||
++s_recoverCount;
|
||||
ctx->pc = recoveryPc;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasPcFunction)
|
||||
{
|
||||
s_recoverCount = 0u;
|
||||
s_loggedContext = false;
|
||||
}
|
||||
else if (pc < 0x00100000u && ra == pc && s_recoverCount < 4096u)
|
||||
{
|
||||
uint32_t recoveryPc = selectStackRecoveryPc(rdram, ctx, runtime);
|
||||
if (recoveryPc == 0u)
|
||||
{
|
||||
recoveryPc = selectDispatchRecoveryPc(runtime);
|
||||
}
|
||||
if (recoveryPc != 0u && recoveryPc != pc)
|
||||
{
|
||||
if (s_recoverCount < 128u)
|
||||
{
|
||||
std::cerr << "[dispatch:recover-low-pc] bad=0x" << std::hex << pc
|
||||
<< " ra=0x" << ra
|
||||
<< " fallback=0x" << recoveryPc
|
||||
<< " sp=0x" << sp
|
||||
<< std::dec << std::endl;
|
||||
}
|
||||
++s_recoverCount;
|
||||
ctx->pc = recoveryPc;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << "Error: Called unimplemented function at address 0x" << std::hex << (ctx ? ctx->pc : 0u)
|
||||
oss << "[guest-branch:missing-target] kind=" << describeGuestBranchKind(kind)
|
||||
<< " op=" << (debugName ? debugName : "<unknown>")
|
||||
<< " source=0x" << std::hex << sourcePc
|
||||
<< " target=0x" << targetPc
|
||||
<< " pc=0x" << pc
|
||||
<< " ra=0x" << ra
|
||||
<< " sp=0x" << sp
|
||||
<< " gp=0x" << gp
|
||||
<< " a0=0x" << a0
|
||||
<< " hostTid=" << std::this_thread::get_id()
|
||||
<< " pcTrace=" << formatDispatchHistory()
|
||||
<< " a1=0x" << a1
|
||||
<< " v0=0x" << v0
|
||||
<< " v1=0x" << v1
|
||||
<< " a0Readable=" << (a0Readable ? "yes" : "no")
|
||||
<< " a0[0]=0x" << a0Word0
|
||||
<< " a0[4]=0x" << a0Word4
|
||||
<< " a0[8]=0x" << a0Word8
|
||||
<< " a0[c]=0x" << a0WordC
|
||||
<< " vtableReadable=" << (vtableReadable ? "yes" : "no")
|
||||
<< " vtbl[0]=0x" << vtableSlot0
|
||||
<< " vtbl[4]=0x" << vtableSlot4
|
||||
<< " vtbl[8]=0x" << vtableSlot8
|
||||
<< " vtbl[c]=0x" << vtableSlotC
|
||||
<< " codeRegion=" << (m_memory.isCodeAddress(targetPc) ? "yes" : "no")
|
||||
<< " policy=" << static_cast<uint32_t>(policy)
|
||||
<< " trace=" << formatDispatchHistory()
|
||||
<< std::dec;
|
||||
|
||||
static std::mutex s_defaultFnLogMutex;
|
||||
static std::mutex s_missingFunctionLogMutex;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_defaultFnLogMutex);
|
||||
std::lock_guard<std::mutex> lock(s_missingFunctionLogMutex);
|
||||
std::cerr << oss.str() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
runtime->requestStop();
|
||||
};
|
||||
if (firstReport && policy == MissingFunctionPolicy::BreakOnce)
|
||||
{
|
||||
#if defined(_MSC_VER)
|
||||
__debugbreak();
|
||||
#endif // TODO others breakpoints
|
||||
}
|
||||
|
||||
return defaultFunction;
|
||||
if (ctx)
|
||||
{
|
||||
ctx->pc = targetPc;
|
||||
}
|
||||
|
||||
if (policy == MissingFunctionPolicy::Stop)
|
||||
{
|
||||
requestStop();
|
||||
}
|
||||
}
|
||||
|
||||
bool PS2Runtime::dispatchGuestBranch(uint8_t *rdram,
|
||||
R5900Context *ctx,
|
||||
uint32_t targetPc,
|
||||
uint32_t sourcePc,
|
||||
uint32_t fallthroughPc,
|
||||
GuestBranchKind kind,
|
||||
const char *debugName)
|
||||
{
|
||||
ctx->pc = targetPc;
|
||||
const bool isCall = (kind == GuestBranchKind::DirectCall || kind == GuestBranchKind::IndirectCall);
|
||||
|
||||
if (kind == GuestBranchKind::Return)
|
||||
{
|
||||
if (!hasFunction(targetPc))
|
||||
{
|
||||
reportMissingFunction(rdram, ctx, targetPc, sourcePc, kind, debugName);
|
||||
}
|
||||
|
||||
// Prevent nested dispatch.
|
||||
ctx->pc = targetPc;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hasFunction(targetPc))
|
||||
{
|
||||
reportMissingFunction(rdram, ctx, targetPc, sourcePc, kind, debugName);
|
||||
|
||||
const MissingFunctionPolicy policy = missingFunctionPolicy();
|
||||
|
||||
if (policy == MissingFunctionPolicy::SkipCallDebug && isCall)
|
||||
{
|
||||
ctx->pc = fallthroughPc;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (policy == MissingFunctionPolicy::ContinueToTarget)
|
||||
{
|
||||
ctx->pc = targetPc;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
RecompiledFunction targetFn = lookupFunction(targetPc);
|
||||
const uint32_t entryPc = ctx->pc;
|
||||
targetFn(rdram, ctx, this);
|
||||
|
||||
if (isStopRequested() || ctx->pc == 0u)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isCall)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ctx->pc == entryPc)
|
||||
{
|
||||
ctx->pc = fallthroughPc;
|
||||
}
|
||||
|
||||
return ctx->pc == fallthroughPc;
|
||||
}
|
||||
|
||||
void PS2Runtime::SignalException(R5900Context *ctx, PS2Exception exception)
|
||||
@@ -2036,9 +1963,7 @@ void PS2Runtime::yieldGuestExecutionAfterWake()
|
||||
GuestExecutionReleaseScope releaseGuestExecution(this);
|
||||
std::unique_lock<std::mutex> lock(m_guestExecutionHandoffMutex);
|
||||
m_guestExecutionHandoffCv.wait_for(lock, std::chrono::milliseconds(2), [&]()
|
||||
{
|
||||
return m_guestExecutionHandoffEpoch.load(std::memory_order_acquire) != handoffEpoch;
|
||||
});
|
||||
{ return m_guestExecutionHandoffEpoch.load(std::memory_order_acquire) != handoffEpoch; });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2274,19 +2199,19 @@ void PS2Runtime::run()
|
||||
const int activeThreads = g_activeThreads.load(std::memory_order_relaxed);
|
||||
|
||||
RUNTIME_LOG("[run:tick] tick=" << tick
|
||||
<< " pc=0x" << std::hex << dbgPc
|
||||
<< " ra=0x" << dbgRa
|
||||
<< " sp=0x" << dbgSp
|
||||
<< " gp=0x" << dbgGp
|
||||
<< " dispfb1=0x" << gs.dispfb1
|
||||
<< " display1=0x" << gs.display1
|
||||
<< std::dec
|
||||
<< " activeThreads=" << activeThreads
|
||||
<< " dma=" << curDma
|
||||
<< " gif=" << curGif
|
||||
<< " gsw=" << curGs
|
||||
<< " vif=" << curVif
|
||||
<< std::endl);
|
||||
<< " pc=0x" << std::hex << dbgPc
|
||||
<< " ra=0x" << dbgRa
|
||||
<< " sp=0x" << dbgSp
|
||||
<< " gp=0x" << dbgGp
|
||||
<< " dispfb1=0x" << gs.dispfb1
|
||||
<< " display1=0x" << gs.display1
|
||||
<< std::dec
|
||||
<< " activeThreads=" << activeThreads
|
||||
<< " dma=" << curDma
|
||||
<< " gif=" << curGif
|
||||
<< " gsw=" << curGs
|
||||
<< " vif=" << curVif
|
||||
<< std::endl);
|
||||
}
|
||||
});
|
||||
uint32_t presentWidth = FB_WIDTH;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "ps2_runtime.h"
|
||||
#include "register_functions.h"
|
||||
#include "games_database.h"
|
||||
#if defined(PS2X_ENABLE_DEBUG_UI) && !defined(PLATFORM_VITA)
|
||||
#include "ps2_debug_panel.h"
|
||||
@@ -143,8 +142,6 @@ int main(int argc, char *argv[])
|
||||
return 1;
|
||||
}
|
||||
|
||||
registerAllFunctions(runtime);
|
||||
|
||||
if (!runtime.loadELF(filePathStr))
|
||||
{
|
||||
std::cerr << "Failed to load ELF file: " << filePathStr << std::endl;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "register_functions.h"
|
||||
#include "ps2_runtime.h"
|
||||
#include "runtime/ps2_memory.h"
|
||||
|
||||
// Replace this file with the actual implementation of the functions that was generated by the compiler
|
||||
void registerAllFunctions(PS2Runtime &runtime)
|
||||
{
|
||||
}
|
||||
extern const uint32_t g_ps2RecompiledFunctionTableBase = 0x00000000u;
|
||||
extern const uint32_t g_ps2RecompiledFunctionTableEnd = 0x01000000u;
|
||||
extern const uint32_t g_ps2RecompiledFunctionTableSlotCount = (g_ps2RecompiledFunctionTableEnd - g_ps2RecompiledFunctionTableBase) >> 2;
|
||||
PS2Runtime::RecompiledFunction g_ps2RecompiledFunctionTable[g_ps2RecompiledFunctionTableSlotCount] = {};
|
||||
@@ -111,6 +111,10 @@ file(GLOB_RECURSE STUDIO_HEADERS "include/*.hpp")
|
||||
|
||||
add_executable(ps2xStudio ${STUDIO_SOURCES} ${STUDIO_HEADERS})
|
||||
|
||||
if(TARGET ps2_test_function_table)
|
||||
target_sources(ps2xStudio PRIVATE $<TARGET_OBJECTS:ps2_test_function_table>)
|
||||
endif()
|
||||
|
||||
target_include_directories(ps2xStudio PRIVATE
|
||||
include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../ps2xAnalyzer/include
|
||||
|
||||
@@ -6,6 +6,14 @@ set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Static library with test logic (no main), used by ps2xStudio
|
||||
add_library(ps2_test_function_table OBJECT
|
||||
src/test_function_table.cpp
|
||||
)
|
||||
|
||||
target_include_directories(ps2_test_function_table PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/ps2xRuntime/include
|
||||
)
|
||||
|
||||
add_library(ps2_test_lib STATIC
|
||||
src/code_generator_tests.cpp
|
||||
src/r5900_decoder_tests.cpp
|
||||
@@ -37,6 +45,7 @@ target_link_libraries(ps2_test_lib PRIVATE
|
||||
|
||||
add_executable(ps2x_tests
|
||||
src/main.cpp
|
||||
$<TARGET_OBJECTS:ps2_test_function_table>
|
||||
)
|
||||
|
||||
option(PRINT_GENERATED_CODE "Print generated code in tests" OFF)
|
||||
|
||||
@@ -107,6 +107,7 @@ class MiniTest
|
||||
{
|
||||
private:
|
||||
inline static std::map<std::string, TestCaseCallback> m_cases;
|
||||
inline static TestBeforeCallback m_beforeEach;
|
||||
|
||||
public:
|
||||
static void Case(const std::string& caseName, const TestCaseCallback& fn)
|
||||
@@ -114,6 +115,11 @@ public:
|
||||
m_cases[caseName] = fn;
|
||||
}
|
||||
|
||||
static void BeforeEach(const TestBeforeCallback& fn)
|
||||
{
|
||||
m_beforeEach = fn;
|
||||
}
|
||||
|
||||
static int Run()
|
||||
{
|
||||
int failedCount = 0;
|
||||
@@ -142,6 +148,11 @@ public:
|
||||
totalTests++;
|
||||
testCase.ClearFailures();
|
||||
|
||||
if (m_beforeEach)
|
||||
{
|
||||
m_beforeEach();
|
||||
}
|
||||
|
||||
if (testCase.m_beforeEach)
|
||||
{
|
||||
testCase.m_beforeEach();
|
||||
@@ -197,4 +208,4 @@ public:
|
||||
|
||||
return failedCount;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -403,9 +403,9 @@ void register_code_generator_tests()
|
||||
std::string registration = gen.generateFunctionRegistration({func}, {});
|
||||
printGeneratedCode("resume entry targets register to the owner wrapper", registration);
|
||||
|
||||
t.IsTrue(registration.find("runtime.registerFunction(0x7008, resume_owner_0x7000);") != std::string::npos,
|
||||
t.IsTrue(registration.find("g_ps2RecompiledFunctionTable[2] = resume_owner_0x7000; // 0x7008") != std::string::npos,
|
||||
"resume entry pc should register to the owner wrapper");
|
||||
t.IsTrue(registration.find("runtime.registerFunction(0x700c, resume_owner_0x7000);") != std::string::npos,
|
||||
t.IsTrue(registration.find("g_ps2RecompiledFunctionTable[3] = resume_owner_0x7000; // 0x700c") != std::string::npos,
|
||||
"multiple resume pcs should register to the same owner wrapper");
|
||||
});
|
||||
|
||||
@@ -450,7 +450,7 @@ void register_code_generator_tests()
|
||||
std::string registration = gen.generateFunctionRegistration({owner}, {});
|
||||
printGeneratedCode("external mid-function entry can register to the owner wrapper", registration);
|
||||
|
||||
t.IsTrue(registration.find("runtime.registerFunction(0x5004, owner_0x5000);") != std::string::npos,
|
||||
t.IsTrue(registration.find("g_ps2RecompiledFunctionTable[1] = owner_0x5000; // 0x5004") != std::string::npos,
|
||||
"mid-function external entry should register back to the owner wrapper");
|
||||
});
|
||||
|
||||
@@ -995,16 +995,15 @@ void register_code_generator_tests()
|
||||
// SET_GPR_U32(ctx, 31, 0xA008u);
|
||||
// ctx->pc = 0xA004u;
|
||||
// ... delay slot ...
|
||||
// some_func(rdram, ctx, runtime);
|
||||
// if (ctx->pc != 0xA008u) { return; }
|
||||
// runtime->dispatchGuestBranch(..., DirectCall, "JAL")
|
||||
|
||||
t.IsTrue(generated.find("SET_GPR_U32(ctx, 31, 0xA008u);") != std::string::npos, "JAL should set RA");
|
||||
t.IsTrue(generated.find("some_func(rdram, ctx, runtime);") != std::string::npos, "JAL should call function");
|
||||
t.IsTrue(generated.find("const uint32_t __entryPc = ctx->pc;") != std::string::npos,
|
||||
"JAL should capture entry PC before call");
|
||||
t.IsTrue(generated.find("if (ctx->pc == __entryPc) { ctx->pc = 0xA008u; }") != std::string::npos,
|
||||
"JAL should recover fallthrough when callee leaves ctx->pc unchanged");
|
||||
t.IsTrue(generated.find("if (ctx->pc != 0xA008u) { return; }") != std::string::npos, "JAL should check return PC");
|
||||
t.IsTrue(generated.find("runtime->dispatchGuestBranch(rdram, ctx, 0xB000u") != std::string::npos,
|
||||
"JAL should dispatch through the runtime branch helper");
|
||||
t.IsTrue(generated.find("PS2Runtime::GuestBranchKind::DirectCall") != std::string::npos,
|
||||
"JAL should identify itself as a direct call");
|
||||
t.IsTrue(generated.find("0xA000u, 0xA008u") != std::string::npos,
|
||||
"JAL should pass call-site and fallthrough PCs to the runtime helper");
|
||||
});
|
||||
|
||||
tc.Run("trailing JAL without decoded delay slot still emits call flow", [](TestCase &t) {
|
||||
@@ -1028,10 +1027,10 @@ void register_code_generator_tests()
|
||||
|
||||
t.IsTrue(generated.find("SET_GPR_U32(ctx, 31, 0xA108u);") != std::string::npos,
|
||||
"truncated trailing JAL should still set RA");
|
||||
t.IsTrue(generated.find("some_func(rdram, ctx, runtime);") != std::string::npos,
|
||||
"truncated trailing JAL should still emit the call");
|
||||
t.IsTrue(generated.find("if (ctx->pc != 0xA108u) { return; }") != std::string::npos,
|
||||
"truncated trailing JAL should still enforce fallthrough");
|
||||
t.IsTrue(generated.find("runtime->dispatchGuestBranch(rdram, ctx, 0xB000u") != std::string::npos,
|
||||
"truncated trailing JAL should still emit the call dispatch");
|
||||
t.IsTrue(generated.find("0xA100u, 0xA108u") != std::string::npos,
|
||||
"truncated trailing JAL should still pass the fallthrough to runtime dispatch");
|
||||
t.IsTrue(generated.find("// JAL 0xB000 - Handled by branch logic") == std::string::npos,
|
||||
"truncated trailing JAL must not degrade to comment-only output");
|
||||
});
|
||||
@@ -1101,15 +1100,15 @@ void register_code_generator_tests()
|
||||
std::string generated = gen.generateFunction(func, {jalr, delay}, false);
|
||||
printGeneratedCode("JALR emits indirect call", generated);
|
||||
|
||||
t.IsTrue(generated.find("uint32_t jumpTarget = GPR_U32(ctx, 4);") != std::string::npos, "JALR should read target from RS");
|
||||
t.IsTrue(generated.find("const uint32_t jumpTarget = GPR_U32(ctx, 4);") != std::string::npos, "JALR should read target from RS");
|
||||
t.IsTrue(generated.find("SET_GPR_U32(ctx, 31, 0xD008u);") != std::string::npos, "JALR should set link register");
|
||||
t.IsTrue(generated.find("auto targetFn = runtime->lookupFunction(jumpTarget);") != std::string::npos, "JALR should lookup function");
|
||||
t.IsTrue(generated.find("targetFn(rdram, ctx, runtime);") != std::string::npos, "JALR should call function");
|
||||
t.IsTrue(generated.find("const uint32_t __entryPc = ctx->pc;") != std::string::npos,
|
||||
"JALR should capture entry PC before indirect call");
|
||||
t.IsTrue(generated.find("if (ctx->pc == __entryPc) { ctx->pc = 0xD008u; }") != std::string::npos,
|
||||
"JALR should recover fallthrough when callee leaves ctx->pc unchanged");
|
||||
t.IsTrue(generated.find("if (ctx->pc != 0xD008u) { return; }") != std::string::npos, "JALR should check return PC");
|
||||
t.IsTrue(generated.find("runtime->dispatchGuestBranch(rdram, ctx, jumpTarget") != std::string::npos, "JALR should dispatch through runtime branch helper");
|
||||
t.IsTrue(generated.find("PS2Runtime::GuestBranchKind::IndirectCall") != std::string::npos,
|
||||
"JALR should identify itself as an indirect call");
|
||||
t.IsTrue(generated.find("0xD000u, 0xD008u") != std::string::npos,
|
||||
"JALR should pass call-site and fallthrough PCs to the runtime helper");
|
||||
t.IsFalse(generated.find("jumpTarget == 0u") != std::string::npos,
|
||||
"JALR must not silently turn target 0 into a successful call return");
|
||||
});
|
||||
|
||||
tc.Run("backward BEQ returns to the dispatcher through a resumable loop head", [](TestCase &t) {
|
||||
@@ -1218,7 +1217,7 @@ void register_code_generator_tests()
|
||||
std::string generated = gen.generateFunction(func, { jal, jalDelay, atReturn, atTarget, jr, jrDelay }, false);
|
||||
printGeneratedCode("JR $31 returns through dynamic target without broad local switch", generated);
|
||||
|
||||
t.IsTrue(generated.find("uint32_t jumpTarget = GPR_U32(ctx, 31);") != std::string::npos,
|
||||
t.IsTrue(generated.find("const uint32_t jumpTarget = GPR_U32(ctx, 31);") != std::string::npos,
|
||||
"JR $31 should still read the dynamic return target");
|
||||
t.IsFalse(generated.find("switch (jumpTarget)") != std::string::npos,
|
||||
"JR $31 should not emit a broad local switch over internal labels");
|
||||
@@ -1226,6 +1225,10 @@ void register_code_generator_tests()
|
||||
"internal JAL return address should still be emitted as a label");
|
||||
t.IsTrue(generated.find(" return;") != std::string::npos,
|
||||
"JR $31 should return to the dispatcher/runtime after setting ctx->pc");
|
||||
t.IsTrue(generated.find("PS2Runtime::GuestBranchKind::Return") != std::string::npos,
|
||||
"JR $31 should use the Return branch kind for precise diagnostics");
|
||||
t.IsTrue(generated.find("\"JR $ra\"") != std::string::npos,
|
||||
"JR $31 should pass a return-specific debug name");
|
||||
});
|
||||
|
||||
tc.Run("trailing JR $31 without decoded delay slot still emits return flow", [](TestCase &t) {
|
||||
@@ -1246,7 +1249,7 @@ void register_code_generator_tests()
|
||||
std::string generated = gen.generateFunction(func, {jal, jalDelay, atReturn, atTarget, jr}, false);
|
||||
printGeneratedCode("trailing JR $31 without decoded delay slot still emits return flow", generated);
|
||||
|
||||
t.IsTrue(generated.find("uint32_t jumpTarget = GPR_U32(ctx, 31);") != std::string::npos,
|
||||
t.IsTrue(generated.find("const uint32_t jumpTarget = GPR_U32(ctx, 31);") != std::string::npos,
|
||||
"truncated trailing JR should still read the return target");
|
||||
t.IsFalse(generated.find("switch (jumpTarget)") != std::string::npos,
|
||||
"truncated trailing JR should not emit a broad local return-target switch");
|
||||
@@ -1254,6 +1257,8 @@ void register_code_generator_tests()
|
||||
"truncated trailing JR should still include the internal return label");
|
||||
t.IsTrue(generated.find("// JR $31 - Handled by branch logic") == std::string::npos,
|
||||
"truncated trailing JR must not degrade to comment-only output");
|
||||
t.IsTrue(generated.find("PS2Runtime::GuestBranchKind::Return") != std::string::npos,
|
||||
"truncated JR $31 should still use return diagnostics");
|
||||
});
|
||||
|
||||
tc.Run("unresolved JR non-RA uses dispatcher resume entries without broad local switch", [](TestCase &t) {
|
||||
@@ -1289,6 +1294,8 @@ void register_code_generator_tests()
|
||||
"owner resume switch should include internal fallback labels");
|
||||
t.IsTrue(generated.find("ctx->pc = jumpTarget;") != std::string::npos,
|
||||
"unresolved JR should hand the dynamic target back through ctx->pc");
|
||||
t.IsTrue(generated.find("PS2Runtime::GuestBranchKind::IndirectJump") != std::string::npos,
|
||||
"unresolved non-RA JR should use indirect-jump diagnostics");
|
||||
});
|
||||
|
||||
tc.Run("configured jump table addresses drive JR dispatch targets", [](TestCase &t) {
|
||||
@@ -1402,12 +1409,12 @@ void register_code_generator_tests()
|
||||
|
||||
t.IsFalse(generated.find("switch (jumpTarget)") != std::string::npos,
|
||||
"unresolved JALR should not emit a broad local switch over every internal label");
|
||||
t.IsTrue(generated.find("auto targetFn = runtime->lookupFunction(jumpTarget);") != std::string::npos,
|
||||
"unresolved JALR should dispatch through the runtime");
|
||||
t.IsTrue(generated.find("if (ctx->pc == __entryPc) { ctx->pc = 0x151Cu; }") != std::string::npos,
|
||||
"JALR should contain unchanged-PC fallback to fallthrough");
|
||||
t.IsTrue(generated.find("if (ctx->pc != 0x151Cu) { return; }") != std::string::npos,
|
||||
"JALR should retain non-fallthrough guard");
|
||||
t.IsTrue(generated.find("runtime->dispatchGuestBranch(rdram, ctx, jumpTarget") != std::string::npos,
|
||||
"unresolved JALR should dispatch through the runtime branch helper");
|
||||
t.IsTrue(generated.find("PS2Runtime::GuestBranchKind::IndirectCall") != std::string::npos,
|
||||
"JALR should retain indirect-call dispatch kind");
|
||||
t.IsTrue(generated.find("0x1514u, 0x151Cu") != std::string::npos,
|
||||
"JALR should pass call-site and fallthrough PCs to runtime dispatch");
|
||||
});
|
||||
|
||||
tc.Run("JALR fallback should not expose epilogue tail-jump labels", [](TestCase &t) {
|
||||
|
||||
@@ -15,9 +15,12 @@ void register_ps2_sif_rpc_tests();
|
||||
void register_ps2_sif_dma_tests();
|
||||
void register_ps2_recompiler_tests();
|
||||
void register_ps2_runtime_expansion_tests();
|
||||
void reset_ps2_test_function_table();
|
||||
|
||||
int main()
|
||||
{
|
||||
MiniTest::BeforeEach(reset_ps2_test_function_table);
|
||||
|
||||
register_code_generator_tests();
|
||||
register_r5900_decoder_tests();
|
||||
register_elf_analyzer_tests();
|
||||
|
||||
@@ -217,6 +217,25 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
void testGuestBranchImplicitReturnHandler(uint8_t *, R5900Context *ctx, PS2Runtime *)
|
||||
{
|
||||
if (ctx)
|
||||
{
|
||||
setRegU32(*ctx, 2, 0x00FACE42u);
|
||||
// Leave ctx->pc at the entry point. dispatchGuestBranch should convert
|
||||
// unchanged call PC into the supplied fallthrough PC for call-like edges.
|
||||
}
|
||||
}
|
||||
|
||||
void testGuestBranchTransferHandler(uint8_t *, R5900Context *ctx, PS2Runtime *)
|
||||
{
|
||||
if (ctx)
|
||||
{
|
||||
setRegU32(*ctx, 2, 0x00BEEFu);
|
||||
ctx->pc = 0x33330000u;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr uint32_t kAsyncCounterAddr = 0x2400u;
|
||||
|
||||
void testWaitForAsyncCounter(uint8_t *rdram, R5900Context *ctx, PS2Runtime *)
|
||||
@@ -476,9 +495,10 @@ void register_ps2_runtime_expansion_tests()
|
||||
"first guest worker should observe that the runtime requested preemption under contention");
|
||||
});
|
||||
|
||||
tc.Run("lookupFunction aliases internal resume PCs to nearest owner", [](TestCase &t)
|
||||
tc.Run("lookupFunction rejects internal resume PCs without exact registration", [](TestCase &t)
|
||||
{
|
||||
PS2Runtime runtime;
|
||||
runtime.setMissingFunctionPolicy(PS2Runtime::MissingFunctionPolicy::Stop);
|
||||
runtime.registerFunction(0x1000u, &testResumeOwnerFallbackHandler);
|
||||
runtime.registerFunction(0x1100u, &testResumeNextFunctionHandler);
|
||||
|
||||
@@ -487,13 +507,16 @@ void register_ps2_runtime_expansion_tests()
|
||||
auto fn = runtime.lookupFunction(ctx.pc);
|
||||
fn(nullptr, &ctx, &runtime);
|
||||
|
||||
t.Equals(::getRegU32(&ctx, 2), 0x00ABC123u,
|
||||
"internal resume PC should dispatch to its owner function");
|
||||
t.Equals(::getRegU32(&ctx, 2), 0u,
|
||||
"unregistered resume PC should not alias to the nearest owner");
|
||||
t.IsTrue(runtime.isStopRequested(),
|
||||
"missing exact dispatch target should request runtime stop");
|
||||
});
|
||||
|
||||
tc.Run("lookupFunction aliases final-function resume PCs inside code regions", [](TestCase &t)
|
||||
tc.Run("lookupFunction rejects final-function PCs inside code regions without exact registration", [](TestCase &t)
|
||||
{
|
||||
PS2Runtime runtime;
|
||||
runtime.setMissingFunctionPolicy(PS2Runtime::MissingFunctionPolicy::Stop);
|
||||
runtime.memory().registerCodeRegion(0x2000u, 0x2100u);
|
||||
runtime.registerFunction(0x2000u, &testResumeOwnerFallbackHandler);
|
||||
|
||||
@@ -502,8 +525,84 @@ void register_ps2_runtime_expansion_tests()
|
||||
auto fn = runtime.lookupFunction(ctx.pc);
|
||||
fn(nullptr, &ctx, &runtime);
|
||||
|
||||
t.Equals(::getRegU32(&ctx, 2), 0x00ABC123u,
|
||||
"last function should own resumable PCs within its code region");
|
||||
t.Equals(::getRegU32(&ctx, 2), 0u,
|
||||
"code-region membership alone should not alias to the previous function");
|
||||
t.IsTrue(runtime.isStopRequested(),
|
||||
"missing exact final-function target should request runtime stop");
|
||||
});
|
||||
|
||||
tc.Run("dispatchGuestBranch call normalizes unchanged callee PC to fallthrough", [](TestCase &t)
|
||||
{
|
||||
PS2Runtime runtime;
|
||||
runtime.registerFunction(0x3000u, &testGuestBranchImplicitReturnHandler);
|
||||
|
||||
R5900Context ctx{};
|
||||
ctx.pc = 0x2000u;
|
||||
|
||||
const bool returnedToFallthrough = runtime.dispatchGuestBranch(
|
||||
nullptr,
|
||||
&ctx,
|
||||
0x3000u,
|
||||
0x2000u,
|
||||
0x2008u,
|
||||
PS2Runtime::GuestBranchKind::IndirectCall,
|
||||
"test-jalr");
|
||||
|
||||
t.IsTrue(returnedToFallthrough,
|
||||
"call-like dispatch should report true when it resumes at fallthrough");
|
||||
t.Equals(ctx.pc, 0x2008u,
|
||||
"unchanged callee PC should be converted to call fallthrough");
|
||||
t.Equals(::getRegU32(&ctx, 2), 0x00FACE42u,
|
||||
"callee should still execute normally");
|
||||
});
|
||||
|
||||
tc.Run("dispatchGuestBranch call returns false when callee transfers elsewhere", [](TestCase &t)
|
||||
{
|
||||
PS2Runtime runtime;
|
||||
runtime.registerFunction(0x3100u, &testGuestBranchTransferHandler);
|
||||
|
||||
R5900Context ctx{};
|
||||
ctx.pc = 0x2000u;
|
||||
|
||||
const bool returnedToFallthrough = runtime.dispatchGuestBranch(
|
||||
nullptr,
|
||||
&ctx,
|
||||
0x3100u,
|
||||
0x2000u,
|
||||
0x2008u,
|
||||
PS2Runtime::GuestBranchKind::IndirectCall,
|
||||
"test-jalr-transfer");
|
||||
|
||||
t.IsFalse(returnedToFallthrough,
|
||||
"call-like dispatch should stop caller flow when callee transfers elsewhere");
|
||||
t.Equals(ctx.pc, 0x33330000u,
|
||||
"callee transfer PC should be preserved");
|
||||
});
|
||||
|
||||
tc.Run("dispatchGuestBranch rejects missing exact targets", [](TestCase &t)
|
||||
{
|
||||
PS2Runtime runtime;
|
||||
runtime.setMissingFunctionPolicy(PS2Runtime::MissingFunctionPolicy::Stop);
|
||||
runtime.registerFunction(0x3200u, &testGuestBranchImplicitReturnHandler);
|
||||
|
||||
R5900Context ctx{};
|
||||
ctx.pc = 0x2000u;
|
||||
|
||||
const bool returnedToFallthrough = runtime.dispatchGuestBranch(
|
||||
nullptr,
|
||||
&ctx,
|
||||
0x3210u,
|
||||
0x2000u,
|
||||
0x2008u,
|
||||
PS2Runtime::GuestBranchKind::IndirectCall,
|
||||
"test-missing");
|
||||
|
||||
t.IsFalse(returnedToFallthrough,
|
||||
"missing target should not resume caller flow");
|
||||
t.IsTrue(runtime.isStopRequested(),
|
||||
"missing exact target should request runtime stop");
|
||||
t.Equals(ctx.pc, 0x3210u,
|
||||
"missing target should remain visible in ctx->pc for diagnostics");
|
||||
});
|
||||
|
||||
tc.Run("vblank intc handlers can preempt serialized guest execution", [](TestCase &t)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
#include "ps2_runtime.h"
|
||||
#include "runtime/ps2_memory.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
// For Unit tests link ps2_runtime without the generated runner source.
|
||||
extern const uint32_t g_ps2RecompiledFunctionTableBase = 0x00000000u;
|
||||
extern const uint32_t g_ps2RecompiledFunctionTableEnd = PS2_RAM_SIZE;
|
||||
extern const uint32_t g_ps2RecompiledFunctionTableSlotCount = (g_ps2RecompiledFunctionTableEnd - g_ps2RecompiledFunctionTableBase) >> 2;
|
||||
|
||||
PS2Runtime::RecompiledFunction g_ps2RecompiledFunctionTable[g_ps2RecompiledFunctionTableSlotCount] = {};
|
||||
|
||||
void reset_ps2_test_function_table()
|
||||
{
|
||||
std::fill(g_ps2RecompiledFunctionTable,
|
||||
g_ps2RecompiledFunctionTable + g_ps2RecompiledFunctionTableSlotCount,
|
||||
nullptr);
|
||||
}
|
||||
Reference in New Issue
Block a user