mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-16 16:00:30 +02:00
After clang's ARMv7 output for vfpu_dot_neon(), in Q8-Q15, which the register cache leaves alone. The end is done in 32-bit integers instead of 64-bit ones, checked bit-exact against vfpu_dot_finish(). The call site gathers the lanes into Q8 and Q9 and flushes whatever guest state is in R2, R3, R12 and LR, usually nothing. Inf and NaN go through a routine that calls vfpu_dot_reference(). Without NEON, Vec4VfpuDot still goes through the IR interpreter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
328 lines
8.8 KiB
C++
328 lines
8.8 KiB
C++
// Copyright (c) 2012- 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(ARM)
|
|
|
|
#include "Common/CPUDetect.h"
|
|
#include "Core/MIPS/ARM/ArmIRJit.h"
|
|
#include "Core/MIPS/ARM/ArmIRRegCache.h"
|
|
#include "Core/MIPS/ARM/ArmVfpuDot.h"
|
|
|
|
// This file contains compilation for Vec4 operations.
|
|
//
|
|
// 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 ArmGen;
|
|
using namespace ArmIRJitConstants;
|
|
|
|
// Floats aren't mapped as SIMD, so each lane of a Vec4 is its own S register and these work lane
|
|
// by lane. Vec4 regs are aligned to 4, so a dest and a source are either the same or disjoint,
|
|
// but a scalar source can be one of the dest's lanes.
|
|
|
|
void ArmJitBackend::CompIR_VecArith(IRInst inst) {
|
|
CONDITIONAL_DISABLE;
|
|
|
|
auto lanes3 = [&](void (ARMXEmitter::*op)(ARMReg, ARMReg, ARMReg)) {
|
|
regs_.Map(inst);
|
|
for (int i = 0; i < 4; ++i)
|
|
(this->*op)(regs_.F(inst.dest + i), regs_.F(inst.src1 + i), regs_.F(inst.src2 + i));
|
|
};
|
|
auto lanes2 = [&](void (ARMXEmitter::*op)(ARMReg, ARMReg)) {
|
|
regs_.Map(inst);
|
|
for (int i = 0; i < 4; ++i)
|
|
(this->*op)(regs_.F(inst.dest + i), regs_.F(inst.src1 + i));
|
|
};
|
|
|
|
switch (inst.op) {
|
|
case IROp::Vec4Add:
|
|
lanes3(&ARMXEmitter::VADD);
|
|
break;
|
|
|
|
case IROp::Vec4Sub:
|
|
lanes3(&ARMXEmitter::VSUB);
|
|
break;
|
|
|
|
case IROp::Vec4Mul:
|
|
lanes3(&ARMXEmitter::VMUL);
|
|
break;
|
|
|
|
case IROp::Vec4Div:
|
|
lanes3(&ARMXEmitter::VDIV);
|
|
break;
|
|
|
|
case IROp::Vec4Scale:
|
|
regs_.Map(inst);
|
|
// The factor may be one of the dest lanes.
|
|
VMOV(SCRATCHF1, regs_.F(inst.src2));
|
|
for (int i = 0; i < 4; ++i)
|
|
VMUL(regs_.F(inst.dest + i), regs_.F(inst.src1 + i), SCRATCHF1);
|
|
break;
|
|
|
|
case IROp::Vec4Neg:
|
|
lanes2(&ARMXEmitter::VNEG);
|
|
break;
|
|
|
|
case IROp::Vec4Abs:
|
|
lanes2(&ARMXEmitter::VABS);
|
|
break;
|
|
|
|
default:
|
|
INVALIDOP;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ArmJitBackend::CompIR_VecAssign(IRInst inst) {
|
|
CONDITIONAL_DISABLE;
|
|
|
|
switch (inst.op) {
|
|
case IROp::Vec4Init:
|
|
{
|
|
static const float values[7][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 },
|
|
};
|
|
if (inst.src1 >= ARRAY_SIZE(values)) {
|
|
CompIR_Generic(inst);
|
|
break;
|
|
}
|
|
regs_.Map(inst);
|
|
for (int i = 0; i < 4; ++i)
|
|
MOVI2F(regs_.F(inst.dest + i), values[inst.src1][i], SCRATCH1);
|
|
break;
|
|
}
|
|
|
|
case IROp::Vec4Shuffle:
|
|
regs_.Map(inst);
|
|
if (inst.dest == inst.src1) {
|
|
// Go through the scratch regs, since a lane may be read after it's written.
|
|
for (int i = 0; i < 4; ++i)
|
|
VMOV((ARMReg)(SCRATCHF1 + i), regs_.F(inst.src1 + Vec4ShuffleLane(inst.src2, i)));
|
|
for (int i = 0; i < 4; ++i)
|
|
VMOV(regs_.F(inst.dest + i), (ARMReg)(SCRATCHF1 + i));
|
|
} else {
|
|
for (int i = 0; i < 4; ++i)
|
|
VMOV(regs_.F(inst.dest + i), regs_.F(inst.src1 + Vec4ShuffleLane(inst.src2, i)));
|
|
}
|
|
break;
|
|
|
|
case IROp::Vec4Blend:
|
|
regs_.Map(inst);
|
|
for (int i = 0; i < 4; ++i) {
|
|
IRReg src = ((inst.constant >> i) & 1) ? inst.src2 : inst.src1;
|
|
if (src != inst.dest)
|
|
VMOV(regs_.F(inst.dest + i), regs_.F(src + i));
|
|
}
|
|
break;
|
|
|
|
case IROp::Vec4Mov:
|
|
if (inst.dest != inst.src1) {
|
|
regs_.Map(inst);
|
|
for (int i = 0; i < 4; ++i)
|
|
VMOV(regs_.F(inst.dest + i), regs_.F(inst.src1 + i));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
INVALIDOP;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ArmJitBackend::CompIR_VecHoriz(IRInst inst) {
|
|
CONDITIONAL_DISABLE;
|
|
|
|
switch (inst.op) {
|
|
case IROp::Vec4Dot:
|
|
// In the IR interpreter's order, without fusing, into scratch since dest may be a source lane.
|
|
regs_.Map(inst);
|
|
VMUL(SCRATCHF1, regs_.F(inst.src1), regs_.F(inst.src2));
|
|
for (int i = 1; i < 4; ++i) {
|
|
VMUL(SCRATCHF2, regs_.F(inst.src1 + i), regs_.F(inst.src2 + i));
|
|
VADD(SCRATCHF1, SCRATCHF1, SCRATCHF2);
|
|
}
|
|
VMOV(regs_.F(inst.dest), SCRATCHF1);
|
|
break;
|
|
|
|
case IROp::Vec4VfpuDot:
|
|
if (cpu_info.bNEON) {
|
|
CompVfpuDot(inst);
|
|
} else {
|
|
CompIR_Generic(inst);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
INVALIDOP;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ArmJitBackend::CompVfpuDot(IRInst inst) {
|
|
regs_.Map(inst);
|
|
// The lanes into Q8 and Q9, through S0-S3 (Q0), since Q8-Q15 have no S registers.
|
|
for (int i = 0; i < 4; i++)
|
|
VMOV((ARMReg)(S0 + i), regs_.F(inst.src1 + i));
|
|
VMOV_neon(Q8, Q0);
|
|
for (int i = 0; i < 4; i++)
|
|
VMOV((ARMReg)(S0 + i), regs_.F(inst.src2 + i));
|
|
VMOV_neon(Q9, Q0);
|
|
|
|
// The shared routine clobbers these too, so whatever guest state is there gets flushed. They come
|
|
// late in the allocation order, so usually that's nothing.
|
|
regs_.FlushHostRegs({ R2, R3, R12, R14 });
|
|
BL(vfpuDot_);
|
|
VMOV(regs_.F(inst.dest), R3);
|
|
}
|
|
|
|
void ArmJitBackend::CompIR_VecPack(IRInst inst) {
|
|
CONDITIONAL_DISABLE;
|
|
|
|
// These are bit manipulations, so they go through the integer registers. Every source is read
|
|
// before any dest lane is written.
|
|
switch (inst.op) {
|
|
case IROp::Vec2Unpack16To31:
|
|
case IROp::Vec2Unpack16To32:
|
|
{
|
|
regs_.Map(inst);
|
|
VMOV(SCRATCH1, regs_.F(inst.src1));
|
|
bool to31 = inst.op == IROp::Vec2Unpack16To31;
|
|
LSL(SCRATCH2, SCRATCH1, Operand2(16));
|
|
if (to31)
|
|
LSR(SCRATCH2, SCRATCH2, Operand2(1));
|
|
VMOV(regs_.F(inst.dest), SCRATCH2);
|
|
BIC(SCRATCH1, SCRATCH1, Operand2(0xFF, 0));
|
|
BIC(SCRATCH1, SCRATCH1, Operand2(0xFF, 12));
|
|
if (to31)
|
|
LSR(SCRATCH1, SCRATCH1, Operand2(1));
|
|
VMOV(regs_.F(inst.dest + 1), SCRATCH1);
|
|
break;
|
|
}
|
|
|
|
case IROp::Vec4Unpack8To32:
|
|
regs_.Map(inst);
|
|
VMOV(SCRATCH1, regs_.F(inst.src1));
|
|
for (int i = 0; i < 4; ++i) {
|
|
// Byte i goes to the top of lane i.
|
|
if (i < 3)
|
|
LSL(SCRATCH2, SCRATCH1, Operand2(24 - i * 8));
|
|
else
|
|
MOV(SCRATCH2, SCRATCH1);
|
|
if (i != 0)
|
|
AND(SCRATCH2, SCRATCH2, Operand2(0xFF, 4));
|
|
VMOV(regs_.F(inst.dest + i), SCRATCH2);
|
|
}
|
|
break;
|
|
|
|
case IROp::Vec2Pack32To16:
|
|
regs_.Map(inst);
|
|
VMOV(SCRATCH1, regs_.F(inst.src1));
|
|
VMOV(SCRATCH2, regs_.F(inst.src1 + 1));
|
|
LSR(SCRATCH1, SCRATCH1, Operand2(16));
|
|
// Keep the top half of the second.
|
|
BFI(SCRATCH2, SCRATCH1, 0, 16);
|
|
VMOV(regs_.F(inst.dest), SCRATCH2);
|
|
break;
|
|
|
|
case IROp::Vec2Pack31To16:
|
|
// Negative lanes clamp to zero.
|
|
regs_.Map(inst);
|
|
VMOV(SCRATCH1, regs_.F(inst.src1));
|
|
VMOV(SCRATCH2, regs_.F(inst.src1 + 1));
|
|
CMP(SCRATCH1, Operand2(0));
|
|
SetCC(CC_LT);
|
|
MOV(SCRATCH1, Operand2(0));
|
|
SetCC(CC_AL);
|
|
CMP(SCRATCH2, Operand2(0));
|
|
SetCC(CC_LT);
|
|
MOV(SCRATCH2, Operand2(0));
|
|
SetCC(CC_AL);
|
|
LSR(SCRATCH1, SCRATCH1, Operand2(15));
|
|
LSL(SCRATCH2, SCRATCH2, Operand2(1));
|
|
BFI(SCRATCH2, SCRATCH1, 0, 16);
|
|
VMOV(regs_.F(inst.dest), SCRATCH2);
|
|
break;
|
|
|
|
case IROp::Vec4Pack32To8:
|
|
case IROp::Vec4Pack31To8:
|
|
{
|
|
// The top byte of each lane (bits 30-23 for 31, with negative lanes as zero.)
|
|
bool from31 = inst.op == IROp::Vec4Pack31To8;
|
|
ARMReg temp = regs_.GetAndLockTempGPR();
|
|
regs_.Map(inst);
|
|
for (int i = 0; i < 4; ++i) {
|
|
ARMReg target = i == 0 ? SCRATCH1 : SCRATCH2;
|
|
VMOV(target, regs_.F(inst.src1 + i));
|
|
if (from31) {
|
|
CMP(target, Operand2(0));
|
|
SetCC(CC_LT);
|
|
MOV(target, Operand2(0));
|
|
SetCC(CC_AL);
|
|
UBFX(target, target, 23, 8);
|
|
} else {
|
|
LSR(target, target, Operand2(24));
|
|
}
|
|
if (i != 0) {
|
|
// Accumulate into temp, then insert at the end.
|
|
if (i == 1)
|
|
MOV(temp, Operand2(target, ST_LSL, 8));
|
|
else
|
|
ORR(temp, temp, Operand2(target, ST_LSL, i * 8));
|
|
}
|
|
}
|
|
ORR(SCRATCH1, SCRATCH1, temp);
|
|
VMOV(regs_.F(inst.dest), SCRATCH1);
|
|
break;
|
|
}
|
|
|
|
case IROp::Vec4DuplicateUpperBitsAndShift1:
|
|
regs_.Map(inst);
|
|
for (int i = 0; i < 4; ++i)
|
|
VMOV((ARMReg)(SCRATCHF1 + i), regs_.F(inst.src1 + i));
|
|
for (int i = 0; i < 4; ++i) {
|
|
VMOV(SCRATCH1, (ARMReg)(SCRATCHF1 + i));
|
|
ORR(SCRATCH1, SCRATCH1, Operand2(SCRATCH1, ST_LSR, 8));
|
|
ORR(SCRATCH1, SCRATCH1, Operand2(SCRATCH1, ST_LSR, 16));
|
|
LSR(SCRATCH1, SCRATCH1, Operand2(1));
|
|
VMOV(regs_.F(inst.dest + i), SCRATCH1);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
INVALIDOP;
|
|
break;
|
|
}
|
|
}
|
|
|
|
} // namespace MIPSComp
|
|
|
|
#endif
|