From 0f2a26c460d7810332cdabbdd8a00a67c0998574 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Sat, 2 May 2026 13:50:10 +0200 Subject: [PATCH] fix(cpu): PPCBUG-068/078/080 mcrfs VX recompute + mtmsrd L=1 + mfvscr zero MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- crates/xenia-cpu/src/interpreter.rs | 32 +++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/crates/xenia-cpu/src/interpreter.rs b/crates/xenia-cpu/src/interpreter.rs index 8143d7d..a606b4d 100644 --- a/crates/xenia-cpu/src/interpreter.rs +++ b/crates/xenia-cpu/src/interpreter.rs @@ -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; }