mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
arm64/EE: write-through pinned read-cache for $sp/$ra in x22/x23
Pin the two hottest guest GPRs into callee-saved host registers: x22 mirrors GPR.r[29].UD[0] ($sp), x23 mirrors GPR.r[31].UD[0] ($ra). Memory stays canonical — every guest-visible write still stores to cpuRegs.GPR and refreshes the mirror at the same emission point — so there is no new C-call or block-exit contract, no const-prop coupling, and no allocator involvement. Reads that would load UD[0] from memory use the mirror instead: - armLoadEERegPtr substitutes Mov-from-pin for the Ldr (covers the allocator MODE_READ load, _eeMoveGPRtoR's memory fallback, and every op-file read), deleting the dependent load at the head of scalar ops. - The scalar ALU/imm/shift memLoad helpers return the pin register directly (zero instructions for a pinned source); memStore helpers take the value register so pass-through cases (DADDU rd, rs, $zero; DSLL rd, rt, 0; ...) store the pin with no intermediate Mov. - armStoreEERegPtr write-through: Mov for 64-bit stores, Bfi for UL[0]/UL[1] halves, reload-from-memory for odd shapes. New armStoreEEGPRQuad covers the 128-bit writers (NEON writeback, MMI, LQ, QMFC2) with a lane-0 UMOV refresh. - Pins reload from memory at every JIT entry and after the C calls that can write guest GPRs: recEventTest (savestate load), recRecompile (ELF hooks), recCall/recBranchCall (interpreter fallbacks), eeloadHook/eeloadHook2. - x22/x23 leave the dynamic allocator pool (ALLOCATABLE_MASK). They were the only pin-capable callee-saved regs: x26-x28 are the mVU macro-mode VI cache and the vtlb unaligned helpers' 3 simultaneous callee-saved temps. COP2 macro mode never touches x22/x23 (flags go through the s_cop2DenormStatusFlag memory scratch, not gprF2/F3); the mVU micro dispatcher saves/restores x19-x28. This is the P2 extraction from the neither/LRPS2 RegCache deep-dive (2026-07-01): the safe subset of static pinning — reads free, writes keep the str — without neither's cross-block dirty-pin hazard. SD865 A/B (codegen_ab.sh, 5 runs, --renderer null): SotC insns -0.06% / cycles -1.36% (IPC 1.826->1.850), UYA insns -0.50% / cycles -1.36% (IPC 2.390->2.411) — the guest-reg load latency was not OoO-hidden on the A77, confirming the deep-dive's prediction. M2: SotC cyc -0.67%, UYA cyc +0.86% (wider OoO window hides the loads; device is the gate). Tests: new ee_rec_pinned_gpr_tests (5 cases: scalar reads, scalar write-through + read-back, 128-bit MMI write-through, vtlb-load write-through, CFC2 half-word Bfi path), each red under a deliberate pin-update mutation and green with it restored. Full recompiler_tests 1052/1052. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
02cc7c7f89
commit
e575485963
@@ -710,7 +710,7 @@ void recCOP2_QMFC2()
|
||||
|
||||
// 128-bit copy: VU0.VF[fs] → cpuRegs.GPR.r[rt]
|
||||
armAsm->Ldr(RQSCRATCH, armVU0Mem(&VU0.VF[_Rd_]));
|
||||
armAsm->Str(RQSCRATCH, armCpuRegMem(&cpuRegs.GPR.r[_Rt_]));
|
||||
armStoreEEGPRQuad(RQSCRATCH, _Rt_);
|
||||
}
|
||||
|
||||
// QMTC2: VU0.VF[fs] = cpuRegs.GPR[rt] (128-bit copy, EE GPR → VF)
|
||||
@@ -748,16 +748,16 @@ void recCOP2_CFC2()
|
||||
// REG_R: mask to 23 bits, write only UL[0]
|
||||
armAsm->Ldr(RWSCRATCH, armVU0Mem(&VU0.VI[REG_R]));
|
||||
armAsm->And(RWSCRATCH, RWSCRATCH, 0x7FFFFF);
|
||||
armAsm->Str(RWSCRATCH, armCpuRegMem(&cpuRegs.GPR.r[_Rt_].UL[0]));
|
||||
armStoreEERegPtr(RWSCRATCH, &cpuRegs.GPR.r[_Rt_].UL[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// General VI: load 32-bit, sign-extend to UL[0]+UL[1]
|
||||
armAsm->Ldr(RWSCRATCH, armVU0Mem(&VU0.VI[_Rd_]));
|
||||
armAsm->Str(RWSCRATCH, armCpuRegMem(&cpuRegs.GPR.r[_Rt_].UL[0]));
|
||||
armStoreEERegPtr(RWSCRATCH, &cpuRegs.GPR.r[_Rt_].UL[0]);
|
||||
// Sign-extend: UL[1] = (UL[0] & 0x80000000) ? 0xFFFFFFFF : 0
|
||||
armAsm->Asr(RWSCRATCH, RWSCRATCH, 31);
|
||||
armAsm->Str(RWSCRATCH, armCpuRegMem(&cpuRegs.GPR.r[_Rt_].UL[1]));
|
||||
armStoreEERegPtr(RWSCRATCH, &cpuRegs.GPR.r[_Rt_].UL[1]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,8 @@ _arm64neonregs arm64neon[NUM_ARM_NEON_REGS], s_saveArm64NEONregs[NUM_ARM_NEON_RE
|
||||
// x19: RFASTMEMBASE — NOT allocatable (reserved for fastmem base)
|
||||
// x20: RSTATE — NOT allocatable (reserved for cpuRegs pointer)
|
||||
// x21: RPSXSTATE — NOT allocatable (reserved for psxRegs pointer in IOP JIT)
|
||||
// x22-x23: callee-saved (allocatable)
|
||||
// x22: REEPIN_SP — NOT allocatable (pinned mirror of GPR.r[29].UD[0], $sp)
|
||||
// x23: REEPIN_RA — NOT allocatable (pinned mirror of GPR.r[31].UD[0], $ra)
|
||||
// x24: RVU0 — NOT allocatable (reserved for &VU0 pointer in EE COP2 JIT)
|
||||
// x25: RECCYCLE — NOT allocatable (pinned cycle delta: cycle - nextEventCycle)
|
||||
// x26-x28: callee-saved (allocatable)
|
||||
@@ -77,6 +78,7 @@ _arm64neonregs arm64neon[NUM_ARM_NEON_REGS], s_saveArm64NEONregs[NUM_ARM_NEON_RE
|
||||
// bit 19 — x19 : RFASTMEMBASE
|
||||
// bit 20 — x20 : RSTATE (cpuRegs base pointer)
|
||||
// bit 21 — x21 : RPSXSTATE (psxRegs base; shared alloc table with EE)
|
||||
// bits 22-23 — x22/x23 : REEPIN_SP/REEPIN_RA (pinned $sp/$ra mirrors)
|
||||
// bit 24 — x24 : RVU0 (pinned &VU0 for iCOP2)
|
||||
// bit 25 — x25 : RECCYCLE (pinned cycle delta)
|
||||
// bits 29-30 — x29/x30 : FP, LR — never allocatable
|
||||
@@ -87,6 +89,7 @@ static constexpr uint32_t ALLOCATABLE_MASK = ~((1u << 8)
|
||||
| (1u << 9) | (1u << 10)
|
||||
| (7u << 16)
|
||||
| (1u << 19) | (1u << 20) | (1u << 21)
|
||||
| (3u << 22)
|
||||
| (1u << 24) | (1u << 25)
|
||||
| (3u << 29));
|
||||
|
||||
@@ -437,7 +440,7 @@ void _flushConstReg(int reg)
|
||||
if (GPR_IS_CONST1(reg) && !(g_cpuFlushedConstReg & (1 << reg)))
|
||||
{
|
||||
armAsm->Mov(RXSCRATCH, static_cast<s64>(g_cpuConstRegs[reg].SD[0]));
|
||||
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.GPR.r[reg].UD[0]));
|
||||
armStoreEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[reg].UD[0]);
|
||||
g_cpuFlushedConstReg |= (1 << reg);
|
||||
if (reg == 0)
|
||||
DevCon.Warning("Flushing r0!");
|
||||
@@ -452,7 +455,7 @@ void _flushConstRegs(bool delete_const)
|
||||
continue;
|
||||
|
||||
armAsm->Mov(RXSCRATCH, static_cast<u64>(g_cpuConstRegs[i].UD[0]));
|
||||
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.GPR.r[i].UD[0]));
|
||||
armStoreEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[i].UD[0]);
|
||||
g_cpuFlushedConstReg |= 1u << i;
|
||||
}
|
||||
|
||||
@@ -922,7 +925,7 @@ void _writebackNEONreg(int neonreg)
|
||||
else if (reg == NEONGPR_HI)
|
||||
armStorePtr(armQRegister(neonreg), &cpuRegs.HI.UQ);
|
||||
else
|
||||
armStorePtr(armQRegister(neonreg), &cpuRegs.GPR.r[reg].UQ);
|
||||
armStoreEEGPRQuad(armQRegister(neonreg), reg);
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ static void mmiLoadReg(const a64::VRegister& qreg, int gpr)
|
||||
static void mmiStoreReg(int gpr, const a64::VRegister& qreg)
|
||||
{
|
||||
pxAssert(gpr != 0);
|
||||
armAsm->Str(qreg, armCpuRegMem(&cpuRegs.GPR.r[gpr].UQ));
|
||||
armStoreEEGPRQuad(qreg, gpr);
|
||||
}
|
||||
|
||||
// Standard 3-operand MMI: rd = rs OP rt (128-bit).
|
||||
@@ -1269,7 +1269,7 @@ void recQFSRV()
|
||||
armMoveAddressToReg(RSCRATCHADDR, &cpuRegs.GPR.r[_Rt_]);
|
||||
armAsm->Add(RSCRATCHADDR, RSCRATCHADDR, RXSCRATCH);
|
||||
armAsm->Ldr(RQSCRATCH, a64::MemOperand(RSCRATCHADDR));
|
||||
armAsm->Str(RQSCRATCH, armCpuRegMem(&cpuRegs.GPR.r[_Rd_]));
|
||||
armStoreEEGPRQuad(RQSCRATCH, _Rd_);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1291,7 +1291,7 @@ void recQFSRV()
|
||||
armAsm->Ldr(RQSCRATCH, a64::MemOperand(RSCRATCHADDR));
|
||||
|
||||
// Store result to Rd
|
||||
armAsm->Str(RQSCRATCH, armCpuRegMem(&cpuRegs.GPR.r[_Rd_]));
|
||||
armStoreEEGPRQuad(RQSCRATCH, _Rd_);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -422,6 +422,9 @@ static const void* _DynGen_JITCompile()
|
||||
armEmitCall((void*)recRecompile);
|
||||
|
||||
armReloadCycleDelta();
|
||||
// Compile-time hooks (EntryPointCompilingOnCPUThread → game starting /
|
||||
// ELF load) can write guest GPRs — refresh the pin mirrors.
|
||||
armReloadEEGPRPins();
|
||||
|
||||
armEmitJmp(DispatcherReg);
|
||||
|
||||
@@ -438,6 +441,9 @@ static const void* _DynGen_DispatcherEvent()
|
||||
armFlushCycleDelta();
|
||||
armEmitCall((void*)recEventTest);
|
||||
armReloadCycleDelta();
|
||||
// Event processing can rewrite every guest GPR (savestate load on the EE
|
||||
// thread, debugger pokes at a pause point) — refresh the pin mirrors.
|
||||
armReloadEEGPRPins();
|
||||
return retval; // falls through to DispatcherReg
|
||||
}
|
||||
|
||||
@@ -480,6 +486,10 @@ static const void* _DynGen_EnterRecompiledCode()
|
||||
// calls that touch cycle/nextEventCycle.
|
||||
armReloadCycleDelta();
|
||||
|
||||
// Load the $sp/$ra write-through pin mirrors (see REEPIN_* doc in
|
||||
// iR5900-arm64.h).
|
||||
armReloadEEGPRPins();
|
||||
|
||||
// Load &VU0 into RVU0. Same idea as RSTATE: VU0 is a static reference
|
||||
// (constant address), so iCOP2 codegen can reach every VURegs field via
|
||||
// [RVU0, #imm12]. Survives both armEmitCall and mVU dispatcher runs.
|
||||
@@ -674,6 +684,9 @@ void recCall(void (*func)())
|
||||
// rescheduled nextEventCycle.
|
||||
armReloadCycleDelta();
|
||||
|
||||
// The interpreter writes guest GPRs in memory — refresh the pin mirrors.
|
||||
armReloadEEGPRPins();
|
||||
|
||||
// After interpreter calls, dispatch a pending TLB-miss exception.
|
||||
recEmitInterpTlbMissCheck();
|
||||
}
|
||||
@@ -696,6 +709,7 @@ void recBranchCall(void (*func)())
|
||||
g_branch = 2;
|
||||
|
||||
armReloadCycleDelta();
|
||||
armReloadEEGPRPins();
|
||||
}
|
||||
|
||||
// s_nBlockCycles is 3-bit fixed point. Divide by 8 when done!
|
||||
@@ -1887,8 +1901,8 @@ static bool skipMPEG_By_Pattern(u32 sPC)
|
||||
|
||||
// v0 = 1 (low) / 0 (high); pc = ra.
|
||||
armAsm->Mov(RWSCRATCH, 1);
|
||||
armAsm->Str(RWSCRATCH, armCpuRegMem(&cpuRegs.GPR.n.v0.UL[0]));
|
||||
armAsm->Str(a64::wzr, armCpuRegMem(&cpuRegs.GPR.n.v0.UL[1]));
|
||||
armStoreEERegPtr(RWSCRATCH, &cpuRegs.GPR.n.v0.UL[0]);
|
||||
armStoreEERegPtr(a64::wzr, &cpuRegs.GPR.n.v0.UL[1]);
|
||||
armLoadEERegPtr(a64::w0, &cpuRegs.GPR.n.ra.UL[0]);
|
||||
armAsm->Str(a64::w0, armCpuRegMem(&cpuRegs.pc));
|
||||
|
||||
@@ -1950,12 +1964,11 @@ static bool recSkipTimeoutLoop(s32 reg, bool is_timeout_loop)
|
||||
// event check uses RECCYCLE directly).
|
||||
armAsm->Mov(RECCYCLE, a64::x5);
|
||||
|
||||
// reg -= iterations consumed
|
||||
// reg -= iterations consumed; sign-extend into the 64-bit guest reg
|
||||
// (the full UD[0] store covers the UL[0] half).
|
||||
armAsm->Sub(a64::w4, a64::w4, a64::w6);
|
||||
armAsm->Str(a64::w4, armCpuRegMem(&cpuRegs.GPR.r[reg].UL[0]));
|
||||
// Also sign-extend to upper 32 bits (EE GPRs are 64-bit for lower half)
|
||||
armAsm->Sxtw(a64::x4, a64::w4);
|
||||
armAsm->Str(a64::x4, armCpuRegMem(&cpuRegs.GPR.r[reg].UD[0]));
|
||||
armStoreEERegPtr(a64::x4, &cpuRegs.GPR.r[reg].UD[0]);
|
||||
|
||||
// if reg != 0, event interrupted the loop — go to dispatcher
|
||||
armEmitCbnz(a64::w4, DispatcherEvent);
|
||||
@@ -2076,6 +2089,7 @@ static void recRecompile(const u32 startpc)
|
||||
armFlushCycleDelta();
|
||||
armEmitCall((void*)eeloadHook);
|
||||
armReloadCycleDelta();
|
||||
armReloadEEGPRPins(); // ELF load / arg injection writes guest GPRs
|
||||
if (VMManager::Internal::IsFastBootInProgress())
|
||||
{
|
||||
// Four known EELOAD versions, identified by the location of the 'jal' to
|
||||
@@ -2100,6 +2114,7 @@ static void recRecompile(const u32 startpc)
|
||||
armFlushCycleDelta();
|
||||
armEmitCall((void*)eeloadHook2);
|
||||
armReloadCycleDelta();
|
||||
armReloadEEGPRPins(); // eeloadHook2 injects launch arguments into GPRs
|
||||
}
|
||||
|
||||
// Goemon TLB-cache preload/unload intercept (mirrors x86 iR5900.cpp:2241-2255,
|
||||
|
||||
@@ -53,6 +53,28 @@
|
||||
// scheduled event — worst case ~one hblank later — instead of the very
|
||||
// next block tail. recEventTest still observes eeRecExitRequested.)
|
||||
#define RECCYCLE vixl::aarch64::x25
|
||||
// x22/x23: Write-through pinned read-cache for the two hottest guest GPRs:
|
||||
// x22 = cpuRegs.GPR.r[29].UD[0] ($sp)
|
||||
// x23 = cpuRegs.GPR.r[31].UD[0] ($ra)
|
||||
// MEMORY STAYS CANONICAL. Every guest-visible write still stores to
|
||||
// cpuRegs.GPR; the pin mirror is refreshed at the same emission point
|
||||
// (armStoreEERegPtr write-through, armStoreEEGPRQuad for 128-bit stores).
|
||||
// Reads that would load UD[0] from memory use the pin register instead
|
||||
// (armLoadEERegPtr substitution / armEEPinForGPR in the scalar templates).
|
||||
// Because the pin never holds a value memory doesn't, there is no new
|
||||
// C-call or block-exit contract: C code reads and writes memory exactly as
|
||||
// before, and the pins are re-read from memory (armReloadEEGPRPins) after
|
||||
// the C calls that can write guest GPRs — interpreter fallbacks
|
||||
// (recCall/recBranchCall), recEventTest (savestate load), recRecompile
|
||||
// (ELF entry hooks), and eeloadHook/eeloadHook2. The upper 64 bits of the
|
||||
// 128-bit guest reg are NOT mirrored; only UD[0] accesses match.
|
||||
// Both host regs are callee-saved, carved out of the dynamic allocator
|
||||
// pool (ALLOCATABLE_MASK in iCore-arm64.cpp), and are not used by any
|
||||
// emission context reachable from inside an EE block: COP2 macro-mode flag
|
||||
// code uses the s_cop2DenormStatusFlag memory scratch (not gprF2/F3), and
|
||||
// the mVU micro dispatcher saves/restores x19-x28 around VU execution.
|
||||
#define REEPIN_SP vixl::aarch64::x22
|
||||
#define REEPIN_RA vixl::aarch64::x23
|
||||
|
||||
// Build a MemOperand addressing a cpuRegs field via RSTATE.
|
||||
// Replaces the 3-instruction `armMoveAddressToReg(RSCRATCHADDR, &cpuRegs.X);
|
||||
@@ -98,8 +120,72 @@ static __fi bool armIsCpuRegPtr(const void* field)
|
||||
const u8* p = reinterpret_cast<const u8*>(field);
|
||||
return p >= base && p < base + sizeof(cpuRegistersPack);
|
||||
}
|
||||
// Pin lookup by guest GPR index. Returns the pinned host register mirroring
|
||||
// GPR.r[gpr].UD[0], or nullptr when gpr is not pinned.
|
||||
static __fi const vixl::aarch64::Register* armEEPinForGPR(int gpr)
|
||||
{
|
||||
if (gpr == 29)
|
||||
return &REEPIN_SP;
|
||||
if (gpr == 31)
|
||||
return &REEPIN_RA;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Pin lookup by target pointer: matches any byte within the LOWER 64 bits of
|
||||
// a pinned guest GPR slot. *offset_in_dword receives the byte offset (0..7)
|
||||
// of `field` within UD[0]. UD[1]/UL[2]/UL[3] accesses do not match (the
|
||||
// upper half is not mirrored).
|
||||
static __fi const vixl::aarch64::Register* armEEPinForPtr(const void* field, int* offset_in_dword)
|
||||
{
|
||||
const u8* p = reinterpret_cast<const u8*>(field);
|
||||
const ptrdiff_t off_sp = p - reinterpret_cast<const u8*>(&cpuRegs.GPR.r[29]);
|
||||
if (off_sp >= 0 && off_sp < 8)
|
||||
{
|
||||
*offset_in_dword = static_cast<int>(off_sp);
|
||||
return &REEPIN_SP;
|
||||
}
|
||||
const ptrdiff_t off_ra = p - reinterpret_cast<const u8*>(&cpuRegs.GPR.r[31]);
|
||||
if (off_ra >= 0 && off_ra < 8)
|
||||
{
|
||||
*offset_in_dword = static_cast<int>(off_ra);
|
||||
return &REEPIN_RA;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Re-read the pin mirrors from canonical memory. Needed after any C call
|
||||
// that can write guest GPRs, and at every JIT entry (see the REEPIN_* doc).
|
||||
static __fi void armReloadEEGPRPins()
|
||||
{
|
||||
armAsm->Ldr(REEPIN_SP, armCpuRegMem(&cpuRegs.GPR.r[29].UD[0]));
|
||||
armAsm->Ldr(REEPIN_RA, armCpuRegMem(&cpuRegs.GPR.r[31].UD[0]));
|
||||
}
|
||||
|
||||
static __fi void armLoadEERegPtr(const vixl::aarch64::CPURegister& reg, const void* field)
|
||||
{
|
||||
// Pinned guest GPR: serve the read from the mirror register. The mirror
|
||||
// always equals memory, so this is exactly the load it replaces.
|
||||
int off;
|
||||
if (const vixl::aarch64::Register* pin = armEEPinForPtr(field, &off); pin && reg.IsRegister())
|
||||
{
|
||||
const vixl::aarch64::Register dst(reg);
|
||||
if (off == 0 && reg.Is64Bits())
|
||||
{
|
||||
armAsm->Mov(dst, *pin);
|
||||
return;
|
||||
}
|
||||
if (off == 0 && reg.Is32Bits())
|
||||
{
|
||||
armAsm->Mov(dst, pin->W());
|
||||
return;
|
||||
}
|
||||
if (off == 4 && reg.Is32Bits())
|
||||
{
|
||||
armAsm->Lsr(dst.X(), *pin, 32);
|
||||
return;
|
||||
}
|
||||
// Unusual shapes fall through to the (identical) canonical memory load.
|
||||
}
|
||||
if (armIsCpuRegPtr(field))
|
||||
armAsm->Ldr(reg, armCpuRegMem(field));
|
||||
else
|
||||
@@ -111,6 +197,44 @@ static __fi void armStoreEERegPtr(const vixl::aarch64::CPURegister& reg, const v
|
||||
armAsm->Str(reg, armCpuRegMem(field));
|
||||
else
|
||||
armStorePtr(reg, field);
|
||||
|
||||
// Write-through: keep the pin mirror equal to the memory just written.
|
||||
int off;
|
||||
if (const vixl::aarch64::Register* pin = armEEPinForPtr(field, &off))
|
||||
{
|
||||
if (reg.IsRegister())
|
||||
{
|
||||
const vixl::aarch64::Register src(reg);
|
||||
if (off == 0 && reg.Is64Bits())
|
||||
{
|
||||
if (!src.Is(*pin))
|
||||
armAsm->Mov(*pin, src);
|
||||
return;
|
||||
}
|
||||
if (off == 0 && reg.Is32Bits())
|
||||
{
|
||||
armAsm->Bfi(*pin, src.X(), 0, 32);
|
||||
return;
|
||||
}
|
||||
if (off == 4 && reg.Is32Bits())
|
||||
{
|
||||
armAsm->Bfi(*pin, src.X(), 32, 32);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Odd store shape (vector reg / sub-word): reload the mirror from the
|
||||
// just-written canonical memory.
|
||||
armAsm->Ldr(*pin, armCpuRegMem(pin == &REEPIN_SP ? static_cast<const void*>(&cpuRegs.GPR.r[29].UD[0]) : static_cast<const void*>(&cpuRegs.GPR.r[31].UD[0])));
|
||||
}
|
||||
}
|
||||
|
||||
// 128-bit guest-GPR store (MMI/NEON writeback, LQ, QMFC2): store the full
|
||||
// quad, then refresh the pin mirror from lane 0 when gpr is pinned.
|
||||
static __fi void armStoreEEGPRQuad(const vixl::aarch64::VRegister& q, int gpr)
|
||||
{
|
||||
armAsm->Str(q, armCpuRegMem(&cpuRegs.GPR.r[gpr].UQ));
|
||||
if (const vixl::aarch64::Register* pin = armEEPinForGPR(gpr))
|
||||
armAsm->Mov(*pin, q.V2D(), 0);
|
||||
}
|
||||
|
||||
// Build a MemOperand addressing a VU0 field via RVU0. VURegs is < 2 KB, so
|
||||
|
||||
+184
-119
File diff suppressed because it is too large
Load Diff
@@ -26,10 +26,26 @@ REC_FUNC(SLTI);
|
||||
REC_FUNC(SLTIU);
|
||||
#else
|
||||
|
||||
// Memory load/store helpers
|
||||
static void memLoadS32() { armLoadEERegPtr(RWSCRATCH, &cpuRegs.GPR.r[_Rs_].UL[0]); }
|
||||
static void memLoadS64() { armLoadEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rs_].UD[0]); }
|
||||
static void memStoreT() { armStoreEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rt_].UD[0]); }
|
||||
// Memory load/store helpers. Loads return the register holding the operand:
|
||||
// the write-through pin mirror when the guest reg is pinned ($sp/$ra — no
|
||||
// load emitted), the scratch otherwise. Callers must treat the returned
|
||||
// register as READ-ONLY and put results in scratch (or pass them straight
|
||||
// to the store helper).
|
||||
static a64::Register memLoadS32()
|
||||
{
|
||||
if (const a64::Register* pin = armEEPinForGPR(_Rs_))
|
||||
return pin->W();
|
||||
armLoadEERegPtr(RWSCRATCH, &cpuRegs.GPR.r[_Rs_].UL[0]);
|
||||
return RWSCRATCH;
|
||||
}
|
||||
static a64::Register memLoadS64()
|
||||
{
|
||||
if (const a64::Register* pin = armEEPinForGPR(_Rs_))
|
||||
return *pin;
|
||||
armLoadEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rs_].UD[0]);
|
||||
return RXSCRATCH;
|
||||
}
|
||||
static void memStoreT(const a64::Register& src) { armStoreEERegPtr(src, &cpuRegs.GPR.r[_Rt_].UD[0]); }
|
||||
|
||||
//// ADDI / ADDIU — rt = sign_extend(rs + imm)
|
||||
static void recADDI_const()
|
||||
@@ -39,11 +55,17 @@ static void recADDI_const()
|
||||
|
||||
static void recADDI_(int info)
|
||||
{
|
||||
memLoadS32();
|
||||
const a64::Register rs = memLoadS32();
|
||||
if (_Imm_ != 0)
|
||||
armAsm->Add(RWSCRATCH, RWSCRATCH, _Imm_);
|
||||
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
|
||||
memStoreT();
|
||||
{
|
||||
armAsm->Add(RWSCRATCH, rs, _Imm_);
|
||||
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
|
||||
}
|
||||
else
|
||||
{
|
||||
armAsm->Sxtw(RXSCRATCH, rs);
|
||||
}
|
||||
memStoreT(RXSCRATCH);
|
||||
}
|
||||
|
||||
EERECOMPILE_CODEX_MEM(eeRecompileCodeRC1_MEM, ADDI, XMMINFO_WRITET | XMMINFO_READS);
|
||||
@@ -58,14 +80,18 @@ static void recDADDI_const()
|
||||
|
||||
static void recDADDI_(int info)
|
||||
{
|
||||
memLoadS64();
|
||||
const a64::Register rs = memLoadS64();
|
||||
if (_Imm_ != 0)
|
||||
{
|
||||
// vixl's Add(int64_t) picks the right ADD/SUB-imm encoding and
|
||||
// materializes via x16 when the immediate is unencodable.
|
||||
armAsm->Add(RXSCRATCH, RXSCRATCH, static_cast<int64_t>(static_cast<s32>(_Imm_)));
|
||||
armAsm->Add(RXSCRATCH, rs, static_cast<int64_t>(static_cast<s32>(_Imm_)));
|
||||
memStoreT(RXSCRATCH);
|
||||
}
|
||||
else
|
||||
{
|
||||
memStoreT(rs);
|
||||
}
|
||||
memStoreT();
|
||||
}
|
||||
|
||||
EERECOMPILE_CODEX_MEM(eeRecompileCodeRC1_MEM, DADDI, XMMINFO_WRITET | XMMINFO_READS | XMMINFO_64BITOP);
|
||||
@@ -80,12 +106,14 @@ static void recANDI_const()
|
||||
|
||||
static void recANDI_(int info)
|
||||
{
|
||||
memLoadS64();
|
||||
if (_ImmU_ == 0)
|
||||
armAsm->Mov(RXSCRATCH, 0);
|
||||
else
|
||||
armAsm->And(RXSCRATCH, RXSCRATCH, static_cast<uint64_t>(static_cast<u16>(_ImmU_)));
|
||||
memStoreT();
|
||||
{
|
||||
memStoreT(a64::xzr);
|
||||
return;
|
||||
}
|
||||
const a64::Register rs = memLoadS64();
|
||||
armAsm->And(RXSCRATCH, rs, static_cast<uint64_t>(static_cast<u16>(_ImmU_)));
|
||||
memStoreT(RXSCRATCH);
|
||||
}
|
||||
|
||||
EERECOMPILE_CODEX_MEM(eeRecompileCodeRC1_MEM, ANDI, XMMINFO_WRITET | XMMINFO_READS | XMMINFO_64BITOP);
|
||||
@@ -98,10 +126,16 @@ static void recORI_const()
|
||||
|
||||
static void recORI_(int info)
|
||||
{
|
||||
memLoadS64();
|
||||
const a64::Register rs = memLoadS64();
|
||||
if (_ImmU_ != 0)
|
||||
armAsm->Orr(RXSCRATCH, RXSCRATCH, static_cast<uint64_t>(static_cast<u16>(_ImmU_)));
|
||||
memStoreT();
|
||||
{
|
||||
armAsm->Orr(RXSCRATCH, rs, static_cast<uint64_t>(static_cast<u16>(_ImmU_)));
|
||||
memStoreT(RXSCRATCH);
|
||||
}
|
||||
else
|
||||
{
|
||||
memStoreT(rs);
|
||||
}
|
||||
}
|
||||
|
||||
EERECOMPILE_CODEX_MEM(eeRecompileCodeRC1_MEM, ORI, XMMINFO_WRITET | XMMINFO_READS | XMMINFO_64BITOP);
|
||||
@@ -114,10 +148,16 @@ static void recXORI_const()
|
||||
|
||||
static void recXORI_(int info)
|
||||
{
|
||||
memLoadS64();
|
||||
const a64::Register rs = memLoadS64();
|
||||
if (_ImmU_ != 0)
|
||||
armAsm->Eor(RXSCRATCH, RXSCRATCH, static_cast<uint64_t>(static_cast<u16>(_ImmU_)));
|
||||
memStoreT();
|
||||
{
|
||||
armAsm->Eor(RXSCRATCH, rs, static_cast<uint64_t>(static_cast<u16>(_ImmU_)));
|
||||
memStoreT(RXSCRATCH);
|
||||
}
|
||||
else
|
||||
{
|
||||
memStoreT(rs);
|
||||
}
|
||||
}
|
||||
|
||||
EERECOMPILE_CODEX_MEM(eeRecompileCodeRC1_MEM, XORI, XMMINFO_WRITET | XMMINFO_READS | XMMINFO_64BITOP);
|
||||
@@ -130,10 +170,10 @@ static void recSLTI_const()
|
||||
|
||||
static void recSLTI_(int info)
|
||||
{
|
||||
memLoadS64();
|
||||
armAsm->Cmp(RXSCRATCH, static_cast<int64_t>(static_cast<s32>(_Imm_)));
|
||||
const a64::Register rs = memLoadS64();
|
||||
armAsm->Cmp(rs, static_cast<int64_t>(static_cast<s32>(_Imm_)));
|
||||
armAsm->Cset(RXSCRATCH, a64::lt);
|
||||
memStoreT();
|
||||
memStoreT(RXSCRATCH);
|
||||
}
|
||||
|
||||
EERECOMPILE_CODEX_MEM(eeRecompileCodeRC1_MEM, SLTI, XMMINFO_WRITET | XMMINFO_READS | XMMINFO_64BITOP);
|
||||
@@ -146,12 +186,12 @@ static void recSLTIU_const()
|
||||
|
||||
static void recSLTIU_(int info)
|
||||
{
|
||||
memLoadS64();
|
||||
const a64::Register rs = memLoadS64();
|
||||
// Sign-extended imm — Cmp condition flags are signedness-agnostic; only
|
||||
// the Cset (lo = unsigned-less-than) differs from SLTI.
|
||||
armAsm->Cmp(RXSCRATCH, static_cast<int64_t>(static_cast<s32>(_Imm_)));
|
||||
armAsm->Cmp(rs, static_cast<int64_t>(static_cast<s32>(_Imm_)));
|
||||
armAsm->Cset(RXSCRATCH, a64::lo);
|
||||
memStoreT();
|
||||
memStoreT(RXSCRATCH);
|
||||
}
|
||||
|
||||
EERECOMPILE_CODEX_MEM(eeRecompileCodeRC1_MEM, SLTIU, XMMINFO_WRITET | XMMINFO_READS | XMMINFO_64BITOP);
|
||||
|
||||
@@ -451,7 +451,7 @@ static void recBranchLink(bool ltz)
|
||||
_deleteEEreg(31, 0);
|
||||
// Store return address directly to memory
|
||||
armAsm->Mov(RXSCRATCH, (u64)(pc + 4));
|
||||
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.GPR.n.ra.UD[0]));
|
||||
armStoreEERegPtr(RXSCRATCH, &cpuRegs.GPR.n.ra.UD[0]);
|
||||
|
||||
if (GPR_IS_CONST1(_Rs_))
|
||||
{
|
||||
@@ -492,7 +492,7 @@ static void recBranchLinkLikely(bool ltz)
|
||||
|
||||
_deleteEEreg(31, 0);
|
||||
armAsm->Mov(RXSCRATCH, (u64)(pc + 4));
|
||||
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.GPR.n.ra.UD[0]));
|
||||
armStoreEERegPtr(RXSCRATCH, &cpuRegs.GPR.n.ra.UD[0]);
|
||||
|
||||
if (GPR_IS_CONST1(_Rs_))
|
||||
{
|
||||
|
||||
@@ -53,7 +53,7 @@ void recJAL()
|
||||
else
|
||||
{
|
||||
armAsm->Mov(RXSCRATCH, (u64)(pc + 4));
|
||||
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.GPR.r[31].UD[0]));
|
||||
armStoreEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[31].UD[0]);
|
||||
}
|
||||
|
||||
recompileNextInstruction(true, false);
|
||||
|
||||
+188
-100
@@ -33,30 +33,46 @@ REC_FUNC(DSRLV);
|
||||
REC_FUNC(DSRAV);
|
||||
#else
|
||||
|
||||
// Memory load/store helpers — always use cpuRegs memory
|
||||
static void memLoadS32()
|
||||
// Memory load/store helpers. Loads return the register holding the operand:
|
||||
// the write-through pin mirror when the guest reg is pinned ($sp/$ra — no
|
||||
// load emitted), the scratch otherwise. Callers must treat the returned
|
||||
// register as READ-ONLY and put results in scratch (or pass them straight
|
||||
// to the store helper).
|
||||
static a64::Register memLoadS32()
|
||||
{
|
||||
if (const a64::Register* pin = armEEPinForGPR(_Rs_))
|
||||
return pin->W();
|
||||
armLoadEERegPtr(RWARG1, &cpuRegs.GPR.r[_Rs_].UL[0]);
|
||||
return RWARG1;
|
||||
}
|
||||
|
||||
static void memLoadS64()
|
||||
static a64::Register memLoadS64()
|
||||
{
|
||||
if (const a64::Register* pin = armEEPinForGPR(_Rs_))
|
||||
return *pin;
|
||||
armLoadEERegPtr(RXARG1, &cpuRegs.GPR.r[_Rs_].UD[0]);
|
||||
return RXARG1;
|
||||
}
|
||||
|
||||
static void memLoadT32()
|
||||
static a64::Register memLoadT32()
|
||||
{
|
||||
if (const a64::Register* pin = armEEPinForGPR(_Rt_))
|
||||
return pin->W();
|
||||
armLoadEERegPtr(RWSCRATCH, &cpuRegs.GPR.r[_Rt_].UL[0]);
|
||||
return RWSCRATCH;
|
||||
}
|
||||
|
||||
static void memLoadT64()
|
||||
static a64::Register memLoadT64()
|
||||
{
|
||||
if (const a64::Register* pin = armEEPinForGPR(_Rt_))
|
||||
return *pin;
|
||||
armLoadEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rt_].UD[0]);
|
||||
return RXSCRATCH;
|
||||
}
|
||||
|
||||
static void memStoreD()
|
||||
static void memStoreD(const a64::Register& src)
|
||||
{
|
||||
armStoreEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rd_].UD[0]);
|
||||
armStoreEERegPtr(src, &cpuRegs.GPR.r[_Rd_].UD[0]);
|
||||
}
|
||||
|
||||
/*********************************************************
|
||||
@@ -72,11 +88,17 @@ static void recSLL_const()
|
||||
|
||||
static void recSLL_(int info)
|
||||
{
|
||||
memLoadT32();
|
||||
const a64::Register rt = memLoadT32();
|
||||
if (_Sa_ != 0)
|
||||
armAsm->Lsl(RWSCRATCH, RWSCRATCH, _Sa_);
|
||||
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
|
||||
memStoreD();
|
||||
{
|
||||
armAsm->Lsl(RWSCRATCH, rt, _Sa_);
|
||||
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
|
||||
}
|
||||
else
|
||||
{
|
||||
armAsm->Sxtw(RXSCRATCH, rt);
|
||||
}
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
|
||||
EERECOMPILE_CODEX_MEM(eeRecompileCodeRC2_MEM, SLL, XMMINFO_WRITED | XMMINFO_READT);
|
||||
@@ -89,11 +111,17 @@ static void recSRL_const()
|
||||
|
||||
static void recSRL_(int info)
|
||||
{
|
||||
memLoadT32();
|
||||
const a64::Register rt = memLoadT32();
|
||||
if (_Sa_ != 0)
|
||||
armAsm->Lsr(RWSCRATCH, RWSCRATCH, _Sa_);
|
||||
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
|
||||
memStoreD();
|
||||
{
|
||||
armAsm->Lsr(RWSCRATCH, rt, _Sa_);
|
||||
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
|
||||
}
|
||||
else
|
||||
{
|
||||
armAsm->Sxtw(RXSCRATCH, rt);
|
||||
}
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
|
||||
EERECOMPILE_CODEX_MEM(eeRecompileCodeRC2_MEM, SRL, XMMINFO_WRITED | XMMINFO_READT);
|
||||
@@ -106,11 +134,17 @@ static void recSRA_const()
|
||||
|
||||
static void recSRA_(int info)
|
||||
{
|
||||
memLoadT32();
|
||||
const a64::Register rt = memLoadT32();
|
||||
if (_Sa_ != 0)
|
||||
armAsm->Asr(RWSCRATCH, RWSCRATCH, _Sa_);
|
||||
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
|
||||
memStoreD();
|
||||
{
|
||||
armAsm->Asr(RWSCRATCH, rt, _Sa_);
|
||||
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
|
||||
}
|
||||
else
|
||||
{
|
||||
armAsm->Sxtw(RXSCRATCH, rt);
|
||||
}
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
|
||||
EERECOMPILE_CODEX_MEM(eeRecompileCodeRC2_MEM, SRA, XMMINFO_WRITED | XMMINFO_READT);
|
||||
@@ -123,10 +157,16 @@ static void recDSLL_const()
|
||||
|
||||
static void recDSLL_(int info)
|
||||
{
|
||||
memLoadT64();
|
||||
const a64::Register rt = memLoadT64();
|
||||
if (_Sa_ != 0)
|
||||
armAsm->Lsl(RXSCRATCH, RXSCRATCH, _Sa_);
|
||||
memStoreD();
|
||||
{
|
||||
armAsm->Lsl(RXSCRATCH, rt, _Sa_);
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
else
|
||||
{
|
||||
memStoreD(rt);
|
||||
}
|
||||
}
|
||||
|
||||
EERECOMPILE_CODEX_MEM(eeRecompileCodeRC2_MEM, DSLL, XMMINFO_WRITED | XMMINFO_READT | XMMINFO_64BITOP);
|
||||
@@ -139,10 +179,16 @@ static void recDSRL_const()
|
||||
|
||||
static void recDSRL_(int info)
|
||||
{
|
||||
memLoadT64();
|
||||
const a64::Register rt = memLoadT64();
|
||||
if (_Sa_ != 0)
|
||||
armAsm->Lsr(RXSCRATCH, RXSCRATCH, _Sa_);
|
||||
memStoreD();
|
||||
{
|
||||
armAsm->Lsr(RXSCRATCH, rt, _Sa_);
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
else
|
||||
{
|
||||
memStoreD(rt);
|
||||
}
|
||||
}
|
||||
|
||||
EERECOMPILE_CODEX_MEM(eeRecompileCodeRC2_MEM, DSRL, XMMINFO_WRITED | XMMINFO_READT | XMMINFO_64BITOP);
|
||||
@@ -155,10 +201,16 @@ static void recDSRA_const()
|
||||
|
||||
static void recDSRA_(int info)
|
||||
{
|
||||
memLoadT64();
|
||||
const a64::Register rt = memLoadT64();
|
||||
if (_Sa_ != 0)
|
||||
armAsm->Asr(RXSCRATCH, RXSCRATCH, _Sa_);
|
||||
memStoreD();
|
||||
{
|
||||
armAsm->Asr(RXSCRATCH, rt, _Sa_);
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
else
|
||||
{
|
||||
memStoreD(rt);
|
||||
}
|
||||
}
|
||||
|
||||
EERECOMPILE_CODEX_MEM(eeRecompileCodeRC2_MEM, DSRA, XMMINFO_WRITED | XMMINFO_READT | XMMINFO_64BITOP);
|
||||
@@ -171,9 +223,9 @@ static void recDSLL32_const()
|
||||
|
||||
static void recDSLL32_(int info)
|
||||
{
|
||||
memLoadT64();
|
||||
armAsm->Lsl(RXSCRATCH, RXSCRATCH, _Sa_ + 32);
|
||||
memStoreD();
|
||||
const a64::Register rt = memLoadT64();
|
||||
armAsm->Lsl(RXSCRATCH, rt, _Sa_ + 32);
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
|
||||
EERECOMPILE_CODEX_MEM(eeRecompileCodeRC2_MEM, DSLL32, XMMINFO_WRITED | XMMINFO_READT | XMMINFO_64BITOP);
|
||||
@@ -186,9 +238,9 @@ static void recDSRL32_const()
|
||||
|
||||
static void recDSRL32_(int info)
|
||||
{
|
||||
memLoadT64();
|
||||
armAsm->Lsr(RXSCRATCH, RXSCRATCH, _Sa_ + 32);
|
||||
memStoreD();
|
||||
const a64::Register rt = memLoadT64();
|
||||
armAsm->Lsr(RXSCRATCH, rt, _Sa_ + 32);
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
|
||||
EERECOMPILE_CODEX_MEM(eeRecompileCodeRC2_MEM, DSRL32, XMMINFO_WRITED | XMMINFO_READT | XMMINFO_64BITOP);
|
||||
@@ -201,9 +253,9 @@ static void recDSRA32_const()
|
||||
|
||||
static void recDSRA32_(int info)
|
||||
{
|
||||
memLoadT64();
|
||||
armAsm->Asr(RXSCRATCH, RXSCRATCH, _Sa_ + 32);
|
||||
memStoreD();
|
||||
const a64::Register rt = memLoadT64();
|
||||
armAsm->Asr(RXSCRATCH, rt, _Sa_ + 32);
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
|
||||
EERECOMPILE_CODEX_MEM(eeRecompileCodeRC2_MEM, DSRA32, XMMINFO_WRITED | XMMINFO_READT | XMMINFO_64BITOP);
|
||||
@@ -221,30 +273,36 @@ static void recSLLV_const()
|
||||
|
||||
static void recSLLV_consts(int info)
|
||||
{
|
||||
memLoadT32();
|
||||
const a64::Register rt = memLoadT32();
|
||||
u32 sa = g_cpuConstRegs[_Rs_].UL[0] & 0x1f;
|
||||
if (sa != 0)
|
||||
armAsm->Lsl(RWSCRATCH, RWSCRATCH, sa);
|
||||
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
|
||||
memStoreD();
|
||||
{
|
||||
armAsm->Lsl(RWSCRATCH, rt, sa);
|
||||
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
|
||||
}
|
||||
else
|
||||
{
|
||||
armAsm->Sxtw(RXSCRATCH, rt);
|
||||
}
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
|
||||
static void recSLLV_constt(int info)
|
||||
{
|
||||
memLoadS32();
|
||||
const a64::Register rs = memLoadS32();
|
||||
armAsm->Mov(RWSCRATCH, g_cpuConstRegs[_Rt_].UL[0]);
|
||||
armAsm->Lsl(RWSCRATCH, RWSCRATCH, RWARG1);
|
||||
armAsm->Lsl(RWSCRATCH, RWSCRATCH, rs);
|
||||
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
|
||||
memStoreD();
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
|
||||
static void recSLLV_(int info)
|
||||
{
|
||||
memLoadS32();
|
||||
memLoadT32();
|
||||
armAsm->Lsl(RWSCRATCH, RWSCRATCH, RWARG1);
|
||||
const a64::Register rs = memLoadS32();
|
||||
const a64::Register rt = memLoadT32();
|
||||
armAsm->Lsl(RWSCRATCH, rt, rs);
|
||||
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
|
||||
memStoreD();
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
|
||||
EERECOMPILE_CODERC0_MEM(SLLV, XMMINFO_WRITED | XMMINFO_READS | XMMINFO_READT);
|
||||
@@ -257,30 +315,36 @@ static void recSRLV_const()
|
||||
|
||||
static void recSRLV_consts(int info)
|
||||
{
|
||||
memLoadT32();
|
||||
const a64::Register rt = memLoadT32();
|
||||
u32 sa = g_cpuConstRegs[_Rs_].UL[0] & 0x1f;
|
||||
if (sa != 0)
|
||||
armAsm->Lsr(RWSCRATCH, RWSCRATCH, sa);
|
||||
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
|
||||
memStoreD();
|
||||
{
|
||||
armAsm->Lsr(RWSCRATCH, rt, sa);
|
||||
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
|
||||
}
|
||||
else
|
||||
{
|
||||
armAsm->Sxtw(RXSCRATCH, rt);
|
||||
}
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
|
||||
static void recSRLV_constt(int info)
|
||||
{
|
||||
memLoadS32();
|
||||
const a64::Register rs = memLoadS32();
|
||||
armAsm->Mov(RWSCRATCH, g_cpuConstRegs[_Rt_].UL[0]);
|
||||
armAsm->Lsr(RWSCRATCH, RWSCRATCH, RWARG1);
|
||||
armAsm->Lsr(RWSCRATCH, RWSCRATCH, rs);
|
||||
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
|
||||
memStoreD();
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
|
||||
static void recSRLV_(int info)
|
||||
{
|
||||
memLoadS32();
|
||||
memLoadT32();
|
||||
armAsm->Lsr(RWSCRATCH, RWSCRATCH, RWARG1);
|
||||
const a64::Register rs = memLoadS32();
|
||||
const a64::Register rt = memLoadT32();
|
||||
armAsm->Lsr(RWSCRATCH, rt, rs);
|
||||
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
|
||||
memStoreD();
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
|
||||
EERECOMPILE_CODERC0_MEM(SRLV, XMMINFO_WRITED | XMMINFO_READS | XMMINFO_READT);
|
||||
@@ -293,30 +357,36 @@ static void recSRAV_const()
|
||||
|
||||
static void recSRAV_consts(int info)
|
||||
{
|
||||
memLoadT32();
|
||||
const a64::Register rt = memLoadT32();
|
||||
u32 sa = g_cpuConstRegs[_Rs_].UL[0] & 0x1f;
|
||||
if (sa != 0)
|
||||
armAsm->Asr(RWSCRATCH, RWSCRATCH, sa);
|
||||
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
|
||||
memStoreD();
|
||||
{
|
||||
armAsm->Asr(RWSCRATCH, rt, sa);
|
||||
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
|
||||
}
|
||||
else
|
||||
{
|
||||
armAsm->Sxtw(RXSCRATCH, rt);
|
||||
}
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
|
||||
static void recSRAV_constt(int info)
|
||||
{
|
||||
memLoadS32();
|
||||
const a64::Register rs = memLoadS32();
|
||||
armAsm->Mov(RWSCRATCH, g_cpuConstRegs[_Rt_].SL[0]);
|
||||
armAsm->Asr(RWSCRATCH, RWSCRATCH, RWARG1);
|
||||
armAsm->Asr(RWSCRATCH, RWSCRATCH, rs);
|
||||
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
|
||||
memStoreD();
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
|
||||
static void recSRAV_(int info)
|
||||
{
|
||||
memLoadS32();
|
||||
memLoadT32();
|
||||
armAsm->Asr(RWSCRATCH, RWSCRATCH, RWARG1);
|
||||
const a64::Register rs = memLoadS32();
|
||||
const a64::Register rt = memLoadT32();
|
||||
armAsm->Asr(RWSCRATCH, rt, rs);
|
||||
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
|
||||
memStoreD();
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
|
||||
EERECOMPILE_CODERC0_MEM(SRAV, XMMINFO_WRITED | XMMINFO_READS | XMMINFO_READT);
|
||||
@@ -329,27 +399,33 @@ static void recDSLLV_const()
|
||||
|
||||
static void recDSLLV_consts(int info)
|
||||
{
|
||||
memLoadT64();
|
||||
const a64::Register rt = memLoadT64();
|
||||
u32 sa = g_cpuConstRegs[_Rs_].UL[0] & 0x3f;
|
||||
if (sa != 0)
|
||||
armAsm->Lsl(RXSCRATCH, RXSCRATCH, sa);
|
||||
memStoreD();
|
||||
{
|
||||
armAsm->Lsl(RXSCRATCH, rt, sa);
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
else
|
||||
{
|
||||
memStoreD(rt);
|
||||
}
|
||||
}
|
||||
|
||||
static void recDSLLV_constt(int info)
|
||||
{
|
||||
memLoadS64();
|
||||
const a64::Register rs = memLoadS64();
|
||||
armAsm->Mov(RXSCRATCH, g_cpuConstRegs[_Rt_].UD[0]);
|
||||
armAsm->Lsl(RXSCRATCH, RXSCRATCH, RXARG1);
|
||||
memStoreD();
|
||||
armAsm->Lsl(RXSCRATCH, RXSCRATCH, rs);
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
|
||||
static void recDSLLV_(int info)
|
||||
{
|
||||
memLoadS64();
|
||||
memLoadT64();
|
||||
armAsm->Lsl(RXSCRATCH, RXSCRATCH, RXARG1);
|
||||
memStoreD();
|
||||
const a64::Register rs = memLoadS64();
|
||||
const a64::Register rt = memLoadT64();
|
||||
armAsm->Lsl(RXSCRATCH, rt, rs);
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
|
||||
EERECOMPILE_CODERC0_MEM(DSLLV, XMMINFO_WRITED | XMMINFO_READS | XMMINFO_READT | XMMINFO_64BITOP);
|
||||
@@ -362,27 +438,33 @@ static void recDSRLV_const()
|
||||
|
||||
static void recDSRLV_consts(int info)
|
||||
{
|
||||
memLoadT64();
|
||||
const a64::Register rt = memLoadT64();
|
||||
u32 sa = g_cpuConstRegs[_Rs_].UL[0] & 0x3f;
|
||||
if (sa != 0)
|
||||
armAsm->Lsr(RXSCRATCH, RXSCRATCH, sa);
|
||||
memStoreD();
|
||||
{
|
||||
armAsm->Lsr(RXSCRATCH, rt, sa);
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
else
|
||||
{
|
||||
memStoreD(rt);
|
||||
}
|
||||
}
|
||||
|
||||
static void recDSRLV_constt(int info)
|
||||
{
|
||||
memLoadS64();
|
||||
const a64::Register rs = memLoadS64();
|
||||
armAsm->Mov(RXSCRATCH, g_cpuConstRegs[_Rt_].UD[0]);
|
||||
armAsm->Lsr(RXSCRATCH, RXSCRATCH, RXARG1);
|
||||
memStoreD();
|
||||
armAsm->Lsr(RXSCRATCH, RXSCRATCH, rs);
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
|
||||
static void recDSRLV_(int info)
|
||||
{
|
||||
memLoadS64();
|
||||
memLoadT64();
|
||||
armAsm->Lsr(RXSCRATCH, RXSCRATCH, RXARG1);
|
||||
memStoreD();
|
||||
const a64::Register rs = memLoadS64();
|
||||
const a64::Register rt = memLoadT64();
|
||||
armAsm->Lsr(RXSCRATCH, rt, rs);
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
|
||||
EERECOMPILE_CODERC0_MEM(DSRLV, XMMINFO_WRITED | XMMINFO_READS | XMMINFO_READT | XMMINFO_64BITOP);
|
||||
@@ -395,27 +477,33 @@ static void recDSRAV_const()
|
||||
|
||||
static void recDSRAV_consts(int info)
|
||||
{
|
||||
memLoadT64();
|
||||
const a64::Register rt = memLoadT64();
|
||||
u32 sa = g_cpuConstRegs[_Rs_].UL[0] & 0x3f;
|
||||
if (sa != 0)
|
||||
armAsm->Asr(RXSCRATCH, RXSCRATCH, sa);
|
||||
memStoreD();
|
||||
{
|
||||
armAsm->Asr(RXSCRATCH, rt, sa);
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
else
|
||||
{
|
||||
memStoreD(rt);
|
||||
}
|
||||
}
|
||||
|
||||
static void recDSRAV_constt(int info)
|
||||
{
|
||||
memLoadS64();
|
||||
const a64::Register rs = memLoadS64();
|
||||
armAsm->Mov(RXSCRATCH, g_cpuConstRegs[_Rt_].SD[0]);
|
||||
armAsm->Asr(RXSCRATCH, RXSCRATCH, RXARG1);
|
||||
memStoreD();
|
||||
armAsm->Asr(RXSCRATCH, RXSCRATCH, rs);
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
|
||||
static void recDSRAV_(int info)
|
||||
{
|
||||
memLoadS64();
|
||||
memLoadT64();
|
||||
armAsm->Asr(RXSCRATCH, RXSCRATCH, RXARG1);
|
||||
memStoreD();
|
||||
const a64::Register rs = memLoadS64();
|
||||
const a64::Register rt = memLoadT64();
|
||||
armAsm->Asr(RXSCRATCH, rt, rs);
|
||||
memStoreD(RXSCRATCH);
|
||||
}
|
||||
|
||||
EERECOMPILE_CODERC0_MEM(DSRAV, XMMINFO_WRITED | XMMINFO_READS | XMMINFO_READT | XMMINFO_64BITOP);
|
||||
|
||||
@@ -705,7 +705,7 @@ void recLQ()
|
||||
{
|
||||
_deleteEEreg(_Rt_, 0);
|
||||
GPR_DEL_CONST(_Rt_);
|
||||
armAsm->Str(a64::q0, armCpuRegMem(&cpuRegs.GPR.r[_Rt_].UD[0]));
|
||||
armStoreEEGPRQuad(a64::q0, _Rt_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ add_pcsx2_test(recompiler_tests
|
||||
ee_rec_mmi_simd_tests.cpp
|
||||
ee_rec_mmi_tests.cpp
|
||||
ee_rec_move_tests.cpp
|
||||
ee_rec_pinned_gpr_tests.cpp
|
||||
ee_rec_muldiv_tests.cpp
|
||||
ee_rec_multiblock_tests.cpp
|
||||
ee_rec_shift_tests.cpp
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
// Write-through pinned read-cache coverage ($sp → x22, $ra → x23; see
|
||||
// REEPIN_* in arm64/iR5900-arm64.h). The pins mirror GPR.r[29/31].UD[0]
|
||||
// while memory stays canonical, so a broken write-through path does NOT
|
||||
// corrupt memory — it strands the mirror. These tests make that observable
|
||||
// by writing a pinned reg through each write path and then READING IT BACK
|
||||
// inside the same block: a stale mirror feeds the read-back a wrong value
|
||||
// and the JIT-vs-interp diff goes red.
|
||||
//
|
||||
// The harness parks the machine via a trailing JR $ra (SeedEntryState sets
|
||||
// $ra = kParkingPc), so every block that scribbles $ra saves it to a temp
|
||||
// first and restores it last — the save/restore pair itself exercises
|
||||
// pinned reads and writes.
|
||||
|
||||
#include "harness/EeRecTestHarness.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace recompiler_tests;
|
||||
using namespace mips;
|
||||
using namespace mips::ee;
|
||||
|
||||
namespace {
|
||||
constexpr u32 kScratch = RecompilerTestEnvironment::kScratchAddr;
|
||||
}
|
||||
|
||||
// Pinned $sp/$ra as scalar sources: imm-ALU, 3-op ALU, shift, set-on-lt.
|
||||
TEST(EeRecPinnedGpr, ScalarReadsOfPinnedRegs)
|
||||
{
|
||||
EeRecTestHarness h;
|
||||
h.SetGpr64(reg::sp, 0x0000000001F00010ull);
|
||||
h.SetGpr64(reg::t5, 0xFFFFFFFF80001234ull);
|
||||
h.LoadProgram({
|
||||
OR(reg::t6, reg::ra, reg::zero), // save parking $ra (pinned read)
|
||||
OR(reg::ra, reg::t5, reg::zero), // write pinned $ra
|
||||
ADDIU(reg::t0, reg::sp, -16),
|
||||
DADDU(reg::t1, reg::sp, reg::ra),
|
||||
SLTI(reg::t2, reg::ra, 0),
|
||||
DSLL(reg::t3, reg::ra, 4),
|
||||
OR(reg::t4, reg::sp, reg::ra),
|
||||
OR(reg::ra, reg::t6, reg::zero), // restore parking $ra
|
||||
});
|
||||
h.Run();
|
||||
EXPECT_EQ(h.GetGpr64Interp(reg::t0), 0x0000000001F00000ull);
|
||||
EXPECT_EQ(h.GetGpr64Interp(reg::t1), 0xFFFFFFFF81F01244ull);
|
||||
EXPECT_EQ(h.GetGpr64Interp(reg::t2), 1ull);
|
||||
EXPECT_EQ(h.GetGpr64Interp(reg::t3), 0xFFFFFFF800012340ull);
|
||||
EXPECT_EQ(h.GetGpr64Interp(reg::t4), 0xFFFFFFFF81F01234ull);
|
||||
}
|
||||
|
||||
// Scalar write-through: writes to $sp/$ra go through the memStore helpers'
|
||||
// armStoreEERegPtr, which must refresh the mirror the read-backs consume.
|
||||
TEST(EeRecPinnedGpr, ScalarWriteThroughThenReadBack)
|
||||
{
|
||||
EeRecTestHarness h;
|
||||
h.SetGpr64(reg::t0, 0x0000000000010000ull);
|
||||
h.SetGpr64(reg::t1, 0x0000000000000230ull);
|
||||
h.LoadProgram({
|
||||
OR(reg::t6, reg::ra, reg::zero), // save parking $ra
|
||||
DADDU(reg::sp, reg::t0, reg::t1), // write pinned $sp
|
||||
ADDIU(reg::t2, reg::sp, 8), // read $sp back via the pin
|
||||
DADDU(reg::ra, reg::t2, reg::t0), // write pinned $ra
|
||||
DADDU(reg::t3, reg::ra, reg::t1), // read $ra back via the pin
|
||||
ADDIU(reg::sp, reg::sp, -32), // pinned RMW: read + write $sp
|
||||
OR(reg::t4, reg::sp, reg::zero), // read the RMW result back
|
||||
OR(reg::ra, reg::t6, reg::zero), // restore parking $ra
|
||||
});
|
||||
h.Run();
|
||||
EXPECT_EQ(h.GetGpr64Interp(reg::t2), 0x0000000000010238ull);
|
||||
EXPECT_EQ(h.GetGpr64Interp(reg::t3), 0x0000000000020468ull);
|
||||
EXPECT_EQ(h.GetGpr64Interp(reg::t4), 0x0000000000010210ull);
|
||||
}
|
||||
|
||||
// 128-bit write-through: PADDW allocates $sp as a NEON dest; the scalar
|
||||
// read-back forces the NEON→memory writeback (armStoreEEGPRQuad), whose
|
||||
// lane-0 UMOV must refresh the mirror.
|
||||
TEST(EeRecPinnedGpr, MmiQuadWriteThroughThenReadBack)
|
||||
{
|
||||
EeRecTestHarness h;
|
||||
h.SetGpr64(reg::t0, 0x1111111122222222ull);
|
||||
h.SetGpr64(reg::t1, 0x0000000300000004ull);
|
||||
h.LoadProgram({
|
||||
PADDW(reg::sp, reg::t0, reg::t1), // 128-bit write of pinned $sp
|
||||
DADDU(reg::t2, reg::sp, reg::zero), // scalar read-back via the pin
|
||||
});
|
||||
h.Run();
|
||||
EXPECT_EQ(h.GetGpr64Interp(reg::t2), 0x1111111422222226ull);
|
||||
}
|
||||
|
||||
// vtlb-load write-through: LW/LD land in the guest reg via
|
||||
// recStoreLoadResult's armStoreEERegPtr; the read-back consumes the mirror.
|
||||
// (LD $ra, off($sp) is the ubiquitous epilogue stack-restore idiom.)
|
||||
TEST(EeRecPinnedGpr, LoadIntoPinnedThenReadBack)
|
||||
{
|
||||
EeRecTestHarness h;
|
||||
h.WriteU64(kScratch, 0xFFFFFFFF80332211ull);
|
||||
h.WriteU32(kScratch + 8, 0x00445566u);
|
||||
h.SetGpr64(reg::sp, kScratch);
|
||||
h.LoadProgram({
|
||||
OR(reg::t6, reg::ra, reg::zero), // save parking $ra
|
||||
LD(reg::ra, 0, reg::sp), // pinned base, pinned dest
|
||||
DADDU(reg::t0, reg::ra, reg::zero),
|
||||
LW(reg::sp, 8, reg::sp), // pinned base and dest, 32-bit
|
||||
DADDU(reg::t1, reg::sp, reg::zero),
|
||||
OR(reg::ra, reg::t6, reg::zero), // restore parking $ra
|
||||
});
|
||||
h.Run();
|
||||
EXPECT_EQ(h.GetGpr64Interp(reg::t0), 0xFFFFFFFF80332211ull);
|
||||
EXPECT_EQ(h.GetGpr64Interp(reg::t1), 0x0000000000445566ull);
|
||||
}
|
||||
|
||||
// 32-bit-half write-through (Bfi path): CFC2 writes UL[0] and UL[1] of the
|
||||
// pinned reg separately; the read-back consumes the mirror.
|
||||
TEST(EeRecPinnedGpr, HalfWordWriteThroughViaCfc2)
|
||||
{
|
||||
EeRecTestHarness h;
|
||||
h.EnableVu0Capture();
|
||||
h.SeedVu0Vi(1, 0x8123);
|
||||
h.LoadProgram({
|
||||
OR(reg::t6, reg::ra, reg::zero), // save parking $ra
|
||||
CFC2(reg::ra, 1), // UL[0]+UL[1] stores into pinned $ra
|
||||
DADDU(reg::t0, reg::ra, reg::zero), // read $ra back via the pin
|
||||
OR(reg::ra, reg::t6, reg::zero), // restore parking $ra
|
||||
});
|
||||
h.Run();
|
||||
EXPECT_EQ(h.GetGpr64Interp(reg::t0), 0x0000000000008123ull);
|
||||
}
|
||||
Reference in New Issue
Block a user