mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-18 08:50:29 +02:00
RISC-V and LoongArch64 IR JITs: Vec4 vmin, vmax, vslt, vsge and vi2f
The ops from 108e66f2d1, so both now get moreVec4Ops. With LSX they're
vector code: vfpu_minmax_key() of each lane picked by VBITSEL, a compare
ANDed with 1.0, and VFFINT with a scale. RISC-V and LoongArch without LSX
do them lane by lane, vslt and vsge as a compare into a GPR rather than
through fpcond.
Vec4VfpuMultiDot stays ARM64-only, so the frontend still doesn't emit it
for these.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
59b6b4de78
commit
a80cea2b9e
4 files changed
+131
No files matched your search
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user