From b18c3ed4d3ee7a3dd724b81a9d0d2548a2e86a83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 3 Nov 2026 14:18:05 +0100 Subject: [PATCH] IR: Compile vsrt1-4 and vf2h instead of interpreting them Colin McRae Rally 2005 interprets about 2 million of these each 10 emulated seconds, mostly vf2h.q. vsrt1-4 become pairs of FMin/FMax, which order and break ties like the interpreter's vfpu_min and vfpu_max, in the same operand order. The D prefix applies as usual (Colin masks half the lanes); S and T prefixes, which vsrt rewrites, still go to the interpreter. vf2h gets an IR op of its own, FFloatToHalf2, calling vfpu_f2h; the native backends call the IR interpreter's version of it. vf2h with any prefix, and sizes other than pair and quad, still go to the interpreter. The CPU equivalence fuzzer finds no differences on the IR interpreter or the ARM64 JIT, for these ops alone, with random and unknown prefixes, and with the D prefixes Colin uses. Co-Authored-By: Claude Opus 5.5 --- Core/MIPS/IR/IRCompVFPU.cpp | 83 +++++++++++++++++++++++++++++++++ Core/MIPS/IR/IRFrontend.h | 2 + Core/MIPS/IR/IRInst.cpp | 1 + Core/MIPS/IR/IRInst.h | 1 + Core/MIPS/IR/IRInterpreter.cpp | 4 ++ Core/MIPS/IR/IRNativeCommon.cpp | 5 ++ Core/MIPS/IR/IRPassSimplify.cpp | 1 + Core/MIPS/JitCommon/JitCommon.h | 2 + Core/MIPS/MIPSTables.cpp | 10 ++-- 9 files changed, 104 insertions(+), 5 deletions(-) diff --git a/Core/MIPS/IR/IRCompVFPU.cpp b/Core/MIPS/IR/IRCompVFPU.cpp index caaade1540..c944f4019c 100644 --- a/Core/MIPS/IR/IRCompVFPU.cpp +++ b/Core/MIPS/IR/IRCompVFPU.cpp @@ -1503,6 +1503,35 @@ namespace MIPSComp { ApplyPrefixD(dregs, outsize, _VD); } + void IRFrontend::Comp_Vf2h(MIPSOpcode op) { + CONDITIONAL_DISABLE(VFPU_VEC); + // The prefixes act oddly here (the S swizzle applies to all four lanes), so leave those to the interpreter. + if (!js.HasNoPrefix()) { + DISABLE; + } + + // Vector pack float to half + // d[N] = half(s[N*2]) | half(s[N*2+1]) << 16 + VectorSize sz = GetVecSize(op); + if (sz != V_Pair && sz != V_Quad) { + DISABLE; + } + VectorSize outsize = sz == V_Pair ? V_Single : V_Pair; + int nOut = GetNumVectorElements(outsize); + + u8 sregs[4], dregs[2]; + GetVectorRegs(sregs, sz, _VS); + GetVectorRegs(dregs, outsize, _VD); + + // Through temps, since d may overlap s. + for (int i = 0; i < nOut; i++) { + ir.Write(IROp::FFloatToHalf2, IRVTEMP_0 + i, sregs[i * 2], sregs[i * 2 + 1]); + } + for (int i = 0; i < nOut; i++) { + ir.Write(IROp::FMov, dregs[i], IRVTEMP_0 + i); + } + } + void IRFrontend::Comp_Vf2i(MIPSOpcode op) { CONDITIONAL_DISABLE(VFPU_VEC); if (js.HasUnknownPrefix() || !IsPrefixWithinSize(js.prefixS, op) || (js.prefixD & 0xFF) != 0) { @@ -2743,6 +2772,60 @@ namespace MIPSComp { ApplyPrefixD(dregs, outsize, _VD); } + void IRFrontend::Comp_Vsrt(MIPSOpcode op) { + CONDITIONAL_DISABLE(VFPU_VEC); + // T is S re-swizzled, with the T prefix rewritten around that, so S and T prefixes go to the interpreter. + // The D prefix applies normally (Colin McRae masks off half the lanes). + if (js.HasUnknownPrefix() || js.HasSPrefix() || js.HasTPrefix() || GetVecSize(op) != V_Quad) { + DISABLE; + } + + // Sorts pairs of lanes with vmin/vmax, which keep the second operand on a tie, in the + // same operand order as Int_Vsrt1-4, so the same lane wins a tie. + // vsrt1: (x, y) (z, w) ascending. vsrt2: (x, w) (y, z) ascending. + // vsrt3: (x, y) (z, w) descending. vsrt4: (x, w) (y, z) descending. + u8 s[4], dregs[4]; + GetVectorRegs(s, V_Quad, _VS); + GetVectorRegsPrefixD(dregs, V_Quad, _VD); + + u8 tmp[4]; + for (int i = 0; i < 4; i++) { + tmp[i] = IRVTEMP_0 + i; + } + switch ((op >> 16) & 0x1F) { + case 0: // vsrt1, t = yxwz + ir.Write(IROp::FMin, tmp[0], s[1], s[0]); + ir.Write(IROp::FMax, tmp[1], s[1], s[0]); + ir.Write(IROp::FMin, tmp[2], s[3], s[2]); + ir.Write(IROp::FMax, tmp[3], s[3], s[2]); + break; + case 1: // vsrt2, t = wzyx + ir.Write(IROp::FMin, tmp[0], s[3], s[0]); + ir.Write(IROp::FMin, tmp[1], s[2], s[1]); + ir.Write(IROp::FMax, tmp[2], s[2], s[1]); + ir.Write(IROp::FMax, tmp[3], s[3], s[0]); + break; + case 8: // vsrt3, t = yxwz + ir.Write(IROp::FMax, tmp[0], s[0], s[1]); + ir.Write(IROp::FMin, tmp[1], s[0], s[1]); + ir.Write(IROp::FMax, tmp[2], s[2], s[3]); + ir.Write(IROp::FMin, tmp[3], s[2], s[3]); + break; + case 9: // vsrt4, t = wzyx + ir.Write(IROp::FMax, tmp[0], s[0], s[3]); + ir.Write(IROp::FMax, tmp[1], s[1], s[2]); + ir.Write(IROp::FMin, tmp[2], s[1], s[2]); + ir.Write(IROp::FMin, tmp[3], s[0], s[3]); + break; + default: + DISABLE; + } + for (int i = 0; i < 4; i++) { + ir.Write(IROp::FMov, dregs[i], tmp[i]); + } + ApplyPrefixD(dregs, V_Quad, _VD); + } + void IRFrontend::Comp_Vbfy(MIPSOpcode op) { CONDITIONAL_DISABLE(VFPU_VEC); if (js.HasUnknownPrefix() || !IsPrefixWithinSize(js.prefixS, op) || js.HasTPrefix() || (js.prefixS & VFPU_NEGATE(1, 1, 1, 1)) != 0) { diff --git a/Core/MIPS/IR/IRFrontend.h b/Core/MIPS/IR/IRFrontend.h index 12e98a28be..a363900c8b 100644 --- a/Core/MIPS/IR/IRFrontend.h +++ b/Core/MIPS/IR/IRFrontend.h @@ -92,6 +92,8 @@ public: void Comp_Vocp(MIPSOpcode op) override; void Comp_ColorConv(MIPSOpcode op) override; void Comp_Vbfy(MIPSOpcode op) override; + void Comp_Vsrt(MIPSOpcode op) override; + void Comp_Vf2h(MIPSOpcode op) override; int Replace_fabsf() override; // Returns true if a loaded state means blocks compiled before it can't run any more. diff --git a/Core/MIPS/IR/IRInst.cpp b/Core/MIPS/IR/IRInst.cpp index 8cd54e8985..bd0fa47c33 100644 --- a/Core/MIPS/IR/IRInst.cpp +++ b/Core/MIPS/IR/IRInst.cpp @@ -122,6 +122,7 @@ static const IRMeta irMeta[] = { { IROp::FLog2, "FLog2", "FF" }, { IROp::FHalfToFloat, "FHalfToFloat", "FFI" }, { IROp::FSinCos, "FSinCos", "2F" }, + { IROp::FFloatToHalf2, "FFloatToHalf2", "FFF" }, { IROp::FNeg, "FNeg", "FF" }, { IROp::FSign, "FSign", "FF" }, { IROp::FAbs, "FAbs", "FF" }, diff --git a/Core/MIPS/IR/IRInst.h b/Core/MIPS/IR/IRInst.h index ab8b5c38d3..47d74d63f4 100644 --- a/Core/MIPS/IR/IRInst.h +++ b/Core/MIPS/IR/IRInst.h @@ -231,6 +231,7 @@ enum class IROp : uint8_t { FLog2, // vlog2, bit-exact with the PSP FHalfToFloat, // vh2f of the lower (src2 = 0) or upper (src2 = 1) half of src1 FSinCos, // dest = sin(src1), dest + 1 = cos(src1), from one argument reduction + FFloatToHalf2, // vf2h of a pair: dest = half(src1) | half(src2) << 16, bit-exact with the PSP // Fake/System instructions Interpret, diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index b399fe4a2a..cd9d5c7871 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -272,6 +272,7 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const IRInst *chainArena) { dispatch[(int)IROp::FExp2] = &&L_FExp2; dispatch[(int)IROp::FLog2] = &&L_FLog2; dispatch[(int)IROp::FSinCos] = &&L_FSinCos; + dispatch[(int)IROp::FFloatToHalf2] = &&L_FFloatToHalf2; dispatch[(int)IROp::FHalfToFloat] = &&L_FHalfToFloat; dispatch[(int)IROp::ShlImm] = &&L_ShlImm; dispatch[(int)IROp::ShrImm] = &&L_ShrImm; @@ -992,6 +993,9 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const IRInst *chainArena) { mips->f[inst->dest + 1] = c; IR_NEXT; } + IR_CASE(FFloatToHalf2) + mips->fi[inst->dest] = vfpu_f2h(mips->fi[inst->src1]) | ((u32)vfpu_f2h(mips->fi[inst->src2]) << 16); + IR_NEXT; IR_CASE(FHalfToFloat) mips->fi[inst->dest] = vfpu_h2f((u16)(inst->src2 ? mips->fi[inst->src1] >> 16 : mips->fi[inst->src1] & 0xFFFF)); IR_NEXT; diff --git a/Core/MIPS/IR/IRNativeCommon.cpp b/Core/MIPS/IR/IRNativeCommon.cpp index b24d5a5b84..7e9199c873 100644 --- a/Core/MIPS/IR/IRNativeCommon.cpp +++ b/Core/MIPS/IR/IRNativeCommon.cpp @@ -459,6 +459,11 @@ void IRNativeBackend::CompileIRInst(IRInst inst) { CompIR_FSpecial(inst); break; + case IROp::FFloatToHalf2: + // Rare enough that the backends just call the IR interpreter's version. + CompIR_Generic(inst); + break; + case IROp::Interpret: CompIR_Interpret(inst); break; diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index bc8c5a19b7..529a6c0140 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -809,6 +809,7 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts case IROp::FLog2: case IROp::FHalfToFloat: case IROp::FSinCos: + case IROp::FFloatToHalf2: case IROp::FMin: case IROp::FMax: case IROp::FSign: diff --git a/Core/MIPS/JitCommon/JitCommon.h b/Core/MIPS/JitCommon/JitCommon.h index aba216c6e7..7bee89689c 100644 --- a/Core/MIPS/JitCommon/JitCommon.h +++ b/Core/MIPS/JitCommon/JitCommon.h @@ -109,6 +109,8 @@ namespace MIPSComp { virtual void Comp_Vocp(MIPSOpcode op) = 0; virtual void Comp_ColorConv(MIPSOpcode op) = 0; virtual void Comp_Vbfy(MIPSOpcode op) = 0; + virtual void Comp_Vsrt(MIPSOpcode op) = 0; + virtual void Comp_Vf2h(MIPSOpcode op) = 0; virtual void Comp_DoNothing(MIPSOpcode op) = 0; virtual int Replace_fabsf() = 0; diff --git a/Core/MIPS/MIPSTables.cpp b/Core/MIPS/MIPSTables.cpp index 55604cefda..bea541fe52 100644 --- a/Core/MIPS/MIPSTables.cpp +++ b/Core/MIPS/MIPSTables.cpp @@ -613,7 +613,7 @@ static const MIPSInstruction tableVFPU7[32] = // 110100 00001 xxxxx . ....... . //16 INVALID, INVALID, - INSTR("vf2h", JITFUNC(Comp_Generic), Dis_Vf2h, Int_Vf2h, IN_OTHER|OUT_OTHER|IS_VFPU|OUT_EAT_PREFIX), + INSTR("vf2h", JITFUNC(Comp_Vf2h), Dis_Vf2h, Int_Vf2h, IN_OTHER|OUT_OTHER|IS_VFPU|OUT_EAT_PREFIX), INSTR("vh2f", JITFUNC(Comp_Vh2f), Dis_Vh2f, Int_Vh2f, IN_OTHER|OUT_OTHER|IS_VFPU|OUT_EAT_PREFIX), INVALID, @@ -738,8 +738,8 @@ static const MIPSInstruction tableVFPUMatrixSet1[16] = // 111100 11100 .xxxx . . static const MIPSInstruction tableVFPU9[32] = // 110100 00010 xxxxx . ....... . ....... { - INSTR("vsrt1", JITFUNC(Comp_Generic), Dis_Vbfy, Int_Vsrt1, IN_OTHER|OUT_OTHER|IS_VFPU|OUT_EAT_PREFIX), - INSTR("vsrt2", JITFUNC(Comp_Generic), Dis_Vbfy, Int_Vsrt2, IN_OTHER|OUT_OTHER|IS_VFPU|OUT_EAT_PREFIX), + INSTR("vsrt1", JITFUNC(Comp_Vsrt), Dis_Vbfy, Int_Vsrt1, IN_OTHER|OUT_OTHER|IS_VFPU|OUT_EAT_PREFIX), + INSTR("vsrt2", JITFUNC(Comp_Vsrt), Dis_Vbfy, Int_Vsrt2, IN_OTHER|OUT_OTHER|IS_VFPU|OUT_EAT_PREFIX), INSTR("vbfy1", JITFUNC(Comp_Vbfy), Dis_Vbfy, Int_Vbfy, IN_OTHER|OUT_OTHER|IS_VFPU|OUT_EAT_PREFIX), INSTR("vbfy2", JITFUNC(Comp_Vbfy), Dis_Vbfy, Int_Vbfy, IN_OTHER|OUT_OTHER|IS_VFPU|OUT_EAT_PREFIX), //4 @@ -748,8 +748,8 @@ static const MIPSInstruction tableVFPU9[32] = // 110100 00010 xxxxx . ....... . INSTR("vfad", JITFUNC(Comp_Vhoriz), Dis_Vfad, Int_Vfad, IN_OTHER|OUT_OTHER|IS_VFPU|OUT_EAT_PREFIX), INSTR("vavg", JITFUNC(Comp_Vhoriz), Dis_Vfad, Int_Vavg, IN_OTHER|OUT_OTHER|IS_VFPU|OUT_EAT_PREFIX), //8 - INSTR("vsrt3", JITFUNC(Comp_Generic), Dis_Vbfy, Int_Vsrt3, IN_OTHER|OUT_OTHER|IS_VFPU|OUT_EAT_PREFIX), - INSTR("vsrt4", JITFUNC(Comp_Generic), Dis_Vbfy, Int_Vsrt4, IN_OTHER|OUT_OTHER|IS_VFPU|OUT_EAT_PREFIX), + INSTR("vsrt3", JITFUNC(Comp_Vsrt), Dis_Vbfy, Int_Vsrt3, IN_OTHER|OUT_OTHER|IS_VFPU|OUT_EAT_PREFIX), + INSTR("vsrt4", JITFUNC(Comp_Vsrt), Dis_Vbfy, Int_Vsrt4, IN_OTHER|OUT_OTHER|IS_VFPU|OUT_EAT_PREFIX), INSTR("vsgn", JITFUNC(Comp_Vsgn), Dis_Vbfy, Int_Vsgn, IN_OTHER|OUT_OTHER|IS_VFPU|OUT_EAT_PREFIX), INVALID, //12