mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
Rs == Rt+1 puts the two sources next to each other in the GPR array, so a 16-byte read at &GPR.r[Rt] + sa funnelled them without a TBL. It was the slower of the two sequences on both cores at every sa, and worst on the twelve adjacent pairs that have a pinned source - the register range a funnel-shift memcpy loop uses. armFlushEEGPRPin had no other caller. The sa mask stays, but it no longer guards a host address. Every writer of SA - MTSA, MTSAB, MTSAH - already keeps four bits, so the two oversized-sa tests could not fail; one goes with the path it named and the other says what it covers.
1504 lines
49 KiB
C++
1504 lines
49 KiB
C++
// SPDX-FileCopyrightText: 2026 yaps2 Dev Team
|
|
// SPDX-License-Identifier: GPL-3.0+
|
|
|
|
// ARM64 EE MMI (Multimedia Instructions) Codegen — NEON-based
|
|
//
|
|
// All MMI instructions are 128-bit SIMD operations on the EE's 128-bit GPRs.
|
|
// NEON Q registers are used throughout: load from cpuRegs.GPR, operate, store back.
|
|
|
|
#include "arm64/iR5900-arm64.h"
|
|
#include "arm64/AsmHelpers.h"
|
|
#include "common/Assertions.h"
|
|
|
|
namespace a64 = vixl::aarch64;
|
|
|
|
namespace R5900 {
|
|
namespace Dynarec {
|
|
namespace OpcodeImpl {
|
|
namespace MMI {
|
|
|
|
namespace Interp = R5900::Interpreter::OpcodeImpl::MMI;
|
|
|
|
// ============================================================================
|
|
// Helpers for 128-bit GPR load/store
|
|
// ============================================================================
|
|
|
|
// Flush any const propagation state for a register and invalidate allocations.
|
|
// Must be called before reading a register's 128-bit value from memory,
|
|
// since const prop only tracks the lower 64 bits.
|
|
static void mmiFlushReg(int reg)
|
|
{
|
|
if (reg == 0) return;
|
|
if (GPR_IS_CONST1(reg))
|
|
{
|
|
// Const prop only has lower 64 bits — flush to memory so upper 64 bits
|
|
// are preserved alongside the correct lower 64 bits.
|
|
_flushEEreg(reg);
|
|
}
|
|
_deleteEEreg(reg, 1);
|
|
}
|
|
|
|
// Prepare destination: invalidate const/alloc state (the full 128 bits will be overwritten)
|
|
static void mmiInvalidateDest(int reg)
|
|
{
|
|
if (reg == 0) return;
|
|
_deleteEEreg(reg, 0);
|
|
GPR_DEL_CONST(reg);
|
|
}
|
|
|
|
// Load 128-bit GPR into a NEON Q register
|
|
static void mmiLoadReg(const a64::VRegister& qreg, int gpr)
|
|
{
|
|
if (gpr == 0)
|
|
{
|
|
// r0 is always zero
|
|
armAsm->Movi(qreg.V16B(), 0);
|
|
}
|
|
else
|
|
{
|
|
armAsm->Ldr(qreg, armCpuRegMem(&cpuRegs.GPR.r[gpr].UQ));
|
|
// Lazy-dirty / residency: merge a dirty pin OR scalar slot over the
|
|
// possibly-stale lower half.
|
|
armMergeEEResidentIntoQuad(qreg, gpr);
|
|
}
|
|
}
|
|
|
|
// Store 128-bit NEON Q register to GPR
|
|
static void mmiStoreReg(int gpr, const a64::VRegister& qreg)
|
|
{
|
|
pxAssert(gpr != 0);
|
|
armStoreEEGPRQuad(qreg, gpr);
|
|
}
|
|
|
|
// Standard 3-operand MMI: rd = rs OP rt (128-bit).
|
|
//
|
|
// Routes through eeRecompileCodeXMM so consecutive MMI ops on the same guest
|
|
// register stay register-resident in the allocator-managed NEON pool instead
|
|
// of bouncing through memory. Allocator handles const tracking, GPR-side
|
|
// eviction, NaN-zero of r0, and "already in NEON" reuse.
|
|
//
|
|
// Each user gets three locals:
|
|
// qs — VRegister view of EEREC_S (Rs input, MODE_READ)
|
|
// qt — VRegister view of EEREC_T (Rt input, MODE_READ)
|
|
// qd — VRegister view of EEREC_D (Rd output, MODE_WRITE)
|
|
//
|
|
// Functions that need a fourth temp can use RQSCRATCH / RQSCRATCH2 — both are
|
|
// outside the allocator pool. Do NOT clobber qs or qt
|
|
// before the final write to qd, otherwise the allocator's MODE_READ state
|
|
// for them is invalidated.
|
|
#define MMI_3OP_SETUP() \
|
|
if (!_Rd_) return; \
|
|
int info = eeRecompileCodeXMM(XMMINFO_READS | XMMINFO_READT | XMMINFO_WRITED); \
|
|
const a64::VRegister qs = armQRegister(EEREC_S); \
|
|
const a64::VRegister qt = armQRegister(EEREC_T); \
|
|
const a64::VRegister qd = armQRegister(EEREC_D); \
|
|
(void)info
|
|
|
|
// 2-operand: rd = OP(rt).
|
|
// qt — VRegister view of EEREC_T (Rt input, MODE_READ)
|
|
// qd — VRegister view of EEREC_D (Rd output, MODE_WRITE)
|
|
#define MMI_2OP_SETUP() \
|
|
if (!_Rd_) return; \
|
|
int info = eeRecompileCodeXMM(XMMINFO_READT | XMMINFO_WRITED); \
|
|
const a64::VRegister qt = armQRegister(EEREC_T); \
|
|
const a64::VRegister qd = armQRegister(EEREC_D); \
|
|
(void)info
|
|
|
|
// ============================================================================
|
|
// Logical Operations (128-bit)
|
|
// ============================================================================
|
|
|
|
void recPAND()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->And(qd.V16B(), qs.V16B(), qt.V16B());
|
|
}
|
|
|
|
void recPOR()
|
|
{
|
|
if (!_Rd_)
|
|
return;
|
|
|
|
// `por rd, r0, rt` is the canonical PS2 128-bit register-move idiom and is
|
|
// common. Special-case an r0 operand to avoid allocating r0 into a NEON reg
|
|
// and materialize a zero just to OR it in (conditional XMMINFO,
|
|
// Movi when both r0, register-copy when one is r0).
|
|
const bool s_zero = (_Rs_ == 0);
|
|
const bool t_zero = (_Rt_ == 0);
|
|
int info = eeRecompileCodeXMM((s_zero ? 0 : XMMINFO_READS) | (t_zero ? 0 : XMMINFO_READT) | XMMINFO_WRITED);
|
|
const a64::VRegister qd = armQRegister(EEREC_D);
|
|
|
|
if (s_zero && t_zero)
|
|
armAsm->Movi(qd.V2D(), 0);
|
|
else if (s_zero)
|
|
armAsm->Mov(qd.V16B(), armQRegister(EEREC_T).V16B());
|
|
else if (t_zero)
|
|
armAsm->Mov(qd.V16B(), armQRegister(EEREC_S).V16B());
|
|
else
|
|
armAsm->Orr(qd.V16B(), armQRegister(EEREC_S).V16B(), armQRegister(EEREC_T).V16B());
|
|
}
|
|
|
|
void recPXOR()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Eor(qd.V16B(), qs.V16B(), qt.V16B());
|
|
}
|
|
|
|
void recPNOR()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Orr(qd.V16B(), qs.V16B(), qt.V16B());
|
|
armAsm->Not(qd.V16B(), qd.V16B());
|
|
}
|
|
|
|
// ============================================================================
|
|
// Packed Arithmetic — Signed
|
|
// ============================================================================
|
|
|
|
void recPADDW()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Add(qd.V4S(), qs.V4S(), qt.V4S());
|
|
}
|
|
|
|
void recPSUBW()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Sub(qd.V4S(), qs.V4S(), qt.V4S());
|
|
}
|
|
|
|
void recPADDH()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Add(qd.V8H(), qs.V8H(), qt.V8H());
|
|
}
|
|
|
|
void recPSUBH()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Sub(qd.V8H(), qs.V8H(), qt.V8H());
|
|
}
|
|
|
|
void recPADDB()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Add(qd.V16B(), qs.V16B(), qt.V16B());
|
|
}
|
|
|
|
void recPSUBB()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Sub(qd.V16B(), qs.V16B(), qt.V16B());
|
|
}
|
|
|
|
// ============================================================================
|
|
// Packed Arithmetic — Unsigned
|
|
// ============================================================================
|
|
|
|
void recPADDUW()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Uqadd(qd.V4S(), qs.V4S(), qt.V4S());
|
|
}
|
|
|
|
void recPSUBUW()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Uqsub(qd.V4S(), qs.V4S(), qt.V4S());
|
|
}
|
|
|
|
void recPADDUH()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Uqadd(qd.V8H(), qs.V8H(), qt.V8H());
|
|
}
|
|
|
|
void recPSUBUH()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Uqsub(qd.V8H(), qs.V8H(), qt.V8H());
|
|
}
|
|
|
|
void recPADDUB()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Uqadd(qd.V16B(), qs.V16B(), qt.V16B());
|
|
}
|
|
|
|
void recPSUBUB()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Uqsub(qd.V16B(), qs.V16B(), qt.V16B());
|
|
}
|
|
|
|
// ============================================================================
|
|
// Packed Arithmetic — Saturating Signed
|
|
// ============================================================================
|
|
|
|
void recPADDSW()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Sqadd(qd.V4S(), qs.V4S(), qt.V4S());
|
|
}
|
|
|
|
void recPSUBSW()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Sqsub(qd.V4S(), qs.V4S(), qt.V4S());
|
|
}
|
|
|
|
void recPADDSH()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Sqadd(qd.V8H(), qs.V8H(), qt.V8H());
|
|
}
|
|
|
|
void recPSUBSH()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Sqsub(qd.V8H(), qs.V8H(), qt.V8H());
|
|
}
|
|
|
|
void recPADDSB()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Sqadd(qd.V16B(), qs.V16B(), qt.V16B());
|
|
}
|
|
|
|
void recPSUBSB()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Sqsub(qd.V16B(), qs.V16B(), qt.V16B());
|
|
}
|
|
|
|
// ============================================================================
|
|
// Packed Compare — Greater Than (signed)
|
|
// ============================================================================
|
|
|
|
void recPCGTW()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Cmgt(qd.V4S(), qs.V4S(), qt.V4S());
|
|
}
|
|
|
|
void recPCGTH()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Cmgt(qd.V8H(), qs.V8H(), qt.V8H());
|
|
}
|
|
|
|
void recPCGTB()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Cmgt(qd.V16B(), qs.V16B(), qt.V16B());
|
|
}
|
|
|
|
// ============================================================================
|
|
// Packed Compare — Equal
|
|
// ============================================================================
|
|
|
|
void recPCEQW()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Cmeq(qd.V4S(), qs.V4S(), qt.V4S());
|
|
}
|
|
|
|
void recPCEQH()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Cmeq(qd.V8H(), qs.V8H(), qt.V8H());
|
|
}
|
|
|
|
void recPCEQB()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Cmeq(qd.V16B(), qs.V16B(), qt.V16B());
|
|
}
|
|
|
|
// ============================================================================
|
|
// Packed Min/Max (signed)
|
|
// ============================================================================
|
|
|
|
void recPMAXW()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Smax(qd.V4S(), qs.V4S(), qt.V4S());
|
|
}
|
|
|
|
void recPMINW()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Smin(qd.V4S(), qs.V4S(), qt.V4S());
|
|
}
|
|
|
|
void recPMAXH()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Smax(qd.V8H(), qs.V8H(), qt.V8H());
|
|
}
|
|
|
|
void recPMINH()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Smin(qd.V8H(), qs.V8H(), qt.V8H());
|
|
}
|
|
|
|
// ============================================================================
|
|
// Packed Absolute Value (signed)
|
|
// ============================================================================
|
|
|
|
void recPABSW()
|
|
{
|
|
MMI_2OP_SETUP();
|
|
// PS2 PABSW saturates INT_MIN → INT_MAX (per MMI.cpp _PABSW). NEON Abs
|
|
// preserves INT_MIN; Sqabs is the saturating form that matches.
|
|
armAsm->Sqabs(qd.V4S(), qt.V4S());
|
|
}
|
|
|
|
void recPABSH()
|
|
{
|
|
MMI_2OP_SETUP();
|
|
// Mirror of PABSW for halfword lanes.
|
|
armAsm->Sqabs(qd.V8H(), qt.V8H());
|
|
}
|
|
|
|
// ============================================================================
|
|
// Register Copy / Move
|
|
// ============================================================================
|
|
|
|
// PCPYLD: rd = { rs.UD[0], rt.UD[0] } — copy (concatenate) lower doubleword of each source.
|
|
void recPCPYLD()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Zip1(qd.V2D(), qt.V2D(), qs.V2D());
|
|
}
|
|
|
|
// PCPYUD: rd = {rt[127:64], rs[127:64]} — upper doublewords interleaved
|
|
void recPCPYUD()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Zip2(qd.V2D(), qs.V2D(), qt.V2D());
|
|
}
|
|
|
|
// PCPYH: rd = {rt.UH[4] x4, rt.UH[0] x4} — replicate halfwords.
|
|
// Register-resident via the allocator (MMI_2OP_SETUP) instead of a
|
|
// memory-bounce (Ldr q from Rt + Str q to Rd + const flush), matching
|
|
// sibling single-source MMI ops (PABSW/PCPYLD). Saves a full-width load
|
|
// + store per execution.
|
|
// Broadcast rt.H[4] into scratch FIRST so the qd==qt aliased case stays correct
|
|
// (a qd write would otherwise clobber rt before H[4] is read).
|
|
void recPCPYH()
|
|
{
|
|
MMI_2OP_SETUP();
|
|
armAsm->Dup(RQSCRATCH.V8H(), qt.V8H(), 4); // rt.H[4] x8 (read qt before qd write)
|
|
armAsm->Dup(qd.V8H(), qt.V8H(), 0); // qd = rt.H[0] x8
|
|
armAsm->Mov(qd.V2D(), 1, RQSCRATCH.V2D(), 0); // upper 64 <- rt.H[4] x4
|
|
}
|
|
|
|
// PMFHI: rd = HI (128-bit)
|
|
void recPMFHI()
|
|
{
|
|
if (!_Rd_) return;
|
|
mmiInvalidateDest(_Rd_);
|
|
|
|
armAsm->Ldr(RQSCRATCH, armCpuRegMem(&cpuRegs.HI.UQ));
|
|
mmiStoreReg(_Rd_, RQSCRATCH);
|
|
}
|
|
|
|
// PMFLO: rd = LO (128-bit)
|
|
void recPMFLO()
|
|
{
|
|
if (!_Rd_) return;
|
|
mmiInvalidateDest(_Rd_);
|
|
|
|
armAsm->Ldr(RQSCRATCH, armCpuRegMem(&cpuRegs.LO.UQ));
|
|
mmiStoreReg(_Rd_, RQSCRATCH);
|
|
}
|
|
|
|
// PMTHI: HI = rs (128-bit). Take Rs from its NEON slot when allocated and store
|
|
// straight to HI memory. HI is never NEON-resident in the EE rec (no opcode
|
|
// passes XMMINFO_*HI — see iR5900Templates-arm64.cpp), so no allocator
|
|
// invalidation is needed.
|
|
void recPMTHI()
|
|
{
|
|
int info = eeRecompileCodeXMM(XMMINFO_READS);
|
|
(void)info;
|
|
armAsm->Str(armQRegister(EEREC_S), armCpuRegMem(&cpuRegs.HI.UQ));
|
|
}
|
|
|
|
// PMTLO: LO = rs (128-bit). LO is never NEON-resident (see recPMTHI).
|
|
void recPMTLO()
|
|
{
|
|
int info = eeRecompileCodeXMM(XMMINFO_READS);
|
|
(void)info;
|
|
armAsm->Str(armQRegister(EEREC_S), armCpuRegMem(&cpuRegs.LO.UQ));
|
|
}
|
|
|
|
// ============================================================================
|
|
// Packed Shifts (by immediate sa field)
|
|
// ============================================================================
|
|
|
|
// GE-16: allocator-resident single-insn shifts through the XMM template
|
|
// (rd = SHIFT(rt)) — the old shape memory-bounced via mmiFlush+Ldr+op+Str
|
|
// and evicted residency at every immediate shift. sa==0 degenerates to a
|
|
// register move (skipped when the allocator aliased rd onto rt).
|
|
|
|
void recPSLLW()
|
|
{
|
|
MMI_2OP_SETUP();
|
|
if (_Sa_ == 0)
|
|
{
|
|
if (!qd.Is(qt))
|
|
armAsm->Mov(qd.V16B(), qt.V16B());
|
|
return;
|
|
}
|
|
armAsm->Shl(qd.V4S(), qt.V4S(), _Sa_);
|
|
}
|
|
|
|
void recPSRLW()
|
|
{
|
|
MMI_2OP_SETUP();
|
|
if (_Sa_ == 0)
|
|
{
|
|
if (!qd.Is(qt))
|
|
armAsm->Mov(qd.V16B(), qt.V16B());
|
|
return;
|
|
}
|
|
armAsm->Ushr(qd.V4S(), qt.V4S(), _Sa_);
|
|
}
|
|
|
|
void recPSRAW()
|
|
{
|
|
MMI_2OP_SETUP();
|
|
if (_Sa_ == 0)
|
|
{
|
|
if (!qd.Is(qt))
|
|
armAsm->Mov(qd.V16B(), qt.V16B());
|
|
return;
|
|
}
|
|
armAsm->Sshr(qd.V4S(), qt.V4S(), _Sa_);
|
|
}
|
|
|
|
// Halfword shifts: interp uses (_Sa_ & 0xf) per MMI.cpp:228/240/252 —
|
|
// only 4 of the 5 sa bits are live since the lane is 16-bit. vixl
|
|
// Shl/Ushr/Sshr V8H require shift ∈ [0,15]; mask up front to match
|
|
// interp and stay inside the encoder's range.
|
|
void recPSLLH()
|
|
{
|
|
MMI_2OP_SETUP();
|
|
const u32 sa = _Sa_ & 0xf;
|
|
if (sa == 0)
|
|
{
|
|
if (!qd.Is(qt))
|
|
armAsm->Mov(qd.V16B(), qt.V16B());
|
|
return;
|
|
}
|
|
armAsm->Shl(qd.V8H(), qt.V8H(), sa);
|
|
}
|
|
|
|
void recPSRLH()
|
|
{
|
|
MMI_2OP_SETUP();
|
|
const u32 sa = _Sa_ & 0xf;
|
|
if (sa == 0)
|
|
{
|
|
if (!qd.Is(qt))
|
|
armAsm->Mov(qd.V16B(), qt.V16B());
|
|
return;
|
|
}
|
|
armAsm->Ushr(qd.V8H(), qt.V8H(), sa);
|
|
}
|
|
|
|
void recPSRAH()
|
|
{
|
|
MMI_2OP_SETUP();
|
|
const u32 sa = _Sa_ & 0xf;
|
|
if (sa == 0)
|
|
{
|
|
if (!qd.Is(qt))
|
|
armAsm->Mov(qd.V16B(), qt.V16B());
|
|
return;
|
|
}
|
|
armAsm->Sshr(qd.V8H(), qt.V8H(), sa);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Pack / Unpack (Extend / Compress)
|
|
// ============================================================================
|
|
|
|
// PEXTLW: interleave lower 32-bit words of rs and rt
|
|
// rd = {rs.UL[1], rt.UL[1], rs.UL[0], rt.UL[0]}
|
|
void recPEXTLW()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Zip1(qd.V4S(), qt.V4S(), qs.V4S());
|
|
}
|
|
|
|
// PEXTUW: interleave upper 32-bit words of rs and rt
|
|
// rd = {rs.UL[3], rt.UL[3], rs.UL[2], rt.UL[2]}
|
|
void recPEXTUW()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Zip2(qd.V4S(), qt.V4S(), qs.V4S());
|
|
}
|
|
|
|
// PEXTLH: interleave lower 16-bit halfwords
|
|
void recPEXTLH()
|
|
{
|
|
if (!_Rd_)
|
|
return;
|
|
|
|
// rs==0 fast path: the odd output halfwords are all zero, so this
|
|
// is just a zero-extend of rt's lower 4 halfwords to words. Skip requesting
|
|
// XMMINFO_READS — otherwise the allocator pins a callee-saved NEON reg and
|
|
// materializes a zero vector for r0 just to Zip it in. Zip1(qt,qt) duplicates
|
|
// each halfword, then Ushr clears the high half of every word lane.
|
|
if (_Rs_ == 0)
|
|
{
|
|
int info = eeRecompileCodeXMM(XMMINFO_READT | XMMINFO_WRITED);
|
|
const a64::VRegister qt = armQRegister(EEREC_T);
|
|
const a64::VRegister qd = armQRegister(EEREC_D);
|
|
(void)info;
|
|
armAsm->Zip1(qd.V8H(), qt.V8H(), qt.V8H()); // {H0,H0,H1,H1,H2,H2,H3,H3}
|
|
armAsm->Ushr(qd.V4S(), qd.V4S(), 16); // {0:H0, 0:H1, 0:H2, 0:H3}
|
|
return;
|
|
}
|
|
|
|
MMI_3OP_SETUP();
|
|
armAsm->Zip1(qd.V8H(), qt.V8H(), qs.V8H());
|
|
}
|
|
|
|
// PEXTUH: interleave upper 16-bit halfwords
|
|
void recPEXTUH()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Zip2(qd.V8H(), qt.V8H(), qs.V8H());
|
|
}
|
|
|
|
// PEXTLB: interleave lower bytes
|
|
void recPEXTLB()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Zip1(qd.V16B(), qt.V16B(), qs.V16B());
|
|
}
|
|
|
|
// PEXTUB: interleave upper bytes
|
|
void recPEXTUB()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Zip2(qd.V16B(), qt.V16B(), qs.V16B());
|
|
}
|
|
|
|
// PPACW: pack words — {rs.UL[2], rs.UL[0], rt.UL[2], rt.UL[0]}
|
|
void recPPACW()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Uzp1(qd.V4S(), qt.V4S(), qs.V4S());
|
|
}
|
|
|
|
// PPACH: pack halfwords — even halfwords from rs and rt
|
|
void recPPACH()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Uzp1(qd.V8H(), qt.V8H(), qs.V8H());
|
|
}
|
|
|
|
// PPACB: pack bytes — even bytes from rs and rt
|
|
void recPPACB()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
armAsm->Uzp1(qd.V16B(), qt.V16B(), qs.V16B());
|
|
}
|
|
|
|
// PADSBH: rd.UH[0..3] = rs.UH[0..3] - rt.UH[0..3], rd.UH[4..7] = rs.UH[4..7] + rt.UH[4..7]
|
|
// Lower 4 halfwords: subtract. Upper 4 halfwords: add.
|
|
void recPADSBH()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
// Compute the add into a scratch FIRST. If Rd aliases Rs or Rt, the
|
|
// allocator hands qd back as the same Q-reg as qs/qt — writing qd in
|
|
// the sub step would clobber the source still needed for the add.
|
|
armAsm->Add(RQSCRATCH.V8H(), qs.V8H(), qt.V8H());
|
|
// qd = sub result (all 8 halfwords); safe to clobber qs/qt now.
|
|
armAsm->Sub(qd.V8H(), qs.V8H(), qt.V8H());
|
|
// Blend: keep lower 64 bits of sub in qd, upper 64 bits from add.
|
|
armAsm->Mov(qd.V2D(), 1, RQSCRATCH.V2D(), 1);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Interleave halfwords
|
|
// ============================================================================
|
|
|
|
// PINTH: rd.US[2k]=Rt.US[k], rd.US[2k+1]=Rs.US[k+4], k=0..3 — interleave low 4
|
|
// halfwords of Rt with high 4 of Rs.
|
|
void recPINTH()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
// Move rs upper 64 → low position of scratch (don't clobber qs).
|
|
armAsm->Dup(RQSCRATCH.V2D(), qs.V2D(), 1); // tmp = {rs.UD[1], rs.UD[1]}
|
|
// zip1.8h of rt(lower) and rs_upper(lower) gives interleaved result.
|
|
armAsm->Zip1(qd.V8H(), qt.V8H(), RQSCRATCH.V8H());
|
|
}
|
|
|
|
// PINTEH: rd = {rs.UH[6],rt.UH[6], rs.UH[4],rt.UH[4], rs.UH[2],rt.UH[2], rs.UH[0],rt.UH[0]}
|
|
// Interleave even halfwords
|
|
void recPINTEH()
|
|
{
|
|
MMI_3OP_SETUP();
|
|
// Extract even halfwords from each into scratch — never touch qs/qt.
|
|
armAsm->Uzp1(RQSCRATCH.V8H(), qs.V8H(), qs.V8H()); // rs evens in lower 64
|
|
armAsm->Uzp1(RQSCRATCH2.V8H(), qt.V8H(), qt.V8H()); // rt evens in lower 64
|
|
// Zip the lower 64 bits of each into qd.
|
|
armAsm->Zip1(qd.V8H(), RQSCRATCH2.V8H(), RQSCRATCH.V8H());
|
|
}
|
|
|
|
// ============================================================================
|
|
// Shuffles / Permutations
|
|
// ============================================================================
|
|
|
|
// PEXEW: rd = {rt[2], rt[1], rt[0], rt[3]} (lane order) — swap words 0 and 2.
|
|
// 2-op idiom (Rev64 + Ext) instead of a scratch snapshot + full copy + 2 lane
|
|
// inserts. Both ops read qt fully before writing, so it is alias-safe when the
|
|
// allocator hands back qd == qt.
|
|
void recPEXEW()
|
|
{
|
|
MMI_2OP_SETUP();
|
|
armAsm->Rev64(qd.V4S(), qt.V4S()); // {rt[1],rt[0],rt[3],rt[2]}
|
|
armAsm->Ext(qd.V16B(), qd.V16B(), qd.V16B(), 12); // {rt[2],rt[1],rt[0],rt[3]}
|
|
}
|
|
|
|
// PEXEH: swap halfwords 0↔2 in each 64-bit lane
|
|
// rd = {H[2],H[1],H[0],H[3], H[6],H[5],H[4],H[7]}
|
|
void recPEXEH()
|
|
{
|
|
MMI_2OP_SETUP();
|
|
armAsm->Mov(RQSCRATCH.V16B(), qt.V16B());
|
|
armAsm->Mov(qd.V8H(), RQSCRATCH.V8H());
|
|
armAsm->Mov(qd.V8H(), 0, RQSCRATCH.V8H(), 2);
|
|
armAsm->Mov(qd.V8H(), 2, RQSCRATCH.V8H(), 0);
|
|
armAsm->Mov(qd.V8H(), 4, RQSCRATCH.V8H(), 6);
|
|
armAsm->Mov(qd.V8H(), 6, RQSCRATCH.V8H(), 4);
|
|
}
|
|
|
|
// PREVH: reverse halfwords within each 64-bit lane
|
|
// rd = {H[3],H[2],H[1],H[0], H[7],H[6],H[5],H[4]}
|
|
void recPREVH()
|
|
{
|
|
MMI_2OP_SETUP();
|
|
armAsm->Rev64(qd.V8H(), qt.V8H());
|
|
}
|
|
|
|
// PROT3W: rotate lower 3 words: rd = {rt[1], rt[2], rt[0], rt[3]} (lane order).
|
|
// 3-op shuffle (Rev64 + Ext + Zip1) instead of a scratch snapshot + full copy
|
|
// + 3 lane inserts. Rev64 and Ext read qt into scratches
|
|
// first, so Zip1 → qd is alias-safe when qd == qt.
|
|
// rev = {rt[1],rt[0],rt[3],rt[2]}
|
|
// ext8 = {rt[2],rt[3],rt[0],rt[1]}
|
|
// Zip1(rev,ext8) = {rev[0],ext8[0],rev[1],ext8[1]} = {rt[1],rt[2],rt[0],rt[3]}
|
|
void recPROT3W()
|
|
{
|
|
MMI_2OP_SETUP();
|
|
armAsm->Rev64(RQSCRATCH.V4S(), qt.V4S());
|
|
armAsm->Ext(RQSCRATCH2.V16B(), qt.V16B(), qt.V16B(), 8);
|
|
armAsm->Zip1(qd.V4S(), RQSCRATCH.V4S(), RQSCRATCH2.V4S());
|
|
}
|
|
|
|
// PEXCW: swap words 1 and 2: rd = {rt[0], rt[2], rt[1], rt[3]} (lane order).
|
|
// 2-op idiom (Rev64 + Uzp1) instead of a scratch snapshot + full copy + 2 lane
|
|
// inserts. Both read their sources fully before
|
|
// writing qd, so it is alias-safe when qd == qt.
|
|
// rev = {rt[1],rt[0],rt[3],rt[2]}
|
|
// Uzp1(qt,rev) = {qt[0],qt[2],rev[0],rev[2]} = {rt[0],rt[2],rt[1],rt[3]}
|
|
void recPEXCW()
|
|
{
|
|
MMI_2OP_SETUP();
|
|
armAsm->Rev64(RQSCRATCH.V4S(), qt.V4S());
|
|
armAsm->Uzp1(qd.V4S(), qt.V4S(), RQSCRATCH.V4S());
|
|
}
|
|
|
|
// PEXCH: swap halfwords 1↔2 within each 64-bit lane
|
|
// {H[0],H[2],H[1],H[3], H[4],H[6],H[5],H[7]}
|
|
void recPEXCH()
|
|
{
|
|
MMI_2OP_SETUP();
|
|
armAsm->Mov(RQSCRATCH.V16B(), qt.V16B());
|
|
armAsm->Mov(qd.V8H(), RQSCRATCH.V8H());
|
|
armAsm->Mov(qd.V8H(), 1, RQSCRATCH.V8H(), 2);
|
|
armAsm->Mov(qd.V8H(), 2, RQSCRATCH.V8H(), 1);
|
|
armAsm->Mov(qd.V8H(), 5, RQSCRATCH.V8H(), 6);
|
|
armAsm->Mov(qd.V8H(), 6, RQSCRATCH.V8H(), 5);
|
|
}
|
|
|
|
// PEXT5: expand each 32-bit lane's PS2 RGB1555 field into BGRA8 layout.
|
|
// Per-lane:
|
|
// rd = ((rt & 0x001F) << 3) // R bits [4:0] -> [7:3]
|
|
// | ((rt & 0x03E0) << 6) // G bits [9:5] -> [15:11]
|
|
// | ((rt & 0x7C00) << 9) // B bits [14:10] -> [23:19]
|
|
// | ((rt & 0x8000) << 16); // A bit [15] -> [31]
|
|
void recPEXT5()
|
|
{
|
|
MMI_2OP_SETUP();
|
|
// Preserve qt in case allocator assigned qd == qt — rt is needed for all
|
|
// four shift+mask passes below, but the first write to qd would clobber
|
|
// it if they share a slot.
|
|
armAsm->Mov(RQSCRATCH3.V16B(), qt.V16B());
|
|
|
|
// Field 0: (rt << 3) & 0x000000F8 -> qd
|
|
armAsm->Shl(qd.V4S(), RQSCRATCH3.V4S(), 3);
|
|
armAsm->Movi(RQSCRATCH.V4S(), 0xF8);
|
|
armAsm->And(qd.V16B(), qd.V16B(), RQSCRATCH.V16B());
|
|
|
|
// Field 1: (rt << 6) & 0x0000F800 -> qd
|
|
armAsm->Shl(RQSCRATCH2.V4S(), RQSCRATCH3.V4S(), 6);
|
|
armAsm->Movi(RQSCRATCH.V4S(), 0xF8, vixl::aarch64::LSL, 8);
|
|
armAsm->And(RQSCRATCH2.V16B(), RQSCRATCH2.V16B(), RQSCRATCH.V16B());
|
|
armAsm->Orr(qd.V16B(), qd.V16B(), RQSCRATCH2.V16B());
|
|
|
|
// Field 2: (rt << 9) & 0x00F80000 -> qd
|
|
armAsm->Shl(RQSCRATCH2.V4S(), RQSCRATCH3.V4S(), 9);
|
|
armAsm->Movi(RQSCRATCH.V4S(), 0xF8, vixl::aarch64::LSL, 16);
|
|
armAsm->And(RQSCRATCH2.V16B(), RQSCRATCH2.V16B(), RQSCRATCH.V16B());
|
|
armAsm->Orr(qd.V16B(), qd.V16B(), RQSCRATCH2.V16B());
|
|
|
|
// Field 3: (rt << 16) & 0x80000000 -> qd
|
|
armAsm->Shl(RQSCRATCH2.V4S(), RQSCRATCH3.V4S(), 16);
|
|
armAsm->Movi(RQSCRATCH.V4S(), 0x80, vixl::aarch64::LSL, 24);
|
|
armAsm->And(RQSCRATCH2.V16B(), RQSCRATCH2.V16B(), RQSCRATCH.V16B());
|
|
armAsm->Orr(qd.V16B(), qd.V16B(), RQSCRATCH2.V16B());
|
|
}
|
|
|
|
// PPAC5: pack BGRA8-style 32-bit lanes back into PS2 RGB1555 16-bit layout
|
|
// (the inverse of PEXT5). Upper 16 bits of each lane left as garbage from
|
|
// the shifted-source — interp does not mask them either.
|
|
// Per-lane:
|
|
// rd = ((rt >> 3) & 0x001F)
|
|
// | ((rt >> 6) & 0x03E0)
|
|
// | ((rt >> 9) & 0x7C00)
|
|
// | ((rt >> 16) & 0x8000);
|
|
void recPPAC5()
|
|
{
|
|
MMI_2OP_SETUP();
|
|
armAsm->Mov(RQSCRATCH3.V16B(), qt.V16B());
|
|
|
|
// Field 0: (rt >> 3) & 0x0000001F -> qd
|
|
armAsm->Ushr(qd.V4S(), RQSCRATCH3.V4S(), 3);
|
|
armAsm->Movi(RQSCRATCH.V4S(), 0x1F);
|
|
armAsm->And(qd.V16B(), qd.V16B(), RQSCRATCH.V16B());
|
|
|
|
// Field 1: (rt >> 6) & 0x000003E0 -> qd
|
|
// 0x3E0 has two non-zero bytes; vixl's Movi macro materializes it via
|
|
// Mov scratch_w + Dup (2 host insns) rather than the single LSL form.
|
|
armAsm->Ushr(RQSCRATCH2.V4S(), RQSCRATCH3.V4S(), 6);
|
|
armAsm->Movi(RQSCRATCH.V4S(), 0x3E0);
|
|
armAsm->And(RQSCRATCH2.V16B(), RQSCRATCH2.V16B(), RQSCRATCH.V16B());
|
|
armAsm->Orr(qd.V16B(), qd.V16B(), RQSCRATCH2.V16B());
|
|
|
|
// Field 2: (rt >> 9) & 0x00007C00 -> qd
|
|
armAsm->Ushr(RQSCRATCH2.V4S(), RQSCRATCH3.V4S(), 9);
|
|
armAsm->Movi(RQSCRATCH.V4S(), 0x7C, vixl::aarch64::LSL, 8);
|
|
armAsm->And(RQSCRATCH2.V16B(), RQSCRATCH2.V16B(), RQSCRATCH.V16B());
|
|
armAsm->Orr(qd.V16B(), qd.V16B(), RQSCRATCH2.V16B());
|
|
|
|
// Field 3: (rt >> 16) & 0x00008000 -> qd
|
|
armAsm->Ushr(RQSCRATCH2.V4S(), RQSCRATCH3.V4S(), 16);
|
|
armAsm->Movi(RQSCRATCH.V4S(), 0x80, vixl::aarch64::LSL, 8);
|
|
armAsm->And(RQSCRATCH2.V16B(), RQSCRATCH2.V16B(), RQSCRATCH.V16B());
|
|
armAsm->Orr(qd.V16B(), qd.V16B(), RQSCRATCH2.V16B());
|
|
}
|
|
|
|
// ============================================================================
|
|
// Variable shifts — operate on words 0 and 2 only, sign-extend to 64
|
|
// ============================================================================
|
|
|
|
// PSLLVW: rd.SD[0] = sign_ext(rt.UL[0] << (rs.UL[0] & 0x1F))
|
|
// rd.SD[1] = sign_ext(rt.UL[2] << (rs.UL[2] & 0x1F))
|
|
void recPSLLVW()
|
|
{
|
|
if (!_Rd_) return;
|
|
mmiFlushReg(_Rs_);
|
|
mmiFlushReg(_Rt_);
|
|
mmiInvalidateDest(_Rd_);
|
|
|
|
// Word 0
|
|
armLoadEERegPtr(a64::w0, &cpuRegs.GPR.r[_Rt_].UL[0]);
|
|
armLoadEERegPtr(a64::w1, &cpuRegs.GPR.r[_Rs_].UL[0]);
|
|
armAsm->And(a64::w1, a64::w1, 0x1F);
|
|
armAsm->Lsl(a64::w0, a64::w0, a64::w1);
|
|
armAsm->Sxtw(a64::x0, a64::w0);
|
|
armStoreEERegPtr(a64::x0, &cpuRegs.GPR.r[_Rd_].UD[0]);
|
|
|
|
// Word 2
|
|
armLoadEERegPtr(a64::w0, &cpuRegs.GPR.r[_Rt_].UL[2]);
|
|
armLoadEERegPtr(a64::w1, &cpuRegs.GPR.r[_Rs_].UL[2]);
|
|
armAsm->And(a64::w1, a64::w1, 0x1F);
|
|
armAsm->Lsl(a64::w0, a64::w0, a64::w1);
|
|
armAsm->Sxtw(a64::x0, a64::w0);
|
|
armStoreEERegPtr(a64::x0, &cpuRegs.GPR.r[_Rd_].UD[1]);
|
|
}
|
|
|
|
void recPSRLVW()
|
|
{
|
|
if (!_Rd_) return;
|
|
mmiFlushReg(_Rs_);
|
|
mmiFlushReg(_Rt_);
|
|
mmiInvalidateDest(_Rd_);
|
|
|
|
armLoadEERegPtr(a64::w0, &cpuRegs.GPR.r[_Rt_].UL[0]);
|
|
armLoadEERegPtr(a64::w1, &cpuRegs.GPR.r[_Rs_].UL[0]);
|
|
armAsm->And(a64::w1, a64::w1, 0x1F);
|
|
armAsm->Lsr(a64::w0, a64::w0, a64::w1);
|
|
armAsm->Sxtw(a64::x0, a64::w0);
|
|
armStoreEERegPtr(a64::x0, &cpuRegs.GPR.r[_Rd_].UD[0]);
|
|
|
|
armLoadEERegPtr(a64::w0, &cpuRegs.GPR.r[_Rt_].UL[2]);
|
|
armLoadEERegPtr(a64::w1, &cpuRegs.GPR.r[_Rs_].UL[2]);
|
|
armAsm->And(a64::w1, a64::w1, 0x1F);
|
|
armAsm->Lsr(a64::w0, a64::w0, a64::w1);
|
|
armAsm->Sxtw(a64::x0, a64::w0);
|
|
armStoreEERegPtr(a64::x0, &cpuRegs.GPR.r[_Rd_].UD[1]);
|
|
}
|
|
|
|
void recPSRAVW()
|
|
{
|
|
if (!_Rd_) return;
|
|
mmiFlushReg(_Rs_);
|
|
mmiFlushReg(_Rt_);
|
|
mmiInvalidateDest(_Rd_);
|
|
|
|
armLoadEERegPtr(a64::w0, &cpuRegs.GPR.r[_Rt_].UL[0]);
|
|
armLoadEERegPtr(a64::w1, &cpuRegs.GPR.r[_Rs_].UL[0]);
|
|
armAsm->And(a64::w1, a64::w1, 0x1F);
|
|
armAsm->Asr(a64::w0, a64::w0, a64::w1);
|
|
armAsm->Sxtw(a64::x0, a64::w0);
|
|
armStoreEERegPtr(a64::x0, &cpuRegs.GPR.r[_Rd_].UD[0]);
|
|
|
|
armLoadEERegPtr(a64::w0, &cpuRegs.GPR.r[_Rt_].UL[2]);
|
|
armLoadEERegPtr(a64::w1, &cpuRegs.GPR.r[_Rs_].UL[2]);
|
|
armAsm->And(a64::w1, a64::w1, 0x1F);
|
|
armAsm->Asr(a64::w0, a64::w0, a64::w1);
|
|
armAsm->Sxtw(a64::x0, a64::w0);
|
|
armStoreEERegPtr(a64::x0, &cpuRegs.GPR.r[_Rd_].UD[1]);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Multiply / Divide / MAC - PDIV* stay interp (x86 parity); PMADD*/PMSUBW native
|
|
// ============================================================================
|
|
|
|
// PDIVW / PDIVBW / PDIVUW stay as REC_FUNC because production x86 does too:
|
|
// its MMI2_RECOMPILE "native" path for these is itself `recCall(Interp::PDIV*)`
|
|
// after a targeted `_deleteEEreg(_Rd_, 0)`, so there is no codegen to port, and
|
|
// AArch64 NEON has no integer divide to build one from either.
|
|
//
|
|
// (MMI2_RECOMPILE itself is defined unconditionally at Config.h:1650 — an
|
|
// earlier version of this comment called it "never-defined", which made the
|
|
// rest of pcsx2/x86/iMMI.cpp look like dead reference code when it is in fact
|
|
// the shipping x86 implementation.)
|
|
//
|
|
// PMADDUW gets a native impl below — its interp is plain u64 arithmetic (no
|
|
// errata), so a NEON port matches interp bit-for-bit.
|
|
REC_FUNC(PDIVW);
|
|
REC_FUNC(PDIVBW);
|
|
REC_FUNC(PDIVUW);
|
|
|
|
// One PMADDW/PMSUBW lane pair (dd = dest half, ss = source word index).
|
|
//
|
|
// The accumulator is one 64-bit quantity per lane, not two 32-bit halves:
|
|
// acc = LO.UL[ss] | HI.UL[ss] << 32
|
|
// result = acc ± (s64)Rs.SL[ss] * Rt.SL[ss] (full 64-bit, carry kept)
|
|
// LO.SD[dd] = (s32)result HI.SD[dd] = (s32)(result >> 32)
|
|
// Rd.UD[dd] = LO.UL[dd*2] | HI.UL[dd*2] << 32, i.e. `result` itself
|
|
//
|
|
// This replaces a lane-split version that emitted an SDIV by 0xFFFFFFFF and a
|
|
// conditional +0x70000000 lane-0 addend to reproduce the PS2 multiply errata.
|
|
// Those were wrong: against the ps2autotests console captures the errata form
|
|
// scores 54/64 and this one 64/64 (see MMI.cpp _PMADDW). The interpreter was
|
|
// changed in the same commit, so the two engines still agree.
|
|
//
|
|
// Scratches w8/w9/w10 are outside the allocatable GPR pool and armCpuRegMem is
|
|
// a pure [RSTATE, #imm] MemOperand (no address scratch). Lane 1 reads word 2 of
|
|
// each source, which lane 0's SD[0]/UD[0] stores never touch, so sequential
|
|
// per-lane commit matches the interp's ordering under every Rd/Rs/Rt aliasing.
|
|
static void recPMADDWLane(int dd, int ss, bool isSub)
|
|
{
|
|
// armLoadEERegPtr: lane 0 (word 0) substitutes a pinned reg's mirror —
|
|
// required under lazy-dirty (memory lower half may be stale) and a free
|
|
// Ldr→Mov under write-through; lane 1 (word 2) is upper-half → memory is
|
|
// always canonical there and the helper falls through to the plain Ldr.
|
|
armLoadEERegPtr(a64::w8, &cpuRegs.GPR.r[_Rs_].SL[ss]);
|
|
armLoadEERegPtr(a64::w9, &cpuRegs.GPR.r[_Rt_].SL[ss]);
|
|
armAsm->Smull(a64::x10, a64::w8, a64::w9);
|
|
|
|
// Both loads are 32-bit, so the upper halves are zeroed and a shifted Orr
|
|
// splices the accumulator without masking.
|
|
armAsm->Ldr(a64::w8, armCpuRegMem(&cpuRegs.HI.UL[ss]));
|
|
armAsm->Ldr(a64::w9, armCpuRegMem(&cpuRegs.LO.UL[ss]));
|
|
armAsm->Orr(a64::x9, a64::x9, a64::Operand(a64::x8, a64::LSL, 32));
|
|
|
|
if (!isSub)
|
|
armAsm->Add(a64::x9, a64::x9, a64::x10);
|
|
else
|
|
armAsm->Sub(a64::x9, a64::x9, a64::x10);
|
|
|
|
// An Asr of the 64-bit result by 32 IS the sign-extended high word, so HI
|
|
// needs no separate Sxtw.
|
|
armAsm->Sxtw(a64::x10, a64::w9);
|
|
armAsm->Str(a64::x10, armCpuRegMem(&cpuRegs.LO.SD[dd]));
|
|
armAsm->Asr(a64::x8, a64::x9, 32);
|
|
armAsm->Str(a64::x8, armCpuRegMem(&cpuRegs.HI.SD[dd]));
|
|
|
|
if (_Rd_)
|
|
{
|
|
// Rd.UD[dd] is the raw result: its low word is what LO keeps and its
|
|
// high word is what HI keeps. Through armStoreEERegPtr, NOT a raw Str:
|
|
// a raw store bypassed the pin mirror, leaving a pinned Rd's mirror
|
|
// stale after PMADDW/PMSUBW (latent under write-through — any
|
|
// pin-served read of Rd afterward saw the old value; fatal under
|
|
// lazy-dirty, where the seam flush then clobbered the result).
|
|
armStoreEERegPtr(a64::x9, &cpuRegs.GPR.r[_Rd_].UD[dd]);
|
|
}
|
|
}
|
|
|
|
void recPMADDW()
|
|
{
|
|
mmiFlushReg(_Rs_);
|
|
mmiFlushReg(_Rt_);
|
|
if (_Rd_)
|
|
mmiInvalidateDest(_Rd_);
|
|
recPMADDWLane(0, 0, false);
|
|
recPMADDWLane(1, 2, false);
|
|
}
|
|
|
|
void recPMSUBW()
|
|
{
|
|
mmiFlushReg(_Rs_);
|
|
mmiFlushReg(_Rt_);
|
|
if (_Rd_)
|
|
mmiInvalidateDest(_Rd_);
|
|
recPMADDWLane(0, 0, true);
|
|
recPMADDWLane(1, 2, true);
|
|
}
|
|
|
|
// PMULTW: 2-lane signed 32x32->64 multiply on even-indexed source words.
|
|
// prod[0] = (s64)Rs.SL[0] * (s64)Rt.SL[0]
|
|
// prod[1] = (s64)Rs.SL[2] * (s64)Rt.SL[2]
|
|
// LO.UD[0..1] = sign-extended low32 of each product
|
|
// HI.UD[0..1] = sign-extended high32 of each product
|
|
// Rd.SD[0..1] = full 64-bit products
|
|
void recPMULTW()
|
|
{
|
|
mmiFlushReg(_Rs_);
|
|
mmiFlushReg(_Rt_);
|
|
if (_Rd_)
|
|
mmiInvalidateDest(_Rd_);
|
|
|
|
mmiLoadReg(RQSCRATCH, _Rs_);
|
|
mmiLoadReg(RQSCRATCH2, _Rt_);
|
|
|
|
// Pack even-indexed 32-bit lanes into the low half (SL[0],SL[2] -> S[0],S[1])
|
|
armAsm->Uzp1(RQSCRATCH.V4S(), RQSCRATCH.V4S(), RQSCRATCH.V4S());
|
|
armAsm->Uzp1(RQSCRATCH2.V4S(), RQSCRATCH2.V4S(), RQSCRATCH2.V4S());
|
|
|
|
// 2-lane signed 32x32->64 -> { prod0, prod1 } as 2x64
|
|
armAsm->Smull(RQSCRATCH3.V2D(), RQSCRATCH.V2S(), RQSCRATCH2.V2S());
|
|
|
|
// LO = sign-extended low32 of each product
|
|
armAsm->Xtn(RQSCRATCH.V2S(), RQSCRATCH3.V2D());
|
|
armAsm->Sxtl(RQSCRATCH.V2D(), RQSCRATCH.V2S());
|
|
armAsm->Str(RQSCRATCH, armCpuRegMem(&cpuRegs.LO.UQ));
|
|
|
|
// HI = sign-extended high32 of each product (shift right narrow + sxtl)
|
|
armAsm->Shrn(RQSCRATCH.V2S(), RQSCRATCH3.V2D(), 32);
|
|
armAsm->Sxtl(RQSCRATCH.V2D(), RQSCRATCH.V2S());
|
|
armAsm->Str(RQSCRATCH, armCpuRegMem(&cpuRegs.HI.UQ));
|
|
|
|
if (_Rd_)
|
|
mmiStoreReg(_Rd_, RQSCRATCH3);
|
|
}
|
|
|
|
// PMULTUW: 2-lane unsigned 32x32->64 multiply on even-indexed source words.
|
|
// prod[0] = (u64)Rs.UL[0] * (u64)Rt.UL[0]
|
|
// prod[1] = (u64)Rs.UL[2] * (u64)Rt.UL[2]
|
|
// LO.UD[0..1] = sign-extended low32 of each product (interp casts (s32))
|
|
// HI.UD[0..1] = sign-extended high32 of each product
|
|
// Rd.UD[0..1] = full 64-bit products
|
|
void recPMULTUW()
|
|
{
|
|
mmiFlushReg(_Rs_);
|
|
mmiFlushReg(_Rt_);
|
|
if (_Rd_)
|
|
mmiInvalidateDest(_Rd_);
|
|
|
|
mmiLoadReg(RQSCRATCH, _Rs_);
|
|
mmiLoadReg(RQSCRATCH2, _Rt_);
|
|
|
|
armAsm->Uzp1(RQSCRATCH.V4S(), RQSCRATCH.V4S(), RQSCRATCH.V4S());
|
|
armAsm->Uzp1(RQSCRATCH2.V4S(), RQSCRATCH2.V4S(), RQSCRATCH2.V4S());
|
|
|
|
armAsm->Umull(RQSCRATCH3.V2D(), RQSCRATCH.V2S(), RQSCRATCH2.V2S());
|
|
|
|
armAsm->Xtn(RQSCRATCH.V2S(), RQSCRATCH3.V2D());
|
|
armAsm->Sxtl(RQSCRATCH.V2D(), RQSCRATCH.V2S());
|
|
armAsm->Str(RQSCRATCH, armCpuRegMem(&cpuRegs.LO.UQ));
|
|
|
|
armAsm->Shrn(RQSCRATCH.V2S(), RQSCRATCH3.V2D(), 32);
|
|
armAsm->Sxtl(RQSCRATCH.V2D(), RQSCRATCH.V2S());
|
|
armAsm->Str(RQSCRATCH, armCpuRegMem(&cpuRegs.HI.UQ));
|
|
|
|
if (_Rd_)
|
|
mmiStoreReg(_Rd_, RQSCRATCH3);
|
|
}
|
|
|
|
// PMADDUW: 2-lane unsigned 32x32+64->64 multiply-accumulate on even-indexed
|
|
// source words.
|
|
// tempu[k] = (u64)(LO.UL[2k] | (HI.UL[2k] << 32)) + (u64)Rs.UL[2k] * Rt.UL[2k]
|
|
// LO.UD[k] = sign-extended low32 of tempu[k]
|
|
// HI.UD[k] = sign-extended high32 of tempu[k]
|
|
// Rd.UD[k] = tempu[k] (full u64)
|
|
//
|
|
// Interp has no PS2 multiplication errata for the unsigned variant — plain u64
|
|
// arithmetic — so this matches interp bit-for-bit (unlike PMADDW/PMSUBW which
|
|
// stay REC_FUNC above).
|
|
//
|
|
// Bypasses the LO/HI allocator path: EE rec's info-word layout packs EEREC_LO
|
|
// and EEREC_HI into the same 5-bit field (the EEREC_LO/EEREC_HI info-word
|
|
// macros decode the same bits), so it can't produce two distinct register
|
|
// indices. An op that needs both LO and HI live must load them from memory.
|
|
void recPMADDUW()
|
|
{
|
|
mmiFlushReg(_Rs_);
|
|
mmiFlushReg(_Rt_);
|
|
if (_Rd_)
|
|
mmiInvalidateDest(_Rd_);
|
|
|
|
// LO/HI are never NEON-resident in the EE rec (no opcode passes XMMINFO_*LO/HI),
|
|
// so the Ldrs below already see fresh memory — no allocator flush needed.
|
|
mmiLoadReg(RQSCRATCH, _Rs_);
|
|
mmiLoadReg(RQSCRATCH2, _Rt_);
|
|
|
|
// Pack even-indexed 32-bit lanes into the low half (UL[0],UL[2] -> S[0],S[1])
|
|
armAsm->Uzp1(RQSCRATCH.V4S(), RQSCRATCH.V4S(), RQSCRATCH.V4S());
|
|
armAsm->Uzp1(RQSCRATCH2.V4S(), RQSCRATCH2.V4S(), RQSCRATCH2.V4S());
|
|
|
|
// 2-lane unsigned 32x32->64 product
|
|
armAsm->Umull(RQSCRATCH3.V2D(), RQSCRATCH.V2S(), RQSCRATCH2.V2S());
|
|
|
|
// Compose accumulator: { LO.UL[0] | HI.UL[0]<<32, LO.UL[2] | HI.UL[2]<<32 }
|
|
// Trn1.V4S(d, a, b) = { a[0], b[0], a[2], b[2] } -> as V2D, gives LO|HI<<32 per lane.
|
|
armAsm->Ldr(RQSCRATCH, armCpuRegMem(&cpuRegs.LO.UQ));
|
|
armAsm->Ldr(RQSCRATCH2, armCpuRegMem(&cpuRegs.HI.UQ));
|
|
armAsm->Trn1(RQSCRATCH.V4S(), RQSCRATCH.V4S(), RQSCRATCH2.V4S());
|
|
|
|
// sum = composed + product (2x64 unsigned add)
|
|
armAsm->Add(RQSCRATCH3.V2D(), RQSCRATCH.V2D(), RQSCRATCH3.V2D());
|
|
|
|
// LO = sign-extended low32 of each 64-bit lane
|
|
armAsm->Xtn(RQSCRATCH.V2S(), RQSCRATCH3.V2D());
|
|
armAsm->Sxtl(RQSCRATCH.V2D(), RQSCRATCH.V2S());
|
|
armAsm->Str(RQSCRATCH, armCpuRegMem(&cpuRegs.LO.UQ));
|
|
|
|
// HI = sign-extended high32 of each 64-bit lane
|
|
armAsm->Shrn(RQSCRATCH.V2S(), RQSCRATCH3.V2D(), 32);
|
|
armAsm->Sxtl(RQSCRATCH.V2D(), RQSCRATCH.V2S());
|
|
armAsm->Str(RQSCRATCH, armCpuRegMem(&cpuRegs.HI.UQ));
|
|
|
|
// Rd = full 2x64 unsigned sum
|
|
if (_Rd_)
|
|
mmiStoreReg(_Rd_, RQSCRATCH3);
|
|
}
|
|
|
|
// PHMADH: 8-lane signed 16x16->32 multiply, pair-sum (n + n+1):
|
|
// sum[k] = Rs.SH[2k]*Rt.SH[2k] + Rs.SH[2k+1]*Rt.SH[2k+1] for k = 0..3
|
|
// firsttemp[k] = Rs.SH[2k+1]*Rt.SH[2k+1] (the second product of each pair)
|
|
// LO = { sum[0], firsttemp[0], sum[2], firsttemp[2] }
|
|
// HI = { sum[1], firsttemp[1], sum[3], firsttemp[3] }
|
|
// Rd = { sum[0], sum[1], sum[2], sum[3] } (post-update LO/HI even lanes)
|
|
void recPHMADH()
|
|
{
|
|
mmiFlushReg(_Rs_);
|
|
mmiFlushReg(_Rt_);
|
|
if (_Rd_)
|
|
mmiInvalidateDest(_Rd_);
|
|
|
|
mmiLoadReg(RQSCRATCH, _Rs_);
|
|
mmiLoadReg(RQSCRATCH2, _Rt_);
|
|
|
|
// p_lo = { Rs.SH[i] * Rt.SH[i] } for i = 0..3 (as 4x32)
|
|
// p_hi = { Rs.SH[i] * Rt.SH[i] } for i = 4..7 (as 4x32)
|
|
armAsm->Smull(RQSCRATCH3.V4S(), RQSCRATCH.V4H(), RQSCRATCH2.V4H());
|
|
armAsm->Smull2(RQSCRATCH.V4S(), RQSCRATCH.V8H(), RQSCRATCH2.V8H());
|
|
|
|
// sums = ADDP(p_lo, p_hi).4S = { p0+p1, p2+p3, p4+p5, p6+p7 }
|
|
armAsm->Addp(RQSCRATCH2.V4S(), RQSCRATCH3.V4S(), RQSCRATCH.V4S());
|
|
// firsts = UZP2(p_lo, p_hi).4S = { p1, p3, p5, p7 }
|
|
armAsm->Uzp2(RQSCRATCH3.V4S(), RQSCRATCH3.V4S(), RQSCRATCH.V4S());
|
|
|
|
// LO = TRN1.4S(sums, firsts) = { sum0, p1, sum2, p5 }
|
|
armAsm->Trn1(RQSCRATCH.V4S(), RQSCRATCH2.V4S(), RQSCRATCH3.V4S());
|
|
armAsm->Str(RQSCRATCH, armCpuRegMem(&cpuRegs.LO.UQ));
|
|
|
|
// HI = TRN2.4S(sums, firsts) = { sum1, p3, sum3, p7 }
|
|
armAsm->Trn2(RQSCRATCH.V4S(), RQSCRATCH2.V4S(), RQSCRATCH3.V4S());
|
|
armAsm->Str(RQSCRATCH, armCpuRegMem(&cpuRegs.HI.UQ));
|
|
|
|
if (_Rd_)
|
|
mmiStoreReg(_Rd_, RQSCRATCH2);
|
|
}
|
|
|
|
// PHMSBH: 8-lane signed 16x16->32 multiply, pair-diff (n+1 - n):
|
|
// sum[k] = Rs.SH[2k+1]*Rt.SH[2k+1] - Rs.SH[2k]*Rt.SH[2k] (k = 0..3)
|
|
// firsttemp[k] = Rs.SH[2k+1]*Rt.SH[2k+1] (the second product per pair)
|
|
// LO = { sum[0], ~firsttemp[0], sum[2], ~firsttemp[2] } (note: bitwise NOT)
|
|
// HI = { sum[1], ~firsttemp[1], sum[3], ~firsttemp[3] }
|
|
// Rd = { sum[0], sum[1], sum[2], sum[3] }
|
|
void recPHMSBH()
|
|
{
|
|
mmiFlushReg(_Rs_);
|
|
mmiFlushReg(_Rt_);
|
|
if (_Rd_)
|
|
mmiInvalidateDest(_Rd_);
|
|
|
|
mmiLoadReg(RQSCRATCH, _Rs_);
|
|
mmiLoadReg(RQSCRATCH2, _Rt_);
|
|
|
|
armAsm->Smull(RQSCRATCH3.V4S(), RQSCRATCH.V4H(), RQSCRATCH2.V4H());
|
|
armAsm->Smull2(RQSCRATCH.V4S(), RQSCRATCH.V8H(), RQSCRATCH2.V8H());
|
|
|
|
// odds = UZP2(p_lo, p_hi).4S = { p1, p3, p5, p7 } (firsttemps)
|
|
// evens = UZP1(p_lo, p_hi).4S = { p0, p2, p4, p6 }
|
|
armAsm->Uzp2(RQSCRATCH2.V4S(), RQSCRATCH3.V4S(), RQSCRATCH.V4S());
|
|
armAsm->Uzp1(RQSCRATCH3.V4S(), RQSCRATCH3.V4S(), RQSCRATCH.V4S());
|
|
|
|
// sums = odds - evens = { p1-p0, p3-p2, p5-p4, p7-p6 }
|
|
armAsm->Sub(RQSCRATCH.V4S(), RQSCRATCH2.V4S(), RQSCRATCH3.V4S());
|
|
|
|
// nfirsts = ~odds (reuse RQSCRATCH3 — evens are dead after Sub)
|
|
armAsm->Mvn(RQSCRATCH3.V16B(), RQSCRATCH2.V16B());
|
|
|
|
if (_Rd_)
|
|
mmiStoreReg(_Rd_, RQSCRATCH);
|
|
|
|
// LO = TRN1.4S(sums, nfirsts) = { sum0, ~p1, sum2, ~p5 }
|
|
armAsm->Trn1(RQSCRATCH2.V4S(), RQSCRATCH.V4S(), RQSCRATCH3.V4S());
|
|
armAsm->Str(RQSCRATCH2, armCpuRegMem(&cpuRegs.LO.UQ));
|
|
|
|
// HI = TRN2.4S(sums, nfirsts) = { sum1, ~p3, sum3, ~p7 }
|
|
armAsm->Trn2(RQSCRATCH2.V4S(), RQSCRATCH.V4S(), RQSCRATCH3.V4S());
|
|
armAsm->Str(RQSCRATCH2, armCpuRegMem(&cpuRegs.HI.UQ));
|
|
}
|
|
|
|
// PMULTH: 8-lane signed 16x16->32 multiply.
|
|
// r[i] = Rs.SH[i] * Rt.SH[i] for i in 0..7
|
|
// LO = { r0, r1, r4, r5 }
|
|
// HI = { r2, r3, r6, r7 }
|
|
// Rd = { r0, r2, r4, r6 } (even-indexed products)
|
|
void recPMULTH()
|
|
{
|
|
mmiFlushReg(_Rs_);
|
|
mmiFlushReg(_Rt_);
|
|
if (_Rd_)
|
|
mmiInvalidateDest(_Rd_);
|
|
|
|
mmiLoadReg(RQSCRATCH, _Rs_);
|
|
mmiLoadReg(RQSCRATCH2, _Rt_);
|
|
|
|
// q29 = SMULL Rs.4H, Rt.4H -> { r0,r1,r2,r3 } as 4x32
|
|
// q31 = SMULL2 Rs.8H, Rt.8H -> { r4,r5,r6,r7 } as 4x32 (in-place over Rt)
|
|
armAsm->Smull(RQSCRATCH3.V4S(), RQSCRATCH.V4H(), RQSCRATCH2.V4H());
|
|
armAsm->Smull2(RQSCRATCH2.V4S(), RQSCRATCH.V8H(), RQSCRATCH2.V8H());
|
|
|
|
// LO = TRN1.2D(prod_lo, prod_hi) = { prod_lo.D[0], prod_hi.D[0] } = { r0,r1,r4,r5 }
|
|
armAsm->Trn1(RQSCRATCH.V2D(), RQSCRATCH3.V2D(), RQSCRATCH2.V2D());
|
|
armAsm->Str(RQSCRATCH, armCpuRegMem(&cpuRegs.LO.UQ));
|
|
|
|
// HI = TRN2.2D(prod_lo, prod_hi) = { prod_lo.D[1], prod_hi.D[1] } = { r2,r3,r6,r7 }
|
|
armAsm->Trn2(RQSCRATCH.V2D(), RQSCRATCH3.V2D(), RQSCRATCH2.V2D());
|
|
armAsm->Str(RQSCRATCH, armCpuRegMem(&cpuRegs.HI.UQ));
|
|
|
|
if (_Rd_)
|
|
{
|
|
// Rd = UZP1.4S(prod_lo, prod_hi) = { r0, r2, r4, r6 } (even-indexed)
|
|
armAsm->Uzp1(RQSCRATCH.V4S(), RQSCRATCH3.V4S(), RQSCRATCH2.V4S());
|
|
mmiStoreReg(_Rd_, RQSCRATCH);
|
|
}
|
|
}
|
|
|
|
// PMADDH: 8-lane signed 16x16->32 multiply, accumulate into existing LO/HI.
|
|
// r[i] = Rs.SH[i] * Rt.SH[i] for i in 0..7
|
|
// LO.UL[0..3] += { r0, r1, r4, r5 }
|
|
// HI.UL[0..3] += { r2, r3, r6, r7 }
|
|
// Rd = { new_LO[0], new_HI[0], new_LO[2], new_HI[2] }
|
|
void recPMADDH()
|
|
{
|
|
mmiFlushReg(_Rs_);
|
|
mmiFlushReg(_Rt_);
|
|
if (_Rd_)
|
|
mmiInvalidateDest(_Rd_);
|
|
|
|
mmiLoadReg(RQSCRATCH, _Rs_);
|
|
mmiLoadReg(RQSCRATCH2, _Rt_);
|
|
|
|
armAsm->Smull(RQSCRATCH3.V4S(), RQSCRATCH.V4H(), RQSCRATCH2.V4H());
|
|
armAsm->Smull2(RQSCRATCH2.V4S(), RQSCRATCH.V8H(), RQSCRATCH2.V8H());
|
|
|
|
// q30 = LO_increment = { r0,r1,r4,r5 }
|
|
armAsm->Trn1(RQSCRATCH.V2D(), RQSCRATCH3.V2D(), RQSCRATCH2.V2D());
|
|
// q29 = HI_increment = { r2,r3,r6,r7 }
|
|
armAsm->Trn2(RQSCRATCH3.V2D(), RQSCRATCH3.V2D(), RQSCRATCH2.V2D());
|
|
|
|
// q31 = old LO; add and store
|
|
armAsm->Ldr(RQSCRATCH2, armCpuRegMem(&cpuRegs.LO.UQ));
|
|
armAsm->Add(RQSCRATCH.V4S(), RQSCRATCH2.V4S(), RQSCRATCH.V4S());
|
|
armAsm->Str(RQSCRATCH, armCpuRegMem(&cpuRegs.LO.UQ));
|
|
|
|
// q31 = old HI; add and store
|
|
armAsm->Ldr(RQSCRATCH2, armCpuRegMem(&cpuRegs.HI.UQ));
|
|
armAsm->Add(RQSCRATCH3.V4S(), RQSCRATCH2.V4S(), RQSCRATCH3.V4S());
|
|
armAsm->Str(RQSCRATCH3, armCpuRegMem(&cpuRegs.HI.UQ));
|
|
|
|
if (_Rd_)
|
|
{
|
|
// Rd = TRN1.4S(new_LO, new_HI) = { LO[0], HI[0], LO[2], HI[2] }
|
|
armAsm->Trn1(RQSCRATCH.V4S(), RQSCRATCH.V4S(), RQSCRATCH3.V4S());
|
|
mmiStoreReg(_Rd_, RQSCRATCH);
|
|
}
|
|
}
|
|
|
|
// PMSUBH: 8-lane signed 16x16->32 multiply, subtract from existing LO/HI.
|
|
// r[i] = Rs.SH[i] * Rt.SH[i] for i in 0..7
|
|
// LO.UL[0..3] -= { r0, r1, r4, r5 }
|
|
// HI.UL[0..3] -= { r2, r3, r6, r7 }
|
|
// Rd = { new_LO[0], new_HI[0], new_LO[2], new_HI[2] }
|
|
void recPMSUBH()
|
|
{
|
|
mmiFlushReg(_Rs_);
|
|
mmiFlushReg(_Rt_);
|
|
if (_Rd_)
|
|
mmiInvalidateDest(_Rd_);
|
|
|
|
mmiLoadReg(RQSCRATCH, _Rs_);
|
|
mmiLoadReg(RQSCRATCH2, _Rt_);
|
|
|
|
armAsm->Smull(RQSCRATCH3.V4S(), RQSCRATCH.V4H(), RQSCRATCH2.V4H());
|
|
armAsm->Smull2(RQSCRATCH2.V4S(), RQSCRATCH.V8H(), RQSCRATCH2.V8H());
|
|
|
|
armAsm->Trn1(RQSCRATCH.V2D(), RQSCRATCH3.V2D(), RQSCRATCH2.V2D());
|
|
armAsm->Trn2(RQSCRATCH3.V2D(), RQSCRATCH3.V2D(), RQSCRATCH2.V2D());
|
|
|
|
armAsm->Ldr(RQSCRATCH2, armCpuRegMem(&cpuRegs.LO.UQ));
|
|
armAsm->Sub(RQSCRATCH.V4S(), RQSCRATCH2.V4S(), RQSCRATCH.V4S());
|
|
armAsm->Str(RQSCRATCH, armCpuRegMem(&cpuRegs.LO.UQ));
|
|
|
|
armAsm->Ldr(RQSCRATCH2, armCpuRegMem(&cpuRegs.HI.UQ));
|
|
armAsm->Sub(RQSCRATCH3.V4S(), RQSCRATCH2.V4S(), RQSCRATCH3.V4S());
|
|
armAsm->Str(RQSCRATCH3, armCpuRegMem(&cpuRegs.HI.UQ));
|
|
|
|
if (_Rd_)
|
|
{
|
|
armAsm->Trn1(RQSCRATCH.V4S(), RQSCRATCH.V4S(), RQSCRATCH3.V4S());
|
|
mmiStoreReg(_Rd_, RQSCRATCH);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// QFSRV: Quad Funnel Shift Right Variable
|
|
// ============================================================================
|
|
|
|
// QFSRV: Rd = {Rs, Rt} >> (sa * 8), truncated to 128 bits.
|
|
// cpuRegs.sa is in bytes (0-15). Concatenate Rt (low) and Rs (high)
|
|
// into a 256-bit value, shift right by sa bytes, take lower 128 bits.
|
|
//
|
|
// TBL over a two-register table is exactly this operation: the table is 32
|
|
// bytes with Rt at 0-15 and Rs at 16-31, and byte i of the result is
|
|
// table[sa + i]. The index vector is a {0..15} ramp plus a broadcast sa, so
|
|
// the largest index a legal sa can produce is 15 + 15 = 30 and the table
|
|
// always covers it.
|
|
//
|
|
// SSE has no cross-register variable byte shift, so the x86 emitter spills
|
|
// both operands to a static buffer and reloads 128 bits at offset sa. This
|
|
// did the same until the table form replaced it. The reload was the
|
|
// expensive part: a 16-byte load is forwarded from the two stores only when
|
|
// it is 8-byte aligned, so fourteen of the sixteen sa values drained the
|
|
// store buffer instead.
|
|
//
|
|
// The other consequence is on the And below. An out-of-range index cannot
|
|
// name a host address here — TBL answers zero for one — so that mask now
|
|
// keeps the guest semantics (sa is a byte count mod 16) rather than standing
|
|
// between a guest-written sa and a host out-of-bounds read. The
|
|
// adjacent-source path above still indexes host memory and still needs it for
|
|
// the original reason.
|
|
void recQFSRV()
|
|
{
|
|
if (!_Rd_) return;
|
|
|
|
mmiFlushReg(_Rs_);
|
|
mmiFlushReg(_Rt_);
|
|
mmiInvalidateDest(_Rd_);
|
|
|
|
// Adjacent sources (Rs == Rt+1) are contiguous in the GPR array, so a
|
|
// 16-byte read at &GPR.r[Rt] + sa funnels them without a TBL. That shortcut
|
|
// was removed: it built a host address out of sa, and a pinned source —
|
|
// twelve of the adjacent pairs have one — had to be flushed into the window
|
|
// first, an 8-byte store under a 16-byte load that drains the store buffer
|
|
// at 13.1 cycles instead of forwarding. Unpinned it ran 2.38 against this
|
|
// sequence's 2.13 (A78C) and 2.34 against 2.00 (X1C).
|
|
|
|
// index = sa + {0..15}, built in RQSCRATCH3 before the operands land so
|
|
// the broadcast can borrow RQSCRATCH.
|
|
armAsm->Ldr(RQSCRATCH3, armCpuRegMem(&_cpuRegistersPack.byteRamp));
|
|
armLoadEERegPtr(RWSCRATCH, &cpuRegs.sa);
|
|
// SA is 4 bits and MTSA/MTSAB/MTSAH all mask at the write, so this is
|
|
// belt-and-braces. A TBL index past the 32-byte table answers zero, so no
|
|
// host address depends on it.
|
|
armAsm->And(RWSCRATCH, RWSCRATCH, 0xf);
|
|
armAsm->Dup(RQSCRATCH.V16B(), RWSCRATCH);
|
|
armAsm->Add(RQSCRATCH3.V16B(), RQSCRATCH3.V16B(), RQSCRATCH.V16B());
|
|
|
|
// The table has to be a consecutive pair, which q30/q31 are. Emitted
|
|
// directly rather than through armEmitVTBL, whose assert rejects
|
|
// RQSCRATCH/RQSCRATCH2 as sources: it reserves them for the copy it makes
|
|
// when the pair is not consecutive, the one case this cannot hit.
|
|
mmiLoadReg(RQSCRATCH, _Rt_); // table bytes 0..15
|
|
mmiLoadReg(RQSCRATCH2, _Rs_); // table bytes 16..31
|
|
armAsm->Tbl(RQSCRATCH3.V16B(), RQSCRATCH.V16B(), RQSCRATCH2.V16B(), RQSCRATCH3.V16B());
|
|
|
|
armStoreEEGPRQuad(RQSCRATCH3, _Rd_);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Other MMI
|
|
// ============================================================================
|
|
|
|
// PLZCW: count leading sign bits (excluding the sign bit itself) for words 0 and 1
|
|
void recPLZCW()
|
|
{
|
|
if (!_Rd_) return;
|
|
mmiFlushReg(_Rs_);
|
|
mmiInvalidateDest(_Rd_);
|
|
|
|
// Word 0: ARM64 CLS counts leading sign bits excluding the MSB sign bit itself,
|
|
// which matches the PS2 PLZCW definition (CountLeadingSignBits - 1).
|
|
armLoadEERegPtr(a64::w0, &cpuRegs.GPR.r[_Rs_].UL[0]);
|
|
armAsm->Cls(a64::w0, a64::w0);
|
|
armStoreEERegPtr(a64::w0, &cpuRegs.GPR.r[_Rd_].UL[0]);
|
|
|
|
// Word 1
|
|
armLoadEERegPtr(a64::w0, &cpuRegs.GPR.r[_Rs_].UL[1]);
|
|
armAsm->Cls(a64::w0, a64::w0);
|
|
armStoreEERegPtr(a64::w0, &cpuRegs.GPR.r[_Rd_].UL[1]);
|
|
}
|
|
|
|
// PMFHL — read LO/HI 128-bit register pair, dispatch on sa:
|
|
// 0x00 LW : Rd = { LO.UL[0], HI.UL[0], LO.UL[2], HI.UL[2] }
|
|
// 0x01 UW : Rd = { LO.UL[1], HI.UL[1], LO.UL[3], HI.UL[3] }
|
|
// 0x02 SLW: composed s64 (HI.UL[2k]:LO.UL[2k]) signed-saturated to s32
|
|
// then sign-extended to s64; written to Rd.UD[k] for k=0,1.
|
|
// 0x03 LH : Rd.US lanes from even-indexed LO/HI halfwords, interleaved
|
|
// at 32-bit-pair granularity.
|
|
// 0x04 SH : per-lane s32→s16 signed saturation of LO/HI words, interleaved
|
|
// at 32-bit-pair granularity.
|
|
// sa >= 5 — interpreter is a no-op (no default-case in MMI.cpp PMFHL); mirror
|
|
// that by early-returning without touching Rd. (x86 rec asserts on this path,
|
|
// but the interp doesn't, so the recompiled-vs-interp diff would fail an assert
|
|
// rather than catch real divergence — matching the interp is the safer choice.)
|
|
void recPMFHL()
|
|
{
|
|
if (!_Rd_)
|
|
return;
|
|
if (_Sa_ > 0x04)
|
|
return;
|
|
|
|
// LO/HI loaded directly from memory rather than via the allocator
|
|
// (XMMINFO_READLO | XMMINFO_READHI). Reason: the arm64 EE rec's
|
|
// info-word layout packs PROCESS_EE_SET_LO and PROCESS_EE_SET_HI
|
|
// into the SAME 5-bit field at bits 23..27 (see iCore-arm64.h —
|
|
// EEREC_LO == EEREC_HI == EEREC_ACC). PMFHL needs both LO and HI as
|
|
// simultaneous live inputs, which that field collision cannot represent.
|
|
// Bypass with explicit Ldrs. LO/HI are never NEON-resident in the EE rec
|
|
// (no opcode passes XMMINFO_*LO/HI), so the memory image is already current.
|
|
|
|
int info = eeRecompileCodeXMM(XMMINFO_WRITED);
|
|
const a64::VRegister qd = armQRegister(EEREC_D);
|
|
(void)info;
|
|
|
|
// Pre-loaded into reserved scratch quads (outside the allocator pool).
|
|
const a64::VRegister qlo = RQSCRATCH;
|
|
const a64::VRegister qhi = RQSCRATCH2;
|
|
armAsm->Ldr(qlo, armCpuRegMem(&cpuRegs.LO.UQ));
|
|
armAsm->Ldr(qhi, armCpuRegMem(&cpuRegs.HI.UQ));
|
|
|
|
switch (_Sa_)
|
|
{
|
|
case 0x00: // LW: pick even-indexed words from LO/HI and interleave
|
|
// TRN1.V4S → { LO.S[0], HI.S[0], LO.S[2], HI.S[2] }
|
|
armAsm->Trn1(qd.V4S(), qlo.V4S(), qhi.V4S());
|
|
break;
|
|
|
|
case 0x01: // UW: pick odd-indexed words from LO/HI and interleave
|
|
// TRN2.V4S → { LO.S[1], HI.S[1], LO.S[3], HI.S[3] }
|
|
armAsm->Trn2(qd.V4S(), qlo.V4S(), qhi.V4S());
|
|
break;
|
|
|
|
case 0x02: // SLW: compose s64 (HI:LO) per even-word lane, saturate to s32, sign-extend back
|
|
// TRN1.V4S → V2D { (HI[0]:LO[0]), (HI[2]:LO[2]) } (LO in low 32 of each 64)
|
|
// SQXTN.V2S — signed-saturating narrow 2x64 → 2x32 (matches interp's
|
|
// "in-range -> (s64)(s32)LO.UL[2k]; saturate to INT32_MIN/MAX" bounds).
|
|
// SXTL.V2D — sign-extend 2x32 → 2x64 (= the recorded Rd.UD shape).
|
|
armAsm->Trn1(qd.V4S(), qlo.V4S(), qhi.V4S());
|
|
armAsm->Sqxtn(qd.V2S(), qd.V2D());
|
|
armAsm->Sxtl(qd.V2D(), qd.V2S());
|
|
break;
|
|
|
|
case 0x03: // LH: even halfwords from LO/HI, interleaved at S-pair granularity
|
|
// UZP1.V8H(x, x) gathers x's even halfwords into the low 64 bits of x.
|
|
// ZIP1.V4S picks S[0]/S[1] of each input → output S[0..3] =
|
|
// { (LO[0]:LO[2]), (HI[0]:HI[2]), (LO[4]:LO[6]), (HI[4]:HI[6]) }
|
|
// which as V8H = { LO[0], LO[2], HI[0], HI[2], LO[4], LO[6], HI[4], HI[6] }.
|
|
armAsm->Uzp1(qlo.V8H(), qlo.V8H(), qlo.V8H());
|
|
armAsm->Uzp1(qhi.V8H(), qhi.V8H(), qhi.V8H());
|
|
armAsm->Zip1(qd.V4S(), qlo.V4S(), qhi.V4S());
|
|
break;
|
|
|
|
case 0x04: // SH: signed-saturating narrow 32→16 per word, interleaved at S-pair granularity
|
|
// SQXTN.V4H — 4x32 signed-sat narrowed to 4x16 in low 64 of each scratch.
|
|
// ZIP1.V4S → output S[0..3] = { sat(LO[0..1]), sat(HI[0..1]), sat(LO[2..3]), sat(HI[2..3]) }
|
|
// which as V8H is exactly the interp's PMFHL_CLAMP-per-lane pattern.
|
|
armAsm->Sqxtn(qlo.V4H(), qlo.V4S());
|
|
armAsm->Sqxtn(qhi.V4H(), qhi.V4S());
|
|
armAsm->Zip1(qd.V4S(), qlo.V4S(), qhi.V4S());
|
|
break;
|
|
}
|
|
}
|
|
|
|
// PMTHL.LW: even-indexed words of LO/HI receive Rs's four words; the
|
|
// odd-indexed words (UL[1] and UL[3] of each) are preserved. Matches
|
|
// interp at MMI.cpp:217-224 and x86 BLENDPS/SHUFPS sequence at
|
|
// iMMI.cpp:234-248. Strategy: load LO/HI as Q regs, INS lanes 1+3 from
|
|
// the prior values to preserve them; lane 0 and lane 2 come from Rs's
|
|
// word 0/2 for LO, word 1/3 for HI.
|
|
void recPMTHL()
|
|
{
|
|
if (_Sa_ != 0)
|
|
return;
|
|
|
|
mmiFlushReg(_Rs_);
|
|
mmiLoadReg(RQSCRATCH, _Rs_);
|
|
|
|
// LO_new = [Rs.UL[0], LO.UL[1], Rs.UL[2], LO.UL[3]]
|
|
armAsm->Ldr(RQSCRATCH2, armCpuRegMem(&cpuRegs.LO.UQ));
|
|
armAsm->Mov(RQSCRATCH3.V16B(), RQSCRATCH.V16B());
|
|
armAsm->Ins(RQSCRATCH3.V4S(), 1, RQSCRATCH2.V4S(), 1);
|
|
armAsm->Ins(RQSCRATCH3.V4S(), 3, RQSCRATCH2.V4S(), 3);
|
|
armAsm->Str(RQSCRATCH3, armCpuRegMem(&cpuRegs.LO.UQ));
|
|
|
|
// HI_new = [Rs.UL[1], HI.UL[1], Rs.UL[3], HI.UL[3]]
|
|
armAsm->Ldr(RQSCRATCH3, armCpuRegMem(&cpuRegs.HI.UQ));
|
|
armAsm->Ins(RQSCRATCH3.V4S(), 0, RQSCRATCH.V4S(), 1);
|
|
armAsm->Ins(RQSCRATCH3.V4S(), 2, RQSCRATCH.V4S(), 3);
|
|
armAsm->Str(RQSCRATCH3, armCpuRegMem(&cpuRegs.HI.UQ));
|
|
}
|
|
|
|
} // namespace MMI
|
|
} // namespace OpcodeImpl
|
|
} // namespace Dynarec
|
|
} // namespace R5900
|