fix(cpu): PPCBUG-123/124/125/126/161/162/566 XER TBC + lswi/stswi/lmw
Phase 6 batch 2 — XER TBC enabling + load/store-multiple cleanups. - PPCBUG-123/124/161/566 (coupled): XER TBC field was unmodelled — `ctx.xer()` always returned 0 in bits 0-6, and `ctx.set_xer()` silently discarded any TBC writes. Result: `lswx` and `stswx` were permanent no-ops (the `while bytes_left > 0` loop never executed). Fix: add `pub xer_tbc: u8` to `PpcContext`; wire into `xer()` and `set_xer()`. Initialize to 0 in `PpcContext::new()`. lswx/stswx bodies are correct as-is once the infrastructure is wired. - PPCBUG-125 lmw: PowerISA marks `lmw rT, D(rA)` invalid when rA is in [rT..31]; canary skips the write to rA to preserve the EA base. Now matches canary. - PPCBUG-126/162 lswi/stswi: replaced `instr.rb()` with `instr.nb()` for the NB field. Both accessors return identical values today (bits 16-20), but the maintenance hazard from the misnomer is now removed. A future `rb()` type-system refactor would have broken lswi/stswi silently. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -85,6 +85,10 @@ pub struct PpcContext {
|
|||||||
pub xer_ca: u8,
|
pub xer_ca: u8,
|
||||||
pub xer_ov: u8,
|
pub xer_ov: u8,
|
||||||
pub xer_so: 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.
|
// Altivec VSCR. Only bits 16 (NJ) and 31 (SAT) of word 3 are meaningful.
|
||||||
pub vscr: Vec128,
|
pub vscr: Vec128,
|
||||||
// VRSAVE (SPR 256). Bitmask of which VRs need saving across context switches.
|
// VRSAVE (SPR 256). Bitmask of which VRs need saving across context switches.
|
||||||
@@ -157,6 +161,7 @@ impl PpcContext {
|
|||||||
xer_ca: 0,
|
xer_ca: 0,
|
||||||
xer_ov: 0,
|
xer_ov: 0,
|
||||||
xer_so: 0,
|
xer_so: 0,
|
||||||
|
xer_tbc: 0,
|
||||||
// VSCR starts with NJ bit set (denormals flushed) — matches canary
|
// VSCR starts with NJ bit set (denormals flushed) — matches canary
|
||||||
// thread_state.cc initialization.
|
// thread_state.cc initialization.
|
||||||
vscr: Vec128::from_u32x4(0, 0, 0, VSCR_NJ_MASK),
|
vscr: Vec128::from_u32x4(0, 0, 0, VSCR_NJ_MASK),
|
||||||
@@ -240,7 +245,10 @@ impl PpcContext {
|
|||||||
|
|
||||||
/// Get the full XER register value.
|
/// Get the full XER register value.
|
||||||
pub fn xer(&self) -> u32 {
|
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.
|
/// Set XER from a full 32-bit value.
|
||||||
@@ -248,6 +256,7 @@ impl PpcContext {
|
|||||||
self.xer_so = ((val >> 31) & 1) as u8;
|
self.xer_so = ((val >> 31) & 1) as u8;
|
||||||
self.xer_ov = ((val >> 30) & 1) as u8;
|
self.xer_ov = ((val >> 30) & 1) as u8;
|
||||||
self.xer_ca = ((val >> 29) & 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.
|
/// Read the VSCR SAT (sticky saturation) bit.
|
||||||
|
|||||||
@@ -1520,7 +1520,7 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
|||||||
// String load/store
|
// String load/store
|
||||||
PpcOpcode::lswi => {
|
PpcOpcode::lswi => {
|
||||||
let mut ea = if instr.ra() == 0 { 0u32 } else { ctx.gpr[instr.ra()] as u32 };
|
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 rd = instr.rd();
|
||||||
let mut bytes_left = nb;
|
let mut bytes_left = nb;
|
||||||
while bytes_left > 0 {
|
while bytes_left > 0 {
|
||||||
@@ -1539,7 +1539,7 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
|||||||
}
|
}
|
||||||
PpcOpcode::stswi => {
|
PpcOpcode::stswi => {
|
||||||
let mut ea = if instr.ra() == 0 { 0u32 } else { ctx.gpr[instr.ra()] as u32 };
|
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 rs = instr.rs();
|
||||||
let mut bytes_left = nb;
|
let mut bytes_left = nb;
|
||||||
if let Some(t) = ctx.reservation_table.as_ref().filter(|t| t.is_enabled()) {
|
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 =====
|
// ===== Load multiple =====
|
||||||
PpcOpcode::lmw => {
|
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()] };
|
let mut ea = if instr.ra() == 0 { 0u64 } else { ctx.gpr[instr.ra()] };
|
||||||
ea = ea.wrapping_add(instr.d() as i64 as u64);
|
ea = ea.wrapping_add(instr.d() as i64 as u64);
|
||||||
for r in instr.rd()..32 {
|
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;
|
ctx.gpr[r] = mem.read_u32(ea as u32) as u64;
|
||||||
ea = ea.wrapping_add(4);
|
ea = ea.wrapping_add(4);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user