mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-17 16:30:30 +02:00
IR: Vec4 vmin, vmax, vslt, vsge and vi2f
New IR ops do them four lanes at a time where the backend has them, the ARM64 JIT and the IR interpreter so far. vmin and vmax compare by vfpu_minmax_key() in NEON, which took about twelve instructions a lane through the integer registers before; vslt and vsge are a compare and an AND with 1.0. vi2f becomes one SCVTF with fractional bits. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
e33f38afa6
commit
108e66f2d1
8 files changed
+146
-1
No files matched your search
@@ -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;
|
||||
|
||||
@@ -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++)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user