fix(cpu): PPCBUG-068/078/080 mcrfs VX recompute + mtmsrd L=1 + mfvscr zero

Phase 6 batch 3 — SPR/MSR/VSCR semantics.

- PPCBUG-078 mtmsrd L=1: PowerISA requires partial-MSR-write — only
  MSR[EE] (u64 bit 15) and MSR[RI] (u64 bit 0) modified, all other
  MSR bits preserved. Used by kernel code to toggle external interrupts.
  Previously merged with mtmsr (full overwrite), silently corrupting
  MSR for any L=1 caller.
- PPCBUG-080 mfvscr: ISA places VSCR in the rightmost word of VD with
  bytes 0-11 zeroed. Previously copied the full 128-bit ctx.vscr,
  leaking stale upper data to guest. Now zero-extends per canary.
- PPCBUG-068 mcrfs VX summary: when mcrfs clears VX* exception bits,
  the VX summary bit at FPSCR[2] must be recomputed (clears if all
  contributors are 0; remains 1 otherwise). Previously left stale,
  causing subsequent CR-test sequences to misread the FPU state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-05-02 13:50:10 +02:00
parent 68c0ee55ce
commit 0f2a26c460

View File

@@ -1647,7 +1647,18 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
ctx.pc += 4;
}
PpcOpcode::mtmsr | PpcOpcode::mtmsrd => {
ctx.msr = ctx.gpr[instr.rs()];
// PPCBUG-078: mtmsrd L=1 is a partial-MSR-write — only MSR[EE]
// (u64 bit 15) and MSR[RI] (u64 bit 0) are modified; all other
// MSR bits preserved. Used by kernel code to re-enable external
// interrupts without disturbing the rest of the MSR.
let l = (instr.raw >> (31 - 15)) & 1;
let rs = ctx.gpr[instr.rs()];
if matches!(instr.opcode, PpcOpcode::mtmsrd) && l == 1 {
let mask: u64 = (1u64 << 15) | 1u64;
ctx.msr = (ctx.msr & !mask) | (rs & mask);
} else {
ctx.msr = rs;
}
ctx.pc += 4;
}
PpcOpcode::mftb => {
@@ -2493,7 +2504,11 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
// SAT (bit 31) are defined. Canary stores the full Vec128 so we do
// the same: mfvscr copies the register, mtvscr overwrites it.
PpcOpcode::mfvscr => {
ctx.vr[instr.rd()] = ctx.vscr;
// PPCBUG-080: ISA places VSCR in the rightmost word of VD with
// bytes 0-11 zeroed. Previously the full 128-bit ctx.vscr was
// copied (leaking stale upper data to guest).
let vscr_word = ctx.vscr.as_u32x4()[3];
ctx.vr[instr.rd()] = xenia_types::Vec128::from_u32x4_array([0, 0, 0, vscr_word]);
ctx.pc += 4;
}
PpcOpcode::mtvscr => {
@@ -4717,6 +4732,19 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
(1 << (31 - 21)) | (1 << (31 - 22)) | (1 << (31 - 23));
let nibble_mask = 0xFu32 << shift;
ctx.fpscr &= !(nibble_mask & CLEARABLE_MASK);
// PPCBUG-068: recompute the VX summary bit. If any VX* exception
// bit remains set, VX must remain set; if all are cleared, VX
// must clear. (FEX recomputation omitted — xenia doesn't model
// enabled-exception dispatch.)
const VX_ALL_MASK: u32 =
fpscr::VXSNAN | fpscr::VXISI | fpscr::VXIDI |
fpscr::VXZDZ | fpscr::VXIMZ | fpscr::VXVC |
fpscr::VXSOFT | fpscr::VXSQRT | fpscr::VXCVI;
if ctx.fpscr & VX_ALL_MASK != 0 {
ctx.fpscr |= fpscr::VX;
} else {
ctx.fpscr &= !fpscr::VX;
}
ctx.pc += 4;
}