fix(cpu): PPCBUG-095/096/097/098/105 halfword + lwa load truncation
Phase 4 batch 5: 5 PPCBUGs in the load family. lha/lhax/lhau/lhaux sign-extended halfword results to u64 (active poisoning for negative halfwords); lwa/lwax/lwaux sign-extended u32 results. - PPCBUG-095/096/097/098 lha[ux]: `as i16 as i64 as u64` → `as i16 as i32 as u32 as u64`. Sign-extend to i32 then zero-extend. Common trigger: int16_t struct fields, PCM samples, packed vertex deltas. Memory 0x8000 was producing 0xFFFFFFFF_FFFF8000. - PPCBUG-105 lwa/lwax/lwaux: `as i32 as i64 as u64` → `as u64`. Per-canary the 64-bit-mode form sign-extends, but in 32-bit ABI we must zero-extend (canary's behavior is rescued by x86 register zeroing in JIT; pure interpreter has no escape). Memory 0x80000000 was producing 0xFFFFFFFF_80000000. Tests: - lha_negative_halfword_zero_extends_upper (PPCBUG-095). - lhaux_negative_halfword_clean_writeback (PPCBUG-098 + EA update). - lwa_high_bit_set_zero_extends_upper (PPCBUG-105). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1030,13 +1030,13 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
|||||||
PpcOpcode::lha => {
|
PpcOpcode::lha => {
|
||||||
let ea = if instr.ra() == 0 { 0u64 } else { ctx.gpr[instr.ra()] };
|
let ea = if instr.ra() == 0 { 0u64 } else { ctx.gpr[instr.ra()] };
|
||||||
let ea = ea.wrapping_add(instr.d() as i64 as u64) as u32;
|
let ea = ea.wrapping_add(instr.d() as i64 as u64) as u32;
|
||||||
ctx.gpr[instr.rd()] = mem.read_u16(ea) as i16 as i64 as u64;
|
ctx.gpr[instr.rd()] = mem.read_u16(ea) as i16 as i32 as u32 as u64;
|
||||||
ctx.pc += 4;
|
ctx.pc += 4;
|
||||||
}
|
}
|
||||||
PpcOpcode::lhax => {
|
PpcOpcode::lhax => {
|
||||||
let ea = if instr.ra() == 0 { 0u64 } else { ctx.gpr[instr.ra()] };
|
let ea = if instr.ra() == 0 { 0u64 } else { ctx.gpr[instr.ra()] };
|
||||||
let ea = ea.wrapping_add(ctx.gpr[instr.rb()]) as u32;
|
let ea = ea.wrapping_add(ctx.gpr[instr.rb()]) as u32;
|
||||||
ctx.gpr[instr.rd()] = mem.read_u16(ea) as i16 as i64 as u64;
|
ctx.gpr[instr.rd()] = mem.read_u16(ea) as i16 as i32 as u32 as u64;
|
||||||
ctx.pc += 4;
|
ctx.pc += 4;
|
||||||
}
|
}
|
||||||
PpcOpcode::lhzux => {
|
PpcOpcode::lhzux => {
|
||||||
@@ -1047,13 +1047,13 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
|||||||
}
|
}
|
||||||
PpcOpcode::lhau => {
|
PpcOpcode::lhau => {
|
||||||
let ea = ctx.gpr[instr.ra()].wrapping_add(instr.d() as i64 as u64) as u32;
|
let ea = ctx.gpr[instr.ra()].wrapping_add(instr.d() as i64 as u64) as u32;
|
||||||
ctx.gpr[instr.rd()] = mem.read_u16(ea) as i16 as i64 as u64;
|
ctx.gpr[instr.rd()] = mem.read_u16(ea) as i16 as i32 as u32 as u64;
|
||||||
ctx.gpr[instr.ra()] = ea as u64;
|
ctx.gpr[instr.ra()] = ea as u64;
|
||||||
ctx.pc += 4;
|
ctx.pc += 4;
|
||||||
}
|
}
|
||||||
PpcOpcode::lhaux => {
|
PpcOpcode::lhaux => {
|
||||||
let ea = ctx.gpr[instr.ra()].wrapping_add(ctx.gpr[instr.rb()]) as u32;
|
let ea = ctx.gpr[instr.ra()].wrapping_add(ctx.gpr[instr.rb()]) as u32;
|
||||||
ctx.gpr[instr.rd()] = mem.read_u16(ea) as i16 as i64 as u64;
|
ctx.gpr[instr.rd()] = mem.read_u16(ea) as i16 as i32 as u32 as u64;
|
||||||
ctx.gpr[instr.ra()] = ea as u64;
|
ctx.gpr[instr.ra()] = ea as u64;
|
||||||
ctx.pc += 4;
|
ctx.pc += 4;
|
||||||
}
|
}
|
||||||
@@ -1072,18 +1072,18 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
|||||||
PpcOpcode::lwa => {
|
PpcOpcode::lwa => {
|
||||||
let ea = if instr.ra() == 0 { 0u64 } else { ctx.gpr[instr.ra()] };
|
let ea = if instr.ra() == 0 { 0u64 } else { ctx.gpr[instr.ra()] };
|
||||||
let ea = ea.wrapping_add(instr.ds() as i64 as u64) as u32;
|
let ea = ea.wrapping_add(instr.ds() as i64 as u64) as u32;
|
||||||
ctx.gpr[instr.rd()] = mem.read_u32(ea) as i32 as i64 as u64;
|
ctx.gpr[instr.rd()] = mem.read_u32(ea) as u64;
|
||||||
ctx.pc += 4;
|
ctx.pc += 4;
|
||||||
}
|
}
|
||||||
PpcOpcode::lwax => {
|
PpcOpcode::lwax => {
|
||||||
let ea = if instr.ra() == 0 { 0u64 } else { ctx.gpr[instr.ra()] };
|
let ea = if instr.ra() == 0 { 0u64 } else { ctx.gpr[instr.ra()] };
|
||||||
let ea = ea.wrapping_add(ctx.gpr[instr.rb()]) as u32;
|
let ea = ea.wrapping_add(ctx.gpr[instr.rb()]) as u32;
|
||||||
ctx.gpr[instr.rd()] = mem.read_u32(ea) as i32 as i64 as u64;
|
ctx.gpr[instr.rd()] = mem.read_u32(ea) as u64;
|
||||||
ctx.pc += 4;
|
ctx.pc += 4;
|
||||||
}
|
}
|
||||||
PpcOpcode::lwaux => {
|
PpcOpcode::lwaux => {
|
||||||
let ea = ctx.gpr[instr.ra()].wrapping_add(ctx.gpr[instr.rb()]) as u32;
|
let ea = ctx.gpr[instr.ra()].wrapping_add(ctx.gpr[instr.rb()]) as u32;
|
||||||
ctx.gpr[instr.rd()] = mem.read_u32(ea) as i32 as i64 as u64;
|
ctx.gpr[instr.rd()] = mem.read_u32(ea) as u64;
|
||||||
ctx.gpr[instr.ra()] = ea as u64;
|
ctx.gpr[instr.ra()] = ea as u64;
|
||||||
ctx.pc += 4;
|
ctx.pc += 4;
|
||||||
}
|
}
|
||||||
@@ -5203,6 +5203,55 @@ mod tests {
|
|||||||
assert_eq!(ctx.xer_ca, 1, "rb>=ra → CA=1 (10 > 5)");
|
assert_eq!(ctx.xer_ca, 1, "rb>=ra → CA=1 (10 > 5)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lha_negative_halfword_zero_extends_upper() {
|
||||||
|
// PPCBUG-095: memory 0x8000 must yield gpr[rD] = 0x00000000_FFFF8000.
|
||||||
|
let mut ctx = PpcContext::new();
|
||||||
|
let mem = TestMem::new();
|
||||||
|
mem.write_u16(0x100, 0x8000);
|
||||||
|
ctx.gpr[3] = 0x100;
|
||||||
|
// lha r5, 0(r3): opcode 42
|
||||||
|
let raw = (42u32 << 26) | (5 << 21) | (3 << 16) | 0;
|
||||||
|
write_instr(&mem, 0, raw);
|
||||||
|
ctx.pc = 0;
|
||||||
|
step(&mut ctx, &mem);
|
||||||
|
assert_eq!(ctx.gpr[5], 0x0000_0000_FFFF_8000u64);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lhaux_negative_halfword_clean_writeback() {
|
||||||
|
// PPCBUG-098: indexed update form. Memory 0xFFFF → rD = 0x00000000_FFFFFFFF;
|
||||||
|
// rA must update to the EA.
|
||||||
|
let mut ctx = PpcContext::new();
|
||||||
|
let mem = TestMem::new();
|
||||||
|
mem.write_u16(0x200, 0xFFFF);
|
||||||
|
ctx.gpr[3] = 0x100; // ra
|
||||||
|
ctx.gpr[4] = 0x100; // rb
|
||||||
|
// lhaux r5, r3, r4 (XO=375)
|
||||||
|
let raw = (31u32 << 26) | (5 << 21) | (3 << 16) | (4 << 11) | (375 << 1);
|
||||||
|
write_instr(&mem, 0, raw);
|
||||||
|
ctx.pc = 0;
|
||||||
|
step(&mut ctx, &mem);
|
||||||
|
assert_eq!(ctx.gpr[5], 0x0000_0000_FFFF_FFFFu64);
|
||||||
|
assert_eq!(ctx.gpr[3], 0x200, "rA updated to EA");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lwa_high_bit_set_zero_extends_upper() {
|
||||||
|
// PPCBUG-105: memory 0x80000000 must yield rD = 0x00000000_80000000
|
||||||
|
// under 32-bit ABI (no sign extension to bits 32-63).
|
||||||
|
let mut ctx = PpcContext::new();
|
||||||
|
let mem = TestMem::new();
|
||||||
|
mem.write_u32(0x100, 0x8000_0000);
|
||||||
|
ctx.gpr[3] = 0x100;
|
||||||
|
// lwa r5, 0(r3): opcode 58, XO=2 (DS-form, ds=0)
|
||||||
|
let raw = (58u32 << 26) | (5 << 21) | (3 << 16) | 2;
|
||||||
|
write_instr(&mem, 0, raw);
|
||||||
|
ctx.pc = 0;
|
||||||
|
step(&mut ctx, &mem);
|
||||||
|
assert_eq!(ctx.gpr[5], 0x0000_0000_8000_0000u64);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mullwx_overflow_truncates_to_32() {
|
fn mullwx_overflow_truncates_to_32() {
|
||||||
// PPCBUG-009: mullwo r5, r3, r4 with ra=0x10000, rb=0x10000 → product
|
// PPCBUG-009: mullwo r5, r3, r4 with ra=0x10000, rb=0x10000 → product
|
||||||
|
|||||||
Reference in New Issue
Block a user