fix(cpu): PPCBUG-034+035+036+037 extsbx/extshx writeback + CR0 (coupled)
Phase 4 batch 2: extsbx and extshx writeback truncation + CR0 view fix. Coupled per audit — must land together because the writeback fix would silently break CR0 sign classification if the CR0 fix didn't ship in the same commit. Before: - extsbx: `as i8 as i64 as u64` — every negative byte poisoned upper 32 bits (active poisoning, not latent). 0x80 → 0xFFFFFFFF_FFFFFF80. - extshx: same shape for halfwords. - CR0: `as i64` view — accidentally correct on the buggy 64-bit form because the high bits matched the byte's sign bit. After: - extsbx: `as i8 as i32 as u32 as u64` — sign-extend to i32 then zero-extend to u64. 0x80 → 0x00000000_FFFFFF80. - extshx: same for halfwords. - CR0: `as u32 as i32 as i64` — i32 view, so a result with bit 31 set is correctly classified as negative under the 32-bit ABI. Tests: - extsbx_negative_byte_zero_extends_upper: 0x80 input → 0x00000000_FFFFFF80 with CR0.LT set. - extshx_negative_halfword_zero_extends_upper: same shape for 0x8000. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -556,13 +556,17 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
|||||||
|
|
||||||
// ===== Extend/Count =====
|
// ===== Extend/Count =====
|
||||||
PpcOpcode::extsbx => {
|
PpcOpcode::extsbx => {
|
||||||
ctx.gpr[instr.ra()] = ctx.gpr[instr.rs()] as i8 as i64 as u64;
|
// PPCBUG-034: 32-bit ABI — sign-extend byte to i32, write zero-extended.
|
||||||
if instr.rc_bit() { ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as i64); }
|
// PPCBUG-036 (coupled): CR0 must view result as i32, not i64.
|
||||||
|
ctx.gpr[instr.ra()] = ctx.gpr[instr.rs()] as i8 as i32 as u32 as u64;
|
||||||
|
if instr.rc_bit() { ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as u32 as i32 as i64); }
|
||||||
ctx.pc += 4;
|
ctx.pc += 4;
|
||||||
}
|
}
|
||||||
PpcOpcode::extshx => {
|
PpcOpcode::extshx => {
|
||||||
ctx.gpr[instr.ra()] = ctx.gpr[instr.rs()] as i16 as i64 as u64;
|
// PPCBUG-035: same shape as extsbx for halfwords.
|
||||||
if instr.rc_bit() { ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as i64); }
|
// PPCBUG-037 (coupled): CR0 i32 view.
|
||||||
|
ctx.gpr[instr.ra()] = ctx.gpr[instr.rs()] as i16 as i32 as u32 as u64;
|
||||||
|
if instr.rc_bit() { ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as u32 as i32 as i64); }
|
||||||
ctx.pc += 4;
|
ctx.pc += 4;
|
||||||
}
|
}
|
||||||
PpcOpcode::extswx => {
|
PpcOpcode::extswx => {
|
||||||
@@ -5182,6 +5186,39 @@ 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 extsbx_negative_byte_zero_extends_upper() {
|
||||||
|
// PPCBUG-034+036 coupled: extsb of 0x80 (negative byte) must produce
|
||||||
|
// 0x00000000_FFFFFF80, NOT 0xFFFFFFFF_FFFFFF80. CR0.LT must still fire
|
||||||
|
// (i32 view of 0xFFFFFF80 is negative).
|
||||||
|
let mut ctx = PpcContext::new();
|
||||||
|
let mut mem = TestMem::new();
|
||||||
|
ctx.gpr[3] = 0x80;
|
||||||
|
// extsbx. r5, r3 (XO=954, Rc=1)
|
||||||
|
let raw = (31u32 << 26) | (3 << 21) | (5 << 16) | (954 << 1) | 1;
|
||||||
|
write_instr(&mut mem, 0, raw);
|
||||||
|
ctx.pc = 0;
|
||||||
|
step(&mut ctx, &mut mem);
|
||||||
|
assert_eq!(ctx.gpr[5], 0x0000_0000_FFFF_FF80);
|
||||||
|
assert!(ctx.cr[0].lt, "CR0.LT must fire for negative i32");
|
||||||
|
assert!(!ctx.cr[0].gt);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extshx_negative_halfword_zero_extends_upper() {
|
||||||
|
// PPCBUG-035+037 coupled: extsh of 0x8000 must produce 0x00000000_FFFF8000.
|
||||||
|
let mut ctx = PpcContext::new();
|
||||||
|
let mut mem = TestMem::new();
|
||||||
|
ctx.gpr[3] = 0x8000;
|
||||||
|
// extshx. r5, r3 (XO=922, Rc=1)
|
||||||
|
let raw = (31u32 << 26) | (3 << 21) | (5 << 16) | (922 << 1) | 1;
|
||||||
|
write_instr(&mut mem, 0, raw);
|
||||||
|
ctx.pc = 0;
|
||||||
|
step(&mut ctx, &mut mem);
|
||||||
|
assert_eq!(ctx.gpr[5], 0x0000_0000_FFFF_8000);
|
||||||
|
assert!(ctx.cr[0].lt);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn subfmex_ra_max_ca_zero_clears_ca() {
|
fn subfmex_ra_max_ca_zero_clears_ca() {
|
||||||
// PPCBUG-019: `subfme` with RA=u32::MAX and CA=0 should set CA=0
|
// PPCBUG-019: `subfme` with RA=u32::MAX and CA=0 should set CA=0
|
||||||
|
|||||||
Reference in New Issue
Block a user