From 49617104077a925f7cddc89c668505e7694acaf6 Mon Sep 17 00:00:00 2001 From: Antonio Guastella <65413476+Whoneon@users.noreply.github.com> Date: Tue, 17 Feb 2026 02:20:48 +0100 Subject: [PATCH] Issue 11 vu0 macro (#48) * Add missing VU0 Special1 macro translations * Fix VU0 macro decode and field ops Align Special2 decoding with fhi_flo, implement VCLIPw/VABS, and fix field variants (VADDx/VSUBx/VMULx). Keep C++17 compatibility by replacing unordered_set::contains and remove dead ADDIU return. Update Special2 metadata for VWAITQ. * vu0: complete macro mappings and add coverage test * review: clean up vu macro handling * fix: restore C++20 contains in code generator * fix: correct VU0 Special2 instruction encoding in decoder test --------- Co-authored-by: Antonio Guastella Co-authored-by: Ranieri --- ps2xAnalyzer/src/elf_analyzer.cpp | 1 + ps2xRecomp/include/ps2recomp/code_generator.h | 36 + ps2xRecomp/src/lib/code_generator.cpp | 692 +++++++++++++++--- ps2xRecomp/src/lib/r5900_decoder.cpp | 56 +- ps2xTest/src/code_generator_tests.cpp | 86 +++ ps2xTest/src/r5900_decoder_tests.cpp | 6 +- 6 files changed, 743 insertions(+), 134 deletions(-) diff --git a/ps2xAnalyzer/src/elf_analyzer.cpp b/ps2xAnalyzer/src/elf_analyzer.cpp index 301a442..df7d40d 100644 --- a/ps2xAnalyzer/src/elf_analyzer.cpp +++ b/ps2xAnalyzer/src/elf_analyzer.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include diff --git a/ps2xRecomp/include/ps2recomp/code_generator.h b/ps2xRecomp/include/ps2recomp/code_generator.h index 074a069..085907a 100644 --- a/ps2xRecomp/include/ps2recomp/code_generator.h +++ b/ps2xRecomp/include/ps2recomp/code_generator.h @@ -90,6 +90,12 @@ namespace ps2recomp std::string translateVU_VADD_Field(const Instruction &inst); std::string translateVU_VSUB_Field(const Instruction &inst); std::string translateVU_VMUL_Field(const Instruction &inst); + std::string translateVU_VMSUB_Field(const Instruction &inst); + std::string translateVU_VADDA_Field(const Instruction &inst); + std::string translateVU_VSUBA_Field(const Instruction &inst); + std::string translateVU_VMADDA_Field(const Instruction &inst); + std::string translateVU_VMSUBA_Field(const Instruction &inst); + std::string translateVU_VMULA_Field(const Instruction &inst); std::string translateVU_VADD(const Instruction &inst); std::string translateVU_VSUB(const Instruction &inst); std::string translateVU_VMUL(const Instruction &inst); @@ -113,10 +119,40 @@ namespace ps2recomp std::string translateVU_VRXOR(const Instruction &inst); std::string translateVU_VMADD_Field(const Instruction &inst); std::string translateVU_VMINI_Field(const Instruction &inst); + std::string translateVU_VMAX_Field(const Instruction &inst); std::string translateVU_VMADD(const Instruction &inst); + std::string translateVU_VMADDq(const Instruction &inst); + std::string translateVU_VMADDi(const Instruction &inst); std::string translateVU_VMAX(const Instruction &inst); + std::string translateVU_VMAXi(const Instruction &inst); + std::string translateVU_VADDA(const Instruction &inst); + std::string translateVU_VADDAq(const Instruction &inst); + std::string translateVU_VADDAi(const Instruction &inst); + std::string translateVU_VSUBA(const Instruction &inst); + std::string translateVU_VSUBAq(const Instruction &inst); + std::string translateVU_VSUBAi(const Instruction &inst); + std::string translateVU_VMADDA(const Instruction &inst); + std::string translateVU_VMADDAq(const Instruction &inst); + std::string translateVU_VMADDAi(const Instruction &inst); + std::string translateVU_VMSUBA(const Instruction &inst); + std::string translateVU_VMSUBAq(const Instruction &inst); + std::string translateVU_VMSUBAi(const Instruction &inst); + std::string translateVU_VMULA(const Instruction &inst); + std::string translateVU_VMULAq(const Instruction &inst); + std::string translateVU_VMULAi(const Instruction &inst); + std::string translateVU_VOPMULA(const Instruction &inst); std::string translateVU_VOPMSUB(const Instruction &inst); std::string translateVU_VMINI(const Instruction &inst); + std::string translateVU_VMINIi(const Instruction &inst); + std::string translateVU_VMSUB(const Instruction &inst); + std::string translateVU_VMSUBq(const Instruction &inst); + std::string translateVU_VMSUBi(const Instruction &inst); + std::string translateVU_VITOF(const Instruction &inst, int shift); + std::string translateVU_VFTOI(const Instruction &inst, int shift); + std::string translateVU_VLQI(const Instruction &inst); + std::string translateVU_VSQI(const Instruction &inst); + std::string translateVU_VLQD(const Instruction &inst); + std::string translateVU_VSQD(const Instruction &inst); // Jump Table Generation std::string generateJumpTableSwitch(const Instruction &inst, uint32_t tableAddress, diff --git a/ps2xRecomp/src/lib/code_generator.cpp b/ps2xRecomp/src/lib/code_generator.cpp index 5963684..b6467a4 100644 --- a/ps2xRecomp/src/lib/code_generator.cpp +++ b/ps2xRecomp/src/lib/code_generator.cpp @@ -1769,10 +1769,93 @@ namespace ps2recomp case COP2_CO + 15: { uint8_t vu_func = inst.function; - if (vu_func >= 0x3C) // Special2 Table + if (vu_func >= 0x3C) { - switch (vu_func) + uint8_t vu_fhi_flo = (uint8_t)((((inst.raw >> 6) & 0x1F) << 2) | (inst.raw & 0x3)); + + switch (vu_fhi_flo) { + 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: @@ -1780,7 +1863,7 @@ namespace ps2recomp case VU0_S2_VRSQRT: return translateVU_VRSQRT(inst); case VU0_S2_VWAITQ: - return fmt::format("// Unhandled VU0 VWAITQ instruction: 0x{:X}", vu_func); + return fmt::format("// VWAITQ (Q already resolved in this runtime)"); case VU0_S2_VMTIR: return translateVU_VMTIR(inst); case VU0_S2_VMFIR: @@ -1789,6 +1872,42 @@ namespace ps2recomp 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.rs, + (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, + (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, + inst.rt, inst.rt); + } + case VU0_S2_VMOVE: + return fmt::format("ctx->vu0_vf[{}] = ctx->vu0_vf[{}];", inst.rt, inst.rs); + case VU0_S2_VMR32: + return fmt::format("ctx->vu0_vf[{}] = _mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], _MM_SHUFFLE(0,0,0,1));", inst.rt, inst.rs, inst.rs); + 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.rs, 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: @@ -1797,95 +1916,107 @@ namespace ps2recomp return translateVU_VRINIT(inst); case VU0_S2_VRXOR: return translateVU_VRXOR(inst); - case VU0_S2_VABS: - return fmt::format("ctx->vu0_vf[{}] = _mm_andnot_ps(_mm_set1_ps(-0.0f), ctx->vu0_vf[{}]);", inst.rt, inst.rs); // FT, FS - case VU0_S2_VNOP: - return fmt::format("// NOP operation, no action needed for VU0"); // No operation - case VU0_S2_VMOVE: - return fmt::format("ctx->vu0_vf[{}] = ctx->vu0_vf[{}];", inst.rt, inst.rs); // FT, FS - case VU0_S2_VMR32: - return fmt::format("ctx->vu0_vf[{}] = _mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], _MM_SHUFFLE(0,0,0,1));", inst.rt, inst.rs, inst.rs); // FT, FS default: - return fmt::format("// Unhandled VU0 Special2 function: 0x{:X}", vu_func); + return fmt::format("// Unhandled VU0 Special2 fhi_flo: 0x{:X}", vu_fhi_flo); } } - else // Special1 Table + + // Special1 Table (function-based) + switch (vu_func) { - switch (vu_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 fmt::format("ctx->vu0_vf[{}] = PS2_VADD(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q));", inst.rd, inst.rs); - case VU0_S1_VSUBq: - return fmt::format("ctx->vu0_vf[{}] = PS2_VSUB(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q));", inst.rd, inst.rs); - case VU0_S1_VMULq: - return fmt::format("ctx->vu0_vf[{}] = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q));", inst.rd, inst.rs); - case VU0_S1_VADDi: - return fmt::format("ctx->vu0_vf[{}] = PS2_VADD(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i));", inst.rd, inst.rs); - case VU0_S1_VSUBi: - return fmt::format("ctx->vu0_vf[{}] = PS2_VSUB(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i));", inst.rd, inst.rs); - case VU0_S1_VMULi: - return fmt::format("ctx->vu0_vf[{}] = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i));", inst.rd, inst.rs); - case VU0_S1_VMADDx: - case VU0_S1_VMADDy: - case VU0_S1_VMADDz: - case VU0_S1_VMADDw: - return translateVU_VMADD_Field(inst); - case VU0_S1_VMAXx: - return fmt::format("ctx->vu0_vf[{}] = _mm_max_ps(ctx->vu0_vf[{}], _mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], _MM_SHUFFLE(0,0,0,0)));", inst.rd, inst.rs, inst.rt, inst.rt); - case VU0_S1_VMAXz: - return fmt::format("ctx->vu0_vf[{}] = _mm_max_ps(ctx->vu0_vf[{}], _mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], _MM_SHUFFLE(2,2,2,2)));", inst.rd, inst.rs, inst.rt, inst.rt); - case VU0_S1_VMINIx: - case VU0_S1_VMINIy: - case VU0_S1_VMINIw: - return translateVU_VMINI_Field(inst); - case VU0_S1_VMADD: - return translateVU_VMADD(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); - default: - return fmt::format("// Unhandled VU0 Special1 function: 0x{:X}", vu_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 fmt::format("ctx->vu0_vf[{}] = PS2_VADD(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q));", inst.rd, inst.rs); + case VU0_S1_VSUBq: + return fmt::format("ctx->vu0_vf[{}] = PS2_VSUB(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q));", inst.rd, inst.rs); + case VU0_S1_VMULq: + return fmt::format("ctx->vu0_vf[{}] = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q));", inst.rd, inst.rs); + case VU0_S1_VADDi: + return fmt::format("ctx->vu0_vf[{}] = PS2_VADD(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i));", inst.rd, inst.rs); + case VU0_S1_VSUBi: + return fmt::format("ctx->vu0_vf[{}] = PS2_VSUB(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i));", inst.rd, inst.rs); + case VU0_S1_VMULi: + return fmt::format("ctx->vu0_vf[{}] = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i));", inst.rd, inst.rs); + 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}", vu_func); + } } - } default: return fmt::format("// Unhandled COP2 format: 0x{:X}", format); } @@ -1894,19 +2025,25 @@ namespace ps2recomp std::string CodeGenerator::translateVU_VADD_Field(const Instruction &inst) { 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)); }}", inst.rs, inst.rt, (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, inst.rd, inst.rd); + 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)); }}", inst.rs, inst.rt, inst.rt, shuffle_pattern, (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, inst.rd, inst.rd); } std::string CodeGenerator::translateVU_VSUB_Field(const Instruction &inst) { 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)); }}", inst.rs, inst.rt, (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, inst.rd, inst.rd); + 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)); }}", inst.rs, inst.rt, inst.rt, shuffle_pattern, (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, inst.rd, inst.rd); } std::string CodeGenerator::translateVU_VMUL_Field(const Instruction &inst) { 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)); }}", inst.rs, inst.rt, (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, inst.rd, inst.rd); + 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)); }}", inst.rs, inst.rt, inst.rt, shuffle_pattern, (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, inst.rd, inst.rd); } std::string CodeGenerator::translateVU_VADD(const Instruction &inst) @@ -2268,6 +2405,24 @@ namespace ps2recomp inst.rd, inst.rd); } + std::string CodeGenerator::translateVU_VMSUB_Field(const Instruction &inst) + { + 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; }}", + inst.rs, inst.rt, inst.rt, shuffle_pattern, + (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, + (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, + inst.rd, inst.rd); + } + std::string CodeGenerator::translateVU_VMINI_Field(const Instruction &inst) { uint8_t dest_mask = inst.vectorInfo.vectorField; @@ -2284,6 +2439,22 @@ namespace ps2recomp inst.rd, inst.rd); } + std::string CodeGenerator::translateVU_VMAX_Field(const Instruction &inst) + { + 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)); }}", + inst.rs, inst.rt, inst.rt, shuffle_pattern, + (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, + (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, + inst.rd, inst.rd); + } + std::string CodeGenerator::translateVU_VMADD(const Instruction &inst) { uint8_t dest_mask = inst.vectorInfo.vectorField; @@ -2298,6 +2469,34 @@ namespace ps2recomp inst.rd, inst.rd); } + std::string CodeGenerator::translateVU_VMADDq(const Instruction &inst) + { + 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; }}", + inst.rs, + (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, + (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, + inst.rd, inst.rd); + } + + std::string CodeGenerator::translateVU_VMADDi(const Instruction &inst) + { + 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; }}", + inst.rs, + (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, + (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, + inst.rd, inst.rd); + } + std::string CodeGenerator::translateVU_VMAX(const Instruction &inst) { uint8_t dest_mask = inst.vectorInfo.vectorField; @@ -2310,6 +2509,18 @@ namespace ps2recomp inst.rd, inst.rd); } + std::string CodeGenerator::translateVU_VMAXi(const Instruction &inst) + { + 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)); }}", + inst.rs, + (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, + (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, + inst.rd, inst.rd); + } + std::string CodeGenerator::translateVU_VOPMSUB(const Instruction &inst) { uint8_t dest_mask = inst.vectorInfo.vectorField; @@ -2336,6 +2547,307 @@ namespace ps2recomp inst.rd, inst.rd); } + std::string CodeGenerator::translateVU_VMINIi(const Instruction &inst) + { + 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)); }}", + inst.rs, + (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, + (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, + inst.rd, inst.rd); + } + + std::string CodeGenerator::translateVU_VMSUB(const Instruction &inst) + { + 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; }}", + inst.rs, inst.rt, + (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, + (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, + inst.rd, inst.rd); + } + + std::string CodeGenerator::translateVU_VMSUBq(const Instruction &inst) + { + 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; }}", + inst.rs, + (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, + (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, + inst.rd, inst.rd); + } + + std::string CodeGenerator::translateVU_VMSUBi(const Instruction &inst) + { + 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; }}", + inst.rs, + (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, + (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, + inst.rd, inst.rd); + } + + std::string CodeGenerator::translateVU_VADDA_Field(const Instruction &inst) + { + 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 = res; }}", + inst.rs, inst.rt, inst.rt, shuffle_pattern); + } + + std::string CodeGenerator::translateVU_VSUBA_Field(const Instruction &inst) + { + 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 = res; }}", + inst.rs, inst.rt, inst.rt, shuffle_pattern); + } + + std::string CodeGenerator::translateVU_VMADDA_Field(const Instruction &inst) + { + 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 = res; }}", + inst.rs, inst.rt, inst.rt, shuffle_pattern); + } + + std::string CodeGenerator::translateVU_VMSUBA_Field(const Instruction &inst) + { + 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 = res; }}", + inst.rs, inst.rt, inst.rt, shuffle_pattern); + } + + std::string CodeGenerator::translateVU_VMULA_Field(const Instruction &inst) + { + 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 = res; }}", + inst.rs, inst.rt, inst.rt, shuffle_pattern); + } + + std::string CodeGenerator::translateVU_VADDA(const Instruction &inst) + { + return fmt::format("ctx->vu0_acc = PS2_VADD(ctx->vu0_vf[{}], ctx->vu0_vf[{}]);", + inst.rs, inst.rt); + } + + std::string CodeGenerator::translateVU_VADDAq(const Instruction &inst) + { + return fmt::format("ctx->vu0_acc = PS2_VADD(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q));", + inst.rs); + } + + std::string CodeGenerator::translateVU_VADDAi(const Instruction &inst) + { + return fmt::format("ctx->vu0_acc = PS2_VADD(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i));", + inst.rs); + } + + std::string CodeGenerator::translateVU_VSUBA(const Instruction &inst) + { + return fmt::format("ctx->vu0_acc = PS2_VSUB(ctx->vu0_vf[{}], ctx->vu0_vf[{}]);", + inst.rs, inst.rt); + } + + std::string CodeGenerator::translateVU_VSUBAq(const Instruction &inst) + { + return fmt::format("ctx->vu0_acc = PS2_VSUB(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q));", + inst.rs); + } + + std::string CodeGenerator::translateVU_VSUBAi(const Instruction &inst) + { + return fmt::format("ctx->vu0_acc = PS2_VSUB(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i));", + inst.rs); + } + + std::string CodeGenerator::translateVU_VMADDA(const Instruction &inst) + { + return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); " + "ctx->vu0_acc = PS2_VADD(ctx->vu0_acc, mul_res); }}", + inst.rs, inst.rt); + } + + std::string CodeGenerator::translateVU_VMADDAq(const Instruction &inst) + { + return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q)); " + "ctx->vu0_acc = PS2_VADD(ctx->vu0_acc, mul_res); }}", + inst.rs); + } + + std::string CodeGenerator::translateVU_VMADDAi(const Instruction &inst) + { + return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i)); " + "ctx->vu0_acc = PS2_VADD(ctx->vu0_acc, mul_res); }}", + inst.rs); + } + + std::string CodeGenerator::translateVU_VMSUBA(const Instruction &inst) + { + return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); " + "ctx->vu0_acc = PS2_VSUB(ctx->vu0_acc, mul_res); }}", + inst.rs, inst.rt); + } + + std::string CodeGenerator::translateVU_VMSUBAq(const Instruction &inst) + { + return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q)); " + "ctx->vu0_acc = PS2_VSUB(ctx->vu0_acc, mul_res); }}", + inst.rs); + } + + std::string CodeGenerator::translateVU_VMSUBAi(const Instruction &inst) + { + return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i)); " + "ctx->vu0_acc = PS2_VSUB(ctx->vu0_acc, mul_res); }}", + inst.rs); + } + + std::string CodeGenerator::translateVU_VMULA(const Instruction &inst) + { + return fmt::format("ctx->vu0_acc = PS2_VMUL(ctx->vu0_vf[{}], ctx->vu0_vf[{}]);", + inst.rs, inst.rt); + } + + std::string CodeGenerator::translateVU_VMULAq(const Instruction &inst) + { + return fmt::format("ctx->vu0_acc = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q));", + inst.rs); + } + + std::string CodeGenerator::translateVU_VMULAi(const Instruction &inst) + { + return fmt::format("ctx->vu0_acc = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i));", + inst.rs); + } + + std::string CodeGenerator::translateVU_VOPMULA(const Instruction &inst) + { + return fmt::format("ctx->vu0_acc = PS2_VMUL(ctx->vu0_vf[{}], ctx->vu0_vf[{}]);", + inst.rs, inst.rt); + } + + std::string CodeGenerator::translateVU_VITOF(const Instruction &inst, int shift) + { + 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({}f)); " + "__m128i mask = _mm_set_epi32({}, {}, {}, {}); " + "ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", + inst.rs, scale, + (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, + (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, + inst.rt, inst.rt); + } + + std::string CodeGenerator::translateVU_VFTOI(const Instruction &inst, int shift) + { + 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({}f)); " + "__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)); }}", + inst.rs, scale, + (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, + (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, + inst.rt, inst.rt); + } + + std::string CodeGenerator::translateVU_VLQI(const Instruction &inst) + { + 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; }}", + inst.rs, + (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, + (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, + inst.rt, inst.rt, + inst.rs, inst.rs); + } + + std::string CodeGenerator::translateVU_VSQI(const Instruction &inst) + { + 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; }}", + inst.rs, + inst.rt, + (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, + (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, + inst.rs, inst.rs); + } + + std::string CodeGenerator::translateVU_VLQD(const Instruction &inst) + { + 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)); }}", + inst.rs, inst.rs, + inst.rs, + (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, + (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, + inst.rt, inst.rt); + } + + std::string CodeGenerator::translateVU_VSQD(const Instruction &inst) + { + 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)); }}", + inst.rs, inst.rs, + inst.rs, + inst.rt, + (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, + (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0); + } + std::string CodeGenerator::translateVU_VRGET(const Instruction &inst) { uint8_t dest_mask = inst.vectorInfo.vectorField; diff --git a/ps2xRecomp/src/lib/r5900_decoder.cpp b/ps2xRecomp/src/lib/r5900_decoder.cpp index bc6f968..4f11824 100644 --- a/ps2xRecomp/src/lib/r5900_decoder.cpp +++ b/ps2xRecomp/src/lib/r5900_decoder.cpp @@ -808,8 +808,15 @@ namespace ps2recomp case COP2_CO + 15: { // Refine based on specific VU function uint8_t vu_func = inst.function; + bool is_special2 = vu_func >= 0x3C; + uint8_t vu_fhi_flo = 0; - if (vu_func == VU0_S2_VDIV || vu_func == VU0_S2_VSQRT || vu_func == VU0_S2_VRSQRT) + if (is_special2) + { + vu_fhi_flo = static_cast((((inst.raw >> 6) & 0x1F) << 2) | (inst.raw & 0x3)); + } + + if (is_special2 && (vu_fhi_flo == VU0_S2_VDIV || vu_fhi_flo == VU0_S2_VSQRT || vu_fhi_flo == VU0_S2_VRSQRT)) { inst.vectorInfo.fsf = (inst.raw >> 10) & 0x3; // Extract bits 10-11 inst.vectorInfo.ftf = (inst.raw >> 8) & 0x3; // Extract bits 8-9 @@ -820,9 +827,9 @@ namespace ps2recomp inst.modificationInfo.modifiesVFR = true; // Default: Modifies Vector Float Reg inst.modificationInfo.modifiesControl = true; // Default: Modifies Flags/Special Regs (Q, P, I, MAC, Clip...) - if (vu_func >= 0x3C) // Special2 Table + if (is_special2) // Special2 Table { - switch (vu_func) + switch (vu_fhi_flo) { case VU0_S2_VDIV: case VU0_S2_VSQRT: @@ -859,6 +866,10 @@ namespace ps2recomp inst.modificationInfo.modifiesControl = true; /* Modifies R */ inst.modificationInfo.modifiesVFR = false; break; // Writes R + case VU0_S2_VWAITQ: + inst.modificationInfo.modifiesVFR = false; + inst.modificationInfo.modifiesControl = false; + break; case VU0_S2_VABS: case VU0_S2_VMOVE: case VU0_S2_VMR32: @@ -873,45 +884,6 @@ namespace ps2recomp break; } } - else // Special1 Table - { - if (vu_func >= VU0_S1_VIADD && vu_func <= VU0_S1_VIOR) - { - inst.modificationInfo.modifiesVFR = false; - inst.modificationInfo.modifiesVIR = true; - } // Integer ops - if (vu_func == VU0_S1_VIADDI) - { - inst.modificationInfo.modifiesVFR = false; - inst.modificationInfo.modifiesVIR = true; - } - if (vu_func == VU0_S2_VMFIR) - { - inst.modificationInfo.modifiesVIR = false; - } // Only reads VIR - if (vu_func == VU0_S2_VMTIR) - { - inst.modificationInfo.modifiesVFR = false; - inst.modificationInfo.modifiesVIC = true; - } // Modifies I reg - if (vu_func == VU0_S2_VILWR) - { - inst.modificationInfo.modifiesVFR = false; - inst.modificationInfo.modifiesVIR = true; - inst.isLoad = true; - } - if (vu_func == VU0_S2_VISWR) - { - inst.modificationInfo.modifiesVFR = false; - inst.modificationInfo.modifiesVIR = false; - inst.isStore = true; - inst.modificationInfo.modifiesMemory = true; - } - if (vu_func == VU0_S2_VDIV || vu_func == VU0_S2_VSQRT || vu_func == VU0_S2_VRSQRT) - { - inst.vectorInfo.usesQReg = true; - } - } break; } } diff --git a/ps2xTest/src/code_generator_tests.cpp b/ps2xTest/src/code_generator_tests.cpp index c540bd5..bb2deb5 100644 --- a/ps2xTest/src/code_generator_tests.cpp +++ b/ps2xTest/src/code_generator_tests.cpp @@ -2,6 +2,10 @@ #include "ps2recomp/code_generator.h" #include "ps2recomp/instructions.h" #include "ps2recomp/types.h" +#include +#include +#include +#include using namespace ps2recomp; @@ -30,6 +34,34 @@ static Instruction makeNop(uint32_t address) return inst; } +static std::string readFileFromCandidates(const std::vector &candidates) +{ + for (const auto &path : candidates) + { + std::ifstream file(path); + if (file) + { + std::ostringstream ss; + ss << file.rdbuf(); + return ss.str(); + } + } + return {}; +} + +static std::vector parseEnumValues(const std::string &text, const std::string &prefix) +{ + std::vector values; + std::regex re("\\b(" + prefix + "[A-Za-z0-9_]+)\\b\\s*=\\s*0x([0-9A-Fa-f]+)"); + for (auto it = std::sregex_iterator(text.begin(), text.end(), re); it != std::sregex_iterator(); ++it) + { + const auto &match = *it; + uint32_t value = static_cast(std::stoul(match[2].str(), nullptr, 16)); + values.push_back(value); + } + return values; +} + static Instruction makeJal(uint32_t address, uint32_t target) { Instruction inst{}; @@ -274,6 +306,60 @@ void register_code_generator_tests() "call should use sanitized name but got: " + generated); }); + tc.Run("VU0 macro mappings cover all S1/S2 enums", [](TestCase &t) { + const std::vector candidates = { + "ps2xRecomp/include/ps2recomp/instructions.h", + "../ps2xRecomp/include/ps2recomp/instructions.h", + "../../ps2xRecomp/include/ps2recomp/instructions.h" + }; + + std::string text = readFileFromCandidates(candidates); + t.IsTrue(!text.empty(), "instructions.h should be readable from the test working directory"); + + std::vector s1 = parseEnumValues(text, "VU0_S1_"); + std::vector s2 = parseEnumValues(text, "VU0_S2_"); + t.IsTrue(!s1.empty(), "VU0_S1 enum list should not be empty"); + t.IsTrue(!s2.empty(), "VU0_S2 enum list should not be empty"); + + CodeGenerator gen({}); + + for (uint32_t value : s1) + { + Instruction inst; + inst.opcode = OPCODE_COP2; + inst.rs = COP2_CO; // format + inst.rt = 2; + inst.rd = 3; + inst.function = value; + inst.vectorInfo.vectorField = 0xF; + + std::string out = gen.translateInstruction(inst); + std::ostringstream msg; + msg << "VU0 S1 0x" << std::hex << value << " should be mapped"; + t.IsTrue(out.find("Unhandled VU0 Special1") == std::string::npos, msg.str().c_str()); + } + + for (uint32_t value : s2) + { + Instruction inst; + inst.opcode = OPCODE_COP2; + inst.rs = COP2_CO; // format + inst.rt = 2; + inst.rd = 3; + inst.function = 0x3C; // force Special2 path + inst.vectorInfo.vectorField = 0xF; + + uint32_t upper = (value >> 2) & 0x1F; + uint32_t lower = value & 0x3; + inst.raw = (upper << 6) | lower; + + std::string out = gen.translateInstruction(inst); + std::ostringstream msg; + msg << "VU0 S2 0x" << std::hex << value << " should be mapped"; + t.IsTrue(out.find("Unhandled VU0 Special2") == std::string::npos, msg.str().c_str()); + } + }); + tc.Run("JAL to known function emits call and check", [](TestCase &t) { Function func; func.name = "jal_test"; diff --git a/ps2xTest/src/r5900_decoder_tests.cpp b/ps2xTest/src/r5900_decoder_tests.cpp index 2affebe..1942a6c 100644 --- a/ps2xTest/src/r5900_decoder_tests.cpp +++ b/ps2xTest/src/r5900_decoder_tests.cpp @@ -133,8 +133,10 @@ void register_r5900_decoder_tests() tc.Run("COP2 VU macro op marks VU flags", [](TestCase &t) { uint32_t address = 0x7000; - // COP2, rs = COP2_CO (macro), function = VU0_S2_VDIV (0x31) - uint32_t raw = (OPCODE_COP2 << 26) | (COP2_CO << 21) | VU0_S2_VDIV; + uint8_t s2op = VU0_S2_VDIV; // 0x38 + uint8_t fhi = s2op >> 2; // 0x0E -> bits[10:6] + uint8_t flo = s2op & 0x3; // 0x00 -> bits[1:0] + uint32_t raw = (OPCODE_COP2 << 26) | (COP2_CO << 21) | (fhi << 6) | (0x3C | flo); R5900Decoder decoder; Instruction inst = decoder.decodeInstruction(address, raw);