mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-17 16:30:30 +02:00
The operands go in xmm0 and xmm1 and the result comes back in edi. The routine saves nothing: the call site flushes whatever guest state is in the registers it clobbers, the xmms allocated last and rax, rcx, rdx, rsi and rdi, the last two never allocated outside Windows. 32-bit x86 keeps going through the IR interpreter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
432 lines
13 KiB
C++
432 lines
13 KiB
C++
// Copyright (c) 2023- PPSSPP Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
#include "ppsspp_config.h"
|
|
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
|
|
|
|
#include <algorithm>
|
|
#include "Common/CPUDetect.h"
|
|
#include "Core/MemMap.h"
|
|
#include "Core/MIPS/x86/X64IRJit.h"
|
|
#include "Core/MIPS/x86/X64IRRegCache.h"
|
|
|
|
// This file contains compilation for vector instructions.
|
|
//
|
|
// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly.
|
|
// Currently known non working ones should have DISABLE. No flags because that's in IR already.
|
|
|
|
// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; }
|
|
#define CONDITIONAL_DISABLE {}
|
|
#define DISABLE { CompIR_Generic(inst); return; }
|
|
#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; }
|
|
|
|
namespace MIPSComp {
|
|
|
|
using namespace Gen;
|
|
using namespace X64IRJitConstants;
|
|
|
|
static bool Overlap(IRReg r1, int l1, IRReg r2, int l2) {
|
|
return r1 < r2 + l2 && r1 + l1 > r2;
|
|
}
|
|
|
|
void X64JitBackend::EmitVecConstants() {
|
|
static const float vec4InitData[8][4] = {
|
|
{ 0.0f, 0.0f, 0.0f, 0.0f },
|
|
{ 1.0f, 1.0f, 1.0f, 1.0f },
|
|
{ -1.0f, -1.0f, -1.0f, -1.0f },
|
|
{ 1.0f, 0.0f, 0.0f, 0.0f },
|
|
{ 0.0f, 1.0f, 0.0f, 0.0f },
|
|
{ 0.0f, 0.0f, 1.0f, 0.0f },
|
|
{ 0.0f, 0.0f, 0.0f, 1.0f },
|
|
};
|
|
|
|
constants.vec4InitValues = (const Float4Constant *)GetCodePointer();
|
|
for (size_t type = 0; type < ARRAY_SIZE(vec4InitData); ++type) {
|
|
for (int i = 0; i < 4; ++i) {
|
|
uint32_t val;
|
|
memcpy(&val, &vec4InitData[type][i], sizeof(val));
|
|
Write32(val);
|
|
}
|
|
}
|
|
}
|
|
|
|
void X64JitBackend::CompIR_VecArith(IRInst inst) {
|
|
CONDITIONAL_DISABLE;
|
|
|
|
switch (inst.op) {
|
|
case IROp::Vec4Add:
|
|
regs_.Map(inst);
|
|
if (inst.dest == inst.src1) {
|
|
ADDPS(regs_.FX(inst.dest), regs_.F(inst.src2));
|
|
} else if (inst.dest == inst.src2) {
|
|
ADDPS(regs_.FX(inst.dest), regs_.F(inst.src1));
|
|
} else if (cpu_info.bAVX) {
|
|
VADDPS(128, regs_.FX(inst.dest), regs_.FX(inst.src1), regs_.F(inst.src2));
|
|
} else {
|
|
MOVAPS(regs_.FX(inst.dest), regs_.F(inst.src1));
|
|
ADDPS(regs_.FX(inst.dest), regs_.F(inst.src2));
|
|
}
|
|
break;
|
|
|
|
case IROp::Vec4Sub:
|
|
if (inst.dest == inst.src1) {
|
|
regs_.Map(inst);
|
|
SUBPS(regs_.FX(inst.dest), regs_.F(inst.src2));
|
|
} else if (cpu_info.bAVX) {
|
|
regs_.Map(inst);
|
|
VSUBPS(128, regs_.FX(inst.dest), regs_.FX(inst.src1), regs_.F(inst.src2));
|
|
} else if (inst.dest == inst.src2) {
|
|
X64Reg tempReg = regs_.MapWithFPRTemp(inst);
|
|
MOVAPS(tempReg, regs_.F(inst.src2));
|
|
MOVAPS(regs_.FX(inst.dest), regs_.F(inst.src1));
|
|
SUBPS(regs_.FX(inst.dest), R(tempReg));
|
|
} else {
|
|
regs_.Map(inst);
|
|
MOVAPS(regs_.FX(inst.dest), regs_.F(inst.src1));
|
|
SUBPS(regs_.FX(inst.dest), regs_.F(inst.src2));
|
|
}
|
|
break;
|
|
|
|
case IROp::Vec4Mul:
|
|
regs_.Map(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 {
|
|
MOVAPS(regs_.FX(inst.dest), regs_.F(inst.src1));
|
|
MULPS(regs_.FX(inst.dest), regs_.F(inst.src2));
|
|
}
|
|
break;
|
|
|
|
case IROp::Vec4Div:
|
|
if (inst.dest == inst.src1) {
|
|
regs_.Map(inst);
|
|
DIVPS(regs_.FX(inst.dest), regs_.F(inst.src2));
|
|
} else if (cpu_info.bAVX) {
|
|
regs_.Map(inst);
|
|
VDIVPS(128, regs_.FX(inst.dest), regs_.FX(inst.src1), regs_.F(inst.src2));
|
|
} else if (inst.dest == inst.src2) {
|
|
X64Reg tempReg = regs_.MapWithFPRTemp(inst);
|
|
MOVAPS(tempReg, regs_.F(inst.src2));
|
|
MOVAPS(regs_.FX(inst.dest), regs_.F(inst.src1));
|
|
DIVPS(regs_.FX(inst.dest), R(tempReg));
|
|
} else {
|
|
regs_.Map(inst);
|
|
MOVAPS(regs_.FX(inst.dest), regs_.F(inst.src1));
|
|
DIVPS(regs_.FX(inst.dest), regs_.F(inst.src2));
|
|
}
|
|
break;
|
|
|
|
case IROp::Vec4Scale:
|
|
// TODO: Handle "aliasing" of sizes.
|
|
if (Overlap(inst.dest, 4, inst.src2, 1) || Overlap(inst.src1, 4, inst.src2, 1))
|
|
DISABLE;
|
|
|
|
regs_.Map(inst);
|
|
SHUFPS(regs_.FX(inst.src2), regs_.F(inst.src2), 0);
|
|
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 {
|
|
MOVAPS(regs_.FX(inst.dest), regs_.F(inst.src1));
|
|
MULPS(regs_.FX(inst.dest), regs_.F(inst.src2));
|
|
}
|
|
break;
|
|
|
|
case IROp::Vec4Neg:
|
|
regs_.Map(inst);
|
|
if (cpu_info.bAVX) {
|
|
VXORPS(128, regs_.FX(inst.dest), regs_.FX(inst.src1), M(constants.signBitAll)); // rip accessible
|
|
} else {
|
|
if (inst.dest != inst.src1)
|
|
MOVAPS(regs_.FX(inst.dest), regs_.F(inst.src1));
|
|
XORPS(regs_.FX(inst.dest), M(constants.signBitAll)); // rip accessible
|
|
}
|
|
break;
|
|
|
|
case IROp::Vec4Abs:
|
|
regs_.Map(inst);
|
|
if (cpu_info.bAVX) {
|
|
VANDPS(128, regs_.FX(inst.dest), regs_.FX(inst.src1), M(constants.noSignMask)); // rip accessible
|
|
} else {
|
|
if (inst.dest != inst.src1)
|
|
MOVAPS(regs_.FX(inst.dest), regs_.F(inst.src1));
|
|
ANDPS(regs_.FX(inst.dest), M(constants.noSignMask)); // rip accessible
|
|
}
|
|
break;
|
|
|
|
default:
|
|
INVALIDOP;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void X64JitBackend::CompIR_VecAssign(IRInst inst) {
|
|
CONDITIONAL_DISABLE;
|
|
|
|
switch (inst.op) {
|
|
case IROp::Vec4Init:
|
|
regs_.Map(inst);
|
|
if (inst.src1 == (int)Vec4Init::AllZERO) {
|
|
XORPS(regs_.FX(inst.dest), regs_.F(inst.dest));
|
|
} else {
|
|
MOVAPS(regs_.FX(inst.dest), M(&constants.vec4InitValues[inst.src1])); // rip accessible
|
|
}
|
|
break;
|
|
|
|
case IROp::Vec4Shuffle:
|
|
if (regs_.GetFPRLaneCount(inst.src1) == 1 && (inst.src1 & 3) == 0 && inst.src2 == 0) {
|
|
// This is a broadcast. If dest == src1, this won't clear it.
|
|
regs_.SpillLockFPR(inst.src1);
|
|
regs_.MapVec4(inst.dest, MIPSMap::NOINIT);
|
|
} else {
|
|
regs_.Map(inst);
|
|
}
|
|
if (cpu_info.bAVX) {
|
|
VPERMILPS(128, regs_.FX(inst.dest), regs_.F(inst.src1), inst.src2);
|
|
} else {
|
|
if (inst.dest != inst.src1)
|
|
MOVAPS(regs_.FX(inst.dest), regs_.F(inst.src1));
|
|
SHUFPS(regs_.FX(inst.dest), regs_.F(inst.dest), inst.src2);
|
|
}
|
|
break;
|
|
|
|
case IROp::Vec4Blend:
|
|
if (cpu_info.bAVX) {
|
|
regs_.Map(inst);
|
|
VBLENDPS(128, regs_.FX(inst.dest), regs_.FX(inst.src1), regs_.F(inst.src2), (uint8_t)inst.constant);
|
|
} else if (cpu_info.bSSE4_1) {
|
|
regs_.Map(inst);
|
|
if (inst.dest == inst.src1) {
|
|
BLENDPS(regs_.FX(inst.dest), regs_.F(inst.src2), (uint8_t)inst.constant);
|
|
} else if (inst.dest == inst.src2) {
|
|
BLENDPS(regs_.FX(inst.dest), regs_.F(inst.src1), (uint8_t)~inst.constant);
|
|
} else {
|
|
MOVAPS(regs_.FX(inst.dest), regs_.F(inst.src1));
|
|
BLENDPS(regs_.FX(inst.dest), regs_.F(inst.src2), (uint8_t)inst.constant);
|
|
}
|
|
} else {
|
|
// Could use some shuffles...
|
|
DISABLE;
|
|
}
|
|
break;
|
|
|
|
case IROp::Vec4Mov:
|
|
regs_.Map(inst);
|
|
MOVAPS(regs_.FX(inst.dest), regs_.F(inst.src1));
|
|
break;
|
|
|
|
default:
|
|
INVALIDOP;
|
|
break;
|
|
}
|
|
}
|
|
|
|
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)
|
|
if (!vfpuDot_ || Overlap(inst.dest, 1, inst.src1, 4) || Overlap(inst.dest, 1, inst.src2, 4)) {
|
|
CompIR_Generic(inst);
|
|
break;
|
|
}
|
|
// The operands go to the shared routine in xmm0 and xmm1, and the result comes back in edi.
|
|
// Whatever guest state is in the registers it clobbers gets flushed first: these come last in
|
|
// the allocation order, so usually that's nothing. Then again after the operands are moved,
|
|
// in case they were mapped into one of them.
|
|
auto flushClobbered = [&]() {
|
|
regs_.FlushHostRegs({ RCX, RDX, RSI, RDI }, { XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM14, XMM15 });
|
|
};
|
|
flushClobbered();
|
|
regs_.SpillLockFPR(inst.src1, inst.src2);
|
|
const X64Reg x = regs_.MapVec4(inst.src1);
|
|
const X64Reg y = regs_.MapVec4(inst.src2);
|
|
if (x == XMM1 && y == XMM0) {
|
|
MOVAPS(XMM2, R(XMM0));
|
|
MOVAPS(XMM0, R(XMM1));
|
|
MOVAPS(XMM1, R(XMM2));
|
|
} else if (y == XMM0) {
|
|
MOVAPS(XMM1, R(y));
|
|
MOVAPS(XMM0, R(x));
|
|
} else {
|
|
if (x != XMM0)
|
|
MOVAPS(XMM0, R(x));
|
|
if (y != XMM1)
|
|
MOVAPS(XMM1, R(y));
|
|
}
|
|
regs_.ReleaseSpillLockFPR(inst.src1, inst.src2);
|
|
flushClobbered();
|
|
CALL(vfpuDot_);
|
|
regs_.MapFPR(inst.dest, MIPSMap::NOINIT);
|
|
MOVD_xmm(regs_.FX(inst.dest), R(EDI));
|
|
#else
|
|
// 32-bit x86 has too few registers for the shared routine.
|
|
CompIR_Generic(inst);
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
default:
|
|
INVALIDOP;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void X64JitBackend::CompIR_VecPack(IRInst inst) {
|
|
CONDITIONAL_DISABLE;
|
|
|
|
// Only SSE2 here. A destination overlapping the source goes the slow way, it's rare.
|
|
switch (inst.op) {
|
|
case IROp::Vec4Pack32To8:
|
|
case IROp::Vec4Pack31To8:
|
|
{
|
|
if (Overlap(inst.dest, 1, inst.src1, 4)) {
|
|
DISABLE;
|
|
}
|
|
// The top byte of each lane, packed into one lane. For 31, the byte below the sign, with
|
|
// negative lanes clamped to zero: the arithmetic shift makes them negative, and PACKUSWB
|
|
// saturates them to 0.
|
|
X64Reg tempReg = regs_.MapWithFPRTemp(inst);
|
|
MOVDQA(tempReg, regs_.F(inst.src1));
|
|
if (inst.op == IROp::Vec4Pack31To8)
|
|
PSRAD(tempReg, 23);
|
|
else
|
|
PSRLD(tempReg, 24);
|
|
PACKSSDW(tempReg, R(tempReg));
|
|
PACKUSWB(tempReg, R(tempReg));
|
|
MOVDQA(regs_.FX(inst.dest), R(tempReg));
|
|
break;
|
|
}
|
|
|
|
case IROp::Vec2Pack32To16:
|
|
case IROp::Vec2Pack31To16:
|
|
{
|
|
if (Overlap(inst.dest, 1, inst.src1, 2)) {
|
|
DISABLE;
|
|
}
|
|
// The top 16 bits of each lane (for 31, the 16 below the sign, with negative lanes clamped
|
|
// to zero), packed into one lane. The arithmetic shift keeps them in PACKSSDW's range, so
|
|
// the bits come through unchanged.
|
|
X64Reg tempReg = regs_.MapWithFPRTemp(inst);
|
|
MOVDQA(tempReg, regs_.F(inst.src1));
|
|
if (inst.op == IROp::Vec2Pack31To16) {
|
|
PSRAD(tempReg, 31);
|
|
PANDN(tempReg, regs_.F(inst.src1));
|
|
PSLLD(tempReg, 1);
|
|
}
|
|
PSRAD(tempReg, 16);
|
|
PACKSSDW(tempReg, R(tempReg));
|
|
MOVDQA(regs_.FX(inst.dest), R(tempReg));
|
|
break;
|
|
}
|
|
|
|
case IROp::Vec4Unpack8To32:
|
|
{
|
|
if (Overlap(inst.dest, 4, inst.src1, 1)) {
|
|
DISABLE;
|
|
}
|
|
// Each byte into the top of a lane: duplicate bytes, then words, then shift the rest out.
|
|
X64Reg tempReg = regs_.MapWithFPRTemp(inst);
|
|
MOVDQA(tempReg, regs_.F(inst.src1));
|
|
PUNPCKLBW(tempReg, R(tempReg));
|
|
PUNPCKLWD(tempReg, R(tempReg));
|
|
PSLLD(tempReg, 24);
|
|
MOVDQA(regs_.FX(inst.dest), R(tempReg));
|
|
break;
|
|
}
|
|
|
|
case IROp::Vec2Unpack16To32:
|
|
case IROp::Vec2Unpack16To31:
|
|
{
|
|
if (Overlap(inst.dest, 2, inst.src1, 1)) {
|
|
DISABLE;
|
|
}
|
|
// Each half into the top of a lane, shifted down one more for 31.
|
|
X64Reg tempReg = regs_.MapWithFPRTemp(inst);
|
|
MOVDQA(tempReg, regs_.F(inst.src1));
|
|
PUNPCKLWD(tempReg, R(tempReg));
|
|
PSLLD(tempReg, 16);
|
|
if (inst.op == IROp::Vec2Unpack16To31)
|
|
PSRLD(tempReg, 1);
|
|
MOVDQA(regs_.FX(inst.dest), R(tempReg));
|
|
break;
|
|
}
|
|
|
|
case IROp::Vec4DuplicateUpperBitsAndShift1:
|
|
{
|
|
if (inst.dest != inst.src1 && Overlap(inst.dest, 4, inst.src1, 4)) {
|
|
DISABLE;
|
|
}
|
|
// v |= v >> 8; v |= v >> 16; v >>= 1, which turns 0xFF000000 into 0x7FFFFFFF.
|
|
X64Reg tempReg = regs_.MapWithFPRTemp(inst);
|
|
MOVDQA(tempReg, regs_.F(inst.src1));
|
|
PSRLD(tempReg, 8);
|
|
POR(tempReg, regs_.F(inst.src1));
|
|
MOVDQA(regs_.FX(inst.dest), R(tempReg));
|
|
PSRLD(tempReg, 16);
|
|
POR(regs_.FX(inst.dest), R(tempReg));
|
|
PSRLD(regs_.FX(inst.dest), 1);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
INVALIDOP;
|
|
break;
|
|
}
|
|
}
|
|
|
|
} // namespace MIPSComp
|
|
|
|
#endif
|