fix(cpu): PPCBUG-009/010+011/041+042+043 mul/div + srawx truncation
Phase 4 batch 4: mulwx, divwx (coupled +CR0), srawx/srawix (coupled +CR0). - PPCBUG-009 mullwx: 32-bit ABI. Product truncated to u32 before write. OE handler still uses full i64 product to detect overflow. - PPCBUG-010+011 divwx (coupled): quotient zero-extended (canary uses ZeroExtend(v, INT64_TYPE)). CR0 view via i32 — without this, a negative i32 quotient (e.g. -3 from -10/3) would be classified as positive in i64 view of the now-zero-extended writeback. - PPCBUG-041+042+043 srawx/srawix (coupled): writeback uses `as u32 as u64` (was `as i64 as u64`). All-ones case (sh>=32 with negative input) writes 0x00000000_FFFFFFFF instead of u64::MAX. CR0 view via i32. CA logic preserved unchanged (audit-verified independently correct). Tests: - mullwx_overflow_truncates_to_32 (PPCBUG-009). - divwx_negative_quotient_zero_extends (PPCBUG-010+011). - srawx_negative_value_zero_extends_upper (PPCBUG-041+043). - srawix_high_count_negative_input_yields_low32_all_ones (PPCBUG-042+043). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -347,16 +347,17 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
|||||||
ctx.pc += 4;
|
ctx.pc += 4;
|
||||||
}
|
}
|
||||||
PpcOpcode::mullwx => {
|
PpcOpcode::mullwx => {
|
||||||
|
// PPCBUG-009: 32-bit ABI. Truncate product to u32 — overflow detection
|
||||||
|
// (mullw_ov) still uses the full i64 product to catch the overflow.
|
||||||
let ra = ctx.gpr[instr.ra()] as i32 as i64;
|
let ra = ctx.gpr[instr.ra()] as i32 as i64;
|
||||||
let rb = ctx.gpr[instr.rb()] as i32 as i64;
|
let rb = ctx.gpr[instr.rb()] as i32 as i64;
|
||||||
let product = ra.wrapping_mul(rb);
|
let product = ra.wrapping_mul(rb);
|
||||||
ctx.gpr[instr.rd()] = product as u64;
|
ctx.gpr[instr.rd()] = product as u32 as u64;
|
||||||
if instr.oe() {
|
if instr.oe() {
|
||||||
// OV iff the 64-bit product can't fit into 32-bit signed.
|
|
||||||
overflow::apply(ctx, overflow::mullw_ov(product));
|
overflow::apply(ctx, overflow::mullw_ov(product));
|
||||||
}
|
}
|
||||||
if instr.rc_bit() {
|
if instr.rc_bit() {
|
||||||
ctx.update_cr_signed(0, ctx.gpr[instr.rd()] as i64);
|
ctx.update_cr_signed(0, ctx.gpr[instr.rd()] as u32 as i32 as i64);
|
||||||
}
|
}
|
||||||
ctx.pc += 4;
|
ctx.pc += 4;
|
||||||
}
|
}
|
||||||
@@ -381,20 +382,21 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
|||||||
ctx.pc += 4;
|
ctx.pc += 4;
|
||||||
}
|
}
|
||||||
PpcOpcode::divwx => {
|
PpcOpcode::divwx => {
|
||||||
|
// PPCBUG-010+011 coupled: 32-bit ABI. Quotient zero-extended to u64
|
||||||
|
// (canary explicitly uses ZeroExtend(v, INT64_TYPE)). CR0 view via i32.
|
||||||
let ra = ctx.gpr[instr.ra()] as i32;
|
let ra = ctx.gpr[instr.ra()] as i32;
|
||||||
let rb = ctx.gpr[instr.rb()] as i32;
|
let rb = ctx.gpr[instr.rb()] as i32;
|
||||||
let ov = overflow::divw_ov_signed(ra, rb);
|
let ov = overflow::divw_ov_signed(ra, rb);
|
||||||
if ov {
|
if ov {
|
||||||
// PPC: RT undefined on div-by-zero / INT_MIN/-1. Canary uses 0.
|
|
||||||
ctx.gpr[instr.rd()] = 0;
|
ctx.gpr[instr.rd()] = 0;
|
||||||
} else {
|
} else {
|
||||||
ctx.gpr[instr.rd()] = (ra / rb) as i64 as u64;
|
ctx.gpr[instr.rd()] = (ra / rb) as u32 as u64;
|
||||||
}
|
}
|
||||||
if instr.oe() {
|
if instr.oe() {
|
||||||
overflow::apply(ctx, ov);
|
overflow::apply(ctx, ov);
|
||||||
}
|
}
|
||||||
if instr.rc_bit() {
|
if instr.rc_bit() {
|
||||||
ctx.update_cr_signed(0, ctx.gpr[instr.rd()] as i64);
|
ctx.update_cr_signed(0, ctx.gpr[instr.rd()] as u32 as i32 as i64);
|
||||||
}
|
}
|
||||||
ctx.pc += 4;
|
ctx.pc += 4;
|
||||||
}
|
}
|
||||||
@@ -615,34 +617,37 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
|||||||
ctx.pc += 4;
|
ctx.pc += 4;
|
||||||
}
|
}
|
||||||
PpcOpcode::srawx => {
|
PpcOpcode::srawx => {
|
||||||
|
// PPCBUG-041+043 coupled: 32-bit ABI writeback truncation + CR0 i32.
|
||||||
|
// CA logic is independently correct (uses u32 shifted-out test).
|
||||||
let rs = ctx.gpr[instr.rs()] as i32;
|
let rs = ctx.gpr[instr.rs()] as i32;
|
||||||
let sh = ctx.gpr[instr.rb()] as u32 & 0x3F;
|
let sh = ctx.gpr[instr.rb()] as u32 & 0x3F;
|
||||||
if sh == 0 {
|
if sh == 0 {
|
||||||
ctx.gpr[instr.ra()] = rs as i64 as u64;
|
ctx.gpr[instr.ra()] = rs as u32 as u64;
|
||||||
ctx.xer_ca = 0;
|
ctx.xer_ca = 0;
|
||||||
} else if sh < 32 {
|
} else if sh < 32 {
|
||||||
let result = rs >> sh;
|
let result = rs >> sh;
|
||||||
ctx.xer_ca = if rs < 0 && (rs as u32) << (32 - sh) != 0 { 1 } else { 0 };
|
ctx.xer_ca = if rs < 0 && (rs as u32) << (32 - sh) != 0 { 1 } else { 0 };
|
||||||
ctx.gpr[instr.ra()] = result as i64 as u64;
|
ctx.gpr[instr.ra()] = result as u32 as u64;
|
||||||
} else {
|
} else {
|
||||||
ctx.gpr[instr.ra()] = if rs < 0 { u64::MAX } else { 0 };
|
ctx.gpr[instr.ra()] = if rs < 0 { 0xFFFF_FFFFu64 } else { 0 };
|
||||||
ctx.xer_ca = if rs < 0 { 1 } else { 0 };
|
ctx.xer_ca = if rs < 0 { 1 } else { 0 };
|
||||||
}
|
}
|
||||||
if instr.rc_bit() { ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as i64); }
|
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::srawix => {
|
PpcOpcode::srawix => {
|
||||||
|
// PPCBUG-042+043 coupled: same shape as srawx for the sh-immediate form.
|
||||||
let rs = ctx.gpr[instr.rs()] as i32;
|
let rs = ctx.gpr[instr.rs()] as i32;
|
||||||
let sh = instr.sh();
|
let sh = instr.sh();
|
||||||
if sh == 0 {
|
if sh == 0 {
|
||||||
ctx.gpr[instr.ra()] = rs as i64 as u64;
|
ctx.gpr[instr.ra()] = rs as u32 as u64;
|
||||||
ctx.xer_ca = 0;
|
ctx.xer_ca = 0;
|
||||||
} else {
|
} else {
|
||||||
let result = rs >> sh;
|
let result = rs >> sh;
|
||||||
ctx.xer_ca = if rs < 0 && (rs as u32) << (32 - sh) != 0 { 1 } else { 0 };
|
ctx.xer_ca = if rs < 0 && (rs as u32) << (32 - sh) != 0 { 1 } else { 0 };
|
||||||
ctx.gpr[instr.ra()] = result as i64 as u64;
|
ctx.gpr[instr.ra()] = result as u32 as u64;
|
||||||
}
|
}
|
||||||
if instr.rc_bit() { ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as i64); }
|
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::sldx => {
|
PpcOpcode::sldx => {
|
||||||
@@ -5198,6 +5203,72 @@ 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 mullwx_overflow_truncates_to_32() {
|
||||||
|
// PPCBUG-009: mullwo r5, r3, r4 with ra=0x10000, rb=0x10000 → product
|
||||||
|
// 0x100000000 (overflow). Low 32 = 0; OE must fire.
|
||||||
|
let mut ctx = PpcContext::new();
|
||||||
|
let mut mem = TestMem::new();
|
||||||
|
ctx.gpr[3] = 0x10000;
|
||||||
|
ctx.gpr[4] = 0x10000;
|
||||||
|
// mullwo r5, r3, r4 (XO=235, OE=1)
|
||||||
|
let raw = (31u32 << 26) | (5 << 21) | (3 << 16) | (4 << 11) | (1 << 10) | (235 << 1);
|
||||||
|
write_instr(&mut mem, 0, raw);
|
||||||
|
ctx.pc = 0;
|
||||||
|
step(&mut ctx, &mut mem);
|
||||||
|
assert_eq!(ctx.gpr[5], 0, "low 32 bits = 0");
|
||||||
|
assert_eq!(ctx.xer_ov, 1, "overflow detected");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn divwx_negative_quotient_zero_extends() {
|
||||||
|
// PPCBUG-010+011: -10 / 3 = -3 must produce 0x00000000_FFFFFFFD,
|
||||||
|
// not 0xFFFFFFFF_FFFFFFFD. CR0.LT must still fire (i32 view of FFFFFFFD is negative).
|
||||||
|
let mut ctx = PpcContext::new();
|
||||||
|
let mut mem = TestMem::new();
|
||||||
|
ctx.gpr[3] = (-10i32) as u32 as u64;
|
||||||
|
ctx.gpr[4] = 3;
|
||||||
|
// divwx. r5, r3, r4 (XO=491, Rc=1)
|
||||||
|
let raw = (31u32 << 26) | (5 << 21) | (3 << 16) | (4 << 11) | (491 << 1) | 1;
|
||||||
|
write_instr(&mut mem, 0, raw);
|
||||||
|
ctx.pc = 0;
|
||||||
|
step(&mut ctx, &mut mem);
|
||||||
|
assert_eq!(ctx.gpr[5], 0x0000_0000_FFFF_FFFDu64);
|
||||||
|
assert!(ctx.cr[0].lt, "CR0.LT must fire for negative i32 quotient");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn srawx_negative_value_zero_extends_upper() {
|
||||||
|
// PPCBUG-041+043: srawx of negative i32 by 1 produces a negative i32;
|
||||||
|
// writeback must zero-extend to u64 (not sign-extend).
|
||||||
|
let mut ctx = PpcContext::new();
|
||||||
|
let mut mem = TestMem::new();
|
||||||
|
ctx.gpr[3] = 0x8000_0000u64; // i32::MIN
|
||||||
|
ctx.gpr[4] = 1;
|
||||||
|
// srawx. r5, r3, r4 (XO=792, Rc=1)
|
||||||
|
let raw = (31u32 << 26) | (3 << 21) | (5 << 16) | (4 << 11) | (792 << 1) | 1;
|
||||||
|
write_instr(&mut mem, 0, raw);
|
||||||
|
ctx.pc = 0;
|
||||||
|
step(&mut ctx, &mut mem);
|
||||||
|
assert_eq!(ctx.gpr[5], 0x0000_0000_C000_0000u64);
|
||||||
|
assert!(ctx.cr[0].lt);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn srawix_high_count_negative_input_yields_low32_all_ones() {
|
||||||
|
// PPCBUG-042+043: srawi with count=31 on negative input → low 32 bits
|
||||||
|
// all ones (0xFFFFFFFF), upper 32 zero (was u64::MAX before fix).
|
||||||
|
let mut ctx = PpcContext::new();
|
||||||
|
let mut mem = TestMem::new();
|
||||||
|
ctx.gpr[3] = 0x8000_0000u64;
|
||||||
|
// srawix r5, r3, 31 (XO=824)
|
||||||
|
let raw = (31u32 << 26) | (3 << 21) | (5 << 16) | (31 << 11) | (824 << 1);
|
||||||
|
write_instr(&mut mem, 0, raw);
|
||||||
|
ctx.pc = 0;
|
||||||
|
step(&mut ctx, &mut mem);
|
||||||
|
assert_eq!(ctx.gpr[5], 0x0000_0000_FFFF_FFFFu64);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn addi_li_neg_one_zero_extends_upper() {
|
fn addi_li_neg_one_zero_extends_upper() {
|
||||||
// PPCBUG-001: `li r3, -1` (= addi r3, r0, -1) must produce
|
// PPCBUG-001: `li r3, -1` (= addi r3, r0, -1) must produce
|
||||||
|
|||||||
Reference in New Issue
Block a user