diff --git a/crates/xenia-cpu/src/context.rs b/crates/xenia-cpu/src/context.rs index 5826120..60e658f 100644 --- a/crates/xenia-cpu/src/context.rs +++ b/crates/xenia-cpu/src/context.rs @@ -85,6 +85,10 @@ pub struct PpcContext { pub xer_ca: u8, pub xer_ov: u8, pub xer_so: u8, + /// XER[25:31] string-byte count (`TBC`). Read/written by `mtspr XER`, + /// consumed by `lswx`/`stswx`. Per PPCBUG-123/124/161: was previously + /// unmodelled, making `lswx`/`stswx` a permanent no-op. + pub xer_tbc: u8, // Altivec VSCR. Only bits 16 (NJ) and 31 (SAT) of word 3 are meaningful. pub vscr: Vec128, // VRSAVE (SPR 256). Bitmask of which VRs need saving across context switches. @@ -157,6 +161,7 @@ impl PpcContext { xer_ca: 0, xer_ov: 0, xer_so: 0, + xer_tbc: 0, // VSCR starts with NJ bit set (denormals flushed) — matches canary // thread_state.cc initialization. vscr: Vec128::from_u32x4(0, 0, 0, VSCR_NJ_MASK), @@ -240,7 +245,10 @@ impl PpcContext { /// Get the full XER register value. pub fn xer(&self) -> u32 { - ((self.xer_so as u32) << 31) | ((self.xer_ov as u32) << 30) | ((self.xer_ca as u32) << 29) + ((self.xer_so as u32) << 31) + | ((self.xer_ov as u32) << 30) + | ((self.xer_ca as u32) << 29) + | (self.xer_tbc as u32) // PPCBUG-123/566: bits 0-6 (TBC). } /// Set XER from a full 32-bit value. @@ -248,6 +256,7 @@ impl PpcContext { self.xer_so = ((val >> 31) & 1) as u8; self.xer_ov = ((val >> 30) & 1) as u8; self.xer_ca = ((val >> 29) & 1) as u8; + self.xer_tbc = (val & 0x7F) as u8; // PPCBUG-124. } /// Read the VSCR SAT (sticky saturation) bit. diff --git a/crates/xenia-cpu/src/interpreter.rs b/crates/xenia-cpu/src/interpreter.rs index 4f0b8e6..8143d7d 100644 --- a/crates/xenia-cpu/src/interpreter.rs +++ b/crates/xenia-cpu/src/interpreter.rs @@ -1520,7 +1520,7 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) - // String load/store PpcOpcode::lswi => { let mut ea = if instr.ra() == 0 { 0u32 } else { ctx.gpr[instr.ra()] as u32 }; - let nb = if instr.rb() == 0 { 32 } else { instr.rb() as u32 }; + let nb = if instr.nb() == 0 { 32 } else { instr.nb() }; let mut rd = instr.rd(); let mut bytes_left = nb; while bytes_left > 0 { @@ -1539,7 +1539,7 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) - } PpcOpcode::stswi => { let mut ea = if instr.ra() == 0 { 0u32 } else { ctx.gpr[instr.ra()] as u32 }; - let nb = if instr.rb() == 0 { 32 } else { instr.rb() as u32 }; + let nb = if instr.nb() == 0 { 32 } else { instr.nb() }; let mut rs = instr.rs(); let mut bytes_left = nb; if let Some(t) = ctx.reservation_table.as_ref().filter(|t| t.is_enabled()) { @@ -1707,9 +1707,15 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) - // ===== Load multiple ===== PpcOpcode::lmw => { + // PPCBUG-125: PowerISA marks `lmw` invalid when rA is in [rT..31]; + // canary skips the write to rA in that case to preserve the EA base. let mut ea = if instr.ra() == 0 { 0u64 } else { ctx.gpr[instr.ra()] }; ea = ea.wrapping_add(instr.d() as i64 as u64); for r in instr.rd()..32 { + if r == instr.ra() { + ea = ea.wrapping_add(4); + continue; + } ctx.gpr[r] = mem.read_u32(ea as u32) as u64; ea = ea.wrapping_add(4); }