From 5196a6672a9489bd8e8437b6cf602632e2c2eae2 Mon Sep 17 00:00:00 2001 From: Ranieri Date: Sat, 4 Jul 2026 00:43:22 -0300 Subject: [PATCH] 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 --- .../ps2recomp/Emitters/control_flow_emitter.h | 84 + .../ps2recomp/Emitters/function_emitter.h | 25 + .../Emitters/function_table_emitter.h | 26 + .../ps2recomp/Translators/cop0_translator.h | 22 + .../ps2recomp/Translators/fpu_translator.h | 22 + .../Translators/instruction_translator.h | 22 + .../ps2recomp/Translators/mmi_translator.h | 22 + .../ps2recomp/Translators/regimm_translator.h | 22 + .../Translators/special_translator.h | 22 + .../ps2recomp/Translators/vu_translator.h | 22 + ps2xRecomp/include/ps2recomp/code_generator.h | 14 +- .../include/ps2recomp/codegen_helpers.h | 37 + ps2xRecomp/include/ps2recomp/config_manager.h | 4 + .../include/ps2recomp/control_flow_analyzer.h | 43 + .../include/ps2recomp/control_flow_utils.h | 35 + ps2xRecomp/include/ps2recomp/elf_parser.h | 3 + ps2xRecomp/include/ps2recomp/ps2_recompiler.h | 5 + .../include/ps2recomp/recompiler_reporter.h | 93 + ps2xRecomp/src/lib/code_generator.cpp | 3924 +---------------- ps2xRecomp/src/lib/config_manager.cpp | 25 +- ps2xRecomp/src/lib/control_flow_analyzer.cpp | 423 ++ ps2xRecomp/src/lib/control_flow_emitter.cpp | 567 +++ ps2xRecomp/src/lib/cop0_translator.cpp | 167 + ps2xRecomp/src/lib/elf_parser.cpp | 69 +- ps2xRecomp/src/lib/fpu_translator.cpp | 146 + ps2xRecomp/src/lib/function_emitter.cpp | 184 + ps2xRecomp/src/lib/function_table_emitter.cpp | 176 + ps2xRecomp/src/lib/instruction_translator.cpp | 262 ++ .../src/lib/jump_table_switch_emitter.cpp | 46 + .../src/lib/mmi_translation_helpers.cpp | 608 +++ ps2xRecomp/src/lib/mmi_translator.cpp | 139 + ps2xRecomp/src/lib/ps2_recompiler.cpp | 214 +- ps2xRecomp/src/lib/recompiler_reporter.cpp | 229 + ps2xRecomp/src/lib/regimm_translator.cpp | 57 + ps2xRecomp/src/lib/special_translator.cpp | 186 + ps2xRecomp/src/lib/vu_translation_helpers.cpp | 890 ++++ ps2xRecomp/src/lib/vu_translator.cpp | 446 ++ ps2xRecomp/src/runner/main.cpp | 3 + ps2xRuntime/CMakeLists.txt | 7 + ps2xRuntime/include/ps2_runtime.h | 54 +- ps2xRuntime/include/register_functions.h | 8 - ps2xRuntime/src/lib/game_overrides.cpp | 3 +- ps2xRuntime/src/lib/ps2_debug_panel.cpp | 6 + ps2xRuntime/src/lib/ps2_runtime.cpp | 629 ++- ps2xRuntime/src/main.cpp | 3 - ps2xRuntime/src/runner/register_functions.cpp | 11 +- ps2xStudio/CMakeLists.txt | 4 + ps2xTest/CMakeLists.txt | 9 + ps2xTest/include/MiniTest.h | 13 +- ps2xTest/src/code_generator_tests.cpp | 69 +- ps2xTest/src/main.cpp | 3 + ps2xTest/src/ps2_runtime_expansion_tests.cpp | 111 +- ps2xTest/src/test_function_table.cpp | 18 + 53 files changed, 5879 insertions(+), 4353 deletions(-) create mode 100644 ps2xRecomp/include/ps2recomp/Emitters/control_flow_emitter.h create mode 100644 ps2xRecomp/include/ps2recomp/Emitters/function_emitter.h create mode 100644 ps2xRecomp/include/ps2recomp/Emitters/function_table_emitter.h create mode 100644 ps2xRecomp/include/ps2recomp/Translators/cop0_translator.h create mode 100644 ps2xRecomp/include/ps2recomp/Translators/fpu_translator.h create mode 100644 ps2xRecomp/include/ps2recomp/Translators/instruction_translator.h create mode 100644 ps2xRecomp/include/ps2recomp/Translators/mmi_translator.h create mode 100644 ps2xRecomp/include/ps2recomp/Translators/regimm_translator.h create mode 100644 ps2xRecomp/include/ps2recomp/Translators/special_translator.h create mode 100644 ps2xRecomp/include/ps2recomp/Translators/vu_translator.h create mode 100644 ps2xRecomp/include/ps2recomp/codegen_helpers.h create mode 100644 ps2xRecomp/include/ps2recomp/control_flow_analyzer.h create mode 100644 ps2xRecomp/include/ps2recomp/control_flow_utils.h create mode 100644 ps2xRecomp/include/ps2recomp/recompiler_reporter.h create mode 100644 ps2xRecomp/src/lib/control_flow_analyzer.cpp create mode 100644 ps2xRecomp/src/lib/control_flow_emitter.cpp create mode 100644 ps2xRecomp/src/lib/cop0_translator.cpp create mode 100644 ps2xRecomp/src/lib/fpu_translator.cpp create mode 100644 ps2xRecomp/src/lib/function_emitter.cpp create mode 100644 ps2xRecomp/src/lib/function_table_emitter.cpp create mode 100644 ps2xRecomp/src/lib/instruction_translator.cpp create mode 100644 ps2xRecomp/src/lib/jump_table_switch_emitter.cpp create mode 100644 ps2xRecomp/src/lib/mmi_translation_helpers.cpp create mode 100644 ps2xRecomp/src/lib/mmi_translator.cpp create mode 100644 ps2xRecomp/src/lib/recompiler_reporter.cpp create mode 100644 ps2xRecomp/src/lib/regimm_translator.cpp create mode 100644 ps2xRecomp/src/lib/special_translator.cpp create mode 100644 ps2xRecomp/src/lib/vu_translation_helpers.cpp create mode 100644 ps2xRecomp/src/lib/vu_translator.cpp delete mode 100644 ps2xRuntime/include/register_functions.h create mode 100644 ps2xTest/src/test_function_table.cpp diff --git a/ps2xRecomp/include/ps2recomp/Emitters/control_flow_emitter.h b/ps2xRecomp/include/ps2recomp/Emitters/control_flow_emitter.h new file mode 100644 index 0000000..6258519 --- /dev/null +++ b/ps2xRecomp/include/ps2recomp/Emitters/control_flow_emitter.h @@ -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 +#include +#include +#include +#include + +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 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 diff --git a/ps2xRecomp/include/ps2recomp/Emitters/function_emitter.h b/ps2xRecomp/include/ps2recomp/Emitters/function_emitter.h new file mode 100644 index 0000000..68ca1c4 --- /dev/null +++ b/ps2xRecomp/include/ps2recomp/Emitters/function_emitter.h @@ -0,0 +1,25 @@ +#ifndef PS2RECOMP_FUNCTION_EMITTER_H +#define PS2RECOMP_FUNCTION_EMITTER_H + +#include +#include + +namespace ps2recomp +{ + struct Function; + struct Instruction; + class CodeGenerator; + + class FunctionEmitter + { + public: + explicit FunctionEmitter(CodeGenerator &codeGenerator); + + std::string emit(const Function &function, const std::vector &instructions, bool useHeaders); + + private: + CodeGenerator &m_codeGenerator; + }; +} + +#endif // PS2RECOMP_FUNCTION_EMITTER_H diff --git a/ps2xRecomp/include/ps2recomp/Emitters/function_table_emitter.h b/ps2xRecomp/include/ps2recomp/Emitters/function_table_emitter.h new file mode 100644 index 0000000..67b7b90 --- /dev/null +++ b/ps2xRecomp/include/ps2recomp/Emitters/function_table_emitter.h @@ -0,0 +1,26 @@ +#ifndef PS2RECOMP_FUNCTION_TABLE_EMITTER_H +#define PS2RECOMP_FUNCTION_TABLE_EMITTER_H + +#include +#include +#include +#include + +namespace ps2recomp +{ + struct Function; + class CodeGenerator; + + class FunctionTableEmitter + { + public: + explicit FunctionTableEmitter(CodeGenerator &codeGenerator); + + std::string emit(const std::vector &functions, const std::map &stubs); + + private: + CodeGenerator &m_codeGenerator; + }; +} + +#endif // PS2RECOMP_FUNCTION_TABLE_EMITTER_H diff --git a/ps2xRecomp/include/ps2recomp/Translators/cop0_translator.h b/ps2xRecomp/include/ps2recomp/Translators/cop0_translator.h new file mode 100644 index 0000000..68c7bb5 --- /dev/null +++ b/ps2xRecomp/include/ps2recomp/Translators/cop0_translator.h @@ -0,0 +1,22 @@ +#ifndef PS2RECOMP_COP0_TRANSLATOR_H +#define PS2RECOMP_COP0_TRANSLATOR_H + +#include + +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 diff --git a/ps2xRecomp/include/ps2recomp/Translators/fpu_translator.h b/ps2xRecomp/include/ps2recomp/Translators/fpu_translator.h new file mode 100644 index 0000000..b2a7ee3 --- /dev/null +++ b/ps2xRecomp/include/ps2recomp/Translators/fpu_translator.h @@ -0,0 +1,22 @@ +#ifndef PS2RECOMP_FPU_TRANSLATOR_H +#define PS2RECOMP_FPU_TRANSLATOR_H + +#include + +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 diff --git a/ps2xRecomp/include/ps2recomp/Translators/instruction_translator.h b/ps2xRecomp/include/ps2recomp/Translators/instruction_translator.h new file mode 100644 index 0000000..ef0b274 --- /dev/null +++ b/ps2xRecomp/include/ps2recomp/Translators/instruction_translator.h @@ -0,0 +1,22 @@ +#ifndef PS2RECOMP_INSTRUCTION_TRANSLATOR_H +#define PS2RECOMP_INSTRUCTION_TRANSLATOR_H + +#include + +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 diff --git a/ps2xRecomp/include/ps2recomp/Translators/mmi_translator.h b/ps2xRecomp/include/ps2recomp/Translators/mmi_translator.h new file mode 100644 index 0000000..48cb0a1 --- /dev/null +++ b/ps2xRecomp/include/ps2recomp/Translators/mmi_translator.h @@ -0,0 +1,22 @@ +#ifndef PS2RECOMP_MMI_TRANSLATOR_H +#define PS2RECOMP_MMI_TRANSLATOR_H + +#include + +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 diff --git a/ps2xRecomp/include/ps2recomp/Translators/regimm_translator.h b/ps2xRecomp/include/ps2recomp/Translators/regimm_translator.h new file mode 100644 index 0000000..d90cfc4 --- /dev/null +++ b/ps2xRecomp/include/ps2recomp/Translators/regimm_translator.h @@ -0,0 +1,22 @@ +#ifndef PS2RECOMP_REGIMM_TRANSLATOR_H +#define PS2RECOMP_REGIMM_TRANSLATOR_H + +#include + +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 diff --git a/ps2xRecomp/include/ps2recomp/Translators/special_translator.h b/ps2xRecomp/include/ps2recomp/Translators/special_translator.h new file mode 100644 index 0000000..5e6422f --- /dev/null +++ b/ps2xRecomp/include/ps2recomp/Translators/special_translator.h @@ -0,0 +1,22 @@ +#ifndef PS2RECOMP_SPECIAL_TRANSLATOR_H +#define PS2RECOMP_SPECIAL_TRANSLATOR_H + +#include + +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 diff --git a/ps2xRecomp/include/ps2recomp/Translators/vu_translator.h b/ps2xRecomp/include/ps2recomp/Translators/vu_translator.h new file mode 100644 index 0000000..fb672b6 --- /dev/null +++ b/ps2xRecomp/include/ps2recomp/Translators/vu_translator.h @@ -0,0 +1,22 @@ +#ifndef PS2RECOMP_VU_TRANSLATOR_H +#define PS2RECOMP_VU_TRANSLATOR_H + +#include + +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 diff --git a/ps2xRecomp/include/ps2recomp/code_generator.h b/ps2xRecomp/include/ps2recomp/code_generator.h index 5fdecf0..d2a3c8f 100644 --- a/ps2xRecomp/include/ps2recomp/code_generator.h +++ b/ps2xRecomp/include/ps2recomp/code_generator.h @@ -7,6 +7,7 @@ #include #include #include +#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 kKeywords; @@ -35,13 +37,7 @@ namespace ps2recomp std::string entryName; }; - struct AnalysisResult { - std::unordered_set entryPoints; - std::unordered_set externalEntryPoints; - std::unordered_set resumeEntryPoints; - std::unordered_set indirectFallbackEntryPoints; - std::unordered_map> jumpTableTargets; - }; + using AnalysisResult = ControlFlowAnalysisResult; std::string generateFunction(const Function &function, const std::vector &instructions, const bool &useHeaders); std::string generateFunctionRegistration(const std::vector &functions, const std::map &stubs); @@ -54,6 +50,7 @@ namespace ps2recomp void setConfiguredJumpTables(const std::vector &jumpTables); void setResumeEntryTargets(const std::unordered_map> &resumeTargetsByOwner); void setEmitInstructionComments(bool emitInstructionComments); + void setReporter(RecompilerReporter *reporter); AnalysisResult collectInternalBranchTargets(const Function &function, const std::vector &instructions, @@ -68,8 +65,11 @@ namespace ps2recomp const std::vector
& 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); diff --git a/ps2xRecomp/include/ps2recomp/codegen_helpers.h b/ps2xRecomp/include/ps2recomp/codegen_helpers.h new file mode 100644 index 0000000..1264f31 --- /dev/null +++ b/ps2xRecomp/include/ps2recomp/codegen_helpers.h @@ -0,0 +1,37 @@ +#ifndef PS2RECOMP_CODEGEN_HELPERS_H +#define PS2RECOMP_CODEGEN_HELPERS_H + +#include +#include +#include +#include + +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 diff --git a/ps2xRecomp/include/ps2recomp/config_manager.h b/ps2xRecomp/include/ps2recomp/config_manager.h index 8f1375d..999260d 100644 --- a/ps2xRecomp/include/ps2recomp/config_manager.h +++ b/ps2xRecomp/include/ps2recomp/config_manager.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; }; } diff --git a/ps2xRecomp/include/ps2recomp/control_flow_analyzer.h b/ps2xRecomp/include/ps2recomp/control_flow_analyzer.h new file mode 100644 index 0000000..d932e5e --- /dev/null +++ b/ps2xRecomp/include/ps2recomp/control_flow_analyzer.h @@ -0,0 +1,43 @@ +#ifndef PS2RECOMP_CONTROL_FLOW_ANALYZER_H +#define PS2RECOMP_CONTROL_FLOW_ANALYZER_H + +#include +#include +#include +#include + +namespace ps2recomp +{ + struct Function; + struct Instruction; + struct Section; + class RecompilerReporter; + + struct ControlFlowAnalysisResult + { + std::unordered_set entryPoints; + std::unordered_set externalEntryPoints; + std::unordered_set resumeEntryPoints; + std::unordered_set indirectFallbackEntryPoints; + std::unordered_map> jumpTableTargets; + }; + + class ControlFlowAnalyzer + { + public: + ControlFlowAnalyzer(const std::vector
§ions, + const std::unordered_map> &configuredJumpTableTargetsByAddress, + RecompilerReporter *reporter); + + ControlFlowAnalysisResult analyze(const Function &function, + const std::vector &instructions, + const std::vector *allFunctions = nullptr) const; + + private: + const std::vector
&m_sections; + const std::unordered_map> &m_configJumpTableTargetsByAddress; + RecompilerReporter *m_reporter = nullptr; + }; +} + +#endif // PS2RECOMP_CONTROL_FLOW_ANALYZER_H diff --git a/ps2xRecomp/include/ps2recomp/control_flow_utils.h b/ps2xRecomp/include/ps2recomp/control_flow_utils.h new file mode 100644 index 0000000..13e9913 --- /dev/null +++ b/ps2xRecomp/include/ps2recomp/control_flow_utils.h @@ -0,0 +1,35 @@ +#ifndef PS2RECOMP_CONTROL_FLOW_UTILS_H +#define PS2RECOMP_CONTROL_FLOW_UTILS_H + +#include "ps2recomp/instructions.h" + +#include + +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(inst.simmediate) == 0; + + return sllZero || addiuZero; + } +} + +#endif // PS2RECOMP_CONTROL_FLOW_UTILS_H diff --git a/ps2xRecomp/include/ps2recomp/elf_parser.h b/ps2xRecomp/include/ps2recomp/elf_parser.h index c03f43f..c8cafe5 100644 --- a/ps2xRecomp/include/ps2recomp/elf_parser.h +++ b/ps2xRecomp/include/ps2recomp/elf_parser.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 m_relocations; std::vector m_extraFunctions; bool m_hasLoadedGhidraMap = false; + RecompilerReporter *m_reporter = nullptr; std::unordered_set m_ghidraMapStarts; void loadSections(); diff --git a/ps2xRecomp/include/ps2recomp/ps2_recompiler.h b/ps2xRecomp/include/ps2recomp/ps2_recompiler.h index 5740de1..4ffeaa0 100644 --- a/ps2xRecomp/include/ps2recomp/ps2_recompiler.h +++ b/ps2xRecomp/include/ps2recomp/ps2_recompiler.h @@ -3,11 +3,14 @@ #include "code_generator.h" #include "config_manager.h" +#include "recompiler_reporter.h" #include #include #include #include #include +#include +#include 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 m_decoder; std::unique_ptr m_codeGenerator; RecompilerConfig m_config; + RecompilerReporter m_reporter; std::vector m_functions; std::vector m_symbols; diff --git a/ps2xRecomp/include/ps2recomp/recompiler_reporter.h b/ps2xRecomp/include/ps2recomp/recompiler_reporter.h new file mode 100644 index 0000000..949b69d --- /dev/null +++ b/ps2xRecomp/include/ps2recomp/recompiler_reporter.h @@ -0,0 +1,93 @@ +#ifndef PS2RECOMP_RECOMPILER_REPORTER_H +#define PS2RECOMP_RECOMPILER_REPORTER_H + +#include +#include +#include +#include +#include +#include + +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 &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 m_events; + }; +} + +#endif diff --git a/ps2xRecomp/src/lib/code_generator.cpp b/ps2xRecomp/src/lib/code_generator.cpp index 3a24521..96e5ce5 100644 --- a/ps2xRecomp/src/lib/code_generator.cpp +++ b/ps2xRecomp/src/lib/code_generator.cpp @@ -1,9 +1,23 @@ #include "ps2recomp/code_generator.h" +#include "ps2recomp/control_flow_analyzer.h" +#include "ps2recomp/Emitters/function_emitter.h" +#include "ps2recomp/Emitters/function_table_emitter.h" +#include "ps2recomp/Translators/instruction_translator.h" +#include "ps2recomp/Translators/special_translator.h" +#include "ps2recomp/Translators/regimm_translator.h" +#include "ps2recomp/Translators/cop0_translator.h" +#include "ps2recomp/Translators/fpu_translator.h" +#include "ps2recomp/Translators/mmi_translator.h" +#include "ps2recomp/Translators/vu_translator.h" +#include "ps2recomp/Emitters/control_flow_emitter.h" +#include "ps2recomp/control_flow_utils.h" #include "ps2recomp/instructions.h" #include "ps2recomp/ps2_recompiler.h" #include "ps2recomp/r5900_decoder.h" +#include "ps2recomp/recompiler_reporter.h" #include "ps2recomp/types.h" #include "ps2_runtime_calls.h" + #include #include #include @@ -34,45 +48,6 @@ namespace ps2recomp namespace ps2recomp { - static uint32_t buildAbsoluteJumpTarget(uint32_t address, uint32_t target) - { - return ((address + 4) & 0xF0000000u) | (target << 2); - } - - static Instruction makeSyntheticDelaySlot(uint32_t address) - { - Instruction inst{}; - inst.address = address; - inst.raw = 0; - inst.opcode = OPCODE_SPECIAL; - inst.function = SPECIAL_SLL; - return inst; - } - - static 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; - } - - static 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); - } static std::string sanitizeIdentifierBody(const std::string &name) { @@ -180,6 +155,11 @@ namespace ps2recomp m_emitInstructionComments = emitInstructionComments; } + void CodeGenerator::setReporter(RecompilerReporter *reporter) + { + m_reporter = reporter; + } + std::string CodeGenerator::getFunctionName(uint32_t address) const { auto it = m_renamedFunctions.find(address); @@ -225,816 +205,52 @@ namespace ps2recomp const Function &function, const AnalysisResult &analysisResult) { - const std::unordered_set &internalTargets = analysisResult.entryPoints; - std::stringstream ss; - - const bool hasValidDelaySlot = !(delaySlot.opcode == OPCODE_SPECIAL && - delaySlot.function == SPECIAL_SLL && - delaySlot.rd == 0 && - delaySlot.rt == 0 && - delaySlot.sa == 0); - - std::string delaySlotCode = ""; - std::string delaySlotPrefix = ""; - std::string delaySlotSuffix = ""; - if (hasValidDelaySlot) { - delaySlotPrefix = "ctx->in_delay_slot = true; ctx->branch_pc = 0x" + fmt::format("{:X}", branchInst.address) + "u;\n "; - if (m_emitInstructionComments) - { - delaySlotCode = " // 0x" + fmt::format("{:x}", delaySlot.address) + ": 0x" + fmt::format("{:x}", delaySlot.raw); - std::string disassembly = R5900Decoder::disassembleInstruction(delaySlot); - if (!disassembly.empty()) { - delaySlotCode += " " + disassembly; - } - delaySlotCode += " (Delay Slot)\n "; - } - delaySlotCode += translateInstruction(delaySlot); - delaySlotSuffix = "\n ctx->in_delay_slot = false;"; - } - - const uint8_t rs_reg = branchInst.rs; - const uint8_t rt_reg = branchInst.rt; - const uint8_t rd_reg = branchInst.rd; - - const uint32_t branchPc = branchInst.address; - const uint32_t delayPc = branchInst.address + 4u; - const uint32_t fallthroughPc = branchInst.address + 8u; - auto emitInternalTarget = [&](uint32_t target, uint32_t sourcePc, std::string_view indent) - { - ss << fmt::format("{}ctx->pc = 0x{:X}u;\n", indent, target); - const bool isCallLikeEdge = - (branchInst.opcode == OPCODE_JAL) || - (branchInst.opcode == OPCODE_SPECIAL && branchInst.function == SPECIAL_JALR); - if (target <= sourcePc && !isCallLikeEdge) - { - ss << fmt::format("{}if (runtime->shouldPreemptGuestExecution()) {{\n", indent); - ss << fmt::format("{} return;\n", indent); - ss << fmt::format("{}}}\n", indent); - ss << fmt::format("{}goto label_{:x};\n", indent, target); - } - else - { - ss << fmt::format("{}goto label_{:x};\n", indent, target); - } - }; - - std::vector sortedInternalTargets; - if (branchInst.opcode == OPCODE_SPECIAL && - ((branchInst.function == SPECIAL_JR && branchInst.rs != 31) || - branchInst.function == SPECIAL_JALR) && - !internalTargets.empty()) - { - // Only emit local indirect-jump switches for jump tables we actually resolved. - // Falling back to every internal target here can duplicate huge switches at each - // indirect branch. Unresolved JR/JALR targets are registered as resumable entries - // instead, so runtime dispatch can re-enter this function at ctx->pc. - auto jtIt = analysisResult.jumpTableTargets.find(branchInst.address); - if (jtIt != analysisResult.jumpTableTargets.end()) { - sortedInternalTargets = jtIt->second; - std::sort(sortedInternalTargets.begin(), sortedInternalTargets.end()); - } - } - - if (internalTargets.contains(delayPc)) - { - ss << fmt::format(" if (ctx->pc == 0x{:X}u) {{\n", delayPc); - - if (hasValidDelaySlot) - { - ss << fmt::format(" ctx->pc = 0x{:X}u;\n", delayPc); - ss << " " << delaySlotCode << "\n"; - } - - ss << fmt::format(" ctx->pc = 0x{:X}u;\n", fallthroughPc); - - if (internalTargets.contains(fallthroughPc)) - { - ss << fmt::format(" goto label_{:x};\n", fallthroughPc); // label uses lowercase usually, but let's keep consistency. Labels are case insensitive in C but check expectation. - } - else - { - ss << fmt::format(" goto label_fallthrough_0x{:x};\n", branchPc); - } - - ss << " }\n"; - } - - ss << fmt::format(" ctx->pc = 0x{:X}u;\n", branchPc); - - // ------------------------- - // J / JAL (static jump) - // ------------------------- - if (branchInst.opcode == OPCODE_J || branchInst.opcode == OPCODE_JAL) - { - if (branchInst.opcode == OPCODE_JAL) - { - ss << fmt::format(" SET_GPR_U32(ctx, 31, 0x{:X}u);\n", fallthroughPc); - } - - if (hasValidDelaySlot) - { - ss << fmt::format(" ctx->pc = 0x{:X}u;\n", delayPc); - ss << " " << delaySlotPrefix << delaySlotCode << delaySlotSuffix << "\n"; - } - - const uint32_t target = buildAbsoluteJumpTarget(branchInst.address, branchInst.target); - - if (internalTargets.contains(target)) - { - emitInternalTarget(target, branchPc, " "); - } - else - { - std::string funcName = getFunctionName(target); - ss << fmt::format(" ctx->pc = 0x{:X}u;\n", target); - - if (!funcName.empty()) - { - ss << fmt::format(" if (runtime->hasFunction(0x{:X}u)) {{\n", target); - ss << fmt::format(" auto targetFn = runtime->lookupFunction(0x{:X}u);\n", target); - if (branchInst.opcode == OPCODE_J) - { - ss << " targetFn(rdram, ctx, runtime); return;\n"; - } - else - { - ss << " const uint32_t __entryPc = ctx->pc;\n"; - ss << " targetFn(rdram, ctx, runtime);\n"; - ss << fmt::format(" if (ctx->pc == __entryPc) {{ ctx->pc = 0x{:X}u; }}\n", fallthroughPc); - ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ return; }}\n", fallthroughPc); - } - ss << " } else {\n"; - - if (branchInst.opcode == OPCODE_J) - { - ss << " " << funcName << "(rdram, ctx, runtime); return;\n"; - } - else - { - ss << " const uint32_t __entryPc = ctx->pc;\n"; - ss << " " << funcName << "(rdram, ctx, runtime);\n"; - ss << fmt::format(" if (ctx->pc == __entryPc) {{ ctx->pc = 0x{:X}u; }}\n", fallthroughPc); - ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ return; }}\n", fallthroughPc); - } - ss << " }\n"; - } - else - { - bool emittedRelocCall = false; - const auto relocIt = m_relocationCallNames.find(branchInst.address); - if (relocIt != m_relocationCallNames.end() && !relocIt->second.empty()) - { - 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()) - { - const bool isSyscall = !resolvedSyscallName.empty(); - const std::string_view handlerName = isSyscall ? resolvedSyscallName : resolvedStubName; - - ss << " {\n"; - ss << " const uint32_t __entryPc = ctx->pc;\n"; - ss << " " - << (isSyscall ? "ps2_syscalls::" : "ps2_stubs::") - << handlerName << "(rdram, ctx, runtime);\n"; - ss << " if (ctx->pc == __entryPc) { ctx->pc = getRegU32(ctx, 31); }\n"; - ss << " }\n"; - if (branchInst.opcode == OPCODE_J) - { - ss << " return;\n"; - } - else - { - ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ return; }}\n", fallthroughPc); - } - emittedRelocCall = true; - } - } - - if (!emittedRelocCall) - { - ss << " {\n"; - ss << fmt::format(" auto targetFn = runtime->lookupFunction(0x{:X}u);\n", target); - ss << " const uint32_t __entryPc = ctx->pc;\n"; - ss << " targetFn(rdram, ctx, runtime);\n"; - if (branchInst.opcode == OPCODE_J) - { - ss << " return;\n"; - } - else - { - ss << fmt::format(" if (ctx->pc == __entryPc) {{ ctx->pc = 0x{:X}u; }}\n", fallthroughPc); - ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ return; }}\n", fallthroughPc); - } - ss << " }\n"; - } - } - } - } - // ------------------------- - // JR / JALR (register jump) - // ------------------------- - else if (branchInst.opcode == OPCODE_SPECIAL && - (branchInst.function == SPECIAL_JR || branchInst.function == SPECIAL_JALR)) - { - ss << " {\n"; - ss << " uint32_t jumpTarget = GPR_U32(ctx, " << static_cast(rs_reg) << ");\n"; - - if (branchInst.function == SPECIAL_JALR && rd_reg != 0) - { - ss << fmt::format(" SET_GPR_U32(ctx, {}, 0x{:X}u);\n", rd_reg, fallthroughPc); - } - - if (hasValidDelaySlot) - { - ss << fmt::format(" ctx->pc = 0x{:X}u;\n", delayPc); - ss << " " << delaySlotPrefix << delaySlotCode << delaySlotSuffix << "\n"; - } - - if (branchInst.function == SPECIAL_JALR) - { - ss << " if (jumpTarget == 0u) {\n"; - ss << fmt::format(" ctx->pc = 0x{:X}u;\n", fallthroughPc); - ss << " } else {\n"; - } - - ss << " ctx->pc = jumpTarget;\n"; - - if (!sortedInternalTargets.empty()) - { - ss << " switch (jumpTarget) {\n"; - for (uint32_t t : sortedInternalTargets) - { - ss << fmt::format(" case 0x{:X}u: goto label_{:x};\n", t, t); - } - ss << " default: break;\n"; - ss << " }\n"; - } - - if (branchInst.function == SPECIAL_JR) - { - ss << " return;\n"; - } - else - { - ss << " {\n"; - ss << " auto targetFn = runtime->lookupFunction(jumpTarget);\n"; - ss << " const uint32_t __entryPc = ctx->pc;\n"; - ss << " targetFn(rdram, ctx, runtime);\n"; - ss << fmt::format(" if (ctx->pc == __entryPc) {{ ctx->pc = 0x{:X}u; }}\n", fallthroughPc); - ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ return; }}\n", fallthroughPc); - ss << " }\n"; - } - - if (branchInst.function == SPECIAL_JALR) - { - ss << " }\n"; - } - - ss << " }\n"; - } - // ------------------------- - // Conditional Branches - // ------------------------- - else if (branchInst.isBranch) - { - std::string conditionStr = "false"; - std::string conditionalLinkCode; - std::string unconditionalLinkCode; - - switch (branchInst.opcode) - { - case OPCODE_BEQ: - conditionStr = fmt::format("GPR_U64(ctx, {}) == GPR_U64(ctx, {})", rs_reg, rt_reg); - break; - case OPCODE_BNE: - conditionStr = fmt::format("GPR_U64(ctx, {}) != GPR_U64(ctx, {})", rs_reg, rt_reg); - break; - case OPCODE_BLEZ: - conditionStr = fmt::format("GPR_S32(ctx, {}) <= 0", rs_reg); - break; - case OPCODE_BGTZ: - conditionStr = fmt::format("GPR_S32(ctx, {}) > 0", rs_reg); - break; - case OPCODE_BEQL: - conditionStr = fmt::format("GPR_U64(ctx, {}) == GPR_U64(ctx, {})", rs_reg, rt_reg); - break; - case OPCODE_BNEL: - conditionStr = fmt::format("GPR_U64(ctx, {}) != GPR_U64(ctx, {})", rs_reg, rt_reg); - break; - case OPCODE_BLEZL: - conditionStr = fmt::format("GPR_S32(ctx, {}) <= 0", rs_reg); - break; - case OPCODE_BGTZL: - conditionStr = fmt::format("GPR_S32(ctx, {}) > 0", rs_reg); - break; - case OPCODE_REGIMM: - switch (rt_reg) - { - case REGIMM_BLTZ: - conditionStr = fmt::format("GPR_S32(ctx, {}) < 0", rs_reg); - break; - case REGIMM_BGEZ: - conditionStr = fmt::format("GPR_S32(ctx, {}) >= 0", rs_reg); - break; - case REGIMM_BLTZL: - conditionStr = fmt::format("GPR_S32(ctx, {}) < 0", rs_reg); - break; - case REGIMM_BGEZL: - conditionStr = fmt::format("GPR_S32(ctx, {}) >= 0", rs_reg); - break; - case REGIMM_BLTZAL: - conditionStr = fmt::format("GPR_S32(ctx, {}) < 0", rs_reg); - unconditionalLinkCode = fmt::format("SET_GPR_U32(ctx, 31, 0x{:X}u);", fallthroughPc); - break; - case REGIMM_BGEZAL: - conditionStr = fmt::format("GPR_S32(ctx, {}) >= 0", rs_reg); - unconditionalLinkCode = fmt::format("SET_GPR_U32(ctx, 31, 0x{:X}u);", fallthroughPc); - break; - case REGIMM_BLTZALL: - conditionStr = fmt::format("GPR_S32(ctx, {}) < 0", rs_reg); - conditionalLinkCode = fmt::format("SET_GPR_U32(ctx, 31, 0x{:X}u);", fallthroughPc); - break; - case REGIMM_BGEZALL: - conditionStr = fmt::format("GPR_S32(ctx, {}) >= 0", rs_reg); - conditionalLinkCode = fmt::format("SET_GPR_U32(ctx, 31, 0x{:X}u);", fallthroughPc); - break; - default: - break; - } - break; - case OPCODE_COP1: - if (branchInst.rs == COP1_BC) - { - const uint8_t bc_cond = branchInst.rt; - conditionStr = (bc_cond == COP1_BC_BCF || bc_cond == COP1_BC_BCFL) - ? "!(ctx->fcr31 & 0x800000)" - : "(ctx->fcr31 & 0x800000)"; - } - break; - case OPCODE_COP2: - if (branchInst.rs == COP2_BC) - { - const uint8_t bc_cond = branchInst.rt; - conditionStr = (bc_cond == COP2_BC_BCF || bc_cond == COP2_BC_BCFL) - ? "!(ctx->vu0_status & 0x1)" - : "(ctx->vu0_status & 0x1)"; - } - break; - default: - break; - } - - const int32_t offsetBytes = (static_cast(static_cast(branchInst.simmediate)) << 2); - const uint32_t target = static_cast( - static_cast(branchInst.address + 4u) + static_cast(offsetBytes)); - - const bool isLikely = - (branchInst.opcode == OPCODE_BEQL || branchInst.opcode == OPCODE_BNEL || - branchInst.opcode == OPCODE_BLEZL || branchInst.opcode == OPCODE_BGTZL || - (branchInst.opcode == OPCODE_REGIMM && - (branchInst.rt == REGIMM_BLTZL || branchInst.rt == REGIMM_BGEZL || - branchInst.rt == REGIMM_BLTZALL || branchInst.rt == REGIMM_BGEZALL)) || - (branchInst.opcode == OPCODE_COP1 && branchInst.rs == COP1_BC && - (branchInst.rt == COP1_BC_BCFL || branchInst.rt == COP1_BC_BCTL)) || - (branchInst.opcode == OPCODE_COP2 && branchInst.rs == COP2_BC && - (branchInst.rt == COP2_BC_BCFL || branchInst.rt == COP2_BC_BCTL))); - - const std::string branchTakenVar = fmt::format("branch_taken_0x{:x}", branchInst.address); - ss << " {\n"; - ss << " const bool " << branchTakenVar << " = (" << conditionStr << ");\n"; - - if (!unconditionalLinkCode.empty()) - { - ss << " " << unconditionalLinkCode << "\n"; - } - - if (isLikely) - { - ss << " if (" << branchTakenVar << ") {\n"; - if (!conditionalLinkCode.empty()) - { - ss << " " << conditionalLinkCode << "\n"; - } - if (hasValidDelaySlot) - { - ss << fmt::format(" ctx->pc = 0x{:X}u;\n", delayPc); - ss << " " << delaySlotPrefix << delaySlotCode << delaySlotSuffix << "\n"; - } - - if (internalTargets.contains(target)) - { - emitInternalTarget(target, branchPc, " "); - } - else - { - ss << fmt::format(" ctx->pc = 0x{:X}u;\n", target); - ss << " return;\n"; - } - - ss << " }\n"; - } - else - { - if (!conditionalLinkCode.empty()) - { - ss << " if (" << branchTakenVar << ") { " << conditionalLinkCode << " }\n"; - } - - if (hasValidDelaySlot) - { - ss << fmt::format(" ctx->pc = 0x{:X}u;\n", delayPc); - ss << " " << delaySlotPrefix << delaySlotCode << delaySlotSuffix << "\n"; - } - - ss << " if (" << branchTakenVar << ") {\n"; - if (internalTargets.contains(target)) - { - emitInternalTarget(target, branchPc, " "); - } - else - { - ss << fmt::format(" ctx->pc = 0x{:X}u;\n", target); - ss << " return;\n"; - } - ss << " }\n"; - } - - ss << " }\n"; - } - else - { - ss << " " << translateInstruction(branchInst) << "\n"; - if (hasValidDelaySlot) - { - ss << fmt::format(" ctx->pc = 0x{:X}u;\n", delayPc); - ss << " " << delaySlotPrefix << delaySlotCode << delaySlotSuffix << "\n"; - } - } - - if (internalTargets.contains(delayPc) && !internalTargets.contains(fallthroughPc)) - { - ss << fmt::format("label_fallthrough_0x{:x}:\n", branchPc); - } - - ss << fmt::format(" ctx->pc = 0x{:X}u;\n", fallthroughPc); - - return ss.str(); + ControlFlowEmitter emitter(*this, branchInst, delaySlot, function, analysisResult); + return emitter.emit(); } CodeGenerator::~CodeGenerator() = default; + std::string CodeGenerator::translateInstruction(const Instruction &inst) + { + return InstructionTranslator(*this).translate(inst); + } + + std::string CodeGenerator::translateSpecialInstruction(const Instruction &inst) + { + return SpecialTranslator(*this).translate(inst); + } + + std::string CodeGenerator::translateRegimmInstruction(const Instruction &inst) + { + return RegimmTranslator(*this).translate(inst); + } + + std::string CodeGenerator::translateCOP0Instruction(const Instruction &inst) + { + return Cop0Translator(*this).translate(inst); + } + + std::string CodeGenerator::translateFPUInstruction(const Instruction &inst) + { + return FpuTranslator(*this).translate(inst); + } + + std::string CodeGenerator::translateMMIInstruction(const Instruction &inst) + { + return MmiTranslator(*this).translate(inst); + } + + std::string CodeGenerator::translateVUInstruction(const Instruction &inst) + { + return VuTranslator(*this).translate(inst); + } + CodeGenerator::AnalysisResult CodeGenerator::collectInternalBranchTargets( const Function &function, const std::vector &instructions, const std::vector *allFunctions) { - AnalysisResult result; - std::unordered_set instructionAddresses; - instructionAddresses.reserve(instructions.size()); - bool hasIndirectRegisterJump = false; - std::vector 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(static_cast(inst.simmediate)) << 2); - const uint32_t target = static_cast( - static_cast(inst.address + 4u) + static_cast(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 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 jrTargets; - bool validJumpTable = true; - std::unordered_set 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) { - 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; + ControlFlowAnalyzer analyzer(m_sections, m_configJumpTableTargetsByAddress, m_reporter); + return analyzer.analyze(function, instructions, allFunctions); } std::string ps2recomp::CodeGenerator::generateFunction( @@ -1042,3026 +258,24 @@ namespace ps2recomp const std::vector &instructions, const bool &useHeaders) { - std::stringstream ss; + FunctionEmitter emitter(*this); + return emitter.emit(function, instructions, useHeaders); + } - if (useHeaders) + std::string CodeGenerator::emitUnhandledInstruction(const Instruction &inst, const std::string &message) + { + if (m_reporter) { - 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"; + m_reporter->recordUnhandledInstruction(m_currentFunctionName, inst.address, inst.raw, message); } - AnalysisResult analysisResult = collectInternalBranchTargets(function, instructions); - std::vector resumeTargets(analysisResult.resumeEntryPoints.begin(), - analysisResult.resumeEntryPoints.end()); - auto resumeIt = m_resumeEntryTargetsByOwner.find(function.start); - if (resumeIt != 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& internalTargets = analysisResult.entryPoints; - ss << "// Function: " << function.name << "\n"; - ss << "// Address: 0x" << std::hex << function.start << " - 0x" << function.end << std::dec << "\n"; - - std::string sanitizedName = 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 (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 << 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 << " " << translateInstruction(inst); - if (inst.isMmio) - { - ss << " // MMIO: 0x" << std::hex << inst.mmioAddress << std::dec; - } - ss << "\n"; - } - } - catch (const std::exception &e) - { - std::cerr << "Error in CodeGenerator::generateFunction while translating instruction\n" - << " Function: " << function.name << "\n" - << " Start: 0x" << std::hex << function.start << "\n" - << " Instruction address: 0x" << inst.address << "\n" - << " Raw: 0x" << inst.raw << "\n" - << " What: " << e.what() << std::endl; - - throw; - } - } - - ss << "}\n"; - return ss.str(); - } - - std::string CodeGenerator::translateInstruction(const Instruction &inst) - { - if (inst.isMMI) - { - return 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 translateSpecialInstruction(inst); - case OPCODE_REGIMM: - return translateRegimmInstruction(inst); - case OPCODE_COP0: - return translateCOP0Instruction(inst); - case OPCODE_COP1: - return translateFPUInstruction(inst); - case OPCODE_COP2: - return 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 fmt::format("// Unhandled opcode: 0x{:X}", inst.opcode); - } - } - - std::string CodeGenerator::translateSpecialInstruction(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 fmt::format("// Unhandled SPECIAL instruction: 0x{:X}", inst.function); - } - } - - std::string CodeGenerator::translateRegimmInstruction(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(static_cast(inst.simmediate)) << 2); - const uint32_t target = static_cast(static_cast(inst.address + 4u) + static_cast(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 fmt::format("// Unhandled REGIMM instruction: 0x{:X}", inst.rt); - } - } - - std::string CodeGenerator::translateCOP0Instruction(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 fmt::format("// Unhandled COP0 CO-OP: 0x{:X}", function); - } - } - default: - return fmt::format("// Unhandled COP0 instruction format: 0x{:X}", format); - } - } - - std::string CodeGenerator::translateFPUInstruction(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 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 fmt::format("// Unhandled FPU.W instruction: function 0x{:X}", function); - } - default: - return fmt::format("// Unhandled FPU instruction: format 0x{:X}, function 0x{:X}", format, function); - } - } - - std::string CodeGenerator::translateMMIInstruction(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 translateMMI0Instruction(inst); - case MMI_MMI1: - return translateMMI1Instruction(inst); - case MMI_MMI2: - return translateMMI2Instruction(inst); - case MMI_MMI3: - return translateMMI3Instruction(inst); - case MMI_PMFHL: - return translatePMFHLInstruction(inst); - case MMI_PMTHL: - return translatePMTHLInstruction(inst); - default: - return fmt::format("// Unhandled MMI instruction: function 0x{:X}", function); - } - } - - 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 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 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 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 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 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 fmt::format("// Unhandled PMTHL instruction: function 0x{:X}", subfunc); - } - } - - std::string CodeGenerator::translateVUInstruction(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); // TODO: verify vu0_top field exists in R5900Context - 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(inst.function & 0x3F); - if (special1_func >= 0x3C) // Special2 Table - { - const uint8_t vu_func = static_cast((((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 translateVU_VADDA_Field(inst); - case VU0_S2_VSUBAx: - case VU0_S2_VSUBAy: - case VU0_S2_VSUBAz: - case VU0_S2_VSUBAw: - return translateVU_VSUBA_Field(inst); - case VU0_S2_VMADDAx: - case VU0_S2_VMADDAy: - case VU0_S2_VMADDAz: - case VU0_S2_VMADDAw: - return translateVU_VMADDA_Field(inst); - case VU0_S2_VMSUBAx: - case VU0_S2_VMSUBAy: - case VU0_S2_VMSUBAz: - case VU0_S2_VMSUBAw: - return translateVU_VMSUBA_Field(inst); - case VU0_S2_VMULAx: - case VU0_S2_VMULAy: - case VU0_S2_VMULAz: - case VU0_S2_VMULAw: - return translateVU_VMULA_Field(inst); - case VU0_S2_VADDA: - return translateVU_VADDA(inst); - case VU0_S2_VADDAq: - return translateVU_VADDAq(inst); - case VU0_S2_VADDAi: - return translateVU_VADDAi(inst); - case VU0_S2_VMADDA: - return translateVU_VMADDA(inst); - case VU0_S2_VMADDAq: - return translateVU_VMADDAq(inst); - case VU0_S2_VMADDAi: - return translateVU_VMADDAi(inst); - case VU0_S2_VSUBA: - return translateVU_VSUBA(inst); - case VU0_S2_VSUBAq: - return translateVU_VSUBAq(inst); - case VU0_S2_VSUBAi: - return translateVU_VSUBAi(inst); - case VU0_S2_VMSUBA: - return translateVU_VMSUBA(inst); - case VU0_S2_VMSUBAq: - return translateVU_VMSUBAq(inst); - case VU0_S2_VMSUBAi: - return translateVU_VMSUBAi(inst); - case VU0_S2_VMULA: - return translateVU_VMULA(inst); - case VU0_S2_VMULAq: - return translateVU_VMULAq(inst); - case VU0_S2_VMULAi: - return translateVU_VMULAi(inst); - case VU0_S2_VOPMULA: - return translateVU_VOPMULA(inst); - case VU0_S2_VITOF0: - return translateVU_VITOF(inst, 0); - case VU0_S2_VITOF4: - return translateVU_VITOF(inst, 4); - case VU0_S2_VITOF12: - return translateVU_VITOF(inst, 12); - case VU0_S2_VITOF15: - return translateVU_VITOF(inst, 15); - case VU0_S2_VFTOI0: - return translateVU_VFTOI(inst, 0); - case VU0_S2_VFTOI4: - return translateVU_VFTOI(inst, 4); - case VU0_S2_VFTOI12: - return translateVU_VFTOI(inst, 12); - case VU0_S2_VFTOI15: - return translateVU_VFTOI(inst, 15); - case VU0_S2_VLQI: - return translateVU_VLQI(inst); - case VU0_S2_VSQI: - return translateVU_VSQI(inst); - case VU0_S2_VLQD: - return translateVU_VLQD(inst); - case VU0_S2_VSQD: - return translateVU_VSQD(inst); - case VU0_S2_VDIV: - return translateVU_VDIV(inst); - case VU0_S2_VSQRT: - return translateVU_VSQRT(inst); - case VU0_S2_VRSQRT: - return translateVU_VRSQRT(inst); - case VU0_S2_VWAITQ: - return fmt::format("// VWAITQ (Q already resolved in this runtime)"); - case VU0_S2_VMTIR: - return translateVU_VMTIR(inst); - case VU0_S2_VMFIR: - return translateVU_VMFIR(inst); - case VU0_S2_VILWR: - return translateVU_VILWR(inst); - case VU0_S2_VISWR: - return 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 translateVU_VRNEXT(inst); - case VU0_S2_VRGET: - return translateVU_VRGET(inst); - case VU0_S2_VRINIT: - return translateVU_VRINIT(inst); - case VU0_S2_VRXOR: - return translateVU_VRXOR(inst); - default: - return 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 translateVU_VADD_Field(inst); - case VU0_S1_VSUBx: - case VU0_S1_VSUBy: - case VU0_S1_VSUBz: - case VU0_S1_VSUBw: - return translateVU_VSUB_Field(inst); - case VU0_S1_VMULx: - case VU0_S1_VMULy: - case VU0_S1_VMULz: - case VU0_S1_VMULw: - return translateVU_VMUL_Field(inst); - case VU0_S1_VADD: - return translateVU_VADD(inst); - case VU0_S1_VSUB: - return translateVU_VSUB(inst); - case VU0_S1_VMUL: - return translateVU_VMUL(inst); - case VU0_S1_VIADD: - return translateVU_VIADD(inst); - case VU0_S1_VISUB: - return translateVU_VISUB(inst); - case VU0_S1_VIADDI: - return translateVU_VIADDI(inst); - case VU0_S1_VIAND: - return translateVU_VIAND(inst); - case VU0_S1_VIOR: - return translateVU_VIOR(inst); - case VU0_S1_VCALLMS: - return translateVU_VCALLMS(inst); - case VU0_S1_VCALLMSR: - return translateVU_VCALLMSR(inst); - case VU0_S1_VADDq: - return translateVU_VADDq(inst); - case VU0_S1_VSUBq: - return translateVU_VSUBq(inst); - case VU0_S1_VMULq: - return translateVU_VMULq(inst); - case VU0_S1_VADDi: - return translateVU_VADDi(inst); - case VU0_S1_VSUBi: - return translateVU_VSUBi(inst); - case VU0_S1_VMULi: - return translateVU_VMULi(inst); - case VU0_S1_VMADDx: - case VU0_S1_VMADDy: - case VU0_S1_VMADDz: - case VU0_S1_VMADDw: - return translateVU_VMADD_Field(inst); - case VU0_S1_VMSUBx: - case VU0_S1_VMSUBy: - case VU0_S1_VMSUBz: - case VU0_S1_VMSUBw: - return translateVU_VMSUB_Field(inst); - case VU0_S1_VMAXx: - case VU0_S1_VMAXy: - case VU0_S1_VMAXz: - case VU0_S1_VMAXw: - return translateVU_VMAX_Field(inst); - case VU0_S1_VMINIx: - case VU0_S1_VMINIy: - case VU0_S1_VMINIz: - case VU0_S1_VMINIw: - return translateVU_VMINI_Field(inst); - case VU0_S1_VMAXi: - return translateVU_VMAXi(inst); - case VU0_S1_VMINIi: - return translateVU_VMINIi(inst); - case VU0_S1_VMADD: - return translateVU_VMADD(inst); - case VU0_S1_VMADDq: - return translateVU_VMADDq(inst); - case VU0_S1_VMADDi: - return translateVU_VMADDi(inst); - case VU0_S1_VMAX: - return translateVU_VMAX(inst); - case VU0_S1_VOPMSUB: - return translateVU_VOPMSUB(inst); - case VU0_S1_VMINI: - return translateVU_VMINI(inst); - case VU0_S1_VMSUB: - return translateVU_VMSUB(inst); - case VU0_S1_VMSUBq: - return translateVU_VMSUBq(inst); - case VU0_S1_VMSUBi: - return translateVU_VMSUBi(inst); - default: - return fmt::format("// Unhandled VU0 Special1 function: 0x{:X}", special1_func); - } - } - default: - return fmt::format("// Unhandled COP2 format: 0x{:X}", format); - } - } - - 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::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::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(READ32(addr)); }}", inst.rd, inst.rt); // VILWR. 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. 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(inst.sa | ~0x1F) : static_cast(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((inst.raw >> 6) & 0x1FF); // imm15[8:0] - uint32_t target_byte_addr = static_cast(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); + return fmt::format("throw std::runtime_error(\"{} at 0x{:X} raw=0x{:08X}\");", message, inst.address, inst.raw); } - 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) + std::string CodeGenerator::generateFunctionRegistration(const std::vector &functions, const std::map &stubs) { - 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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(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, 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(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, 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); - } - - 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); - } - - std::string CodeGenerator::generateFunctionRegistration(const std::vector &functions, - const std::map &stubs) - { - std::stringstream ss; - - std::unordered_set registeredAddresses; - auto emitRegistration = [&](uint32_t address, const std::string &name) - { - if (!registeredAddresses.insert(address).second) - { - return; - } - - ss << " runtime.registerFunction(0x" << std::hex << address << std::dec - << ", " << name << ");\n"; - }; - - // Begin function - 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"; - - // Registration function - ss << "void registerAllFunctions(PS2Runtime& runtime) {\n"; - - std::vector> normalFunctions; - std::vector> stubFunctions; - std::vector> systemCallFunctions; - std::vector> libraryFunctions; - - uint32_t libBaseAddr = 0x00110000; - uint32_t libOffset = 0; - - for (const auto &function : functions) - { - if (!function.isRecompiled && !function.isStub && !function.isSkipped) - continue; - - std::string generatedName = 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 (m_bootstrapInfo.valid) - { - ss << " // Register ELF entry function\n"; - std::string entryTarget = m_bootstrapInfo.entryName; - if (entryTarget.empty()) - { - entryTarget = getFunctionName(m_bootstrapInfo.entry); - } - if (entryTarget.empty()) - { - throw std::runtime_error("No entry function name available for registration."); - } - emitRegistration(m_bootstrapInfo.entry, entryTarget); - ss << "\n"; - } - - ss << " // Register recompiled functions\n"; - for (const auto &[first, second] : normalFunctions) - { - emitRegistration(first, second); - } - - ss << "\n // Register resumable entry points\n"; - for (const auto &[ownerStart, targets] : m_resumeEntryTargetsByOwner) - { - const std::string ownerName = getFunctionName(ownerStart); - if (ownerName.empty()) - { - continue; - } - - for (uint32_t target : targets) - { - emitRegistration(target, ownerName); - } - } - - ss << "\n // Register stub functions\n"; - for (const auto &[first, second] : stubFunctions) - { - emitRegistration(first, second); - } - - ss << "\n // Register system call stubs\n"; - for (const auto &[first, second] : systemCallFunctions) - { - emitRegistration(first, second); - } - - ss << "\n // Register library stubs\n"; - for (const auto &[first, second] : libraryFunctions) - { - emitRegistration(first, second); - } - - ss << "}\n"; - - return ss.str(); - } - - std::string CodeGenerator::generateJumpTableSwitch(const Instruction &inst, uint32_t tableAddress, - const std::vector &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(); + FunctionTableEmitter emitter(*this); + return emitter.emit(functions, stubs); } const Symbol *CodeGenerator::findSymbolByAddress(uint32_t address) const diff --git a/ps2xRecomp/src/lib/config_manager.cpp b/ps2xRecomp/src/lib/config_manager.cpp index f7b7321..b6f382d 100644 --- a/ps2xRecomp/src/lib/config_manager.cpp +++ b/ps2xRecomp/src/lib/config_manager.cpp @@ -1,4 +1,5 @@ #include "ps2recomp/config_manager.h" +#include "ps2recomp/recompiler_reporter.h" #include #include #include @@ -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(clampedOutputWorkers); config.patchSyscalls = toml::find_or(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; } diff --git a/ps2xRecomp/src/lib/control_flow_analyzer.cpp b/ps2xRecomp/src/lib/control_flow_analyzer.cpp new file mode 100644 index 0000000..a099f5c --- /dev/null +++ b/ps2xRecomp/src/lib/control_flow_analyzer.cpp @@ -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 +#include + +namespace ps2recomp +{ + ControlFlowAnalyzer::ControlFlowAnalyzer( + const std::vector
§ions, + const std::unordered_map> &configuredJumpTableTargetsByAddress, + RecompilerReporter *reporter) + : m_sections(sections), + m_configJumpTableTargetsByAddress(configuredJumpTableTargetsByAddress), + m_reporter(reporter) + { + } + + ControlFlowAnalysisResult ControlFlowAnalyzer::analyze( + const Function &function, + const std::vector &instructions, + const std::vector *allFunctions) const + { + ControlFlowAnalysisResult result; + std::unordered_set instructionAddresses; + instructionAddresses.reserve(instructions.size()); + bool hasIndirectRegisterJump = false; + std::vector 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(static_cast(inst.simmediate)) << 2); + const uint32_t target = static_cast( + static_cast(inst.address + 4u) + static_cast(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 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 jrTargets; + bool validJumpTable = true; + std::unordered_set 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 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; + } + +} diff --git a/ps2xRecomp/src/lib/control_flow_emitter.cpp b/ps2xRecomp/src/lib/control_flow_emitter.cpp new file mode 100644 index 0000000..bf22928 --- /dev/null +++ b/ps2xRecomp/src/lib/control_flow_emitter.cpp @@ -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 +#include +#include + +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 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 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(m_branchInst.rs); + const uint8_t rdReg = static_cast(m_branchInst.rd); + const std::vector sortedInternalTargets = resolvedLocalIndirectTargets(); + + m_ss << " {\n"; + m_ss << " const uint32_t jumpTarget = GPR_U32(ctx, " << static_cast(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(m_branchInst.rs); + const uint8_t rtReg = static_cast(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(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(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(static_cast(m_branchInst.simmediate)) << 2; + return static_cast(static_cast(m_branchInst.address + 4u) + + static_cast(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(); + } +} diff --git a/ps2xRecomp/src/lib/cop0_translator.cpp b/ps2xRecomp/src/lib/cop0_translator.cpp new file mode 100644 index 0000000..1823a39 --- /dev/null +++ b/ps2xRecomp/src/lib/cop0_translator.cpp @@ -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 +#include +#include + + +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)); + } + } + +} diff --git a/ps2xRecomp/src/lib/elf_parser.cpp b/ps2xRecomp/src/lib/elf_parser.cpp index bd91e03..91e43ee 100644 --- a/ps2xRecomp/src/lib/elf_parser.cpp +++ b/ps2xRecomp/src/lib/elf_parser.cpp @@ -1,4 +1,5 @@ #include "ps2recomp/elf_parser.h" +#include "ps2recomp/recompiler_reporter.h" #include "ps2recomp/types.h" #include #include @@ -950,6 +951,11 @@ namespace ps2recomp return static_cast(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; } diff --git a/ps2xRecomp/src/lib/fpu_translator.cpp b/ps2xRecomp/src/lib/fpu_translator.cpp new file mode 100644 index 0000000..944cee7 --- /dev/null +++ b/ps2xRecomp/src/lib/fpu_translator.cpp @@ -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 +#include +#include + +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)); + } + } + +} diff --git a/ps2xRecomp/src/lib/function_emitter.cpp b/ps2xRecomp/src/lib/function_emitter.cpp new file mode 100644 index 0000000..db57747 --- /dev/null +++ b/ps2xRecomp/src/lib/function_emitter.cpp @@ -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 +#include +#include +#include + +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 &instructions, + bool useHeaders) + { + CodeGenerator &cg = m_codeGenerator; + std::stringstream ss; + cg.m_currentFunctionName = function.name; + + if (useHeaders) + { + ss << "#include \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 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 &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(); + } +} diff --git a/ps2xRecomp/src/lib/function_table_emitter.cpp b/ps2xRecomp/src/lib/function_table_emitter.cpp new file mode 100644 index 0000000..27dc9b7 --- /dev/null +++ b/ps2xRecomp/src/lib/function_table_emitter.cpp @@ -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 +#include +#include +#include +#include +#include +#include + +namespace ps2recomp +{ + FunctionTableEmitter::FunctionTableEmitter(CodeGenerator &codeGenerator) + : m_codeGenerator(codeGenerator) + { + } + + std::string FunctionTableEmitter::emit(const std::vector &functions, const std::map &stubs) + { + (void)stubs; + + CodeGenerator &cg = m_codeGenerator; + std::vector> entries; + std::unordered_set 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> normalFunctions; + std::vector> stubFunctions; + std::vector> systemCallFunctions; + std::vector> 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(); + } +} diff --git a/ps2xRecomp/src/lib/instruction_translator.cpp b/ps2xRecomp/src/lib/instruction_translator.cpp new file mode 100644 index 0000000..f4a31c8 --- /dev/null +++ b/ps2xRecomp/src/lib/instruction_translator.cpp @@ -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 +#include +#include + + +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)); + } + } + +} diff --git a/ps2xRecomp/src/lib/jump_table_switch_emitter.cpp b/ps2xRecomp/src/lib/jump_table_switch_emitter.cpp new file mode 100644 index 0000000..2079773 --- /dev/null +++ b/ps2xRecomp/src/lib/jump_table_switch_emitter.cpp @@ -0,0 +1,46 @@ +#include "ps2recomp/code_generator.h" +#include "ps2recomp/codegen_helpers.h" +#include "ps2recomp/instructions.h" +#include "ps2recomp/types.h" +#include +#include +#include + +namespace ps2recomp +{ + std::string CodeGenerator::generateJumpTableSwitch(const Instruction &inst, uint32_t tableAddress, + const std::vector &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(); + } + +} diff --git a/ps2xRecomp/src/lib/mmi_translation_helpers.cpp b/ps2xRecomp/src/lib/mmi_translation_helpers.cpp new file mode 100644 index 0000000..4f7a366 --- /dev/null +++ b/ps2xRecomp/src/lib/mmi_translation_helpers.cpp @@ -0,0 +1,608 @@ +#include "ps2recomp/code_generator.h" +#include "ps2recomp/codegen_helpers.h" +#include "ps2recomp/instructions.h" +#include "ps2recomp/types.h" +#include +#include +#include + +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); + } + +} diff --git a/ps2xRecomp/src/lib/mmi_translator.cpp b/ps2xRecomp/src/lib/mmi_translator.cpp new file mode 100644 index 0000000..d8b3cbc --- /dev/null +++ b/ps2xRecomp/src/lib/mmi_translator.cpp @@ -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 +#include +#include + + +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)); + } + } + +} diff --git a/ps2xRecomp/src/lib/ps2_recompiler.cpp b/ps2xRecomp/src/lib/ps2_recompiler.cpp index 0ac3ee5..df7e356 100644 --- a/ps2xRecomp/src/lib/ps2_recompiler.cpp +++ b/ps2xRecomp/src/lib/ps2_recompiler.cpp @@ -100,6 +100,7 @@ namespace ps2recomp void writeCombinedOutputPreamble(std::ostream &output) { + output << "#include \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(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::max(); uint32_t bssEnd = 0; for (const auto &sec : m_sections) @@ -831,12 +847,16 @@ namespace ps2recomp if (bssStart != std::numeric_limits::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(); m_codeGenerator = std::make_unique(m_symbols, m_sections); + m_codeGenerator->setReporter(&m_reporter); std::unordered_map 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 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; } diff --git a/ps2xRecomp/src/lib/recompiler_reporter.cpp b/ps2xRecomp/src/lib/recompiler_reporter.cpp new file mode 100644 index 0000000..8c08f96 --- /dev/null +++ b/ps2xRecomp/src/lib/recompiler_reporter.cpp @@ -0,0 +1,229 @@ +#include "ps2recomp/recompiler_reporter.h" + +#include +#include +#include +#include + +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 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 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 lock(m_mutex); + ++m_counters.functionsProcessed; + } + + void RecompilerReporter::recordFunctionRecompiled() + { + std::lock_guard lock(m_mutex); + ++m_counters.functionsRecompiled; + } + + void RecompilerReporter::recordFunctionStubbed() + { + std::lock_guard lock(m_mutex); + ++m_counters.functionsStubbed; + } + + void RecompilerReporter::recordFunctionSkipped() + { + std::lock_guard lock(m_mutex); + ++m_counters.functionsSkipped; + } + + void RecompilerReporter::recordDecodeFailure() + { + std::lock_guard lock(m_mutex); + ++m_counters.decodeFailures; + } + + void RecompilerReporter::recordAdditionalEntryPoints(size_t count) + { + std::lock_guard lock(m_mutex); + m_counters.additionalEntryPoints += count; + } + + void RecompilerReporter::recordGeneratedFunctions(size_t count) + { + std::lock_guard lock(m_mutex); + m_counters.generatedFunctions += count; + } + + void RecompilerReporter::recordIndirectFallbackPromotion(const std::string &functionName, + const std::vector &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 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 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 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 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 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 lock(m_mutex); + m_events.push_back(Event{severity, category, message, functionName, address, hasAddress}); + } +} diff --git a/ps2xRecomp/src/lib/regimm_translator.cpp b/ps2xRecomp/src/lib/regimm_translator.cpp new file mode 100644 index 0000000..8d8e09f --- /dev/null +++ b/ps2xRecomp/src/lib/regimm_translator.cpp @@ -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 +#include +#include + + +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(static_cast(inst.simmediate)) << 2); + const uint32_t target = static_cast(static_cast(inst.address + 4u) + static_cast(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)); + } + } + +} diff --git a/ps2xRecomp/src/lib/special_translator.cpp b/ps2xRecomp/src/lib/special_translator.cpp new file mode 100644 index 0000000..ab1ee55 --- /dev/null +++ b/ps2xRecomp/src/lib/special_translator.cpp @@ -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 +#include +#include + + +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)); + } + } + +} diff --git a/ps2xRecomp/src/lib/vu_translation_helpers.cpp b/ps2xRecomp/src/lib/vu_translation_helpers.cpp new file mode 100644 index 0000000..435d356 --- /dev/null +++ b/ps2xRecomp/src/lib/vu_translation_helpers.cpp @@ -0,0 +1,890 @@ +#include "ps2recomp/code_generator.h" +#include "ps2recomp/codegen_helpers.h" +#include "ps2recomp/instructions.h" +#include "ps2recomp/types.h" +#include +#include +#include + +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(READ32(addr)); }}", inst.rd, inst.rt); // VILWR. 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. 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(inst.sa | ~0x1F) : static_cast(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((inst.raw >> 6) & 0x1FF); // imm15[8:0] + uint32_t target_byte_addr = static_cast(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(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(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); + } + +} diff --git a/ps2xRecomp/src/lib/vu_translator.cpp b/ps2xRecomp/src/lib/vu_translator.cpp new file mode 100644 index 0000000..6148c98 --- /dev/null +++ b/ps2xRecomp/src/lib/vu_translator.cpp @@ -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 +#include +#include + + +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(inst.function & 0x3F); + if (special1_func >= 0x3C) // Special2 Table + { + const uint8_t vu_func = static_cast((((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)); + } + } + +} diff --git a/ps2xRecomp/src/runner/main.cpp b/ps2xRecomp/src/runner/main.cpp index b64c2dc..64c7797 100644 --- a/ps2xRecomp/src/runner/main.cpp +++ b/ps2xRecomp/src/runner/main.cpp @@ -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; diff --git a/ps2xRuntime/CMakeLists.txt b/ps2xRuntime/CMakeLists.txt index 92f141b..43dc416 100644 --- a/ps2xRuntime/CMakeLists.txt +++ b/ps2xRuntime/CMakeLists.txt @@ -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" ) diff --git a/ps2xRuntime/include/ps2_runtime.h b/ps2xRuntime/include/ps2_runtime.h index 6113745..924e074 100644 --- a/ps2xRuntime/include/ps2_runtime.h +++ b/ps2xRuntime/include/ps2_runtime.h @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #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 m_functionTable; + std::atomic m_missingFunctionPolicy{static_cast(MissingFunctionPolicy::ContinueToTarget)}; + std::atomic m_missingFunctionReported{false}; std::atomic 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 diff --git a/ps2xRuntime/include/register_functions.h b/ps2xRuntime/include/register_functions.h deleted file mode 100644 index 7515a14..0000000 --- a/ps2xRuntime/include/register_functions.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef REGISTER_FUNCTIONS_H -#define REGISTER_FUNCTIONS_H - -#include "ps2_runtime.h" - -void registerAllFunctions(PS2Runtime &runtime); - -#endif // REGISTER_FUNCTIONS_H \ No newline at end of file diff --git a/ps2xRuntime/src/lib/game_overrides.cpp b/ps2xRuntime/src/lib/game_overrides.cpp index b363696..821491e 100644 --- a/ps2xRuntime/src/lib/game_overrides.cpp +++ b/ps2xRuntime/src/lib/game_overrides.cpp @@ -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) diff --git a/ps2xRuntime/src/lib/ps2_debug_panel.cpp b/ps2xRuntime/src/lib/ps2_debug_panel.cpp index 37766d1..11f5262 100644 --- a/ps2xRuntime/src/lib/ps2_debug_panel.cpp +++ b/ps2xRuntime/src/lib/ps2_debug_panel.cpp @@ -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}, }; diff --git a/ps2xRuntime/src/lib/ps2_runtime.cpp b/ps2xRuntime/src/lib/ps2_runtime.cpp index 54e579e..6f62997 100644 --- a/ps2xRuntime/src/lib/ps2_runtime.cpp +++ b/ps2xRuntime/src/lib/ps2_runtime.cpp @@ -106,8 +106,6 @@ namespace thread_local DispatchHistory g_dispatchHistory; thread_local std::unordered_map g_guestExecutionDepths; - std::mutex g_functionStartsMutex; - std::unordered_map> 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(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(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(rdram[(addr + 0u) & PS2_RAM_MASK]) << 0; - value |= static_cast(rdram[(addr + 1u) & PS2_RAM_MASK]) << 8; - value |= static_cast(rdram[(addr + 2u) & PS2_RAM_MASK]) << 16; - value |= static_cast(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(_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(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 lock(g_functionStartsMutex); - g_functionStartsByRuntime.erase(runtime); - } - - std::vector snapshotFunctionStarts(const PS2Runtime *runtime) - { - std::lock_guard 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 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(ph.vaddr) + static_cast(ph.memsz); + const uint64_t execEnd = static_cast(ph.vaddr) + static_cast(ph.filesz); if (execEnd <= std::numeric_limits::max()) { m_memory.registerCodeRegion(ph.vaddr, static_cast(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 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(policy), std::memory_order_release); +} + +PS2Runtime::MissingFunctionPolicy PS2Runtime::missingFunctionPolicy() const +{ + return static_cast(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(_mm_extract_epi32(ctx->r[31], 0)); + const uint32_t sp = static_cast(_mm_extract_epi32(ctx->r[29], 0)); + const uint32_t gp = static_cast(_mm_extract_epi32(ctx->r[28], 0)); + const uint32_t a0 = static_cast(_mm_extract_epi32(ctx->r[4], 0)); + const uint32_t a1 = static_cast(_mm_extract_epi32(ctx->r[5], 0)); + const uint32_t v0 = static_cast(_mm_extract_epi32(ctx->r[2], 0)); + const uint32_t v1 = static_cast(_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(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(history.pcs.size()) - step) % - static_cast(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="; - } - } - 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(_mm_extract_epi32(ctx->r[31], 0)) : 0u; - const uint32_t sp = ctx ? static_cast(_mm_extract_epi32(ctx->r[29], 0)) : 0u; - const uint32_t gp = ctx ? static_cast(_mm_extract_epi32(ctx->r[28], 0)) : 0u; - const uint32_t a0 = ctx ? static_cast(_mm_extract_epi32(ctx->r[4], 0)) : 0u; - const uint32_t a1 = ctx ? static_cast(_mm_extract_epi32(ctx->r[5], 0)) : 0u; - const uint32_t v0 = ctx ? static_cast(_mm_extract_epi32(ctx->r[2], 0)) : 0u; - const uint32_t v1 = ctx ? static_cast(_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 : "") + << " 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(policy) + << " trace=" << formatDispatchHistory() << std::dec; - static std::mutex s_defaultFnLogMutex; + static std::mutex s_missingFunctionLogMutex; { - std::lock_guard lock(s_defaultFnLogMutex); + std::lock_guard 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 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; diff --git a/ps2xRuntime/src/main.cpp b/ps2xRuntime/src/main.cpp index a2fce86..40b0303 100644 --- a/ps2xRuntime/src/main.cpp +++ b/ps2xRuntime/src/main.cpp @@ -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; diff --git a/ps2xRuntime/src/runner/register_functions.cpp b/ps2xRuntime/src/runner/register_functions.cpp index 42a234b..85cc2e3 100644 --- a/ps2xRuntime/src/runner/register_functions.cpp +++ b/ps2xRuntime/src/runner/register_functions.cpp @@ -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) -{ -} \ No newline at end of file +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] = {}; \ No newline at end of file diff --git a/ps2xStudio/CMakeLists.txt b/ps2xStudio/CMakeLists.txt index b199430..86cd906 100644 --- a/ps2xStudio/CMakeLists.txt +++ b/ps2xStudio/CMakeLists.txt @@ -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 $) +endif() + target_include_directories(ps2xStudio PRIVATE include ${CMAKE_CURRENT_SOURCE_DIR}/../ps2xAnalyzer/include diff --git a/ps2xTest/CMakeLists.txt b/ps2xTest/CMakeLists.txt index 8f65a72..5ab3921 100644 --- a/ps2xTest/CMakeLists.txt +++ b/ps2xTest/CMakeLists.txt @@ -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 + $ ) option(PRINT_GENERATED_CODE "Print generated code in tests" OFF) diff --git a/ps2xTest/include/MiniTest.h b/ps2xTest/include/MiniTest.h index 4a9d0ab..9fc510b 100644 --- a/ps2xTest/include/MiniTest.h +++ b/ps2xTest/include/MiniTest.h @@ -107,6 +107,7 @@ class MiniTest { private: inline static std::map 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; } -}; \ No newline at end of file +}; diff --git a/ps2xTest/src/code_generator_tests.cpp b/ps2xTest/src/code_generator_tests.cpp index 2ce6fde..29ecb85 100644 --- a/ps2xTest/src/code_generator_tests.cpp +++ b/ps2xTest/src/code_generator_tests.cpp @@ -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) { diff --git a/ps2xTest/src/main.cpp b/ps2xTest/src/main.cpp index 5a5da81..0ed5e9b 100644 --- a/ps2xTest/src/main.cpp +++ b/ps2xTest/src/main.cpp @@ -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(); diff --git a/ps2xTest/src/ps2_runtime_expansion_tests.cpp b/ps2xTest/src/ps2_runtime_expansion_tests.cpp index b3a3d00..20fb73d 100644 --- a/ps2xTest/src/ps2_runtime_expansion_tests.cpp +++ b/ps2xTest/src/ps2_runtime_expansion_tests.cpp @@ -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) diff --git a/ps2xTest/src/test_function_table.cpp b/ps2xTest/src/test_function_table.cpp new file mode 100644 index 0000000..be6a888 --- /dev/null +++ b/ps2xTest/src/test_function_table.cpp @@ -0,0 +1,18 @@ +#include "ps2_runtime.h" +#include "runtime/ps2_memory.h" + +#include + +// 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); +}