diff --git a/Core/MIPS/ARM64/Arm64IRCompVec.cpp b/Core/MIPS/ARM64/Arm64IRCompVec.cpp index 3cf9e0069e..d80dc66cfd 100644 --- a/Core/MIPS/ARM64/Arm64IRCompVec.cpp +++ b/Core/MIPS/ARM64/Arm64IRCompVec.cpp @@ -93,6 +93,53 @@ void Arm64JitBackend::CompIR_VecArith(IRInst inst) { fp_.FABS(32, regs_.FQ(inst.dest), regs_.FQ(inst.src1)); break; + case IROp::Vec4Min: + case IROp::Vec4Max: + { + // vfpu_minmax_key() of each lane of both, compared as signed, picks the lanes. A tie + // takes src2. Q0-Q3 are temps the register cache never hands out. + regs_.Map(inst); + const ARM64Reg a = regs_.FQ(inst.src1), b = regs_.FQ(inst.src2); + MOVI2R(SCRATCH1, 0x7F800000); + fp_.DUP(32, Q3, SCRATCH1); + auto key = [&](ARM64Reg dest, ARM64Reg src) { + fp_.SSHR(32, Q2, src, 31); + fp_.USHR(32, Q2, Q2, 1); + fp_.EOR(dest, src, Q2); + fp_.CMTST(32, Q2, src, Q3); + fp_.AND(dest, dest, Q2); + }; + key(Q0, a); + key(Q1, b); + if (inst.op == IROp::Vec4Min) + fp_.CMGT(32, Q0, Q1, Q0); + else + fp_.CMGT(32, Q0, Q0, Q1); + fp_.BSL(Q0, a, b); + fp_.MOV(regs_.FQ(inst.dest), Q0); + 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 (inst.op == IROp::Vec4Slt) + fp_.FCMGT(32, Q0, regs_.FQ(inst.src2), regs_.FQ(inst.src1)); + else + fp_.FCMGE(32, Q0, regs_.FQ(inst.src1), regs_.FQ(inst.src2)); + fp_.FMOV(32, Q1, 0x70); // 1.0 + fp_.AND(regs_.FQ(inst.dest), Q0, Q1); + break; + + case IROp::Vec4CvtScaledSW: + regs_.Map(inst); + if ((inst.src2 & 0x1F) == 0) + fp_.SCVTF(32, regs_.FQ(inst.dest), regs_.FQ(inst.src1)); + else + fp_.SCVTF(32, regs_.FQ(inst.dest), regs_.FQ(inst.src1), inst.src2 & 0x1F); + break; + default: INVALIDOP; break; diff --git a/Core/MIPS/IR/IRCompVFPU.cpp b/Core/MIPS/IR/IRCompVFPU.cpp index 7a48fad33b..24fba0be26 100644 --- a/Core/MIPS/IR/IRCompVFPU.cpp +++ b/Core/MIPS/IR/IRCompVFPU.cpp @@ -1198,7 +1198,7 @@ namespace MIPSComp { case VecDo3Op::VMAX: case VecDo3Op::VSGE: case VecDo3Op::VSLT: - allowSIMD = false; + allowSIMD = opts.moreVec4Ops; break; case VecDo3Op::INVALID: // Can't happen, but to avoid compiler warnings break; @@ -1234,6 +1234,18 @@ namespace MIPSComp { case VecDo3Op::VMUL: // d[i] = s[i] * t[i]; break; //vmul opFunc = IROp::Vec4Mul; break; + case VecDo3Op::VMIN: + opFunc = IROp::Vec4Min; + break; + case VecDo3Op::VMAX: + opFunc = IROp::Vec4Max; + break; + case VecDo3Op::VSLT: + opFunc = IROp::Vec4Slt; + break; + case VecDo3Op::VSGE: + opFunc = IROp::Vec4Sge; + break; default: // Leave it Nop, disabled below. break; @@ -1500,6 +1512,11 @@ namespace MIPSComp { u8 sregs[4], dregs[4], outregs[4]; GetVectorRegsPrefixS(sregs, sz, _VS); GetVectorRegsPrefixD(dregs, sz, _VD); + if (opts.moreVec4Ops && IsVec4(sz, dregs) && IsVec4(sz, sregs)) { + ir.Write(IROp::Vec4CvtScaledSW, dregs[0], sregs[0], imm); + ApplyPrefixD(dregs, sz, _VD); + return; + } // Through temps if a lane would be written before a later one reads it. const bool direct = IsOverlapSafeAllowS(n, dregs, n, sregs); for (int i = 0; i < n; i++) diff --git a/Core/MIPS/IR/IRInst.cpp b/Core/MIPS/IR/IRInst.cpp index f28f8fc39a..5f003d3d7b 100644 --- a/Core/MIPS/IR/IRInst.cpp +++ b/Core/MIPS/IR/IRInst.cpp @@ -162,6 +162,11 @@ static const IRMeta irMeta[] = { { IROp::Vec4VfpuMultiDot, "Vec4VfpuMultiDot", "VMVc" }, { IROp::Vec4Neg, "Vec4Neg", "VV" }, { IROp::Vec4Abs, "Vec4Abs", "VV" }, + { IROp::Vec4Min, "Vec4Min", "VVV" }, + { IROp::Vec4Max, "Vec4Max", "VVV" }, + { IROp::Vec4Slt, "Vec4Slt", "VVV" }, + { IROp::Vec4Sge, "Vec4Sge", "VVV" }, + { IROp::Vec4CvtScaledSW, "Vec4CvtScaledSW", "VVI" }, // Pack/Unpack { IROp::Vec2Unpack16To31, "Vec2Unpack16To31", "2F" }, // Note that the result is shifted down by 1, hence 31 diff --git a/Core/MIPS/IR/IRInst.h b/Core/MIPS/IR/IRInst.h index 94a91f0bc5..2d90f2e3bb 100644 --- a/Core/MIPS/IR/IRInst.h +++ b/Core/MIPS/IR/IRInst.h @@ -191,6 +191,13 @@ enum class IROp : uint8_t { Vec4VfpuMultiDot, // Four of them against one vector, see IRMultiDot. Vec4Neg, Vec4Abs, + // vmin/vmax (by vfpu_minmax_key, a tie taking src2), vslt and vsge (1.0 or 0.0, 0.0 for NaN). + Vec4Min, + Vec4Max, + Vec4Slt, + Vec4Sge, + // vi2f: each lane's int to float, divided by 2^src2. + Vec4CvtScaledSW, // vx2i Vec2Unpack16To31, // Note that the result is shifted down by 1, hence 31 @@ -448,6 +455,8 @@ struct IROptions { bool vfpuMultiDot; // Compile on past conditional branches (see IRFrontend::CanContinuePastBranch). bool continueBranches; + // The backend has Vec4Min, Vec4Max, Vec4Slt, Vec4Sge and Vec4CvtScaledSW. + bool moreVec4Ops; }; const IRMeta *GetIRMeta(IROp op); diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index 79265a5208..28da6e6408 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -171,6 +171,11 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { 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; + dispatch[(int)IROp::Vec4Sge] = &&L_Vec4Sge; + dispatch[(int)IROp::Vec4CvtScaledSW] = &&L_Vec4CvtScaledSW; dispatch[(int)IROp::Vec4VfpuDot] = &&L_Vec4VfpuDot; dispatch[(int)IROp::Vec4VfpuMultiDot] = &&L_Vec4VfpuMultiDot; dispatch[(int)IROp::FSin] = &&L_FSin; @@ -583,6 +588,56 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { IR_NEXT; } + IR_CASE(Vec4Min) + { + float result[4]; + for (int i = 0; i < 4; i++) { + const u32 a = mips->fi[inst->src1 + i], b = mips->fi[inst->src2 + i]; + result[i] = vfpu_minmax_key(a) < vfpu_minmax_key(b) ? mips->f[inst->src1 + i] : mips->f[inst->src2 + i]; + } + memcpy(&mips->f[inst->dest], result, sizeof(result)); + IR_NEXT; + } + + IR_CASE(Vec4Max) + { + float result[4]; + for (int i = 0; i < 4; i++) { + const u32 a = mips->fi[inst->src1 + i], b = mips->fi[inst->src2 + i]; + result[i] = vfpu_minmax_key(a) > vfpu_minmax_key(b) ? mips->f[inst->src1 + i] : mips->f[inst->src2 + i]; + } + memcpy(&mips->f[inst->dest], result, sizeof(result)); + IR_NEXT; + } + + IR_CASE(Vec4Slt) + { + float result[4]; + for (int i = 0; i < 4; i++) + result[i] = mips->f[inst->src1 + i] < mips->f[inst->src2 + i] ? 1.0f : 0.0f; + memcpy(&mips->f[inst->dest], result, sizeof(result)); + IR_NEXT; + } + + IR_CASE(Vec4Sge) + { + float result[4]; + for (int i = 0; i < 4; i++) + result[i] = mips->f[inst->src1 + i] >= mips->f[inst->src2 + i] ? 1.0f : 0.0f; + memcpy(&mips->f[inst->dest], result, sizeof(result)); + IR_NEXT; + } + + IR_CASE(Vec4CvtScaledSW) + { + float result[4]; + const float scale = 1.0f / (1UL << (inst->src2 & 0x1F)); + for (int i = 0; i < 4; i++) + result[i] = (float)mips->fs[inst->src1 + i] * scale; + memcpy(&mips->f[inst->dest], result, sizeof(result)); + IR_NEXT; + } + IR_CASE(Vec4Abs) { #if PPSSPP_ARCH(SSE2) diff --git a/Core/MIPS/IR/IRJit.cpp b/Core/MIPS/IR/IRJit.cpp index 5e613c1c18..755d54d3db 100644 --- a/Core/MIPS/IR/IRJit.cpp +++ b/Core/MIPS/IR/IRJit.cpp @@ -80,8 +80,10 @@ IRJit::IRJit(MIPSState *mipsState, bool actualJit) : frontend_(mipsState->HasDef // The IR interpreter, and the backends with a routine for it. #if PPSSPP_ARCH(ARM64) opts.vfpuMultiDot = true; + opts.moreVec4Ops = true; #else opts.vfpuMultiDot = !actualJit; + opts.moreVec4Ops = !actualJit; #endif opts.continueBranches = true; frontend_.SetOptions(opts); diff --git a/Core/MIPS/IR/IRNativeCommon.cpp b/Core/MIPS/IR/IRNativeCommon.cpp index 3764a1489e..951b40cd6e 100644 --- a/Core/MIPS/IR/IRNativeCommon.cpp +++ b/Core/MIPS/IR/IRNativeCommon.cpp @@ -422,6 +422,11 @@ void IRNativeBackend::CompileIRInst(IRInst inst) { case IROp::Vec4Scale: case IROp::Vec4Neg: case IROp::Vec4Abs: + case IROp::Vec4Min: + case IROp::Vec4Max: + case IROp::Vec4Slt: + case IROp::Vec4Sge: + case IROp::Vec4CvtScaledSW: CompIR_VecArith(inst); break; diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index 0d8da69e99..5f8a35daa4 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -854,6 +854,11 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts case IROp::Vec4Blend: case IROp::Vec4Neg: case IROp::Vec4Abs: + case IROp::Vec4Min: + case IROp::Vec4Max: + case IROp::Vec4Slt: + case IROp::Vec4Sge: + case IROp::Vec4CvtScaledSW: case IROp::Vec4Pack31To8: case IROp::Vec4Pack32To8: case IROp::Vec2Pack32To16: