diff --git a/Core/MIPS/IR/IRJit.cpp b/Core/MIPS/IR/IRJit.cpp index 755d54d3db..5d912650a4 100644 --- a/Core/MIPS/IR/IRJit.cpp +++ b/Core/MIPS/IR/IRJit.cpp @@ -81,6 +81,9 @@ IRJit::IRJit(MIPSState *mipsState, bool actualJit) : frontend_(mipsState->HasDef #if PPSSPP_ARCH(ARM64) opts.vfpuMultiDot = true; opts.moreVec4Ops = true; +#elif PPSSPP_ARCH(RISCV64) || PPSSPP_ARCH(LOONGARCH64) + opts.vfpuMultiDot = !actualJit; + opts.moreVec4Ops = true; #else opts.vfpuMultiDot = !actualJit; opts.moreVec4Ops = !actualJit; diff --git a/Core/MIPS/LoongArch64/LoongArch64CompVec.cpp b/Core/MIPS/LoongArch64/LoongArch64CompVec.cpp index 6c9dea7a4e..967cbba189 100644 --- a/Core/MIPS/LoongArch64/LoongArch64CompVec.cpp +++ b/Core/MIPS/LoongArch64/LoongArch64CompVec.cpp @@ -346,6 +346,91 @@ void LoongArch64JitBackend::CompIR_VecArith(IRInst inst) { FABS_S(regs_.F(inst.dest + i), regs_.F(inst.src1 + i)); break; + case IROp::Vec4Min: + case IROp::Vec4Max: + if (cpu_info.LOONGARCH_LSX) { + // vfpu_minmax_key() of each lane of both, compared as signed, picks the lanes. A tie + // takes src2. + const LoongArch64Reg temp = EncodeRegToV(regs_.MapWithFPRTemp(inst)); + const LoongArch64Reg a = regs_.V(inst.src1), b = regs_.V(inst.src2); + const LoongArch64Reg keyA = EncodeRegToV(SCRATCHF1), keyB = EncodeRegToV(SCRATCHF2); + auto key = [&](LoongArch64Reg k, LoongArch64Reg src) { + VSRAI_W(temp, src, 31); + VSRLI_W(temp, temp, 1); + VXOR_V(k, src, temp); + // Zero if the exponent is. + VSLLI_W(temp, src, 1); + VSRLI_W(temp, temp, 24); + VSEQI_W(temp, temp, 0); + VANDN_V(k, temp, k); + }; + key(keyA, a); + key(keyB, b); + if (inst.op == IROp::Vec4Min) + VSLT_W(keyA, keyA, keyB); + else + VSLT_W(keyA, keyB, keyA); + VBITSEL_V(regs_.V(inst.dest), b, a, keyA); + } else { + // As FMin and FMax, lane by lane. The lanes line up, so overlap doesn't matter. + for (int i = 0; i < 4; ++i) { + IRInst lane{ inst.op == IROp::Vec4Min ? IROp::FMin : IROp::FMax }; + lane.dest = inst.dest + i; + lane.src1 = inst.src1 + i; + lane.src2 = inst.src2 + i; + CompIR_FCondAssign(lane); + } + } + break; + + case IROp::Vec4Slt: + case IROp::Vec4Sge: + // 1.0 where src1 < src2 (vslt) or src1 >= src2 (vsge), 0.0 otherwise, NaN included. + regs_.Map(inst); + if (cpu_info.LOONGARCH_LSX) { + if (inst.op == IROp::Vec4Slt) + VFCMP_CLT_S(EncodeRegToV(SCRATCHF1), regs_.V(inst.src1), regs_.V(inst.src2)); + else + VFCMP_CLE_S(EncodeRegToV(SCRATCHF1), regs_.V(inst.src2), regs_.V(inst.src1)); + LU12I_W(SCRATCH1, 0x3F800000 >> 12); + VREPLGR2VR_W(EncodeRegToV(SCRATCHF2), SCRATCH1); + VAND_V(regs_.V(inst.dest), EncodeRegToV(SCRATCHF1), EncodeRegToV(SCRATCHF2)); + } else { + QuickFLI(32, SCRATCHF1, 1.0f, SCRATCH1); + MOVGR2FR_W(SCRATCHF2, R_ZERO); + for (int i = 0; i < 4; ++i) { + if (inst.op == IROp::Vec4Slt) + FCMP_COND_S(FCC0, regs_.F(inst.src1 + i), regs_.F(inst.src2 + i), LoongArch64Fcond::CLT); + else + FCMP_COND_S(FCC0, regs_.F(inst.src2 + i), regs_.F(inst.src1 + i), LoongArch64Fcond::CLE); + FSEL(regs_.F(inst.dest + i), SCRATCHF2, SCRATCHF1, FCC0); + } + } + break; + + case IROp::Vec4CvtScaledSW: + regs_.Map(inst); + if (cpu_info.LOONGARCH_LSX) { + VFFINT_S_W(regs_.V(inst.dest), regs_.V(inst.src1)); + if ((inst.src2 & 0x1F) != 0) { + float scale = 1.0f / (1UL << (inst.src2 & 0x1F)); + u32 bits; + memcpy(&bits, &scale, 4); + LI(SCRATCH1, (s32)bits); + VREPLGR2VR_W(EncodeRegToV(SCRATCHF1), SCRATCH1); + VFMUL_S(regs_.V(inst.dest), regs_.V(inst.dest), EncodeRegToV(SCRATCHF1)); + } + } else { + if ((inst.src2 & 0x1F) != 0) + QuickFLI(32, SCRATCHF1, 1.0f / (1UL << (inst.src2 & 0x1F)), SCRATCH1); + for (int i = 0; i < 4; ++i) { + FFINT_S_W(regs_.F(inst.dest + i), regs_.F(inst.src1 + i)); + if ((inst.src2 & 0x1F) != 0) + FMUL_S(regs_.F(inst.dest + i), regs_.F(inst.dest + i), SCRATCHF1); + } + } + break; + default: INVALIDOP; break; diff --git a/Core/MIPS/RiscV/RiscVCompVec.cpp b/Core/MIPS/RiscV/RiscVCompVec.cpp index 4280117d2e..0e5a77ca5b 100644 --- a/Core/MIPS/RiscV/RiscVCompVec.cpp +++ b/Core/MIPS/RiscV/RiscVCompVec.cpp @@ -283,6 +283,45 @@ void RiscVJitBackend::CompIR_VecArith(IRInst inst) { FABS(32, regs_.F(inst.dest + i), regs_.F(inst.src1 + i)); break; + case IROp::Vec4Min: + case IROp::Vec4Max: + // As FMin and FMax, lane by lane. The lanes line up, so overlap doesn't matter. + for (int i = 0; i < 4; ++i) { + IRInst lane{ inst.op == IROp::Vec4Min ? IROp::FMin : IROp::FMax }; + lane.dest = inst.dest + i; + lane.src1 = inst.src1 + i; + lane.src2 = inst.src2 + i; + CompIR_FCondAssign(lane); + } + break; + + case IROp::Vec4Slt: + case IROp::Vec4Sge: + // FLT and FLE are false for NaN, as both want. + regs_.Map(inst); + for (int i = 0; i < 4; ++i) { + if (inst.op == IROp::Vec4Slt) + FLT(32, SCRATCH1, regs_.F(inst.src1 + i), regs_.F(inst.src2 + i)); + else + FLE(32, SCRATCH1, regs_.F(inst.src2 + i), regs_.F(inst.src1 + i)); + FCVT(FConv::S, FConv::W, regs_.F(inst.dest + i), SCRATCH1); + } + break; + + case IROp::Vec4CvtScaledSW: + { + const RiscVReg scaleReg = regs_.MapWithFPRTemp(inst); + if ((inst.src2 & 0x1F) != 0) + QuickFLI(32, scaleReg, 1.0f / (1UL << (inst.src2 & 0x1F)), SCRATCH1); + for (int i = 0; i < 4; ++i) { + FMV(FMv::X, FMv::W, SCRATCH1, regs_.F(inst.src1 + i)); + FCVT(FConv::S, FConv::W, regs_.F(inst.dest + i), SCRATCH1); + if ((inst.src2 & 0x1F) != 0) + FMUL(32, regs_.F(inst.dest + i), regs_.F(inst.dest + i), scaleReg); + } + break; + } + default: INVALIDOP; break; diff --git a/docs/cpu-equivalence.md b/docs/cpu-equivalence.md index 8f6967799b..dcd5205420 100644 --- a/docs/cpu-equivalence.md +++ b/docs/cpu-equivalence.md @@ -280,6 +280,10 @@ against the reference on `VFPUDot`'s inputs. Whether the RVV version beats the s hardware hasn't been measured: it moves values between the vector and scalar units three times, which costs more on some cores than on others. +Neither JIT has `Vec4VfpuMultiDot`, so the frontend leaves runs of dots as single +`Vec4VfpuDot`s for them (`IROptions::vfpuMultiDot`). Both have the Vec4 vmin, vmax, vslt, vsge and +vi2f ops (`IROptions::moreVec4Ops`), lane by lane on RISC-V and on LoongArch without LSX. + Before the accurate dot product, the JIT gave the same end state as the IR interpreter in 20000 VFPU-heavy cases (`vvvf`), 20000 mixed and 10000 integer/memory ones, on RISC-V with and without the extensions and on LoongArch without LSX. What was left were host NaNs out of a horizontal op