fix(cpu): PPCBUG-012-017/020/023-026/032/044 4c+4d latent + CR0 catch-all
Phase 4 batch 6: latent writeback truncation (4c) and CR0 catch-all (4d). ~13 PPCBUGs across all remaining 32-bit ABI ALU sites. Latent writeback (4c) — the 4a/4b fixes already eliminate the upstream poisoning, but a defensive truncation here catches any future regression: - PPCBUG-012 addx, PPCBUG-013 addcx, PPCBUG-014 addex, PPCBUG-015 addzex, PPCBUG-016 addmex, PPCBUG-017 subfx — all rewritten to compute on u32 operands and write `as u64`. CA computed via 32-bit unsigned compare. Overflow now uses `true_sum != (result32 as i32) as i128` (32-bit predicate, since sum_overflow_64 is i64-bounded). - PPCBUG-032 andx/orx/xorx — CR0 catch-all only (results inherit upper bits from operands; once those are clean, no truncation needed). CR0 catch-all (4d) — fix the `update_cr_signed(0, X as i64)` pattern at every 32-bit-ABI Rc=1 path: - PPCBUG-020 catch-all: applied to mulhwx, mulhwux, divwux, mullwx (was already done in batch 4), addx/addcx/addex/addzex/addmex/subfx (now in 4c above), andx/orx/xorx, andix, andisx, slwx, srwx, cntlzwx, rlwinmx, rlwimix, rlwnmx, mullwx (already), divwx (already), srawx/srawix (already in batch 4). - PPCBUG-023 andisx: now correctly classifies bit-31 results as CR0.LT. - PPCBUG-024 rlwinmx, PPCBUG-025 rlwimix, PPCBUG-026 rlwnmx. - PPCBUG-044 slwx/srwx: bit-31 result like 0x80000000 now CR0.LT. 64-bit ABI ops (rldicl/rldicr/rldic/rldimi/rldcl/rldcr, sldx/srdx/sradx/ sradix, mulhdx/mulhdux/mulldx, divdx/divdux, cntlzdx) intentionally retain the 64-bit `as i64` form per ISA — these are 64-bit-mode instructions. Updated old tests: - addo_sets_xer_ov_on_signed_overflow_and_stickies_so: i32::MAX + 1 → INT_MIN. - addx_rc_uses_64bit_compare_not_32bit: renamed to ..._uses_32bit_compare_in_xbox_abi with assertions flipped to the correct 32-bit ABI behavior. New tests: - andisx_sign_bit_set_classifies_lt (PPCBUG-023). - slwx_high_bit_result_classifies_lt (PPCBUG-044). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -173,89 +173,97 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
|
||||
// ===== ALU: Register =====
|
||||
PpcOpcode::addx => {
|
||||
let ra = ctx.gpr[instr.ra()];
|
||||
let rb = ctx.gpr[instr.rb()];
|
||||
let result = ra.wrapping_add(rb);
|
||||
ctx.gpr[instr.rd()] = result;
|
||||
// PPCBUG-012+020: 32-bit ABI writeback truncation + CR0 i32 view.
|
||||
let ra32 = ctx.gpr[instr.ra()] as u32;
|
||||
let rb32 = ctx.gpr[instr.rb()] as u32;
|
||||
let result32 = ra32.wrapping_add(rb32);
|
||||
ctx.gpr[instr.rd()] = result32 as u64;
|
||||
if instr.oe() {
|
||||
overflow::apply(ctx, overflow::add_ov_64(ra, rb, result));
|
||||
let true_sum = (ra32 as i32 as i128) + (rb32 as i32 as i128);
|
||||
overflow::apply(ctx, true_sum != (result32 as i32) as i128);
|
||||
}
|
||||
if instr.rc_bit() {
|
||||
ctx.update_cr_signed(0, result as i64);
|
||||
ctx.update_cr_signed(0, result32 as i32 as i64);
|
||||
}
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::addcx => {
|
||||
let ra = ctx.gpr[instr.ra()];
|
||||
let rb = ctx.gpr[instr.rb()];
|
||||
let result = ra.wrapping_add(rb);
|
||||
ctx.xer_ca = if result < ra { 1 } else { 0 };
|
||||
ctx.gpr[instr.rd()] = result;
|
||||
// PPCBUG-013+020: 32-bit truncation; CA from u32 unsigned compare.
|
||||
let ra32 = ctx.gpr[instr.ra()] as u32;
|
||||
let rb32 = ctx.gpr[instr.rb()] as u32;
|
||||
let result32 = ra32.wrapping_add(rb32);
|
||||
ctx.xer_ca = if result32 < ra32 { 1 } else { 0 };
|
||||
ctx.gpr[instr.rd()] = result32 as u64;
|
||||
if instr.oe() {
|
||||
overflow::apply(ctx, overflow::add_ov_64(ra, rb, result));
|
||||
let true_sum = (ra32 as i32 as i128) + (rb32 as i32 as i128);
|
||||
overflow::apply(ctx, true_sum != (result32 as i32) as i128);
|
||||
}
|
||||
if instr.rc_bit() {
|
||||
ctx.update_cr_signed(0, result as i64);
|
||||
ctx.update_cr_signed(0, result32 as i32 as i64);
|
||||
}
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::addex => {
|
||||
let ra = ctx.gpr[instr.ra()];
|
||||
let rb = ctx.gpr[instr.rb()];
|
||||
let ca = ctx.xer_ca as u64;
|
||||
let result = ra.wrapping_add(rb).wrapping_add(ca);
|
||||
ctx.xer_ca = if result < ra || (ca != 0 && result == ra) { 1 } else { 0 };
|
||||
ctx.gpr[instr.rd()] = result;
|
||||
// PPCBUG-014+020: 32-bit truncation; CA from u32 unsigned compare.
|
||||
let ra32 = ctx.gpr[instr.ra()] as u32;
|
||||
let rb32 = ctx.gpr[instr.rb()] as u32;
|
||||
let ca = ctx.xer_ca as u32;
|
||||
let result32 = ra32.wrapping_add(rb32).wrapping_add(ca);
|
||||
ctx.xer_ca = if result32 < ra32 || (ca != 0 && result32 == ra32) { 1 } else { 0 };
|
||||
ctx.gpr[instr.rd()] = result32 as u64;
|
||||
if instr.oe() {
|
||||
let true_sum = (ra as i64 as i128) + (rb as i64 as i128) + (ca as i128);
|
||||
overflow::apply(ctx, overflow::sum_overflow_64(true_sum, result));
|
||||
let true_sum = (ra32 as i32 as i128) + (rb32 as i32 as i128) + (ca as i128);
|
||||
overflow::apply(ctx, true_sum != (result32 as i32) as i128);
|
||||
}
|
||||
if instr.rc_bit() {
|
||||
ctx.update_cr_signed(0, result as i64);
|
||||
ctx.update_cr_signed(0, result32 as i32 as i64);
|
||||
}
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::addzex => {
|
||||
let ra = ctx.gpr[instr.ra()];
|
||||
let ca = ctx.xer_ca as u64;
|
||||
let result = ra.wrapping_add(ca);
|
||||
ctx.xer_ca = if result < ra { 1 } else { 0 };
|
||||
ctx.gpr[instr.rd()] = result;
|
||||
// PPCBUG-015+020: 32-bit truncation.
|
||||
let ra32 = ctx.gpr[instr.ra()] as u32;
|
||||
let ca = ctx.xer_ca as u32;
|
||||
let result32 = ra32.wrapping_add(ca);
|
||||
ctx.xer_ca = if result32 < ra32 { 1 } else { 0 };
|
||||
ctx.gpr[instr.rd()] = result32 as u64;
|
||||
if instr.oe() {
|
||||
let true_sum = (ra as i64 as i128) + (ca as i128);
|
||||
overflow::apply(ctx, overflow::sum_overflow_64(true_sum, result));
|
||||
let true_sum = (ra32 as i32 as i128) + (ca as i128);
|
||||
overflow::apply(ctx, true_sum != (result32 as i32) as i128);
|
||||
}
|
||||
if instr.rc_bit() {
|
||||
ctx.update_cr_signed(0, result as i64);
|
||||
ctx.update_cr_signed(0, result32 as i32 as i64);
|
||||
}
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::addmex => {
|
||||
let ra = ctx.gpr[instr.ra()];
|
||||
let ca = ctx.xer_ca as u64;
|
||||
let result = ra.wrapping_add(ca).wrapping_sub(1);
|
||||
ctx.xer_ca = if ra != 0 || ca != 0 { 1 } else { 0 };
|
||||
ctx.gpr[instr.rd()] = result;
|
||||
// PPCBUG-016+020: 32-bit truncation. RT = RA + CA - 1.
|
||||
let ra32 = ctx.gpr[instr.ra()] as u32;
|
||||
let ca = ctx.xer_ca as u32;
|
||||
let result32 = ra32.wrapping_add(ca).wrapping_sub(1);
|
||||
ctx.xer_ca = if ra32 != 0 || ca != 0 { 1 } else { 0 };
|
||||
ctx.gpr[instr.rd()] = result32 as u64;
|
||||
if instr.oe() {
|
||||
// RT <- RA + CA + (-1)
|
||||
let true_sum = (ra as i64 as i128) + (ca as i128) - 1;
|
||||
overflow::apply(ctx, overflow::sum_overflow_64(true_sum, result));
|
||||
let true_sum = (ra32 as i32 as i128) + (ca as i128) - 1;
|
||||
overflow::apply(ctx, true_sum != (result32 as i32) as i128);
|
||||
}
|
||||
if instr.rc_bit() {
|
||||
ctx.update_cr_signed(0, result as i64);
|
||||
ctx.update_cr_signed(0, result32 as i32 as i64);
|
||||
}
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::subfx => {
|
||||
let ra = ctx.gpr[instr.ra()];
|
||||
let rb = ctx.gpr[instr.rb()];
|
||||
let result = rb.wrapping_sub(ra);
|
||||
ctx.gpr[instr.rd()] = result;
|
||||
// PPCBUG-017+020: 32-bit truncation.
|
||||
let ra32 = ctx.gpr[instr.ra()] as u32;
|
||||
let rb32 = ctx.gpr[instr.rb()] as u32;
|
||||
let result32 = rb32.wrapping_sub(ra32);
|
||||
ctx.gpr[instr.rd()] = result32 as u64;
|
||||
if instr.oe() {
|
||||
overflow::apply(ctx, overflow::sub_ov_64(ra, rb, result));
|
||||
let true_diff = (rb32 as i32 as i128) - (ra32 as i32 as i128);
|
||||
overflow::apply(ctx, overflow::sum_overflow_64(true_diff, result32 as u64));
|
||||
}
|
||||
if instr.rc_bit() {
|
||||
ctx.update_cr_signed(0, result as i64);
|
||||
ctx.update_cr_signed(0, result32 as i32 as i64);
|
||||
}
|
||||
ctx.pc += 4;
|
||||
}
|
||||
@@ -289,7 +297,7 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
if instr.oe() {
|
||||
// RT <- !RA + RB + CA == RB - RA - 1 + CA (32-bit semantics).
|
||||
let true_sum = (rb32 as i32 as i128) - (ra32 as i32 as i128) - 1 + (ca as i128);
|
||||
overflow::apply(ctx, overflow::sum_overflow_64(true_sum, result32 as u64));
|
||||
overflow::apply(ctx, true_sum != (result32 as i32) as i128);
|
||||
}
|
||||
if instr.rc_bit() {
|
||||
ctx.update_cr_signed(0, result32 as i32 as i64);
|
||||
@@ -307,7 +315,7 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
ctx.gpr[instr.rd()] = result32 as u64;
|
||||
if instr.oe() {
|
||||
let true_sum = -(ra32 as i32 as i128) - 1 + (ca as i128);
|
||||
overflow::apply(ctx, overflow::sum_overflow_64(true_sum, result32 as u64));
|
||||
overflow::apply(ctx, true_sum != (result32 as i32) as i128);
|
||||
}
|
||||
if instr.rc_bit() {
|
||||
ctx.update_cr_signed(0, result32 as i32 as i64);
|
||||
@@ -324,7 +332,7 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
ctx.gpr[instr.rd()] = result32 as u64;
|
||||
if instr.oe() {
|
||||
let true_sum = -(ra32 as i32 as i128) - 2 + (ca as i128);
|
||||
overflow::apply(ctx, overflow::sum_overflow_64(true_sum, result32 as u64));
|
||||
overflow::apply(ctx, true_sum != (result32 as i32) as i128);
|
||||
}
|
||||
if instr.rc_bit() {
|
||||
ctx.update_cr_signed(0, result32 as i32 as i64);
|
||||
@@ -362,22 +370,24 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::mulhwx => {
|
||||
// PPCBUG-020: 32-bit ABI CR0 view.
|
||||
let ra = ctx.gpr[instr.ra()] as i32 as i64;
|
||||
let rb = ctx.gpr[instr.rb()] as i32 as i64;
|
||||
let result = ra.wrapping_mul(rb);
|
||||
ctx.gpr[instr.rd()] = ((result >> 32) as i32 as i64 as u64) & 0xFFFF_FFFF;
|
||||
ctx.gpr[instr.rd()] = ((result >> 32) as u32) as u64;
|
||||
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;
|
||||
}
|
||||
PpcOpcode::mulhwux => {
|
||||
// PPCBUG-020: 32-bit ABI CR0 view.
|
||||
let ra = ctx.gpr[instr.ra()] as u32 as u64;
|
||||
let rb = ctx.gpr[instr.rb()] as u32 as u64;
|
||||
let result = ra.wrapping_mul(rb);
|
||||
ctx.gpr[instr.rd()] = (result >> 32) & 0xFFFF_FFFF;
|
||||
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;
|
||||
}
|
||||
@@ -401,6 +411,7 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::divwux => {
|
||||
// PPCBUG-020: 32-bit ABI CR0 view.
|
||||
let ra = ctx.gpr[instr.ra()] as u32;
|
||||
let rb = ctx.gpr[instr.rb()] as u32;
|
||||
let ov = overflow::divw_ov_unsigned(rb);
|
||||
@@ -413,7 +424,7 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
overflow::apply(ctx, ov);
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -486,13 +497,16 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
|
||||
// ===== Logical =====
|
||||
PpcOpcode::andix => {
|
||||
// PPCBUG-020: 32-bit ABI CR0 view.
|
||||
ctx.gpr[instr.ra()] = ctx.gpr[instr.rs()] & (instr.uimm16() as u64);
|
||||
ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as i64);
|
||||
ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as u32 as i32 as i64);
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::andisx => {
|
||||
// PPCBUG-023: 32-bit ABI CR0 view. `andis. rA, rS, 0x8000` to test
|
||||
// sign bit of a 32-bit word now correctly classifies bit 31 = 1 as LT.
|
||||
ctx.gpr[instr.ra()] = ctx.gpr[instr.rs()] & ((instr.uimm16() as u64) << 16);
|
||||
ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as i64);
|
||||
ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as u32 as i32 as i64);
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::ori => {
|
||||
@@ -512,8 +526,9 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::andx => {
|
||||
// PPCBUG-032+020: 32-bit ABI CR0 view (latent under clean inputs).
|
||||
ctx.gpr[instr.ra()] = ctx.gpr[instr.rs()] & ctx.gpr[instr.rb()];
|
||||
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;
|
||||
}
|
||||
PpcOpcode::andcx => {
|
||||
@@ -525,8 +540,9 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::orx => {
|
||||
// PPCBUG-032+020: 32-bit ABI CR0 view.
|
||||
ctx.gpr[instr.ra()] = ctx.gpr[instr.rs()] | ctx.gpr[instr.rb()];
|
||||
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;
|
||||
}
|
||||
PpcOpcode::orcx => {
|
||||
@@ -538,8 +554,9 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::xorx => {
|
||||
// PPCBUG-032+020: 32-bit ABI CR0 view.
|
||||
ctx.gpr[instr.ra()] = ctx.gpr[instr.rs()] ^ ctx.gpr[instr.rb()];
|
||||
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;
|
||||
}
|
||||
PpcOpcode::norx => {
|
||||
@@ -589,8 +606,10 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::cntlzwx => {
|
||||
// Result is 0..=32, fits in u32 with bit 31 always zero, so the
|
||||
// CR0 view is benign — use the catch-all 32-bit form for consistency.
|
||||
ctx.gpr[instr.ra()] = (ctx.gpr[instr.rs()] as u32).leading_zeros() 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;
|
||||
}
|
||||
PpcOpcode::cntlzdx => {
|
||||
@@ -601,19 +620,23 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
|
||||
// ===== Shift =====
|
||||
PpcOpcode::slwx => {
|
||||
// PPCBUG-044: 32-bit ABI CR0 view. A result with bit 31 set
|
||||
// (e.g. 0x80000000) is negative in i32 view but positive in i64.
|
||||
let sh = ctx.gpr[instr.rb()] as u32;
|
||||
ctx.gpr[instr.ra()] = if sh < 32 {
|
||||
((ctx.gpr[instr.rs()] as u32) << sh) as u64
|
||||
} 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;
|
||||
}
|
||||
PpcOpcode::srwx => {
|
||||
// PPCBUG-044: 32-bit ABI CR0 view (zero-extended right shift can never
|
||||
// have bit 31 set, but use the canonical form for consistency).
|
||||
let sh = ctx.gpr[instr.rb()] as u32;
|
||||
ctx.gpr[instr.ra()] = if sh < 32 {
|
||||
((ctx.gpr[instr.rs()] as u32) >> sh) as u64
|
||||
} 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;
|
||||
}
|
||||
PpcOpcode::srawx => {
|
||||
@@ -707,7 +730,8 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
let rotated = rs.rotate_left(sh);
|
||||
let mask = rlw_mask(mb, me);
|
||||
ctx.gpr[instr.ra()] = (rotated & mask) as u64;
|
||||
if instr.rc_bit() { ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as i64); }
|
||||
// PPCBUG-024: 32-bit ABI CR0 view.
|
||||
if instr.rc_bit() { ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as u32 as i32 as i64); }
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::rlwimix => {
|
||||
@@ -719,7 +743,8 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
let mask = rlw_mask(mb, me);
|
||||
let ra = ctx.gpr[instr.ra()] as u32;
|
||||
ctx.gpr[instr.ra()] = ((rotated & mask) | (ra & !mask)) as u64;
|
||||
if instr.rc_bit() { ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as i64); }
|
||||
// PPCBUG-025: 32-bit ABI CR0 view.
|
||||
if instr.rc_bit() { ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as u32 as i32 as i64); }
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::rlwnmx => {
|
||||
@@ -730,7 +755,8 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
let rotated = rs.rotate_left(sh);
|
||||
let mask = rlw_mask(mb, me);
|
||||
ctx.gpr[instr.ra()] = (rotated & mask) as u64;
|
||||
if instr.rc_bit() { ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as i64); }
|
||||
// PPCBUG-026: 32-bit ABI CR0 view.
|
||||
if instr.rc_bit() { ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as u32 as i32 as i64); }
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::rldiclx => {
|
||||
@@ -5004,14 +5030,15 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn addo_sets_xer_ov_on_signed_overflow_and_stickies_so() {
|
||||
// PPCBUG-012: 32-bit ABI. INT32_MAX + 1 overflows to INT32_MIN.
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.gpr[3] = i64::MAX as u64;
|
||||
ctx.gpr[3] = i32::MAX as u32 as u64;
|
||||
ctx.gpr[4] = 1;
|
||||
write_instr(&mut mem, 0, addx_raw(5, 3, 4, true, false));
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert_eq!(ctx.gpr[5], i64::MIN as u64);
|
||||
assert_eq!(ctx.gpr[5], 0x8000_0000u64);
|
||||
assert_eq!(ctx.xer_ov, 1, "OV must be set on signed overflow");
|
||||
assert_eq!(ctx.xer_so, 1, "SO must be stickied from OV");
|
||||
}
|
||||
@@ -5046,9 +5073,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn addx_rc_uses_64bit_compare_not_32bit() {
|
||||
// r3 = 0x0000_0000_FFFF_FFFF, r4 = 0 → result = 0x0000_0000_FFFF_FFFF.
|
||||
// As i32 this is -1 (lt). As i64 this is positive (gt). Spec says 64-bit.
|
||||
fn addx_rc_uses_32bit_compare_in_xbox_abi() {
|
||||
// PPCBUG-012+020: 32-bit ABI. r3 + r4 = 0xFFFFFFFF (low 32). As i32
|
||||
// this is -1 (CR0.LT). The previous 64-bit compare wrongly classified
|
||||
// this as positive (CR0.GT) for Xbox 360 binaries.
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.gpr[3] = 0x0000_0000_FFFF_FFFF;
|
||||
@@ -5057,8 +5085,8 @@ mod tests {
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert_eq!(ctx.gpr[5], 0x0000_0000_FFFF_FFFF);
|
||||
assert!(!ctx.cr[0].lt, "64-bit compare: value is positive, not negative");
|
||||
assert!(ctx.cr[0].gt);
|
||||
assert!(ctx.cr[0].lt, "32-bit ABI: 0xFFFFFFFF as i32 is -1, CR0.LT");
|
||||
assert!(!ctx.cr[0].gt);
|
||||
assert!(!ctx.cr[0].eq);
|
||||
}
|
||||
|
||||
@@ -5203,6 +5231,39 @@ mod tests {
|
||||
assert_eq!(ctx.xer_ca, 1, "rb>=ra → CA=1 (10 > 5)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn andisx_sign_bit_set_classifies_lt() {
|
||||
// PPCBUG-023: andis. r4, r3, 0x8000 with r3=0xFFFFFFFF should produce
|
||||
// result=0x80000000 with CR0.LT=1 (i32 view).
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.gpr[3] = 0xFFFF_FFFFu64;
|
||||
// andis. r4, r3, 0x8000: opcode 29, uimm16 = 0x8000
|
||||
let raw = (29u32 << 26) | (3 << 21) | (4 << 16) | 0x8000;
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert_eq!(ctx.gpr[4], 0x8000_0000u64);
|
||||
assert!(ctx.cr[0].lt, "result=0x80000000 → i32 view negative → CR0.LT");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slwx_high_bit_result_classifies_lt() {
|
||||
// PPCBUG-044: slwx producing 0x80000000 must classify as CR0.LT under
|
||||
// the 32-bit ABI, not CR0.GT (which 64-bit view would give).
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.gpr[3] = 0x4000_0000u64;
|
||||
ctx.gpr[4] = 1;
|
||||
// slwx. r5, r3, r4 (XO=24, Rc=1)
|
||||
let raw = (31u32 << 26) | (3 << 21) | (5 << 16) | (4 << 11) | (24 << 1) | 1;
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert_eq!(ctx.gpr[5], 0x8000_0000u64);
|
||||
assert!(ctx.cr[0].lt, "0x80000000 as i32 is negative");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lha_negative_halfword_zero_extends_upper() {
|
||||
// PPCBUG-095: memory 0x8000 must yield gpr[rD] = 0x00000000_FFFF8000.
|
||||
|
||||
Reference in New Issue
Block a user