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:
@@ -1647,7 +1647,18 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
|||||||
ctx.pc += 4;
|
ctx.pc += 4;
|
||||||
}
|
}
|
||||||
PpcOpcode::mtmsr | PpcOpcode::mtmsrd => {
|
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;
|
ctx.pc += 4;
|
||||||
}
|
}
|
||||||
PpcOpcode::mftb => {
|
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
|
// SAT (bit 31) are defined. Canary stores the full Vec128 so we do
|
||||||
// the same: mfvscr copies the register, mtvscr overwrites it.
|
// the same: mfvscr copies the register, mtvscr overwrites it.
|
||||||
PpcOpcode::mfvscr => {
|
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;
|
ctx.pc += 4;
|
||||||
}
|
}
|
||||||
PpcOpcode::mtvscr => {
|
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));
|
(1 << (31 - 21)) | (1 << (31 - 22)) | (1 << (31 - 23));
|
||||||
let nibble_mask = 0xFu32 << shift;
|
let nibble_mask = 0xFu32 << shift;
|
||||||
ctx.fpscr &= !(nibble_mask & CLEARABLE_MASK);
|
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;
|
ctx.pc += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user