mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-16 16:00:30 +02:00
ARM32 IR JIT: Call a NEON dot routine for the accurate dot products
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>
This commit is contained in:
1 parent
bd54c3e659
commit
bc7afa8845
12 files changed
+252
-3
No files matched your search
@@ -36,6 +36,7 @@ list(APPEND CoreExtra
|
||||
MIPS/ARM/ArmIRRegCache.cpp
|
||||
MIPS/ARM/ArmIRJit.h
|
||||
MIPS/ARM/ArmIRRegCache.h
|
||||
MIPS/ARM/ArmVfpuDot.h
|
||||
)
|
||||
|
||||
list(APPEND CoreExtra
|
||||
|
||||
@@ -1019,6 +1019,7 @@
|
||||
<ClInclude Include="MemMapHelpers.h" />
|
||||
<ClInclude Include="MIPS\ARM\ArmIRJit.h" />
|
||||
<ClInclude Include="MIPS\ARM\ArmIRRegCache.h" />
|
||||
<ClInclude Include="MIPS\ARM\ArmVfpuDot.h" />
|
||||
<ClInclude Include="MIPS\JitCommon\JitBlockCache.h" />
|
||||
<ClInclude Include="MIPS\JitCommon\JitCommon.h" />
|
||||
<ClInclude Include="MIPS\JitCommon\JitState.h" />
|
||||
|
||||
@@ -1725,6 +1725,9 @@
|
||||
<ClInclude Include="MIPS\ARM\ArmIRRegCache.h">
|
||||
<Filter>MIPS\ARM</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MIPS\ARM\ArmVfpuDot.h">
|
||||
<Filter>MIPS\ARM</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Font\PGF.h">
|
||||
<Filter>Font</Filter>
|
||||
</ClInclude>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "Common/Log.h"
|
||||
#include "Common/MemoryUtil.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/MIPS/ARM/ArmVfpuDot.h"
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/MIPS/ARM/ArmIRJit.h"
|
||||
#include "Core/MIPS/ARM/ArmIRRegCache.h"
|
||||
@@ -100,6 +101,8 @@ void ArmJitBackend::GenerateFixedCode(MIPSState *mipsState) {
|
||||
B(R_LR);
|
||||
}
|
||||
|
||||
vfpuDot_ = GenerateArmVfpuDotCall(this, CTXREG, GenerateArmVfpuDotSpecial(this, CTXREG));
|
||||
|
||||
hooks_.enterDispatcher = (IRNativeFuncNoArg)AlignCode16();
|
||||
|
||||
// Nine registers, so we're misaligned by 4 for calls, which the SUB fixes.
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
#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.
|
||||
//
|
||||
@@ -171,8 +173,11 @@ void ArmJitBackend::CompIR_VecHoriz(IRInst inst) {
|
||||
break;
|
||||
|
||||
case IROp::Vec4VfpuDot:
|
||||
// Through the IR interpreter for now.
|
||||
CompIR_Generic(inst);
|
||||
if (cpu_info.bNEON) {
|
||||
CompVfpuDot(inst);
|
||||
} else {
|
||||
CompIR_Generic(inst);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -181,6 +186,23 @@ void ArmJitBackend::CompIR_VecHoriz(IRInst inst) {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -63,7 +63,12 @@ bool ArmJitBackend::CompileBlock(IRBlockCache *irBlockCache, int block_num) {
|
||||
return false;
|
||||
|
||||
IRBlock *block = irBlockCache->GetBlock(block_num);
|
||||
BeginWrite(std::min(GetSpaceLeft(), (size_t)block->GetNumIRInstructions() * 32));
|
||||
// An accurate dot product gathers its lanes and can flush a few registers around its call.
|
||||
size_t sizeEstimate = 0;
|
||||
const IRInst *estimateInsts = irBlockCache->GetBlockInstructionPtr(*block);
|
||||
for (int i = 0; i < block->GetNumIRInstructions(); ++i)
|
||||
sizeEstimate += estimateInsts[i].op == IROp::Vec4VfpuDot ? 96 : 32;
|
||||
BeginWrite(std::min(GetSpaceLeft(), sizeEstimate));
|
||||
|
||||
u32 startPC = block->GetOriginalStart();
|
||||
bool wroteCheckedOffset = false;
|
||||
|
||||
@@ -116,6 +116,8 @@ private:
|
||||
void CompFloatToIntWithRounding(ArmGen::ARMReg dest, ArmGen::ARMReg src, int rmode);
|
||||
// Puts the masked address in SCRATCH1, as an offset from MEMBASEREG. Destroys SCRATCH2.
|
||||
void PrepareSrc1Address(IRInst inst);
|
||||
// Vec4VfpuDot with NEON (see ArmVfpuDot.h).
|
||||
void CompVfpuDot(IRInst inst);
|
||||
|
||||
JitOptions &jo;
|
||||
ArmIRRegCache regs_;
|
||||
@@ -127,6 +129,8 @@ private:
|
||||
const u8 *dispatcherNoCheck_ = nullptr;
|
||||
const u8 *restoreRoundingMode_ = nullptr;
|
||||
const u8 *applyRoundingMode_ = nullptr;
|
||||
// See GenerateArmVfpuDotCall().
|
||||
const u8 *vfpuDot_ = nullptr;
|
||||
|
||||
int jitStartOffset_ = 0;
|
||||
int compilingBlockNum_ = -1;
|
||||
|
||||
@@ -71,6 +71,11 @@ const int *ArmIRRegCache::GetAllocationOrder(MIPSLoc type, MIPSMap flags, int &c
|
||||
}
|
||||
}
|
||||
|
||||
void ArmIRRegCache::FlushHostRegs(std::initializer_list<ARMReg> regs) {
|
||||
for (ARMReg r : regs)
|
||||
FlushNativeReg((IRNativeReg)r);
|
||||
}
|
||||
|
||||
void ArmIRRegCache::FlushBeforeCall() {
|
||||
// These are not preserved by calls.
|
||||
for (int r : { R2, R3, R12, R14 })
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "ppsspp_config.h"
|
||||
|
||||
#include <initializer_list>
|
||||
#if PPSSPP_ARCH(ARM)
|
||||
|
||||
#include "Common/ArmEmitter.h"
|
||||
@@ -58,6 +60,8 @@ public:
|
||||
ArmGen::ARMReg MapWithFPRTemp(const IRInst &inst);
|
||||
|
||||
void FlushBeforeCall();
|
||||
// Flushes whatever guest state these registers hold, for code that clobbers them.
|
||||
void FlushHostRegs(std::initializer_list<ArmGen::ARMReg> regs);
|
||||
void FlushAll(bool gprs = true, bool fprs = true) override;
|
||||
|
||||
ArmGen::ARMReg GetAndLockTempGPR();
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
// 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/.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
#include "Common/ArmEmitter.h"
|
||||
#include "Core/MIPS/MIPS.h"
|
||||
#include "Core/MIPS/MIPSVFPUUtils.h"
|
||||
|
||||
namespace MIPSComp {
|
||||
|
||||
// The core registers EmitArmVfpuDot() works in, all different. The result ends up in result.
|
||||
struct ArmVfpuDotRegs {
|
||||
ArmGen::ARMReg sum;
|
||||
ArmGen::ARMReg ehi;
|
||||
ArmGen::ARMReg sign;
|
||||
ArmGen::ARMReg result;
|
||||
ArmGen::ARMReg lz;
|
||||
ArmGen::ARMReg temp;
|
||||
};
|
||||
|
||||
// Emits vfpu_dot() of the Vec4s in Q8 (x) and Q9 (y): vfpu_dot_neon(), in ARMv7 NEON after what clang
|
||||
// makes of it, with Q0 and Q8-Q15 as temps. The end is in 32-bit integers rather than
|
||||
// vfpu_dot_finish()'s 64-bit ones (checked bit-exact against it): the significand goes to bit 30
|
||||
// instead of 62, the exponent is clamped to [-2, 254], and the zero test is on the result plus 2^24, so
|
||||
// that it and the inf test are unsigned compares. Inf and NaN aren't handled: the returned branch is
|
||||
// taken for them, with Q8 and Q9 intact. Clobbers the flags.
|
||||
inline ArmGen::FixupBranch EmitArmVfpuDot(ArmGen::ARMXEmitter *e, const ArmVfpuDotRegs &r) {
|
||||
using namespace ArmGen;
|
||||
const ARMReg x = Q8, y = Q9;
|
||||
|
||||
// Exponents: a zero or denormal operand makes the product zero, and inf or NaN gets 0x7FF added,
|
||||
// above any real exponent sum, so one maximum finds the alignment and the specials (q11 = e,
|
||||
// q13 = normal).
|
||||
e->MOVI2R(r.ehi, 0x7F800000);
|
||||
e->VDUP(I_32, Q10, r.ehi);
|
||||
e->VAND(Q11, x, Q10);
|
||||
e->VAND(Q12, y, Q10);
|
||||
e->VTST(I_32, Q13, x, Q10);
|
||||
e->VTST(I_32, Q14, y, Q10);
|
||||
e->VAND(Q13, Q13, Q14);
|
||||
e->VMAX(I_32 | I_UNSIGNED, Q14, Q11, Q12);
|
||||
e->VCEQ(I_32, Q14, Q14, Q10);
|
||||
e->VADD(I_32, Q11, Q11, Q12);
|
||||
e->VSHR(I_32 | I_UNSIGNED, Q11, Q11, 23);
|
||||
e->VAND(Q11, Q11, Q13);
|
||||
e->VMOV_imm(I_32, Q12, VIMM__x1__x1, 0x07); // 0x7FF
|
||||
e->VAND(Q14, Q14, Q12);
|
||||
e->VADD(I_32, Q11, Q11, Q14);
|
||||
e->VPMAX(I_32 | I_UNSIGNED, D24, D22, D23);
|
||||
e->VPMAX(I_32 | I_UNSIGNED, D24, D24, D24);
|
||||
e->VMOV_neon(I_32, r.ehi, D24, 0);
|
||||
// Real exponent sums are at most 508.
|
||||
e->CMP(r.ehi, AssumeMakeOperand2(0x7F0));
|
||||
FixupBranch special = e->B_CC(CC_HS);
|
||||
|
||||
// q0 = the signs. 24x24-bit products, kept to 2 extra bits with round-to-odd (q10 = p).
|
||||
e->VEOR(Q0, x, y);
|
||||
e->VSHR(I_32 | I_SIGNED, Q0, Q0, 31);
|
||||
e->VMOV_imm(I_32, Q10, VIMM_x11_x11, 0x7F); // 0x7FFFFF
|
||||
e->VAND(Q15, x, Q10);
|
||||
e->VORR_imm(I_32, Q15, VIMM_x___x__, 0x80);
|
||||
e->VAND(Q14, y, Q10);
|
||||
e->VORR_imm(I_32, Q14, VIMM_x___x__, 0x80);
|
||||
e->VMULL(I_32 | I_UNSIGNED, Q8, D30, D28);
|
||||
e->VMULL(I_32 | I_UNSIGNED, Q9, D31, D29);
|
||||
e->VSHRN(I_64, D20, Q8, 21);
|
||||
e->VSHRN(I_64, D21, Q9, 21);
|
||||
e->VMOVN(I_64, D28, Q8);
|
||||
e->VMOVN(I_64, D29, Q9);
|
||||
e->VMOV_imm(I_32, Q15, VIMM_x11_x11, 0x1F); // (1 << 21) - 1
|
||||
e->VAND(Q14, Q14, Q15);
|
||||
e->VMOV_imm(I_32, Q15, VIMM___x___x, 1);
|
||||
e->VMIN(I_32 | I_UNSIGNED, Q14, Q14, Q15);
|
||||
e->VORR(Q10, Q10, Q14);
|
||||
e->VAND(Q10, Q10, Q13);
|
||||
|
||||
// Align to the largest exponent by truncation, apply the signs, and sum exactly.
|
||||
e->VDUP(I_32, Q12, D24, 0);
|
||||
e->VSUB(I_32, Q11, Q11, Q12);
|
||||
e->VMVN_imm(I_32, Q15, VIMM___x___x, 27); // -28
|
||||
e->VMAX(I_32 | I_SIGNED, Q11, Q11, Q15);
|
||||
e->VSHL(I_32 | I_UNSIGNED, Q10, Q10, Q11);
|
||||
e->VEOR(Q10, Q10, Q0);
|
||||
e->VSUB(I_32, Q10, Q10, Q0);
|
||||
e->VADD(I_32, D20, D20, D21);
|
||||
e->VPADD(I_32, D20, D20, D20);
|
||||
e->VMOV_neon(I_32, r.sum, D20, 0);
|
||||
|
||||
// The end of vfpu_dot(): ehi = the largest exponent sum, sum = the sum, still scaled by 4.
|
||||
e->AND(r.sign, r.sum, AssumeMakeOperand2(0x80000000));
|
||||
e->CMP(r.sum, Operand2(0));
|
||||
e->SetCC(CC_LT);
|
||||
e->RSB(r.sum, r.sum, Operand2(0));
|
||||
e->SetCC(CC_AL);
|
||||
e->MOV(r.result, Operand2(r.sum, ST_LSR, 2));
|
||||
e->ORR(r.lz, r.result, Operand2(1));
|
||||
e->CLZ(r.lz, r.lz);
|
||||
// Round to 24 bits, nearest even, with the top bit at bit 30.
|
||||
e->SUB(r.temp, r.lz, Operand2(1));
|
||||
e->MOV(r.result, Operand2(r.result, ST_LSL, r.temp));
|
||||
e->UBFX(r.temp, r.result, 7, 1);
|
||||
e->ADD(r.result, r.result, r.temp);
|
||||
e->ADD(r.result, r.result, Operand2(0x3F));
|
||||
e->MOV(r.result, Operand2(r.result, ST_LSR, 7));
|
||||
// The exponent, clamped to [-2, 254], which doesn't change the result.
|
||||
e->SUB(r.ehi, r.ehi, r.lz);
|
||||
e->SUB(r.ehi, r.ehi, Operand2(120));
|
||||
e->CMN(r.ehi, Operand2(2));
|
||||
e->SetCC(CC_LT);
|
||||
e->MVN(r.ehi, Operand2(1));
|
||||
e->SetCC(CC_AL);
|
||||
e->CMP(r.ehi, Operand2(254));
|
||||
e->SetCC(CC_GT);
|
||||
e->MOV(r.ehi, Operand2(254));
|
||||
e->SetCC(CC_AL);
|
||||
// Adding the significand, implicit bit and all, lets a carry from the rounding bump the exponent.
|
||||
e->ADD(r.result, r.result, Operand2(r.ehi, ST_LSL, 23));
|
||||
e->ADD(r.temp, r.result, AssumeMakeOperand2(0x01000000));
|
||||
// 0x7F800000 isn't an immediate, so compare the exponent field.
|
||||
e->MOV(r.lz, Operand2(r.result, ST_LSR, 23));
|
||||
e->CMP(r.lz, Operand2(0xFF));
|
||||
e->SetCC(CC_HS);
|
||||
e->MOVI2R(r.result, 0x7F800000);
|
||||
e->SetCC(CC_AL);
|
||||
// Zero if the result is below the smallest normal, or the sum is.
|
||||
e->CMP(r.temp, AssumeMakeOperand2(0x01800000));
|
||||
e->SetCC(CC_LO);
|
||||
e->MOV(r.result, Operand2(0));
|
||||
e->SetCC(CC_AL);
|
||||
e->CMP(r.sum, Operand2(4));
|
||||
e->SetCC(CC_LO);
|
||||
e->MOV(r.result, Operand2(0));
|
||||
e->SetCC(CC_AL);
|
||||
e->ORR(r.result, r.result, r.sign);
|
||||
return special;
|
||||
}
|
||||
|
||||
static void ArmVfpuDotSpecialC(MIPSState *mips) {
|
||||
const float *args = (const float *)mips->vfpuDotArgs;
|
||||
const float result = vfpu_dot_reference(args, args + 4);
|
||||
memcpy(&mips->vfpuDotResult, &result, sizeof(result));
|
||||
}
|
||||
|
||||
// Generates the routine for the inf and NaN dot products: vfpu_dot_reference() of the two Vec4s in
|
||||
// mips->vfpuDotArgs, written to mips->vfpuDotResult. ctx holds the MIPSState pointer. Called with the
|
||||
// stack aligned to 8, and preserves everything that a call may clobber, but Q8-Q15.
|
||||
inline const u8 *GenerateArmVfpuDotSpecial(ArmGen::ARMXEmitter *e, ArmGen::ARMReg ctx) {
|
||||
using namespace ArmGen;
|
||||
const u8 *start = e->AlignCode16();
|
||||
e->PUSH(8, R0, R1, R2, R3, R4, R5, R12, R_LR);
|
||||
e->VPUSH(D0, 8);
|
||||
e->QuickCallFunctionR(R12, &ArmVfpuDotSpecialC, ctx);
|
||||
e->VPOP(D0, 8);
|
||||
e->POP(8, R0, R1, R2, R3, R4, R5, R12, R_PC);
|
||||
return start;
|
||||
}
|
||||
|
||||
// Generates the VFPU dot product routine the IR JIT calls with BL: vfpu_dot() of Q8 and Q9, into R3,
|
||||
// with special (from GenerateArmVfpuDotSpecial()) for inf and NaN. It clobbers Q0, Q8-Q15, R0-R3, R12,
|
||||
// LR and the flags, and saves only R4: the call site flushes whatever of the others holds guest
|
||||
// state, which in most blocks is nothing. Q8-Q15 are never allocated.
|
||||
inline const u8 *GenerateArmVfpuDotCall(ArmGen::ARMXEmitter *e, ArmGen::ARMReg ctx, const u8 *special) {
|
||||
using namespace ArmGen;
|
||||
const u8 *start = e->AlignCode16();
|
||||
e->PUSH(2, R4, R_LR);
|
||||
const ArmVfpuDotRegs regs{ R0, R1, R2, R3, R12, R4 };
|
||||
FixupBranch specialBranch = EmitArmVfpuDot(e, regs);
|
||||
e->POP(2, R4, R_PC);
|
||||
|
||||
// The operands are still in Q8 and Q9.
|
||||
e->SetJumpTarget(specialBranch);
|
||||
e->ADDI2R(R0, ctx, offsetof(MIPSState, vfpuDotArgs), R1);
|
||||
for (int i = 0; i < 4; i++)
|
||||
e->VSTR((ARMReg)(D16 + i), R0, i * 8);
|
||||
e->BL(special);
|
||||
e->LDR(R3, ctx, offsetof(MIPSState, vfpuDotResult));
|
||||
e->POP(2, R4, R_PC);
|
||||
return start;
|
||||
}
|
||||
|
||||
} // namespace MIPSComp
|
||||
@@ -281,6 +281,7 @@
|
||||
<ClInclude Include="..\..\Core\MIPS\ARM64\Arm64VfpuDot.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\ARM\ArmIRJit.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\ARM\ArmIRRegCache.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\ARM\ArmVfpuDot.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\IR\IRFrontend.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\IR\IRInst.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\IR\IRInterpreter.h" />
|
||||
|
||||
@@ -600,6 +600,7 @@
|
||||
<ClInclude Include="..\..\Core\MIPS\ARM64\Arm64VfpuDot.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\ARM\ArmIRJit.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\ARM\ArmIRRegCache.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\ARM\ArmVfpuDot.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\IR\IRFrontend.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\IR\IRInst.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\IR\IRInterpreter.h" />
|
||||
|
||||
Reference in new issue
Block a user