arm64/EE: rehome w4-w7 scratch out of EE-block emitters (rung-3 prep)

x4-x7 become EE-SRA pins in rung 3; every EE-block-inline emission that
used them as scratch moves to the non-allocatable scratches instead:
- recSkipTimeoutLoop: w4/x5/w6 -> w9/x10/w8
- iCOP2 MAC-flag denorm update + CTC2 STATUS denorm: w4 -> w9
- iCOP2 native VCLIP body: w4 clipflag accumulator -> w9
- recMADD/MADDU/MADD1/MADDU1: HI scratch w4 -> w8 (RWSCRATCH)
IOP-rec and VIF-dynarec uses of x4-x7 stay: they only execute behind C
boundaries whose EE call sites reload caller-saved pins.
Pure re-scratching, no semantic change. 1110/1110 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Brian Degenhardt
2026-07-05 16:33:56 -07:00
co-authored by Claude Fable 5
parent fe17838bc1
commit 4473a4b7e5
3 changed files with 39 additions and 36 deletions
+15 -13
View File
@@ -495,9 +495,10 @@ static void cop2EmitFlagUpdate(int xyzw)
armMoveAddressToReg(RSCRATCHADDR, &s_cop2DenormStatusFlag);
armAsm->Ldr(RWSCRATCH, a64::MemOperand(RSCRATCHADDR));
// Clear current (non-sticky) bits 8-15
armAsm->Mov(a64::w4, 0xFF00);
armAsm->Bic(RWSCRATCH, RWSCRATCH, a64::w4);
// Clear current (non-sticky) bits 8-15. w9 scratch: w4 is an EE-SRA pin
// (rung 3) and this body emits inline into EE blocks.
armAsm->Mov(a64::w9, 0xFF00);
armAsm->Bic(RWSCRATCH, RWSCRATCH, a64::w9);
// OR macFlag into sticky bits (0-7) — accumulates over time
armAsm->Orr(RWSCRATCH, RWSCRATCH, macFlag);
@@ -841,8 +842,8 @@ void recCOP2_CTC2()
armAsm->And(a64::w2, a64::w2, 0x1800);
armAsm->Orr(RWSCRATCH, RWSCRATCH, a64::w2);
armAsm->Lsl(a64::w3, RWARG2, 14);
armAsm->Mov(a64::w4, 0x3cf0000); // not a valid logical-imm; materialize
armAsm->And(a64::w3, a64::w3, a64::w4);
armAsm->Mov(a64::w9, 0x3cf0000); // not a valid logical-imm; materialize (w9: w4 is a rung-3 pin)
armAsm->And(a64::w3, a64::w3, a64::w9);
armAsm->Orr(RWSCRATCH, RWSCRATCH, a64::w3);
// Broadcast the denormalized value into all 4 lanes of micro_statusflags.
@@ -1950,9 +1951,10 @@ void recCOP2_VCLIP()
armAsm->Cmp(a64::w2, 0);
armAsm->Csel(a64::w1, a64::w1, a64::w3, a64::ne); // w1 = clip value
// Shift clipflag left by 6
armAsm->Ldr(a64::w4, armVU0Mem(&VU0.clipflag));
armAsm->Lsl(a64::w4, a64::w4, 6);
// Shift clipflag left by 6. Accumulates in w9 across the NEON stretch
// below (w4 is a rung-3 EE-SRA pin; this body emits inline into EE blocks).
armAsm->Ldr(a64::w9, armVU0Mem(&VU0.clipflag));
armAsm->Lsl(a64::w9, a64::w9, 6);
// Load fs = [x,y,z,w] as integers for the lane comparisons.
armAsm->Ldr(RQSCRATCH, armVU0Mem(&VU0.VF[_Fs_cop2])); // q30 = [x,y,z,w]
@@ -1985,19 +1987,19 @@ void recCOP2_VCLIP()
armAsm->Umov(a64::w2, posMask.V4S(), 0); // 6-bit clip field
// Merge into clipflag and mask to 24 bits
armAsm->Orr(a64::w4, a64::w4, a64::w2);
armAsm->And(a64::w4, a64::w4, 0xFFFFFF);
armAsm->Orr(a64::w9, a64::w9, a64::w2);
armAsm->And(a64::w9, a64::w9, 0xFFFFFF);
// Store clipflag and sync to VI[REG_CLIP_FLAG]
armAsm->Str(a64::w4, armVU0Mem(&VU0.clipflag));
armAsm->Str(a64::w4, armVU0Mem(&VU0.VI[REG_CLIP_FLAG]));
armAsm->Str(a64::w9, armVU0Mem(&VU0.clipflag));
armAsm->Str(a64::w9, armVU0Mem(&VU0.VI[REG_CLIP_FLAG]));
// Broadcast the new clipflag into all 4 lanes of micro_clipflags. A
// subsequent VU0 microprogram loads its clip-flag instances directly from
// the VURegs::micro_clipflags field in the mVU Execute prologue — without
// this they would be stale (pre-VCLIP). RQSCRATCH is free here (its earlier
// fs load is consumed).
armAsm->Dup(RQSCRATCH.V4S(), a64::w4);
armAsm->Dup(RQSCRATCH.V4S(), a64::w9);
armAsm->Str(RQSCRATCH, armVU0Mem(&VU0.micro_clipflags));
}
+16 -15
View File
@@ -2063,35 +2063,36 @@ static bool recSkipTimeoutLoop(s32 reg, bool is_timeout_loop)
armAsm->Cmp(RECCYCLE, 0);
armEmitCondBranch(a64::ge, DispatcherEvent);
// w4 = reg value (the decrementing counter)
armAsm->Ldr(a64::w4, armCpuRegMem(&cpuRegs.GPR.r[reg].UL[0]));
// w9 = reg value (the decrementing counter). Scratches are w9/x10/w8:
// w4-w7 are rung-3 EE-SRA pins and this emits inline into EE blocks.
armAsm->Ldr(a64::w9, armCpuRegMem(&cpuRegs.GPR.r[reg].UL[0]));
// x5 = reg * 8 + delta (estimated end delta, s64)
armAsm->Add(a64::x5, RECCYCLE, a64::Operand(a64::x4, a64::LSL, 3));
// x10 = reg * 8 + delta (estimated end delta, s64)
armAsm->Add(a64::x10, RECCYCLE, a64::Operand(a64::x9, a64::LSL, 3));
// x5 = min(x5, 0) — can't run past the next event
armAsm->Cmp(a64::x5, 0);
armAsm->Csel(a64::x5, a64::xzr, a64::x5, a64::gt); // if x5 > 0, clamp to 0
// x10 = min(x10, 0) — can't run past the next event
armAsm->Cmp(a64::x10, 0);
armAsm->Csel(a64::x10, a64::xzr, a64::x10, a64::gt); // if x10 > 0, clamp to 0
// w6 = (new_delta - old_delta) >> 3 = iterations consumed (deltas share
// w8 = (new_delta - old_delta) >> 3 = iterations consumed (deltas share
// the same nextEventCycle base, so their difference equals the absolute
// cycle difference).
armAsm->Sub(a64::w6, a64::w5, RECCYCLE.W());
armAsm->Lsr(a64::w6, a64::w6, 3);
armAsm->Sub(RWSCRATCH, a64::w10, RECCYCLE.W());
armAsm->Lsr(RWSCRATCH, RWSCRATCH, 3);
// Commit the new delta into RECCYCLE (no memory store — DispatcherEvent
// converts and flushes it if we exit there; otherwise the next block-tail
// event check uses RECCYCLE directly).
armAsm->Mov(RECCYCLE, a64::x5);
armAsm->Mov(RECCYCLE, a64::x10);
// 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->Sxtw(a64::x4, a64::w4);
armStoreEERegPtr(a64::x4, &cpuRegs.GPR.r[reg].UD[0]);
armAsm->Sub(a64::w9, a64::w9, RWSCRATCH);
armAsm->Sxtw(a64::x9, a64::w9);
armStoreEERegPtr(a64::x9, &cpuRegs.GPR.r[reg].UD[0]);
// if reg != 0, event interrupted the loop — go to dispatcher
armEmitCbnz(a64::w4, DispatcherEvent);
armEmitCbnz(a64::w9, DispatcherEvent);
// Loop finished — set PC to end of block and dispatch
armAsm->Mov(RWSCRATCH, s_nEndBlock);
+8 -8
View File
@@ -476,8 +476,8 @@ void recMADD()
// Load existing HI:LO into x1
armLoadEERegPtr(a64::w3, &cpuRegs.LO.UL[0]);
armLoadEERegPtr(a64::w4, &cpuRegs.HI.UL[0]);
armAsm->Orr(a64::x3, a64::x3, a64::Operand(a64::x4, a64::LSL, 32));
armLoadEERegPtr(RWSCRATCH, &cpuRegs.HI.UL[0]); // w8: w4 is a rung-3 EE-SRA pin
armAsm->Orr(a64::x3, a64::x3, a64::Operand(RXSCRATCH, a64::LSL, 32));
// Add
armAsm->Add(a64::x0, a64::x0, a64::x3);
@@ -512,8 +512,8 @@ void recMADDU()
armAsm->Umull(a64::x0, a64::w1, a64::w2);
armLoadEERegPtr(a64::w3, &cpuRegs.LO.UL[0]);
armLoadEERegPtr(a64::w4, &cpuRegs.HI.UL[0]);
armAsm->Orr(a64::x3, a64::x3, a64::Operand(a64::x4, a64::LSL, 32));
armLoadEERegPtr(RWSCRATCH, &cpuRegs.HI.UL[0]); // w8: w4 is a rung-3 EE-SRA pin
armAsm->Orr(a64::x3, a64::x3, a64::Operand(RXSCRATCH, a64::LSL, 32));
armAsm->Add(a64::x0, a64::x0, a64::x3);
@@ -547,8 +547,8 @@ void recMADD1()
armAsm->Smull(a64::x0, a64::w1, a64::w2);
armLoadEERegPtr(a64::w3, &cpuRegs.LO.UL[2]); // LO1 = LO.UL[2] (upper 64 bits)
armLoadEERegPtr(a64::w4, &cpuRegs.HI.UL[2]); // HI1 = HI.UL[2]
armAsm->Orr(a64::x3, a64::x3, a64::Operand(a64::x4, a64::LSL, 32));
armLoadEERegPtr(RWSCRATCH, &cpuRegs.HI.UL[2]); // HI1 = HI.UL[2] (w8: w4 is a rung-3 pin)
armAsm->Orr(a64::x3, a64::x3, a64::Operand(RXSCRATCH, a64::LSL, 32));
armAsm->Add(a64::x0, a64::x0, a64::x3);
@@ -582,8 +582,8 @@ void recMADDU1()
armAsm->Umull(a64::x0, a64::w1, a64::w2);
armLoadEERegPtr(a64::w3, &cpuRegs.LO.UL[2]);
armLoadEERegPtr(a64::w4, &cpuRegs.HI.UL[2]);
armAsm->Orr(a64::x3, a64::x3, a64::Operand(a64::x4, a64::LSL, 32));
armLoadEERegPtr(RWSCRATCH, &cpuRegs.HI.UL[2]); // w8: w4 is a rung-3 pin
armAsm->Orr(a64::x3, a64::x3, a64::Operand(RXSCRATCH, a64::LSL, 32));
armAsm->Add(a64::x0, a64::x0, a64::x3);