From ab9476752249489061abe3ea6ad0bd8159b019f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 18 Oct 2026 12:11:46 +0200 Subject: [PATCH] VFPU: Drop the inaccurate dot product paths - Fixes #12082 The bit-exact VFPU dot product is the default everywhere and fast enough, so the plain multiply-add paths in the interpreter and the IR frontend go, along with the AccurateVdot setting, --accurate-vdot, the ForceAccurateVdot compat flag and the Vec4Dot IR op, which nothing emits any more. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/CmdLine.cpp | 5 - Core/CmdLine.h | 1 - Core/Compatibility.cpp | 1 - Core/Compatibility.h | 1 - Core/Config.cpp | 1 - Core/Config.h | 2 - Core/MIPS/ARM/ArmIRCompVec.cpp | 11 - Core/MIPS/ARM64/Arm64IRCompVec.cpp | 19 - Core/MIPS/IR/IRCompVFPU.cpp | 639 +++++-------------- Core/MIPS/IR/IRFrontend.h | 1 - Core/MIPS/IR/IRInst.cpp | 1 - Core/MIPS/IR/IRInst.h | 2 - Core/MIPS/IR/IRInterpreter.cpp | 12 - Core/MIPS/IR/IRNativeCommon.cpp | 1 - Core/MIPS/IR/IRPassSimplify.cpp | 2 - Core/MIPS/InterpreterVFPU.cpp | 184 ++---- Core/MIPS/LoongArch64/LoongArch64CompVec.cpp | 25 - Core/MIPS/RiscV/RiscVCompVec.cpp | 15 - Core/MIPS/x86/X64IRCompVec.cpp | 28 - assets/compat.ini | 24 - docs/cpu-equivalence.md | 1 - unittest/JitHarness.cpp | 8 - 22 files changed, 203 insertions(+), 781 deletions(-) diff --git a/Core/CmdLine.cpp b/Core/CmdLine.cpp index 7c2bad9e61..58e172fa0a 100644 --- a/Core/CmdLine.cpp +++ b/Core/CmdLine.cpp @@ -199,7 +199,6 @@ static const CommandLineParam g_autoParams[] = { {POFF(compare), CmdParamType::Bool, "compare", 'c', "Enable comparison mode", CmdLineMode::Headless}, {POFF(bench), CmdParamType::Bool, "bench", 'b', "Enable benchmark mode", CmdLineMode::Headless}, {POFF(oldAtrac), CmdParamType::Bool, "old-atrac", '\0', "Use old ATRAC decoder"}, - {POFF(accurateVdot), CmdParamType::Bool, "accurate-vdot", '\0', "Compute VFPU dot products bit-exactly like the PSP (default; =false for the faster approximation)"}, {POFF(jitProfile), CmdParamType::String, "jit-profile", '\0', "Profile the CPU core and write its hottest blocks to FILE on exit"}, {POFF(memStickInserted), CmdParamType::BoolInverse, "no-memstick", '\0', "Run with the memory stick removed"}, {POFF(log), CmdParamType::String, "log", '\0', "Output log to FILE", CmdLineMode::Application}, @@ -632,10 +631,6 @@ void CommandLineOptions::ApplyToConfig() const { g_Config.bUseOldAtrac = oldAtrac.value(); g_Config.DoNotSaveSetting(&g_Config.bUseOldAtrac); } - if (accurateVdot.has_value()) { - g_Config.bAccurateVdot = accurateVdot.value(); - g_Config.DoNotSaveSetting(&g_Config.bAccurateVdot); - } if (jitProfile.has_value()) { g_Config.sJitProfileFile = jitProfile.value(); } diff --git a/Core/CmdLine.h b/Core/CmdLine.h index c5b0bd0326..12142eb100 100644 --- a/Core/CmdLine.h +++ b/Core/CmdLine.h @@ -171,7 +171,6 @@ struct CommandLineOptions { bool optionS = false; // a legacy option std::optional oldAtrac; - std::optional accurateVdot; std::optional jitProfile; // Headless options that may also be mildly useful in application mode diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp index c2dd325d5f..57e9957988 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -114,7 +114,6 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) { CheckSetting(iniFile, gameID, "GoWFramerateHack60", &flags_.GoWFramerateHack60); CheckSetting(iniFile, gameID, "FramerateHack30", &flags_.FramerateHack30); CheckSetting(iniFile, gameID, "HideISOFiles", &flags_.HideISOFiles); - CheckSetting(iniFile, gameID, "ForceAccurateVdot", &flags_.ForceAccurateVdot); CheckSetting(iniFile, gameID, "ForceSoftwareRenderer", &flags_.ForceSoftwareRenderer); CheckSetting(iniFile, gameID, "DarkStalkersPresentHack", &flags_.DarkStalkersPresentHack); CheckSetting(iniFile, gameID, "ReportSmallMemstick", &flags_.ReportSmallMemstick); diff --git a/Core/Compatibility.h b/Core/Compatibility.h index 2063013b52..4c2ff6ee39 100644 --- a/Core/Compatibility.h +++ b/Core/Compatibility.h @@ -69,7 +69,6 @@ struct CompatFlags { bool GoWFramerateHack60; bool FramerateHack30; bool HideISOFiles; - bool ForceAccurateVdot; bool ForceSoftwareRenderer; bool DarkStalkersPresentHack; bool ReportSmallMemstick; diff --git a/Core/Config.cpp b/Core/Config.cpp index 0aa51a93b5..f146fe41db 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -435,7 +435,6 @@ static const ConfigSetting cpuSettings[] = { ConfigSetting("IOTimingMethod", SETTING(g_Config, iIOTimingMethod), IOTIMING_FAST, CfgFlag::PER_GAME | CfgFlag::REPORT), ConfigSetting("FastMemoryAccess", SETTING(g_Config, bFastMemory), true, CfgFlag::PER_GAME), ConfigSetting("FunctionReplacements", SETTING(g_Config, bFuncReplacements), true, CfgFlag::PER_GAME | CfgFlag::REPORT), - ConfigSetting("AccurateVdot", SETTING(g_Config, bAccurateVdot), true, CfgFlag::PER_GAME | CfgFlag::REPORT), ConfigSetting("HideSlowWarnings", SETTING(g_Config, bHideSlowWarnings), false, CfgFlag::DEFAULT), ConfigSetting("HideStateWarnings", SETTING(g_Config, bHideStateWarnings), false, CfgFlag::DEFAULT), ConfigSetting("JitDisableFlags", SETTING(g_Config, uJitDisableFlags), (uint32_t)0, CfgFlag::PER_GAME), diff --git a/Core/Config.h b/Core/Config.h index 26911f1ad6..7705d27b43 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -237,8 +237,6 @@ public: bool bWow64WarningDismissed; bool bForceLagSync; bool bFuncReplacements; - // Compute the VFPU's dot products (vdot, vmmul, vtfm and the rest) bit-exactly like the PSP. - bool bAccurateVdot; bool bHideSlowWarnings; bool bHideStateWarnings; uint32_t uJitDisableFlags; diff --git a/Core/MIPS/ARM/ArmIRCompVec.cpp b/Core/MIPS/ARM/ArmIRCompVec.cpp index 42bb52c7c8..56c2489262 100644 --- a/Core/MIPS/ARM/ArmIRCompVec.cpp +++ b/Core/MIPS/ARM/ArmIRCompVec.cpp @@ -161,17 +161,6 @@ void ArmJitBackend::CompIR_VecHoriz(IRInst inst) { CONDITIONAL_DISABLE; switch (inst.op) { - case IROp::Vec4Dot: - // In the IR interpreter's order, without fusing, into scratch since dest may be a source lane. - regs_.Map(inst); - VMUL(SCRATCHF1, regs_.F(inst.src1), regs_.F(inst.src2)); - for (int i = 1; i < 4; ++i) { - VMUL(SCRATCHF2, regs_.F(inst.src1 + i), regs_.F(inst.src2 + i)); - VADD(SCRATCHF1, SCRATCHF1, SCRATCHF2); - } - VMOV(regs_.F(inst.dest), SCRATCHF1); - break; - case IROp::Vec4VfpuDot: if (cpu_info.bNEON) { CompVfpuDot(inst); diff --git a/Core/MIPS/ARM64/Arm64IRCompVec.cpp b/Core/MIPS/ARM64/Arm64IRCompVec.cpp index d80dc66cfd..8426232d29 100644 --- a/Core/MIPS/ARM64/Arm64IRCompVec.cpp +++ b/Core/MIPS/ARM64/Arm64IRCompVec.cpp @@ -628,25 +628,6 @@ void Arm64JitBackend::CompIR_VecHoriz(IRInst inst) { CONDITIONAL_DISABLE; switch (inst.op) { - case IROp::Vec4Dot: - if (Overlap(inst.dest, 1, inst.src1, 4) || Overlap(inst.dest, 1, inst.src2, 4)) { - // To avoid overlap problems, map a little carefully. - regs_.SpillLockFPR(inst.src1, inst.src2); - regs_.MapVec4(inst.src1); - regs_.MapVec4(inst.src2); - regs_.MapVec4(inst.dest & ~3, MIPSMap::DIRTY); - fp_.FMUL(32, EncodeRegToQuad(SCRATCHF1), regs_.FQ(inst.src1), regs_.FQ(inst.src2)); - fp_.FADDP(32, EncodeRegToQuad(SCRATCHF1), EncodeRegToQuad(SCRATCHF1), EncodeRegToQuad(SCRATCHF1)); - fp_.FADDP(32, EncodeRegToQuad(SCRATCHF1), EncodeRegToQuad(SCRATCHF1), EncodeRegToQuad(SCRATCHF1)); - fp_.INS(32, regs_.FQ(inst.dest & ~3), inst.dest & 3, EncodeRegToQuad(SCRATCHF1), 0); - } else { - regs_.Map(inst); - fp_.FMUL(32, regs_.FQ(inst.dest), regs_.FQ(inst.src1), regs_.FQ(inst.src2)); - fp_.FADDP(32, regs_.FQ(inst.dest), regs_.FQ(inst.dest), regs_.FQ(inst.dest)); - fp_.FADDP(32, regs_.FQ(inst.dest), regs_.FQ(inst.dest), regs_.FQ(inst.dest)); - } - break; - case IROp::Vec4VfpuDot: { // The operands go to the shared routine in q0 and q1, and the result comes back in w1. The diff --git a/Core/MIPS/IR/IRCompVFPU.cpp b/Core/MIPS/IR/IRCompVFPU.cpp index a34cc8a3f1..caaade1540 100644 --- a/Core/MIPS/IR/IRCompVFPU.cpp +++ b/Core/MIPS/IR/IRCompVFPU.cpp @@ -20,7 +20,6 @@ #include "Common/CPUDetect.h" #include "Common/Data/Convert/SmallDataConvert.h" #include "Common/Math/math_util.h" -#include "Core/Compatibility.h" #include "Core/Config.h" #include "Core/MemMap.h" #include "Core/MIPS/MIPS.h" @@ -150,10 +149,6 @@ namespace MIPSComp { return true; } - bool IRFrontend::UseAccurateVdot() const { - return g_Config.bAccurateVdot || PSP_CoreParameter().compat.flags().ForceAccurateVdot; - } - // The lanes are already a Vec4 reg, used as is. static bool DotLanesAreVec4(const IRFrontend::DotLane lanes[4]) { for (int i = 0; i < 4; i++) { @@ -956,38 +951,19 @@ namespace MIPSComp { GetVectorRegsPrefixT(tregs, sz, vt); GetVectorRegsPrefixD(dregs, V_Single, vd); - if (UseAccurateVdot()) { - // s's last lane is 1, so t's is added in. - DotLane sl[4], tl[4]; - for (int i = 0; i < 4; i++) { - sl[i] = i < n - 1 ? DotReg(sregs[i]) : DotConst(i == n - 1 ? 1.0f : 0.0f); - tl[i] = i < n ? DotReg(tregs[i]) : DotConst(0.0f); - } - u8 sv, tv; - if (!BuildDotOperands(sl, tl, -1, &sv, &tv)) - DISABLE; - ir.Write(IROp::Vec4VfpuDot, dregs[0], sv, tv); - ApplyPrefixD(dregs, V_Single, vd); - return; + // s's last lane is 1, so t's is added in. + DotLane sl[4], tl[4]; + for (int i = 0; i < 4; i++) { + sl[i] = i < n - 1 ? DotReg(sregs[i]) : DotConst(i == n - 1 ? 1.0f : 0.0f); + tl[i] = i < n ? DotReg(tregs[i]) : DotConst(0.0f); } - - ir.Write(IROp::FMul, IRVTEMP_0, sregs[0], tregs[0]); - - for (int i = 1; i < n; i++) { - if (i == n - 1) { - ir.Write(IROp::FAdd, IRVTEMP_0, IRVTEMP_0, tregs[i]); - } else { - ir.Write(IROp::FMul, IRVTEMP_0 + 1, sregs[i], tregs[i]); - ir.Write(IROp::FAdd, IRVTEMP_0, IRVTEMP_0, IRVTEMP_0 + 1); - } - } - - ir.Write(IROp::FMov, dregs[0], IRVTEMP_0); + u8 sv, tv; + if (!BuildDotOperands(sl, tl, -1, &sv, &tv)) + DISABLE; + ir.Write(IROp::Vec4VfpuDot, dregs[0], sv, tv); ApplyPrefixD(dregs, V_Single, vd); } - alignas(16) static const float vavg_table[4] = { 1.0f, 1.0f / 2.0f, 1.0f / 3.0f, 1.0f / 4.0f }; - void IRFrontend::Comp_Vhoriz(MIPSOpcode op) { CONDITIONAL_DISABLE(VFPU_VEC); if (js.HasUnknownPrefix() || !IsPrefixWithinSize(js.prefixS, op) || js.HasTPrefix()) { @@ -1003,56 +979,26 @@ namespace MIPSComp { VectorSize sz = GetVecSize(op); int n = GetNumVectorElements(sz); - // On the PSP, an S constant past n is added in too (cpu/vfpu/prefix_lanes), which - // IsPrefixWithinSize() lets through. The accurate dot product includes it, the rest leave it - // to the interpreter, as it's rare. - if (!UseAccurateVdot()) { - for (int i = n; i < 4; i++) { - if (VFPUPrefixIsConstant(js.prefixS, i)) - DISABLE; - } - } - u8 sregs[4], dregs[1]; GetVectorRegsPrefixS(sregs, sz, _VS); GetVectorRegsPrefixD(dregs, V_Single, _VD); - if (UseAccurateVdot()) { - // Dot products with constants from the T prefix in every lane: ones for vfad, 1/n for - // vavg (0 for vavg.s), as in the interpreter. - const bool vavg = ((op >> 16) & 31) == 7; - static const float vavgConst[4] = { 0.0f, 0.5f, 1.0f / 3.0f, 0.25f }; - const float c = vavg ? vavgConst[n - 1] : 1.0f; - DotLane sl[4], tl[4]; - for (int i = 0; i < 4; i++) { - const bool constantLane = i >= n && VFPUPrefixIsConstant(js.prefixS, i); - sl[i] = i < n ? DotReg(sregs[i]) : DotConst(constantLane ? VFPUPrefixConstant(js.prefixS, i) : 0.0f); - tl[i] = DotConst(c); - } - u8 sv, tv; - if (!BuildDotOperands(sl, tl, -1, &sv, &tv)) - DISABLE; - ir.Write(IROp::Vec4VfpuDot, dregs[0], sv, tv); - ApplyPrefixD(dregs, V_Single, _VD); - return; + // Dot products with constants from the T prefix in every lane: ones for vfad, 1/n for + // vavg (0 for vavg.s), as in the interpreter. An S constant past n is added in too + // (cpu/vfpu/prefix_lanes), which IsPrefixWithinSize() lets through. + const bool vavg = ((op >> 16) & 31) == 7; + static const float vavgConst[4] = { 0.0f, 0.5f, 1.0f / 3.0f, 0.25f }; + const float c = vavg ? vavgConst[n - 1] : 1.0f; + DotLane sl[4], tl[4]; + for (int i = 0; i < 4; i++) { + const bool constantLane = i >= n && VFPUPrefixIsConstant(js.prefixS, i); + sl[i] = i < n ? DotReg(sregs[i]) : DotConst(constantLane ? VFPUPrefixConstant(js.prefixS, i) : 0.0f); + tl[i] = DotConst(c); } - - // We have to start at +0.000 in case any values are -0.000. - ir.WriteSetConstantFloat(IRVTEMP_0, 0.0f); - for (int i = 0; i < n; ++i) { - ir.Write(IROp::FAdd, IRVTEMP_0, IRVTEMP_0, sregs[i]); - } - - switch ((op >> 16) & 31) { - case 6: // vfad - ir.Write(IROp::FMov, dregs[0], IRVTEMP_0); - break; - case 7: // vavg - ir.WriteSetConstantFloat(IRVTEMP_0 + 1, vavg_table[n - 1]); - ir.Write(IROp::FMul, dregs[0], IRVTEMP_0, IRVTEMP_0 + 1); - break; - } - + u8 sv, tv; + if (!BuildDotOperands(sl, tl, -1, &sv, &tv)) + DISABLE; + ir.Write(IROp::Vec4VfpuDot, dregs[0], sv, tv); ApplyPrefixD(dregs, V_Single, _VD); } @@ -1073,17 +1019,10 @@ namespace MIPSComp { int n = GetNumVectorElements(sz); // 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. The accurate - // dot product includes them, the rest leave them to the interpreter, as they're rare. + // read zero (cpu/vfpu/prefix_lanes). IsPrefixWithinSize() lets those through. auto constantLane = [&](int i) { return i >= n && VFPUPrefixIsConstant(js.prefixS, i) && VFPUPrefixIsConstant(js.prefixT, i); }; - if (!UseAccurateVdot()) { - for (int i = n; i < 4; i++) { - if (constantLane(i)) - DISABLE; - } - } // TODO: Force read one of them into regs? probably not. u8 sregs[4], tregs[4], dregs[1]; @@ -1091,78 +1030,43 @@ namespace MIPSComp { GetVectorRegsPrefixT(tregs, sz, vt); GetVectorRegsPrefixD(dregs, V_Single, vd); - if (UseAccurateVdot()) { - // A run of vdots against one vector (a matrix times a vector, written out) goes as one - // Vec4VfpuMultiDot, eating the rest of the run. - if (opts.vfpuMultiDot && js.HasNoPrefix() && !js.inDelaySlot && n >= 2) { - MultiDot dots[4]; - int count = 0; - for (; count < 4; count++) { - const MIPSOpcode next = count == 0 ? op : GetOffsetInstruction(count); - if ((next.encoding & 0xFF808080) != (op.encoding & 0xFF808080)) - break; - u8 s[4], t[4], d[1]; - GetVectorRegs(s, sz, (next.encoding >> 8) & 0x7F); - GetVectorRegs(t, sz, (next.encoding >> 16) & 0x7F); - GetVectorRegs(d, V_Single, next.encoding & 0x7F); - for (int i = 0; i < 4; i++) { - dots[count].a[i] = i < n ? DotReg(s[i]) : DotConst(0.0f); - dots[count].b[i] = i < n ? DotReg(t[i]) : DotConst(0.0f); - } - dots[count].dest = d[0]; + // A run of vdots against one vector (a matrix times a vector, written out) goes as one + // Vec4VfpuMultiDot, eating the rest of the run. + if (opts.vfpuMultiDot && js.HasNoPrefix() && !js.inDelaySlot && n >= 2) { + MultiDot dots[4]; + int count = 0; + for (; count < 4; count++) { + const MIPSOpcode next = count == 0 ? op : GetOffsetInstruction(count); + if ((next.encoding & 0xFF808080) != (op.encoding & 0xFF808080)) + break; + u8 s[4], t[4], d[1]; + GetVectorRegs(s, sz, (next.encoding >> 8) & 0x7F); + GetVectorRegs(t, sz, (next.encoding >> 16) & 0x7F); + GetVectorRegs(d, V_Single, next.encoding & 0x7F); + for (int i = 0; i < 4; i++) { + dots[count].a[i] = i < n ? DotReg(s[i]) : DotConst(0.0f); + dots[count].b[i] = i < n ? DotReg(t[i]) : DotConst(0.0f); } - for (int c = count; c >= 2; c--) { - if (TryMultiDot(dots, c, n, -1, false)) { - for (int k = 1; k < c; k++) - EatInstruction(GetOffsetInstruction(1)); - return; - } + dots[count].dest = d[0]; + } + for (int c = count; c >= 2; c--) { + if (TryMultiDot(dots, c, n, -1, false)) { + for (int k = 1; k < c; k++) + EatInstruction(GetOffsetInstruction(1)); + return; } } - - DotLane sl[4], tl[4]; - for (int i = 0; i < 4; i++) { - sl[i] = i < n ? DotReg(sregs[i]) : DotConst(constantLane(i) ? VFPUPrefixConstant(js.prefixS, i) : 0.0f); - tl[i] = i < n ? DotReg(tregs[i]) : DotConst(constantLane(i) ? VFPUPrefixConstant(js.prefixT, i) : 0.0f); - } - u8 sv, tv; - if (!BuildDotOperands(sl, tl, -1, &sv, &tv)) - DISABLE; - ir.Write(IROp::Vec4VfpuDot, dregs[0], sv, tv); - ApplyPrefixD(dregs, V_Single, vd); - return; } - if (IsVec4(sz, sregs) && IsVec4(sz, tregs)) { - if (IsOverlapSafe(dregs[0], n, sregs, n, tregs)) { - ir.Write(IROp::Vec4Dot, dregs[0], sregs[0], tregs[0]); - } else { - ir.Write(IROp::Vec4Dot, IRVTEMP_0, sregs[0], tregs[0]); - ir.Write(IROp::FMov, dregs[0], IRVTEMP_0); - } - ApplyPrefixD(dregs, V_Single, vd); - return; - } else if (IsVec3of4(sz, sregs) && IsVec3of4(sz, tregs) && opts.preferVec4Dot) { - // Note: this is often worse than separate muliplies and adds on x86. - if (IsOverlapSafe(dregs[0], n, tregs) || sregs[0] == tregs[0]) { - // Nice example of this in Fat Princess (US) in block 088181A0 (hot.) - // Create a temporary copy of S with the last element zeroed. - ir.Write(IROp::Vec4Init, IRVTEMP_0, (int)Vec4Init::AllZERO); - ir.Write(IROp::Vec4Blend, IRVTEMP_0, IRVTEMP_0, sregs[0], 0x7); - // Now we can just dot like normal, with the last element effectively masked. - ir.Write(IROp::Vec4Dot, dregs[0], IRVTEMP_0, sregs[0] == tregs[0] ? IRVTEMP_0 : tregs[0]); - ApplyPrefixD(dregs, V_Single, vd); - return; - } - } - - int temp0 = IRVTEMP_0; - int temp1 = IRVTEMP_0 + 1; - ir.Write(IROp::FMul, temp0, sregs[0], tregs[0]); - for (int i = 1; i < n; i++) { - ir.Write(IROp::FMul, temp1, sregs[i], tregs[i]); - ir.Write(IROp::FAdd, i == (n - 1) ? dregs[0] : temp0, temp0, temp1); + DotLane sl[4], tl[4]; + for (int i = 0; i < 4; i++) { + sl[i] = i < n ? DotReg(sregs[i]) : DotConst(constantLane(i) ? VFPUPrefixConstant(js.prefixS, i) : 0.0f); + tl[i] = i < n ? DotReg(tregs[i]) : DotConst(constantLane(i) ? VFPUPrefixConstant(js.prefixT, i) : 0.0f); } + u8 sv, tv; + if (!BuildDotOperands(sl, tl, -1, &sv, &tv)) + DISABLE; + ir.Write(IROp::Vec4VfpuDot, dregs[0], sv, tv); ApplyPrefixD(dregs, V_Single, vd); } @@ -1965,23 +1869,6 @@ namespace MIPSComp { ApplyPrefixD(dregs, sz, vd); } - /* - // Capital = straight, lower case = transposed - // 8 possibilities: - ABC 2 - ABc missing - AbC 1 - Abc 1 - - aBC = ACB 2 + swap - aBc = AcB 1 + swap - abC = ACb missing - abc = Acb 1 + swap - - */ - - // This may or may not be a win when using the IR interpreter... - // Many more instructions to interpret. void IRFrontend::Comp_Vmmul(MIPSOpcode op) { CONDITIONAL_DISABLE(VFPU_MTX_VMMUL); if (!js.HasNoPrefix()) { @@ -2002,110 +1889,39 @@ namespace MIPSComp { MatrixOverlapType soverlap = GetMatrixOverlap(vs, vd, sz); MatrixOverlapType toverlap = GetMatrixOverlap(vt, vd, sz); - if (UseAccurateVdot()) { - // d[a][b] = s[b] dot t[a], each a row of n lanes and zeros, as in the interpreter. With - // no overlap, the results can go straight to d. - if (soverlap || toverlap) - DISABLE; - u8 sregs[16], tregs[16], dregs[16]; - GetMatrixRegs(sregs, sz, vs); - GetMatrixRegs(tregs, sz, vt); - GetMatrixRegs(dregs, sz, vd); - for (int a = 0; a < n; a++) { - DotLane tl[4]; - for (int c = 0; c < 4; c++) - tl[c] = c < n ? DotReg(tregs[a * 4 + c]) : DotConst(0.0f); - MultiDot dots[4]; - for (int b = 0; b < n; b++) { - for (int c = 0; c < 4; c++) { - dots[b].a[c] = c < n ? DotReg(sregs[b * 4 + c]) : DotConst(0.0f); - dots[b].b[c] = tl[c]; - } - dots[b].dest = dregs[a * 4 + b]; - } - if (TryMultiDot(dots, n, n, -1, false)) - continue; - for (int b = 0; b < n; b++) { - DotLane sl[4]; - for (int c = 0; c < 4; c++) - sl[c] = c < n ? DotReg(sregs[b * 4 + c]) : DotConst(0.0f); - u8 sv, tv; - if (!BuildDotOperands(sl, tl, -1, &sv, &tv)) - DISABLE; - // Built once per row of t. - for (int c = 0; c < 4; c++) - tl[c] = DotReg(tv + c); - ir.Write(IROp::Vec4VfpuDot, dregs[a * 4 + b], sv, tv); - } - } - return; - } - - // A very common arrangment. Rearrange to something we can handle. - // A 1x1 matrix has no transposed form, the bit then picks a different register. - if (n > 1 && IsMatrixTransposed(vd)) { - // Matrix identity says (At * Bt) = (B * A)t - // D = S * T - // Dt = (S * T)t = (Tt * St) - vd = TransposeMatrixReg(vd); - std::swap(vs, vt); - } - + // d[a][b] = s[b] dot t[a], each a row of n lanes and zeros, as in the interpreter. With + // no overlap, the results can go straight to d. + if (soverlap || toverlap) + DISABLE; u8 sregs[16], tregs[16], dregs[16]; GetMatrixRegs(sregs, sz, vs); GetMatrixRegs(tregs, sz, vt); GetMatrixRegs(dregs, sz, vd); - - if (soverlap || toverlap) { - DISABLE; - } - - // dregs are always consecutive, thanks to our transpose trick. - // However, not sure this is always worth it. - if (IsMatrixVec4(sz, dregs)) { - // TODO: The interpreter would like proper matrix ops better. Can generate those, and - // expand them like this as needed on "real" architectures. - int s0 = IRVTEMP_0; - int s1 = IRVTEMP_PFX_T; - if (IsMatrixTransposedVec4(sz, sregs)) { - // METHOD 1: Handles AbC and Abc - for (int j = 0; j < 4; j++) { - ir.Write(IROp::Vec4Scale, s0, sregs[0], tregs[j * 4]); - for (int i = 1; i < 4; i++) { - ir.Write(IROp::Vec4Scale, s1, sregs[i], tregs[j * 4 + i]); - ir.Write(IROp::Vec4Add, s0, s0, s1); - } - ir.Write(IROp::Vec4Mov, dregs[j * 4], s0); - } - return; - } else if (IsMatrixVec4(sz, sregs) && IsMatrixVec4(sz, tregs)) { - // METHOD 2: Handles ABC only. Not efficient on CPUs that don't do fast dots. - // Dots only work if tregs are consecutive. - // TODO: Skip this and resort to method one and transpose the output? - for (int j = 0; j < 4; j++) { - for (int i = 0; i < 4; i++) { - ir.Write(IROp::Vec4Dot, s0 + i, sregs[i * 4], tregs[j * 4]); - } - ir.Write(IROp::Vec4Mov, dregs[j * 4], s0); - } - return; - } else { - // ABc - s consecutive, t not. - // Tekken uses this. - // logBlocks = 1; - } - } - - // Fallback. Expands a LOT - int temp0 = IRVTEMP_0; - int temp1 = IRVTEMP_0 + 1; for (int a = 0; a < n; a++) { + DotLane tl[4]; + for (int c = 0; c < 4; c++) + tl[c] = c < n ? DotReg(tregs[a * 4 + c]) : DotConst(0.0f); + MultiDot dots[4]; for (int b = 0; b < n; b++) { - ir.Write(IROp::FMul, n == 1 ? dregs[0] : temp0, sregs[b * 4], tregs[a * 4]); - for (int c = 1; c < n; c++) { - ir.Write(IROp::FMul, temp1, sregs[b * 4 + c], tregs[a * 4 + c]); - ir.Write(IROp::FAdd, (c == n - 1) ? dregs[a * 4 + b] : temp0, temp0, temp1); + for (int c = 0; c < 4; c++) { + dots[b].a[c] = c < n ? DotReg(sregs[b * 4 + c]) : DotConst(0.0f); + dots[b].b[c] = tl[c]; } + dots[b].dest = dregs[a * 4 + b]; + } + if (TryMultiDot(dots, n, n, -1, false)) + continue; + for (int b = 0; b < n; b++) { + DotLane sl[4]; + for (int c = 0; c < 4; c++) + sl[c] = c < n ? DotReg(sregs[b * 4 + c]) : DotConst(0.0f); + u8 sv, tv; + if (!BuildDotOperands(sl, tl, -1, &sv, &tv)) + DISABLE; + // Built once per row of t. + for (int c = 0; c < 4; c++) + tl[c] = DotReg(tv + c); + ir.Write(IROp::Vec4VfpuDot, dregs[a * 4 + b], sv, tv); } } } @@ -2143,100 +1959,35 @@ namespace MIPSComp { GetVectorRegs(tregs, sz, _VT); GetVectorRegs(dregs, sz, _VD); - if (UseAccurateVdot()) { - // A dot per row, against t with the lanes past it zero, and lane ins 1 for the - // homogenous form. The results go to the D temp first, since d may overlap s or t. - const int tn = homogenous ? n - 1 : n; - DotLane tl[4]; - for (int i = 0; i < 4; i++) - tl[i] = i < tn ? DotReg(tregs[i]) : DotConst(homogenous && i == ins ? 1.0f : 0.0f); - MultiDot dots[4]; - for (int i = 0; i < n; i++) { - for (int k = 0; k < 4; k++) { - dots[i].a[k] = k < n ? DotReg(sregs[i * 4 + k]) : DotConst(0.0f); - dots[i].b[k] = tl[k]; - } - dots[i].dest = IRVTEMP_PFX_D + i; - } - const bool multi = TryMultiDot(dots, n, n, IRVTEMP_PFX_D, true); - for (int i = 0; i < n && !multi; i++) { - DotLane sl[4]; - for (int k = 0; k < 4; k++) - sl[k] = k < n ? DotReg(sregs[i * 4 + k]) : DotConst(0.0f); - u8 sv, tv; - if (!BuildDotOperands(sl, tl, IRVTEMP_PFX_D, &sv, &tv)) - DISABLE; - // Built once, t is a Vec4 for the rest. - for (int k = 0; k < 4; k++) - tl[k] = DotReg(tv + k); - ir.Write(IROp::Vec4VfpuDot, IRVTEMP_PFX_D + i, sv, tv); - } - for (int i = 0; i < n; i++) - ir.Write(IROp::FMov, dregs[i], IRVTEMP_PFX_D + i); - return; - } - - // SIMD-optimized implementations - if sregs[0..3] is non-consecutive, it's transposed. - if (msz == M_4x4 && IsMatrixTransposedVec4(msz, sregs)) { - int s0 = IRVTEMP_0; - int s1 = IRVTEMP_PFX_S; - // For this algorithm, we don't care if tregs are consecutive or not, - // they are accessed one at a time. This handles homogenous transforms correctly, as well. - // We take advantage of sregs[0] + 1 being sregs[4] here. - ir.Write(IROp::Vec4Scale, s0, sregs[0], tregs[0]); - for (int i = 1; i < 4; i++) { - if (!homogenous || (i != n - 1)) { - ir.Write(IROp::Vec4Scale, s1, sregs[i], tregs[i]); - ir.Write(IROp::Vec4Add, s0, s0, s1); - } else { - ir.Write(IROp::Vec4Add, s0, s0, sregs[i]); - } - } - if (IsVec4(sz, dregs)) { - ir.Write(IROp::Vec4Mov, dregs[0], s0); - } else { - for (int i = 0; i < 4; i++) { - ir.Write(IROp::FMov, dregs[i], s0 + i); - } - } - return; - } else if (msz == M_4x4 && IsMatrixVec4(msz, sregs) && IsVec4(sz, tregs)) { - IRReg t = tregs[0]; - if (homogenous) { - // This is probably even what the hardware basically does, wiring t[3] to 1.0f. - ir.Write(IROp::Vec4Init, IRVTEMP_PFX_T, (int)Vec4Init::AllONE); - ir.Write(IROp::Vec4Blend, IRVTEMP_PFX_T, IRVTEMP_PFX_T, t, 0x7); - t = IRVTEMP_PFX_T; - } - for (int i = 0; i < 4; i++) - ir.Write(IROp::Vec4Dot, IRVTEMP_PFX_D + i, sregs[i * 4], t); - for (int i = 0; i < 4; i++) - ir.Write(IROp::FMov, dregs[i], IRVTEMP_PFX_D + i); - return; - } - - // TODO: test overlap, optimize. - u8 tempregs[4]; - int s0 = IRVTEMP_0; - int temp1 = IRVTEMP_0 + 1; + // A dot per row, against t with the lanes past it zero, and lane ins 1 for the + // homogenous form. The results go to the D temp first, since d may overlap s or t. + const int tn = homogenous ? n - 1 : n; + DotLane tl[4]; + for (int i = 0; i < 4; i++) + tl[i] = i < tn ? DotReg(tregs[i]) : DotConst(homogenous && i == ins ? 1.0f : 0.0f); + MultiDot dots[4]; for (int i = 0; i < n; i++) { - ir.Write(IROp::FMul, s0, sregs[i * 4], tregs[0]); - for (int k = 1; k < n; k++) { - if (!homogenous || k != n - 1) { - ir.Write(IROp::FMul, temp1, sregs[i * 4 + k], tregs[k]); - ir.Write(IROp::FAdd, s0, s0, temp1); - } else { - ir.Write(IROp::FAdd, s0, s0, sregs[i * 4 + k]); - } + for (int k = 0; k < 4; k++) { + dots[i].a[k] = k < n ? DotReg(sregs[i * 4 + k]) : DotConst(0.0f); + dots[i].b[k] = tl[k]; } - int temp = IRVTEMP_PFX_T + i; - ir.Write(IROp::FMov, temp, s0); - tempregs[i] = temp; + dots[i].dest = IRVTEMP_PFX_D + i; } - for (int i = 0; i < n; i++) { - if (tempregs[i] != dregs[i]) - ir.Write(IROp::FMov, dregs[i], tempregs[i]); + const bool multi = TryMultiDot(dots, n, n, IRVTEMP_PFX_D, true); + for (int i = 0; i < n && !multi; i++) { + DotLane sl[4]; + for (int k = 0; k < 4; k++) + sl[k] = k < n ? DotReg(sregs[i * 4 + k]) : DotConst(0.0f); + u8 sv, tv; + if (!BuildDotOperands(sl, tl, IRVTEMP_PFX_D, &sv, &tv)) + DISABLE; + // Built once, t is a Vec4 for the rest. + for (int k = 0; k < 4; k++) + tl[k] = DotReg(tv + k); + ir.Write(IROp::Vec4VfpuDot, IRVTEMP_PFX_D + i, sv, tv); } + for (int i = 0; i < n; i++) + ir.Write(IROp::FMov, dregs[i], IRVTEMP_PFX_D + i); } void IRFrontend::Comp_VCrs(MIPSOpcode op) { @@ -2310,22 +2061,13 @@ namespace MIPSComp { GetVectorRegsPrefixT(tregs, sz, _VT); GetVectorRegsPrefixD(dregs, V_Single, _VD); - if (UseAccurateVdot()) { - // The T prefix's swizzle is forced to yx: s0 * t1 + (-s1) * t0. - const DotLane sl[4] = { DotReg(sregs[0]), DotReg(sregs[1], true), DotConst(0.0f), DotConst(0.0f) }; - const DotLane tl[4] = { DotReg(tregs[1]), DotReg(tregs[0]), DotConst(0.0f), DotConst(0.0f) }; - u8 sv, tv; - if (!BuildDotOperands(sl, tl, -1, &sv, &tv)) - DISABLE; - ir.Write(IROp::Vec4VfpuDot, dregs[0], sv, tv); - ApplyPrefixD(dregs, V_Single, _VD); - return; - } - - ir.Write(IROp::FMul, IRVTEMP_0, sregs[1], tregs[0]); - ir.Write(IROp::FMul, dregs[0], sregs[0], tregs[1]); - ir.Write(IROp::FSub, dregs[0], dregs[0], IRVTEMP_0); - + // The T prefix's swizzle is forced to yx: s0 * t1 + (-s1) * t0. + const DotLane sl[4] = { DotReg(sregs[0]), DotReg(sregs[1], true), DotConst(0.0f), DotConst(0.0f) }; + const DotLane tl[4] = { DotReg(tregs[1]), DotReg(tregs[0]), DotConst(0.0f), DotConst(0.0f) }; + u8 sv, tv; + if (!BuildDotOperands(sl, tl, -1, &sv, &tv)) + DISABLE; + ir.Write(IROp::Vec4VfpuDot, dregs[0], sv, tv); ApplyPrefixD(dregs, V_Single, _VD); } @@ -2545,120 +2287,49 @@ namespace MIPSComp { GetVectorRegs(tregs, sz, _VT); GetVectorRegs(dregs, sz, _VD); - if (UseAccurateVdot() && (sz == V_Triple || sz == V_Quad)) { - // The dots the interpreter does, against permuted and negated copies of t. The results - // go to the D temp first, since d may overlap s or t. - const DotLane zero = DotConst(0.0f); - auto tl = [&](int i, bool neg = false) { return i < n ? DotReg(tregs[i], neg) : zero; }; - DotLane tDots[4][4]; - int dots; - if (sz == V_Triple) { - const DotLane t0[4] = { zero, tl(2), tl(1, true), zero }; - const DotLane t1[4] = { tl(2, true), zero, tl(0), zero }; - const DotLane t2[4] = { tl(1), tl(0, true), zero, tl(2) }; - memcpy(tDots[0], t0, sizeof(t0)); - memcpy(tDots[1], t1, sizeof(t1)); - memcpy(tDots[2], t2, sizeof(t2)); - dots = 3; - } else { - const DotLane t0[4] = { tl(3), tl(2), tl(1, true), tl(0) }; - const DotLane t1[4] = { tl(2, true), tl(3), tl(0), tl(1) }; - const DotLane t2[4] = { tl(1), tl(0, true), tl(3), tl(2) }; - const DotLane t3[4] = { tl(0, true), tl(1, true), tl(2, true), tl(3) }; - memcpy(tDots[0], t0, sizeof(t0)); - memcpy(tDots[1], t1, sizeof(t1)); - memcpy(tDots[2], t2, sizeof(t2)); - memcpy(tDots[3], t3, sizeof(t3)); - dots = 4; - } - DotLane sl[4]; - for (int i = 0; i < 4; i++) - sl[i] = i < n ? DotReg(sregs[i]) : zero; - for (int i = 0; i < dots; i++) { - u8 sv, tv; - if (!BuildDotOperands(sl, tDots[i], IRVTEMP_PFX_D, &sv, &tv)) - DISABLE; - // Built once, s is a Vec4 for the rest. - for (int j = 0; j < 4; j++) - sl[j] = DotReg(sv + j); - ir.Write(IROp::Vec4VfpuDot, IRVTEMP_PFX_D + i, sv, tv); - } - for (int i = 0; i < dots; i++) - ir.Write(IROp::FMov, dregs[i], IRVTEMP_PFX_D + i); - return; - } - - if (sz == V_Triple) { - u8 tempregs[4]{}; - for (int i = 0; i < n; ++i) { - if (!IsOverlapSafe(dregs[i], n, sregs, n, tregs)) { - tempregs[i] = IRVTEMP_PFX_T + i; // using IRTEMP0 for other things - } else { - tempregs[i] = dregs[i]; - } - } - - int temp0 = IRVTEMP_0; - int temp1 = IRVTEMP_0 + 1; - // Compute X - ir.Write(IROp::FMul, temp0, sregs[1], tregs[2]); - ir.Write(IROp::FMul, temp1, sregs[2], tregs[1]); - ir.Write(IROp::FSub, tempregs[0], temp0, temp1); - - // Compute Y - ir.Write(IROp::FMul, temp0, sregs[2], tregs[0]); - ir.Write(IROp::FMul, temp1, sregs[0], tregs[2]); - ir.Write(IROp::FSub, tempregs[1], temp0, temp1); - - // Compute Z - ir.Write(IROp::FMul, temp0, sregs[0], tregs[1]); - ir.Write(IROp::FMul, temp1, sregs[1], tregs[0]); - ir.Write(IROp::FSub, tempregs[2], temp0, temp1); - - for (int i = 0; i < n; i++) { - if (tempregs[i] != dregs[i]) - ir.Write(IROp::FMov, dregs[i], tempregs[i]); - } - } else if (sz == V_Quad) { - // Rather than using vdots, we organize this as SIMD multiplies and adds. - // That means flipping the logic column-wise. Also, luckily no prefix temps used. - if (!IsConsecutive4(sregs) || !IsConsecutive4(tregs) || !IsConsecutive4(dregs)) { - DISABLE; - } - - auto shuffleImm = [](int x, int y, int z, int w) { return x | (y << 2) | (z << 4) | (w << 6); }; - auto blendConst = [](int x, int y, int z, int w) { return x | (y << 1) | (z << 2) | (w << 3); }; - - // Prepare some negatives. - ir.Write(IROp::Vec4Neg, IRVTEMP_0, tregs[0]); - - // tmp = S[x,x,x,x] * T[w,-z,y,-x] - ir.Write(IROp::Vec4Blend, IRVTEMP_PFX_S, tregs[0], IRVTEMP_0, blendConst(1, 0, 1, 0)); - ir.Write(IROp::Vec4Shuffle, IRVTEMP_PFX_T, IRVTEMP_PFX_S, shuffleImm(3, 2, 1, 0)); - ir.Write(IROp::Vec4Shuffle, IRVTEMP_PFX_S, sregs[0], shuffleImm(0, 0, 0, 0)); - ir.Write(IROp::Vec4Mul, IRVTEMP_PFX_D, IRVTEMP_PFX_S, IRVTEMP_PFX_T); - - // tmp += S[y,y,y,y] * T[z,w,-x,-y] - ir.Write(IROp::Vec4Blend, IRVTEMP_PFX_S, tregs[0], IRVTEMP_0, blendConst(1, 1, 0, 0)); - ir.Write(IROp::Vec4Shuffle, IRVTEMP_PFX_T, IRVTEMP_PFX_S, shuffleImm(2, 3, 0, 1)); - ir.Write(IROp::Vec4Shuffle, IRVTEMP_PFX_S, sregs[0], shuffleImm(1, 1, 1, 1)); - ir.Write(IROp::Vec4Mul, IRVTEMP_PFX_S, IRVTEMP_PFX_S, IRVTEMP_PFX_T); - ir.Write(IROp::Vec4Add, IRVTEMP_PFX_D, IRVTEMP_PFX_D, IRVTEMP_PFX_S); - - // tmp += S[z,z,z,z] * T[-y,x,w,-z] - ir.Write(IROp::Vec4Blend, IRVTEMP_PFX_S, tregs[0], IRVTEMP_0, blendConst(0, 1, 1, 0)); - ir.Write(IROp::Vec4Shuffle, IRVTEMP_PFX_T, IRVTEMP_PFX_S, shuffleImm(1, 0, 3, 2)); - ir.Write(IROp::Vec4Shuffle, IRVTEMP_PFX_S, sregs[0], shuffleImm(2, 2, 2, 2)); - ir.Write(IROp::Vec4Mul, IRVTEMP_PFX_S, IRVTEMP_PFX_S, IRVTEMP_PFX_T); - ir.Write(IROp::Vec4Add, IRVTEMP_PFX_D, IRVTEMP_PFX_D, IRVTEMP_PFX_S); - - // tmp += S[w,w,w,w] * T[x,y,z,w] - ir.Write(IROp::Vec4Shuffle, IRVTEMP_PFX_S, sregs[0], shuffleImm(3, 3, 3, 3)); - ir.Write(IROp::Vec4Mul, IRVTEMP_PFX_S, IRVTEMP_PFX_S, tregs[0]); - ir.Write(IROp::Vec4Add, dregs[0], IRVTEMP_PFX_D, IRVTEMP_PFX_S); - } else { + if (sz != V_Triple && sz != V_Quad) { INVALIDOP; } + + // The dots the interpreter does, against permuted and negated copies of t. The results + // go to the D temp first, since d may overlap s or t. + const DotLane zero = DotConst(0.0f); + auto tl = [&](int i, bool neg = false) { return i < n ? DotReg(tregs[i], neg) : zero; }; + DotLane tDots[4][4]; + int dots; + if (sz == V_Triple) { + const DotLane t0[4] = { zero, tl(2), tl(1, true), zero }; + const DotLane t1[4] = { tl(2, true), zero, tl(0), zero }; + const DotLane t2[4] = { tl(1), tl(0, true), zero, tl(2) }; + memcpy(tDots[0], t0, sizeof(t0)); + memcpy(tDots[1], t1, sizeof(t1)); + memcpy(tDots[2], t2, sizeof(t2)); + dots = 3; + } else { + const DotLane t0[4] = { tl(3), tl(2), tl(1, true), tl(0) }; + const DotLane t1[4] = { tl(2, true), tl(3), tl(0), tl(1) }; + const DotLane t2[4] = { tl(1), tl(0, true), tl(3), tl(2) }; + const DotLane t3[4] = { tl(0, true), tl(1, true), tl(2, true), tl(3) }; + memcpy(tDots[0], t0, sizeof(t0)); + memcpy(tDots[1], t1, sizeof(t1)); + memcpy(tDots[2], t2, sizeof(t2)); + memcpy(tDots[3], t3, sizeof(t3)); + dots = 4; + } + DotLane sl[4]; + for (int i = 0; i < 4; i++) + sl[i] = i < n ? DotReg(sregs[i]) : zero; + for (int i = 0; i < dots; i++) { + u8 sv, tv; + if (!BuildDotOperands(sl, tDots[i], IRVTEMP_PFX_D, &sv, &tv)) + DISABLE; + // Built once, s is a Vec4 for the rest. + for (int j = 0; j < 4; j++) + sl[j] = DotReg(sv + j); + ir.Write(IROp::Vec4VfpuDot, IRVTEMP_PFX_D + i, sv, tv); + } + for (int i = 0; i < dots; i++) + ir.Write(IROp::FMov, dregs[i], IRVTEMP_PFX_D + i); } void IRFrontend::Comp_Vcmp(MIPSOpcode op) { diff --git a/Core/MIPS/IR/IRFrontend.h b/Core/MIPS/IR/IRFrontend.h index 8f3a4bf9aa..95d5c6fcce 100644 --- a/Core/MIPS/IR/IRFrontend.h +++ b/Core/MIPS/IR/IRFrontend.h @@ -159,7 +159,6 @@ private: // With destScratch, a Vec4 that has dot k's result at lane k may be written whole. avoid is a Vec4 // temp not to use (or -1). Returns false, having written nothing, if they don't fit. bool TryMultiDot(const MultiDot *dots, int count, int terms, int avoid, bool destScratch); - bool UseAccurateVdot() const; // State JitState js; diff --git a/Core/MIPS/IR/IRInst.cpp b/Core/MIPS/IR/IRInst.cpp index 5f003d3d7b..48f0414d31 100644 --- a/Core/MIPS/IR/IRInst.cpp +++ b/Core/MIPS/IR/IRInst.cpp @@ -157,7 +157,6 @@ static const IRMeta irMeta[] = { { IROp::Vec4Div, "Vec4Div", "VVV" }, { IROp::Vec4Mul, "Vec4Mul", "VVV" }, { IROp::Vec4Scale, "Vec4Scale", "VVF" }, - { IROp::Vec4Dot, "Vec4Dot", "FVV" }, { IROp::Vec4VfpuDot, "Vec4VfpuDot", "FVV" }, { IROp::Vec4VfpuMultiDot, "Vec4VfpuMultiDot", "VMVc" }, { IROp::Vec4Neg, "Vec4Neg", "VV" }, diff --git a/Core/MIPS/IR/IRInst.h b/Core/MIPS/IR/IRInst.h index 50085f824a..952a13b218 100644 --- a/Core/MIPS/IR/IRInst.h +++ b/Core/MIPS/IR/IRInst.h @@ -186,7 +186,6 @@ enum class IROp : uint8_t { Vec4Mul, Vec4Div, Vec4Scale, - Vec4Dot, Vec4VfpuDot, // The VFPU's own dot product, bit-exact (vfpu_dot). Vec4VfpuMultiDot, // Four of them against one vector, see IRMultiDot. Vec4Neg, @@ -456,7 +455,6 @@ struct IROptions { bool unalignedLoadStore; bool unalignedLoadStoreVec4; bool preferVec4; - bool preferVec4Dot; bool optimizeForInterpreter; // The backend has Vec4VfpuMultiDot. bool vfpuMultiDot; diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index a5686aa448..9356444c61 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -180,7 +180,6 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const IRInst *chainArena) { dispatch[(int)IROp::FCmpVfpuBit] = &&L_FCmpVfpuBit; dispatch[(int)IROp::FCmpVfpuAggregate] = &&L_FCmpVfpuAggregate; dispatch[(int)IROp::FCmovVfpuCC] = &&L_FCmovVfpuCC; - dispatch[(int)IROp::Vec4Dot] = &&L_Vec4Dot; dispatch[(int)IROp::Vec4Min] = &&L_Vec4Min; dispatch[(int)IROp::Vec4Max] = &&L_Vec4Max; dispatch[(int)IROp::Vec4Slt] = &&L_Vec4Slt; @@ -852,17 +851,6 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const IRInst *chainArena) { } IR_NEXT; - IR_CASE(Vec4Dot) - { - // Not quickly implementable on all platforms, unfortunately. - // Though, this is still pretty fast compared to one split into multiple IR instructions. - // This might be good though: https://gist.github.com/rikusalminen/3040241 - const float *a = &mips->f[(u32)inst->src1]; - const float *b = &mips->f[(u32)inst->src2]; - mips->f[inst->dest] = a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; - IR_NEXT; - } - IR_CASE(Vec4VfpuDot) mips->f[inst->dest] = vfpu_dot(&mips->f[inst->src1], &mips->f[inst->src2]); IR_NEXT; diff --git a/Core/MIPS/IR/IRNativeCommon.cpp b/Core/MIPS/IR/IRNativeCommon.cpp index c3dd2bf091..4796ddd32c 100644 --- a/Core/MIPS/IR/IRNativeCommon.cpp +++ b/Core/MIPS/IR/IRNativeCommon.cpp @@ -430,7 +430,6 @@ void IRNativeBackend::CompileIRInst(IRInst inst) { CompIR_VecArith(inst); break; - case IROp::Vec4Dot: case IROp::Vec4VfpuDot: case IROp::Vec4VfpuMultiDot: CompIR_VecHoriz(inst); diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index 5f8a35daa4..88d158586e 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -846,7 +846,6 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts case IROp::Vec4Sub: case IROp::Vec4Mul: case IROp::Vec4Div: - case IROp::Vec4Dot: case IROp::Vec4VfpuDot: case IROp::Vec4VfpuMultiDot: case IROp::Vec4Scale: @@ -2212,7 +2211,6 @@ bool ReduceVec4Flush(const IRWriter &in, IRWriter &out, const IROptions &opts) { } break; - case IROp::Vec4Dot: case IROp::Vec4VfpuDot: if (overlapped(inst.dest, 1, inst.src1, 4, inst.src2, 4) && findAvailTempVec4()) { out.Write(inst.op, temp, inst.src1, inst.src2, inst.constant); diff --git a/Core/MIPS/InterpreterVFPU.cpp b/Core/MIPS/InterpreterVFPU.cpp index 73372ea6d4..0d5464396c 100644 --- a/Core/MIPS/InterpreterVFPU.cpp +++ b/Core/MIPS/InterpreterVFPU.cpp @@ -78,11 +78,6 @@ // The VFPU's own handling of NaN and denormal results in vadd, vsub, vmul and vdiv (see Int_VecDo3). static const bool USE_VFPU_RESULT_FIXUP = false; -// The dot products (vdot, vmmul, vtfm and the rest) through vfpu_dot(), bit-exact with the PSP. -static inline bool AccurateVdot() { - return g_Config.bAccurateVdot || PSP_CoreParameter().compat.flags().ForceAccurateVdot; -} - union FloatBits { float f[4]; u32 u[4]; @@ -523,7 +518,6 @@ namespace MIPSInt ReadMatrix(mips, s, sz, vs); ReadMatrix(mips, t, sz, vt); - bool useAccurateDot = AccurateVdot(); for (int a = 0; a < n; a++) { for (int b = 0; b < n; b++) { union { float f; uint32_t u; } sum = { 0.0f }; @@ -533,23 +527,11 @@ namespace MIPSInt ApplySwizzleT(mips, &t[a * 4], V_Quad); } - if (useAccurateDot) { - sum.f = vfpu_dot(&s[b * 4], &t[a * 4]); - if (my_isnan(sum.f)) { - sum.u = 0x7f800001; - } else if ((sum.u & 0x7F800000) == 0) { - sum.u &= 0xFF800000; - } - } else { - if (a == n - 1 && b == n - 1) { - for (int c = 0; c < 4; c++) { - sum.f += s[b * 4 + c] * t[a * 4 + c]; - } - } else { - for (int c = 0; c < n; c++) { - sum.f += s[b * 4 + c] * t[a * 4 + c]; - } - } + sum.f = vfpu_dot(&s[b * 4], &t[a * 4]); + if (my_isnan(sum.f)) { + sum.u = 0x7f800001; + } else if ((sum.u & 0x7F800000) == 0) { + sum.u &= 0xFF800000; } d[a * 4 + b] = sum.f; @@ -1225,18 +1207,11 @@ namespace MIPSInt ReadVector(mips, t, sz, vt); ApplySwizzleT(mips, t, V_Quad); - if (AccurateVdot()) { - d.f = vfpu_dot(s, t); - if (my_isnan(d.f)) { - d.u = 0x7f800001; - } else if ((d.u & 0x7F800000) == 0) { - d.u &= 0xFF800000; - } - } else { - d.f = 0.0f; - for (int i = 0; i < 4; i++) { - d.f += s[i] * t[i]; - } + d.f = vfpu_dot(s, t); + if (my_isnan(d.f)) { + d.u = 0x7f800001; + } else if ((d.u & 0x7F800000) == 0) { + d.u &= 0xFF800000; } ApplyPrefixD(mips, &d.f, V_Single); @@ -1275,14 +1250,7 @@ namespace MIPSInt } ApplyPrefixST(mips, s, VFPURewritePrefix(mips, VFPU_CTRL_SPREFIX, sprefixRemove, sprefixAdd), V_Quad); - float sum = 0.0f; - if (AccurateVdot()) { - sum = vfpu_dot(s, t); - } else { - for (int i = 0; i < 4; i++) { - sum += s[i] * t[i]; - } - } + float sum = vfpu_dot(s, t); d = my_isnan(sum) ? fabsf(sum) : sum; ApplyPrefixD(mips, &d, V_Single); WriteVector(mips, &d, V_Single, vd); @@ -1496,13 +1464,8 @@ namespace MIPSInt u32 tprefixAdd = VFPU_SWIZZLE(1, 0, 0, 0); ApplyPrefixST(mips, t, VFPURewritePrefix(mips, VFPU_CTRL_TPREFIX, tprefixRemove, tprefixAdd), V_Quad); - if (AccurateVdot()) { - s[1] = -s[1]; - d[0] = vfpu_dot(s, t); - } else { - d[0] = s[0] * t[0] - s[1] * t[1]; - d[0] += s[2] * t[2] + s[3] * t[3]; - } + s[1] = -s[1]; + d[0] = vfpu_dot(s, t); ApplyPrefixD(mips, d, V_Single); WriteVector(mips, d, V_Single, vd); @@ -1524,14 +1487,7 @@ namespace MIPSInt u32 tprefixAdd = VFPU_MAKE_CONSTANTS(VFPUConst::ONE, VFPUConst::ONE, VFPUConst::ONE, VFPUConst::ONE); ApplyPrefixST(mips, t, VFPURewritePrefix(mips, VFPU_CTRL_TPREFIX, tprefixRemove, tprefixAdd), V_Quad); - if (AccurateVdot()) { - d = vfpu_dot(s, t); - } else { - d = 0.0f; - for (int i = 0; i < 4; i++) { - d += s[i] * t[i]; - } - } + d = vfpu_dot(s, t); ApplyPrefixD(mips, &d, V_Single); WriteVector(mips, &d, V_Single, vd); PC += 4; @@ -1562,14 +1518,7 @@ namespace MIPSInt tprefixAdd = 0; ApplyPrefixST(mips, t, VFPURewritePrefix(mips, VFPU_CTRL_TPREFIX, tprefixRemove, tprefixAdd), V_Quad); - if (AccurateVdot()) { - d = vfpu_dot(s, t); - } else { - d = 0.0f; - for (int i = 0; i < 4; i++) { - d += s[i] * t[i]; - } - } + d = vfpu_dot(s, t); ApplyPrefixD(mips, &d, V_Single); WriteVector(mips, &d, V_Single, vd); PC += 4; @@ -1726,36 +1675,24 @@ namespace MIPSInt ReadMatrix(mips, s, msz, vs); ReadVector(mips, t, sz, vt); - if (AccurateVdot()) { - float t2[4]; - for (int i = 0; i < 4; i++) { - if (i < tn) { - t2[i] = t[i]; - } else if (i == ins) { - t2[i] = 1.0f; - } else { - t2[i] = 0.0f; - } + float t2[4]; + for (int i = 0; i < 4; i++) { + if (i < tn) { + t2[i] = t[i]; + } else if (i == ins) { + t2[i] = 1.0f; + } else { + t2[i] = 0.0f; } + } - for (int i = 0; i < ins; i++) { - d.f[i] = vfpu_dot(&s[i * 4], t2); + for (int i = 0; i < ins; i++) { + d.f[i] = vfpu_dot(&s[i * 4], t2); - if (my_isnan(d.f[i])) { - d.u[i] = 0x7f800001; - } else if ((d.u[i] & 0x7F800000) == 0) { - d.u[i] &= 0xFF800000; - } - } - } else { - for (int i = 0; i < ins; i++) { - d.f[i] = s[i * 4] * t[0]; - for (int k = 1; k < tn; k++) { - d.f[i] += s[i * 4 + k] * t[k]; - } - if (ins >= n) { - d.f[i] += s[i * 4 + ins]; - } + if (my_isnan(d.f[i])) { + d.u[i] = 0x7f800001; + } else if ((d.u[i] & 0x7F800000) == 0) { + d.u[i] &= 0xFF800000; } } @@ -1780,19 +1717,12 @@ namespace MIPSInt ApplyPrefixST(mips, t, VFPURewritePrefix(mips, VFPU_CTRL_TPREFIX, tprefixRemove, tprefixAdd), V_Quad); // Really this is the operation all rows probably use (with constant wiring.) - if (AccurateVdot()) { - d.f[ins] = vfpu_dot(&s[ins * 4], t); + d.f[ins] = vfpu_dot(&s[ins * 4], t); - if (my_isnan(d.f[ins])) { - d.u[ins] = 0x7f800001; - } else if ((d.u[ins] & 0x7F800000) == 0) { - d.u[ins] &= 0xFF800000; - } - } else { - d.f[ins] = s[ins * 4] * t[0]; - for (int k = 1; k < 4; k++) { - d.f[ins] += s[ins * 4 + k] * t[k]; - } + if (my_isnan(d.f[ins])) { + d.u[ins] = 0x7f800001; + } else if ((d.u[ins] & 0x7F800000) == 0) { + d.u[ins] &= 0xFF800000; } // D prefix applies to the last element only. @@ -2224,52 +2154,34 @@ namespace MIPSInt switch (sz) { case V_Triple: // vcrsp.t { - if (AccurateVdot()) { - float t0[4] = { 0.0f, t[2], -t[1], 0.0f }; - float t1[4] = { -t[2], 0.0f, t[0], 0.0f }; - d[0] = vfpu_dot(s, t0); - d[1] = vfpu_dot(s, t1); - } else { - d[0] = s[1] * t[2] - s[2] * t[1]; - d[1] = s[2] * t[0] - s[0] * t[2]; - } + float t0[4] = { 0.0f, t[2], -t[1], 0.0f }; + float t1[4] = { -t[2], 0.0f, t[0], 0.0f }; + d[0] = vfpu_dot(s, t0); + d[1] = vfpu_dot(s, t1); // T prefix forces swizzle and negate, can be used to have weird constants. tprefixAdd = VFPU_SWIZZLE(1, 0, 3, 2) | VFPU_NEGATE(0, 1, 0, 0); ApplyPrefixST(mips, t, VFPURewritePrefix(mips, VFPU_CTRL_TPREFIX, tprefixRemove, tprefixAdd), V_Quad); ApplySwizzleS(mips, s, V_Quad); - if (AccurateVdot()) { - // Infinities stay: inf * 0 gives the VFPU's NaN here (cpu/vfpu/nan_gen). - d[2] = vfpu_dot(s, t); - } else { - d[2] = s[0] * t[0] + s[1] * t[1] + s[2] * t[2] + s[3] * t[3]; - } + // Infinities stay: inf * 0 gives the VFPU's NaN here (cpu/vfpu/nan_gen). + d[2] = vfpu_dot(s, t); break; } case V_Quad: // vqmul.q { - if (AccurateVdot()) { - float t0[4] = { t[3], t[2], -t[1], t[0] }; - float t1[4] = { -t[2], t[3], t[0], t[1] }; - float t2[4] = { t[1], -t[0], t[3], t[2] }; - d[0] = vfpu_dot(s, t0); - d[1] = vfpu_dot(s, t1); - d[2] = vfpu_dot(s, t2); - } else { - d[0] = s[0] * t[3] + s[1] * t[2] - s[2] * t[1] + s[3] * t[0]; - d[1] = -s[0] * t[2] + s[1] * t[3] + s[2] * t[0] + s[3] * t[1]; - d[2] = s[0] * t[1] - s[1] * t[0] + s[2] * t[3] + s[3] * t[2]; - } + float t0[4] = { t[3], t[2], -t[1], t[0] }; + float t1[4] = { -t[2], t[3], t[0], t[1] }; + float t2[4] = { t[1], -t[0], t[3], t[2] }; + d[0] = vfpu_dot(s, t0); + d[1] = vfpu_dot(s, t1); + d[2] = vfpu_dot(s, t2); // T prefix forces swizzle and negate, can be used to have weird constants. tprefixAdd = VFPU_SWIZZLE(0, 1, 2, 3) | VFPU_NEGATE(1, 1, 1, 0); ApplyPrefixST(mips, t, VFPURewritePrefix(mips, VFPU_CTRL_TPREFIX, tprefixRemove, tprefixAdd), V_Quad); ApplySwizzleS(mips, s, sz); - if (AccurateVdot()) - d[3] = vfpu_dot(s, t); - else - d[3] = s[0] * t[0] + s[1] * t[1] + s[2] * t[2] + s[3] * t[3]; + d[3] = vfpu_dot(s, t); break; } diff --git a/Core/MIPS/LoongArch64/LoongArch64CompVec.cpp b/Core/MIPS/LoongArch64/LoongArch64CompVec.cpp index c85c2e0318..6a32b6239b 100644 --- a/Core/MIPS/LoongArch64/LoongArch64CompVec.cpp +++ b/Core/MIPS/LoongArch64/LoongArch64CompVec.cpp @@ -441,31 +441,6 @@ void LoongArch64JitBackend::CompIR_VecHoriz(IRInst inst) { CONDITIONAL_DISABLE; switch (inst.op) { - case IROp::Vec4Dot: - // Check overlap before mapping: the reg cache can't map a reg as both a vector and a scalar. - if (cpu_info.LOONGARCH_LSX && (Overlap(inst.dest, 1, inst.src1, 4) || Overlap(inst.dest, 1, inst.src2, 4))) - DISABLE; - regs_.Map(inst); - if (cpu_info.LOONGARCH_LSX) { - - VFMUL_S(regs_.V(inst.dest), regs_.V(inst.src1), regs_.V(inst.src2)); - VOR_V(EncodeRegToV(SCRATCHF1), regs_.V(inst.dest), regs_.V(inst.dest)); - VSHUF4I_W(EncodeRegToV(SCRATCHF1), regs_.V(inst.dest), VFPU_SWIZZLE(1, 0, 3, 2)); - VFADD_S(regs_.V(inst.dest), regs_.V(inst.dest), EncodeRegToV(SCRATCHF1)); - VEXTRINS_D(EncodeRegToV(SCRATCHF1), regs_.V(inst.dest), 1); - // Do we need care about upper 96 bits? - VFADD_S(regs_.V(inst.dest), regs_.V(inst.dest), EncodeRegToV(SCRATCHF1)); - } else { - // Multiply and add separately in the IR interpreter's order: FMADD rounds differently. - // The sum builds up in a scratch reg, so dest may be one of the inputs. - FMUL_S(SCRATCHF1, regs_.F(inst.src1), regs_.F(inst.src2)); - for (int i = 1; i < 4; ++i) { - FMUL_S(SCRATCHF2, regs_.F(inst.src1 + i), regs_.F(inst.src2 + i)); - FADD_S(i == 3 ? regs_.F(inst.dest) : SCRATCHF1, SCRATCHF1, SCRATCHF2); - } - } - break; - case IROp::Vec4VfpuDot: { // The operands go to the shared routine, in v8 and v9 (SCRATCHF1 and SCRATCHF2) with LSX and diff --git a/Core/MIPS/RiscV/RiscVCompVec.cpp b/Core/MIPS/RiscV/RiscVCompVec.cpp index 0e5a77ca5b..10ac530cc1 100644 --- a/Core/MIPS/RiscV/RiscVCompVec.cpp +++ b/Core/MIPS/RiscV/RiscVCompVec.cpp @@ -332,21 +332,6 @@ void RiscVJitBackend::CompIR_VecHoriz(IRInst inst) { CONDITIONAL_DISABLE; switch (inst.op) { - case IROp::Vec4Dot: - { - // Multiply and add separately in the IR interpreter's order: FMADD rounds differently. - // The sum builds up in dest, so it can't be one of the inputs. - if (Overlap(inst.dest, 1, inst.src1, 4) || Overlap(inst.dest, 1, inst.src2, 4)) - DISABLE; - RiscVReg tempReg = regs_.MapWithFPRTemp(inst); - FMUL(32, regs_.F(inst.dest), regs_.F(inst.src1), regs_.F(inst.src2)); - for (int i = 1; i < 4; ++i) { - FMUL(32, tempReg, regs_.F(inst.src1 + i), regs_.F(inst.src2 + i)); - FADD(32, regs_.F(inst.dest), regs_.F(inst.dest), tempReg); - } - break; - } - case IROp::Vec4VfpuDot: { // The operands go to mips->vfpuDotArgs for the shared routine, and the result comes back in diff --git a/Core/MIPS/x86/X64IRCompVec.cpp b/Core/MIPS/x86/X64IRCompVec.cpp index a25a699f13..9401fda4b1 100644 --- a/Core/MIPS/x86/X64IRCompVec.cpp +++ b/Core/MIPS/x86/X64IRCompVec.cpp @@ -246,34 +246,6 @@ void X64JitBackend::CompIR_VecHoriz(IRInst inst) { CONDITIONAL_DISABLE; switch (inst.op) { - case IROp::Vec4Dot: - { - // TODO: Handle "aliasing" of sizes. In theory it should be fine if not dirty... - if (Overlap(inst.dest, 1, inst.src1, 4) || Overlap(inst.dest, 1, inst.src2, 4)) - DISABLE; - - X64Reg tempReg = regs_.MapWithFPRTemp(inst); - - if (inst.dest == inst.src1) { - MULPS(regs_.FX(inst.dest), regs_.F(inst.src2)); - } else if (inst.dest == inst.src2) { - MULPS(regs_.FX(inst.dest), regs_.F(inst.src1)); - } else if (cpu_info.bAVX) { - VMULPS(128, regs_.FX(inst.dest), regs_.FX(inst.src1), regs_.F(inst.src2)); - } else if (cpu_info.bSSE4_1) { - MOVAPS(regs_.FX(inst.dest), regs_.F(inst.src1)); - MULPS(regs_.FX(inst.dest), regs_.F(inst.src2)); - } - - // This shuffle can be done in one op for SSE3/AVX, but it's not always faster. - MOVAPS(tempReg, regs_.F(inst.dest)); - SHUFPS(tempReg, regs_.F(inst.dest), VFPU_SWIZZLE(1, 0, 3, 2)); - ADDPS(regs_.FX(inst.dest), R(tempReg)); - MOVHLPS(tempReg, regs_.FX(inst.dest)); - ADDSS(regs_.FX(inst.dest), R(tempReg)); - break; - } - case IROp::Vec4VfpuDot: { #if PPSSPP_ARCH(AMD64) diff --git a/assets/compat.ini b/assets/compat.ini index 909734d662..fb690e880d 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -1110,30 +1110,6 @@ NPJH50559 = true NPEH00030 = true ULKS46191 = true -[ForceAccurateVdot] -# The VFPU's own dot products, even with AccurateVdot turned off. Fixes leg shaking in Tekken 6. -ULUS10466 = true -ULES01376 = true -ULJS00224 = true -NPUH10047 = true -ULAS42214 = true -ULJS19054 = true -NPJH50184 = true - -# Warriors Orochi - might help #9843 -ULJM05314 = true -ULUS10341 = true -ULES01054 = true -ULJM08022 = true -NPJH50126 = true -ULJM08052 = true -ULAS42316 = true - -# Tomb Raider Anniversary - might help #11179 -ULES00826 = true -ULJS00133 = true -ULUS10253 = true - [ForceSoftwareRenderer] # Darkstalkers ULES00016 = true diff --git a/docs/cpu-equivalence.md b/docs/cpu-equivalence.md index aa9ecce54c..7d5925afdd 100644 --- a/docs/cpu-equivalence.md +++ b/docs/cpu-equivalence.md @@ -34,7 +34,6 @@ Environment variables: | `PPSSPP_CPUFUZZ_DUMP` | write every core's end state of every case to a file | | `PPSSPP_CPUFUZZ_STRICT` | fail on any difference, not only on a crash | | `PPSSPP_CPUFUZZ_OPS` | comma-separated instruction name prefixes to generate only; `dot` is the dot products and the prefix ops | -| `PPSSPP_CPUFUZZ_ACCURATE_VDOT` | `0` to run without the accurate dot products, which are on by default | | `PPSSPP_CPUFUZZ_HOSTEXT` | `+` or `-`: turn the JIT's optional host extensions on or off (RISC-V Zba/Zbb/Zbs/Zcb/Zfa/Zfh/Zicond, and V with `-`, LoongArch LSX/LASX); the `VFPUDotJit` test follows it too | | `PPSSPP_CPUFUZZ_BLOCKS` | print the IR and host code of every block the JITs ran, after each case | | `PPSSPP_CPUFUZZ_UNKNOWNPREFIX` | compile as after a block left a prefix uneaten | diff --git a/unittest/JitHarness.cpp b/unittest/JitHarness.cpp index 044f6298b3..2fd8784bf2 100644 --- a/unittest/JitHarness.cpp +++ b/unittest/JitHarness.cpp @@ -258,8 +258,6 @@ bool TestJit() { bool TestJitDotBench() { SetupJitHarness(); g_Config.bFastMemory = true; - const bool oldAccurateVdot = g_Config.bAccurateVdot; - g_Config.bAccurateVdot = true; struct Sequence { const char *name; @@ -307,7 +305,6 @@ bool TestJitDotBench() { printf("%-14s %6.2f ns per dot\n", seq.name, 1e9 / (callsPerSecond * groups * seq.dots)); } - g_Config.bAccurateVdot = oldAccurateVdot; DestroyJitHarness(); return ok; } @@ -475,7 +472,6 @@ bool TestJitReplacementHooks() { // a later case on its own. // PPSSPP_CPUFUZZ_STRICT fail the test on any difference, not only on a crash. // PPSSPP_CPUFUZZ_BLOCKS print the IR and host code of every block the JITs ran, after each case. -// PPSSPP_CPUFUZZ_ACCURATE_VDOT 0 to run without AccurateVdot (the VFPU's own dot products), which is on by default. // PPSSPP_CPUFUZZ_OPS comma-separated instruction name prefixes to generate only, for fast // runs on the instructions being worked on. "dot" is the dot products // and the prefixes. Without PPSSPP_CPUFUZZ_CLASSES, the class is "v" @@ -1145,9 +1141,6 @@ bool TestCPUEquivalence() { // terminating syscall doesn't. const bool oldSkipDeadbeef = g_Config.bSkipDeadbeefFilling; g_Config.bSkipDeadbeefFilling = true; - const bool oldAccurateVdot = g_Config.bAccurateVdot; - const char *accurateVdot = getenv("PPSSPP_CPUFUZZ_ACCURATE_VDOT"); - g_Config.bAccurateVdot = !accurateVdot || strcmp(accurateVdot, "0") != 0; const char *countStr = getenv("PPSSPP_CPUFUZZ_COUNT"); const char *seedStr = getenv("PPSSPP_CPUFUZZ_SEED"); @@ -1306,7 +1299,6 @@ bool TestCPUEquivalence() { mipsr4k.UpdateCore(CPUCore::INTERPRETER); DestroyJitHarness(); g_Config.bSkipDeadbeefFilling = oldSkipDeadbeef; - g_Config.bAccurateVdot = oldAccurateVdot; return success; }