EE-SRA 2 WS-B2: read-site pin merges for 128-bit/raw GPR readers

The JIT-internal readers that bypass armLoadEERegPtr's scalar-only pin
substitution would observe a stale lower half under lazy-dirty: the NEON
dual-residence plain fill, mmiLoadReg, recSQ's quad source, and QMTC2 get an
Ins-lane-0 merge from the pin (armMergeEEPinIntoQuad — no-op in write-through,
keeps laziness intact vs. flushing). CTC2's word read and PMADDW's SL[ss]
lane reads convert to armLoadEERegPtr — correct under both modes and a free
Ldr->Mov substitution under write-through; upper-half (SL[2]) reads fall
through to memory, which is always canonical for the unmirrored half.

The other seam-inventory readers were already safe: LWL/LWR/SWL/SWR/LDL/LDR/
SDL/SDR merge reads route through _eeMoveGPRtoR (pin-aware), and the NEON
fill's const/dirty-scalar arms already Ins the newest value.

recompiler_tests 1112/1112 with the flag off.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Brian Degenhardt
2026-07-07 08:10:41 -07:00
co-authored by Claude Fable 5
parent 39ce31186d
commit b7fb5e6174
5 changed files with 30 additions and 5 deletions
+5 -2
View File
@@ -751,7 +751,8 @@ void recCOP2_QMTC2()
}
else
{
armAsm->Ldr(RQSCRATCH, armCpuRegMem(&cpuRegs.GPR.r[_Rt_]));
armAsm->Ldr(RQSCRATCH, armCpuRegMem(&cpuRegs.GPR.r[_Rt_]));
armMergeEEPinIntoQuad(RQSCRATCH, _Rt_); // lazy-dirty: stale lower half
}
armAsm->Str(RQSCRATCH, armVU0Mem(&VU0.VF[_Rd_]));
}
@@ -816,7 +817,9 @@ void recCOP2_CTC2()
}
else
{
armAsm->Ldr(RWSCRATCH, armCpuRegMem(&cpuRegs.GPR.r[_Rt_].UL[0]));
// armLoadEERegPtr: substitutes a pinned reg's mirror — required under
// lazy-dirty, a free Ldr->Mov under write-through.
armLoadEERegPtr(RWSCRATCH, &cpuRegs.GPR.r[_Rt_].UL[0]);
}
if (fs == REG_R)
+2
View File
@@ -844,6 +844,8 @@ int _allocGPRtoNEONreg(int gprreg, int mode)
else
{
armLoadEERegPtr(armQRegister(neonreg), &cpuRegs.GPR.r[gprreg].UQ);
// Lazy-dirty: a dirty pin makes the memory lower half stale.
armMergeEEPinIntoQuad(armQRegister(neonreg), gprreg);
}
}
+9 -3
View File
@@ -56,7 +56,9 @@ static void mmiLoadReg(const a64::VRegister& qreg, int gpr)
}
else
{
armAsm->Ldr(qreg, armCpuRegMem(&cpuRegs.GPR.r[gpr].UQ));
armAsm->Ldr(qreg, armCpuRegMem(&cpuRegs.GPR.r[gpr].UQ));
// Lazy-dirty: merge the pin over the possibly-stale lower half.
armMergeEEPinIntoQuad(qreg, gpr);
}
}
@@ -936,8 +938,12 @@ REC_FUNC(PDIVUW);
// matches the interp's ordering under every Rd/Rs/Rt aliasing.
static void recPMADDWLane(int dd, int ss, bool isSub)
{
armAsm->Ldr(a64::w8, armCpuRegMem(&cpuRegs.GPR.r[_Rs_].SL[ss]));
armAsm->Ldr(a64::w9, armCpuRegMem(&cpuRegs.GPR.r[_Rt_].SL[ss]));
// armLoadEERegPtr: lane 0 (SL[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 2 (SL[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]);
if (!isSub && ss == 0)
{
+13
View File
@@ -286,6 +286,19 @@ static __fi void armFlushEEClobberedPins()
}
}
// Lazy-dirty read-site merge: a 128-bit guest-GPR read from canonical memory
// (NEON dual-residence fill, MMI quad loads, SQ/QMTC2 sources) may see a
// stale lower half when the pin is dirty — Ins the pin into lane 0 after the
// load. The upper 64 bits are never mirrored, so memory is always right for
// them. No-op in write-through mode (memory == pin by construction).
static __fi void armMergeEEPinIntoQuad(const vixl::aarch64::VRegister& q, int gpr)
{
if (!EE_PIN_LAZY_DIRTY)
return;
if (const vixl::aarch64::Register* pin = armEEPinForGPR(gpr))
armAsm->Ins(q.V2D(), 0, *pin);
}
static __fi void armLoadEERegPtr(const vixl::aarch64::CPURegister& reg, const void* field)
{
// Pinned guest GPR: serve the read from the mirror register. The mirror
+1
View File
@@ -785,6 +785,7 @@ void recSQ()
// preferring NEON for full-128-bit guest GPRs.
iFlushCall(FLUSH_CONSTANT_REGS);
armLoadEERegPtr(a64::q0, &cpuRegs.GPR.r[_Rt_].UQ);
armMergeEEPinIntoQuad(a64::q0, _Rt_); // lazy-dirty: stale lower half
armLoadEERegPtr(a64::w9, &cpuRegs.GPR.r[_Rs_].UL[0]);
if (_Imm_ != 0)