From 124f86d7d0a0cfadb3b2c8b66a04a37fcd80e8df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 14 Oct 2026 21:34:05 +0200 Subject: [PATCH] VFPU: Decode prefixes with shared helpers The prefix fields were decoded by hand in the interpreter, the IR frontend, JitState, MIPSState and the disassembler, with three copies of the constant table. They now share small decoders and one table, in MIPS.h. The backends' Vec4Shuffle lane decoding gets a helper of its own in IRInst.h. Co-Authored-By: Claude Opus 5.5 --- Core/MIPS/ARM/ArmIRCompVec.cpp | 4 +- Core/MIPS/ARM64/Arm64IRCompVec.cpp | 4 +- Core/MIPS/IR/IRCompVFPU.cpp | 64 +++++++------------- Core/MIPS/IR/IRInst.h | 5 ++ Core/MIPS/IR/IRInterpreter.cpp | 2 +- Core/MIPS/InterpreterVFPU.cpp | 21 +++---- Core/MIPS/JitCommon/JitState.h | 4 +- Core/MIPS/LoongArch64/LoongArch64CompVec.cpp | 2 +- Core/MIPS/MIPS.cpp | 2 + Core/MIPS/MIPS.h | 24 +++++++- Core/MIPS/MIPSDisVFPU.cpp | 12 ++-- Core/MIPS/RiscV/RiscVCompVec.cpp | 2 +- 12 files changed, 75 insertions(+), 71 deletions(-) diff --git a/Core/MIPS/ARM/ArmIRCompVec.cpp b/Core/MIPS/ARM/ArmIRCompVec.cpp index b4436570ba..fe98a56139 100644 --- a/Core/MIPS/ARM/ArmIRCompVec.cpp +++ b/Core/MIPS/ARM/ArmIRCompVec.cpp @@ -123,12 +123,12 @@ void ArmJitBackend::CompIR_VecAssign(IRInst inst) { if (inst.dest == inst.src1) { // Go through the scratch regs, since a lane may be read after it's written. for (int i = 0; i < 4; ++i) - VMOV((ARMReg)(SCRATCHF1 + i), regs_.F(inst.src1 + ((inst.src2 >> (i * 2)) & 3))); + VMOV((ARMReg)(SCRATCHF1 + i), regs_.F(inst.src1 + Vec4ShuffleLane(inst.src2, i))); for (int i = 0; i < 4; ++i) VMOV(regs_.F(inst.dest + i), (ARMReg)(SCRATCHF1 + i)); } else { for (int i = 0; i < 4; ++i) - VMOV(regs_.F(inst.dest + i), regs_.F(inst.src1 + ((inst.src2 >> (i * 2)) & 3))); + VMOV(regs_.F(inst.dest + i), regs_.F(inst.src1 + Vec4ShuffleLane(inst.src2, i))); } break; diff --git a/Core/MIPS/ARM64/Arm64IRCompVec.cpp b/Core/MIPS/ARM64/Arm64IRCompVec.cpp index a681eb9e47..45924a088e 100644 --- a/Core/MIPS/ARM64/Arm64IRCompVec.cpp +++ b/Core/MIPS/ARM64/Arm64IRCompVec.cpp @@ -277,8 +277,8 @@ uint8_t Arm64ShuffleResult(uint8_t mask, uint8_t prev) { uint8_t result = 0; for (int i = 0; i < 4; ++i) { - int takeLane = (mask >> (i * 2)) & 3; - int lane = (prev >> (takeLane * 2)) & 3; + int takeLane = Vec4ShuffleLane(mask, i); + int lane = Vec4ShuffleLane(prev, takeLane); result |= lane << (i * 2); } return result; diff --git a/Core/MIPS/IR/IRCompVFPU.cpp b/Core/MIPS/IR/IRCompVFPU.cpp index 81e4350d33..ac8003b9ec 100644 --- a/Core/MIPS/IR/IRCompVFPU.cpp +++ b/Core/MIPS/IR/IRCompVFPU.cpp @@ -156,10 +156,10 @@ namespace MIPSComp { static bool IsPrefixWithinSize(u32 prefix, VectorSize sz) { int n = GetNumVectorElements(sz); for (int i = 0; i < 4; i++) { - int regnum = (prefix >> (i * 2)) & 3; - int abs = (prefix >> (8 + i)) & 1; - int negate = (prefix >> (16 + i)) & 1; - int constants = (prefix >> (12 + i)) & 1; + int regnum = VFPUPrefixSwizzle(prefix, i); + bool abs = VFPUPrefixAbs(prefix, i); + bool negate = VFPUPrefixNegate(prefix, i); + bool constants = VFPUPrefixIsConstant(prefix, i); if (constants) { continue; } @@ -178,19 +178,6 @@ namespace MIPSComp { return IsPrefixWithinSize(prefix, GetVecSize(op)); } - // Whether lane i of an S or T prefix is a constant, and its value. Past the vector size, the dot - // products add in the lanes where both S and T are constants, and read zero for the rest - // (cpu/vfpu/prefix_lanes), which IsPrefixWithinSize() lets through. - static bool PrefixLaneConstant(u32 prefix, int i, float *value) { - static const float constants[8] = { 0.f, 1.f, 2.f, 0.5f, 3.f, 1.f / 3.f, 0.25f, 1.f / 6.f }; - if (((prefix >> (12 + i)) & 1) == 0) - return false; - const int regnum = (prefix >> (i * 2)) & 3; - const int abs = (prefix >> (8 + i)) & 1; - const int negate = (prefix >> (16 + i)) & 1; - *value = negate ? -constants[regnum + (abs << 2)] : constants[regnum + (abs << 2)]; - return true; - } void IRFrontend::Comp_VPFX(MIPSOpcode op) { CONDITIONAL_DISABLE(VFPU_XFER); @@ -229,7 +216,6 @@ namespace MIPSComp { int n = GetNumVectorElements(sz); u8 origV[4]{}; - static const float constantArray[8] = { 0.f, 1.f, 2.f, 0.5f, 3.f, 1.f / 3.f, 0.25f, 1.f / 6.f }; for (int i = 0; i < n; i++) origV[i] = vregs[i]; @@ -277,10 +263,10 @@ namespace MIPSComp { // Check if we're just zeroing certain lanes - this is common. u32 zeroedLanes = 0; for (int i = 0; i < 4; ++i) { - int regnum = (prefix >> (i * 2)) & 3; - int abs = (prefix >> (8 + i)) & 1; - int negate = (prefix >> (16 + i)) & 1; - int constants = (prefix >> (12 + i)) & 1; + int regnum = VFPUPrefixSwizzle(prefix, i); + bool abs = VFPUPrefixAbs(prefix, i); + bool negate = VFPUPrefixNegate(prefix, i); + bool constants = VFPUPrefixIsConstant(prefix, i); if (!constants && regnum == i && !abs && !negate) continue; @@ -304,10 +290,10 @@ namespace MIPSComp { // Alright, fall back to the generic approach. for (int i = 0; i < n; i++) { - int regnum = (prefix >> (i * 2)) & 3; - int abs = (prefix >> (8 + i)) & 1; - int negate = (prefix >> (16 + i)) & 1; - int constants = (prefix >> (12 + i)) & 1; + int regnum = VFPUPrefixSwizzle(prefix, i); + bool abs = VFPUPrefixAbs(prefix, i); + bool negate = VFPUPrefixNegate(prefix, i); + bool constants = VFPUPrefixIsConstant(prefix, i); // Unchanged, hurray. if (!constants && regnum == i && !abs && !negate) @@ -331,11 +317,7 @@ namespace MIPSComp { ir.Write(IROp::FMov, vregs[i], origV[regnum]); } } else { - if (negate) { - ir.WriteSetConstantFloat(vregs[i], -constantArray[regnum + (abs << 2)]); - } else { - ir.WriteSetConstantFloat(vregs[i], constantArray[regnum + (abs << 2)]); - } + ir.WriteSetConstantFloat(vregs[i], VFPUPrefixConstant(prefix, i)); } } } @@ -385,10 +367,6 @@ namespace MIPSComp { } } - inline int GetDSat(int prefix, int i) { - return (prefix >> (i * 2)) & 3; - } - // "D" prefix is really a post process. No need to allocate a temporary register (except // dummies to simulate writemask, which is done in GetVectorRegsPrefixD void IRFrontend::ApplyPrefixD(u8 *vregs, VectorSize sz, int vectorReg) { @@ -402,7 +380,7 @@ namespace MIPSComp { for (int i = 0; i < n; i++) { if (js.VfpuWriteMask(i)) continue; - int sat = GetDSat(js.prefixD, i); + int sat = VFPUPrefixSat(js.prefixD, i); if (sat == 1) { // clamped = x < 0 ? (x > 1 ? 1 : x) : x [0, 1] ir.Write(IROp::FSat0_1, vregs[i], vregs[i]); @@ -752,10 +730,10 @@ namespace MIPSComp { VectorSize sz = GetVecSize(op); int n = GetNumVectorElements(sz); - // Constants past n are added in too (see PrefixLaneConstant()), rare enough for the interpreter. + // On the PSP, an S constant past n is added in too (cpu/vfpu/prefix_lanes), which + // IsPrefixWithinSize() lets through. Rare enough for the interpreter. for (int i = n; i < 4; i++) { - float s; - if (PrefixLaneConstant(js.prefixS, i, &s)) + if (VFPUPrefixIsConstant(js.prefixS, i)) DISABLE; } @@ -798,11 +776,11 @@ namespace MIPSComp { VectorSize sz = GetVecSize(op); int n = GetNumVectorElements(sz); - // Lanes past n where both prefixes are constants add their product (see - // PrefixLaneConstant()), rare enough for the interpreter. + // On the PSP, lanes past n where both prefixes are constants add their product, and the rest + // read zero (cpu/vfpu/prefix_lanes). IsPrefixWithinSize() lets those through, and they're + // rare enough for the interpreter. for (int i = n; i < 4; i++) { - float s, t; - if (PrefixLaneConstant(js.prefixS, i, &s) && PrefixLaneConstant(js.prefixT, i, &t)) + if (VFPUPrefixIsConstant(js.prefixS, i) && VFPUPrefixIsConstant(js.prefixT, i)) DISABLE; } diff --git a/Core/MIPS/IR/IRInst.h b/Core/MIPS/IR/IRInst.h index cc392e2a84..0f746246ae 100644 --- a/Core/MIPS/IR/IRInst.h +++ b/Core/MIPS/IR/IRInst.h @@ -278,6 +278,11 @@ enum class Vec4Init { Set_0001, }; +// The source lane of dest lane i in a Vec4Shuffle, two bits per lane like a VFPU prefix's swizzle. +inline int Vec4ShuffleLane(u32 shuffle, int i) { + return (shuffle >> (i * 2)) & 3; +} + enum class IRRoundMode : uint8_t { RINT_0 = 0, CAST_1 = 1, diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index 0562274298..6d67305c97 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -465,7 +465,7 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { // or a big switch - there are only 256 shuffles possible (4^4) float temp[4]; for (u32 i = 0; i < 4; i++) - temp[i] = mips->f[(u32)inst->src1 + (u32)((inst->src2 >> (i * 2)) & 3)]; + temp[i] = mips->f[(u32)inst->src1 + (u32)Vec4ShuffleLane(inst->src2, i)]; const u32 dest = inst->dest; for (u32 i = 0; i < 4; i++) mips->f[dest + i] = temp[i]; diff --git a/Core/MIPS/InterpreterVFPU.cpp b/Core/MIPS/InterpreterVFPU.cpp index da6ee4b726..598fec521c 100644 --- a/Core/MIPS/InterpreterVFPU.cpp +++ b/Core/MIPS/InterpreterVFPU.cpp @@ -129,17 +129,16 @@ static void ApplyPrefixST(MIPSState *mips, float *r, u32 data, VectorSize size, int n = GetNumVectorElements(size); float origV[4]{ invalid, invalid, invalid, invalid }; - static const float constantArray[8] = {0.f, 1.f, 2.f, 0.5f, 3.f, 1.f/3.f, 0.25f, 1.f/6.f}; for (int i = 0; i < n; i++) { origV[i] = r[i]; } for (int i = 0; i < n; i++) { - int regnum = (data >> (i*2)) & 3; - int abs = (data >> (8+i)) & 1; - int negate = (data >> (16+i)) & 1; - int constants = (data >> (12+i)) & 1; + int regnum = VFPUPrefixSwizzle(data, i); + bool abs = VFPUPrefixAbs(data, i); + bool negate = VFPUPrefixNegate(data, i); + bool constants = VFPUPrefixIsConstant(data, i); if (!constants) { if (regnum >= n) { @@ -150,7 +149,7 @@ static void ApplyPrefixST(MIPSState *mips, float *r, u32 data, VectorSize size, if (abs) ((u32 *)r)[i] = ((u32 *)r)[i] & 0x7FFFFFFF; } else { - r[i] = constantArray[regnum + (abs<<2)]; + r[i] = vfpuPrefixConstants[regnum + (abs << 2)]; } if (negate) @@ -176,7 +175,7 @@ void ApplyPrefixD(MIPSState *mips, float *v, VectorSize size, bool onlyWriteMask int n = GetNumVectorElements(size); for (int i = 0; i < n; i++) { - int sat = (data >> (i * 2)) & 3; + int sat = VFPUPrefixSat(data, i); if (sat == 1) v[i] = vfpu_clamp(v[i], 0.0f, 1.0f); else if (sat == 3) @@ -202,10 +201,10 @@ static void RetainInvalidSwizzleST(MIPSState *mips, float *d, VectorSize sz) { // TODO: We can probably do some faster check of sPrefix and tPrefix to skip over this loop. for (int i = 0; i < n; i++) { - int swizzleS = (sPrefix >> (i + i)) & 3; - int swizzleT = (tPrefix >> (i + i)) & 3; - int constS = (sPrefix >> (12 + i)) & 1; - int constT = (tPrefix >> (12 + i)) & 1; + int swizzleS = VFPUPrefixSwizzle(sPrefix, i); + int swizzleT = VFPUPrefixSwizzle(tPrefix, i); + bool constS = VFPUPrefixIsConstant(sPrefix, i); + bool constT = VFPUPrefixIsConstant(tPrefix, i); if ((swizzleS >= n && !constS) || (swizzleT >= n && !constT)) d[i] = 0.0f; } diff --git a/Core/MIPS/JitCommon/JitState.h b/Core/MIPS/JitCommon/JitState.h index 5f04f20687..a5f2725dd4 100644 --- a/Core/MIPS/JitCommon/JitState.h +++ b/Core/MIPS/JitCommon/JitState.h @@ -143,12 +143,12 @@ namespace MIPSComp { u8 VfpuWriteMask() const { _assert_(prefixDFlag & JitState::PREFIX_KNOWN); - return (prefixD >> 8) & 0xF; + return VFPUPrefixWriteMask(prefixD); } bool VfpuWriteMask(int i) const { _assert_(prefixDFlag & JitState::PREFIX_KNOWN); - return (prefixD >> (8 + i)) & 1; + return VFPUPrefixWriteMask(prefixD, i); } void LogPrefix() { diff --git a/Core/MIPS/LoongArch64/LoongArch64CompVec.cpp b/Core/MIPS/LoongArch64/LoongArch64CompVec.cpp index 4121fa1c70..ab940a082d 100644 --- a/Core/MIPS/LoongArch64/LoongArch64CompVec.cpp +++ b/Core/MIPS/LoongArch64/LoongArch64CompVec.cpp @@ -209,7 +209,7 @@ void LoongArch64JitBackend::CompIR_VecAssign(IRInst inst) { } else { regs_.Map(inst); for (int i = 0; i < 4; ++i) { - int lane = (inst.src2 >> (i * 2)) & 3; + int lane = Vec4ShuffleLane(inst.src2, i); FMOV_S(regs_.F(inst.dest + i), regs_.F(inst.src1 + lane)); } } diff --git a/Core/MIPS/MIPS.cpp b/Core/MIPS/MIPS.cpp index f975cbc95c..527553ae96 100644 --- a/Core/MIPS/MIPS.cpp +++ b/Core/MIPS/MIPS.cpp @@ -369,6 +369,8 @@ void MIPSState::Init() { UpdateBlockShadowHook(); } +const float vfpuPrefixConstants[8] = { 0.f, 1.f, 2.f, 0.5f, 3.f, 1.f / 3.f, 0.25f, 1.f / 6.f }; + bool MIPSState::HasDefaultPrefix() const { return vfpuCtrl[VFPU_CTRL_SPREFIX] == 0xe4 && vfpuCtrl[VFPU_CTRL_TPREFIX] == 0xe4 && vfpuCtrl[VFPU_CTRL_DPREFIX] == 0; } diff --git a/Core/MIPS/MIPS.h b/Core/MIPS/MIPS.h index d38090cc39..9d5e79ef9b 100644 --- a/Core/MIPS/MIPS.h +++ b/Core/MIPS/MIPS.h @@ -109,6 +109,26 @@ enum { //unknown.... }; +// The fields of the VFPU prefixes. Lane i of an S or T prefix takes the lane its swizzle names, or +// with the constant bit, the constant the swizzle and abs bit pick, then the abs and negate. Lane i +// of a D prefix has a saturation (1 = [0:1], 3 = [-1:1]) and a write mask bit, set to keep the lane. +inline int VFPUPrefixSwizzle(u32 prefix, int i) { return (prefix >> (i * 2)) & 3; } +inline bool VFPUPrefixAbs(u32 prefix, int i) { return (prefix >> (8 + i)) & 1; } +inline bool VFPUPrefixIsConstant(u32 prefix, int i) { return (prefix >> (12 + i)) & 1; } +inline bool VFPUPrefixNegate(u32 prefix, int i) { return (prefix >> (16 + i)) & 1; } +inline int VFPUPrefixSat(u32 prefix, int i) { return (prefix >> (i * 2)) & 3; } +inline u8 VFPUPrefixWriteMask(u32 prefix) { return (prefix >> 8) & 0xF; } +inline bool VFPUPrefixWriteMask(u32 prefix, int i) { return (prefix >> (8 + i)) & 1; } + +// The constants of an S or T prefix, by swizzle + 4 * abs. +extern const float vfpuPrefixConstants[8]; + +// The value of a lane with the constant bit, negate and all. +inline float VFPUPrefixConstant(u32 prefix, int i) { + const float c = vfpuPrefixConstants[VFPUPrefixSwizzle(prefix, i) + (VFPUPrefixAbs(prefix, i) << 2)]; + return VFPUPrefixNegate(prefix, i) ? -c : c; +} + enum VCondition { VC_FL, @@ -239,10 +259,10 @@ public: #endif u8 VfpuWriteMask() const { - return (vfpuCtrl[VFPU_CTRL_DPREFIX] >> 8) & 0xF; + return VFPUPrefixWriteMask(vfpuCtrl[VFPU_CTRL_DPREFIX]); } bool VfpuWriteMask(int i) const { - return (vfpuCtrl[VFPU_CTRL_DPREFIX] >> (8 + i)) & 1; + return VFPUPrefixWriteMask(vfpuCtrl[VFPU_CTRL_DPREFIX], i); } bool HasDefaultPrefix() const; diff --git a/Core/MIPS/MIPSDisVFPU.cpp b/Core/MIPS/MIPSDisVFPU.cpp index 6dfc9472bc..da1c3e974d 100644 --- a/Core/MIPS/MIPSDisVFPU.cpp +++ b/Core/MIPS/MIPSDisVFPU.cpp @@ -159,10 +159,10 @@ namespace MIPSDis static const char * const constan[8] = {"0","1","2","1/2","3","1/3","1/4","1/6"}; for (int i=0; i<4; i++) { - int regnum = (data>>(i*2)) & 3; - int abs = (data>>(8+i)) & 1; - int negate = (data>>(16+i)) & 1; - int constants = (data>>(12+i)) & 1; + int regnum = VFPUPrefixSwizzle(data, i); + bool abs = VFPUPrefixAbs(data, i); + bool negate = VFPUPrefixNegate(data, i); + bool constants = VFPUPrefixIsConstant(data, i); if (negate && outpos < outSize) outpos += truncate_cpy(out + outpos, outSize - outpos, "-"); if (abs && !constants && outpos < outSize) @@ -191,8 +191,8 @@ namespace MIPSDis static const char * const satNames[4] = {"", "[0:1]", "X", "[-1:1]"}; for (int i=0; i<4; i++) { - int sat = (data>>i*2)&3; - int mask = (data>>(8+i))&1; + int sat = VFPUPrefixSat(data, i); + bool mask = VFPUPrefixWriteMask(data, i); if (sat && outpos < outSize) outpos += truncate_cpy(out + outpos, outSize - outpos, satNames[sat]); if (mask && outpos < outSize) diff --git a/Core/MIPS/RiscV/RiscVCompVec.cpp b/Core/MIPS/RiscV/RiscVCompVec.cpp index 51141203e4..c2c00e5589 100644 --- a/Core/MIPS/RiscV/RiscVCompVec.cpp +++ b/Core/MIPS/RiscV/RiscVCompVec.cpp @@ -194,7 +194,7 @@ void RiscVJitBackend::CompIR_VecAssign(IRInst inst) { } else { regs_.Map(inst); for (int i = 0; i < 4; ++i) { - int lane = (inst.src2 >> (i * 2)) & 3; + int lane = Vec4ShuffleLane(inst.src2, i); FMV(32, regs_.F(inst.dest + i), regs_.F(inst.src1 + lane)); } }