fix(cpu): PPCBUG-006/008/018/019/028/029/030/031/033 4a active poisoning
Phase 4 batch 1: 9 PPCBUGs in the active-poisoning sub-section. All follow the pattern `!val` on u64, which unconditionally flips the upper 32 bits and poisons the GPR even with clean inputs — every execution corrupts the high 32 bits regardless of upstream state. Sub/neg family: - PPCBUG-006 negx: `(!ra).wrapping_add(1)` on u64 + neg_ov_64 checks 64-bit INT_MIN. Fix: do arithmetic in u32, OE checks PPC[ra32==0x80000000]. - PPCBUG-008 subfex: same shape as above plus 64-bit unsigned CA compare. Fix: cast all operands to u32, compute, write `as u64`. - PPCBUG-018 subfzex: `!ra` on u64. Fix: u32 arithmetic. - PPCBUG-019 subfmex: `!ra` on u64 + always-true CA edge (`!ra != 0` was always true for clean ra<0xFFFFFFFF because high bits of !u64 are non-zero). Fix: u32 arithmetic; CA predicate now correct. Logical NOT family: - PPCBUG-028 orcx: rs | !rb on u64 → high-bit poison. - PPCBUG-029 norx: !(rs|rb) — the `not` simplified mnemonic. Hot path, every `not` corrupted GPR upper 32 bits. - PPCBUG-030 nandx: !(rs&rb). - PPCBUG-031 eqvx: !(rs^rb). The common `eqv rA,rA,rA` set-to-all-ones idiom now produces 0x00000000_FFFFFFFF instead of 0xFFFFFFFF_FFFFFFFF. - PPCBUG-033 andcx: rs & !rb. CR0 update at every Rc=1 path now uses `as u32 as i32 as i64` so a result with bit 31 set gets classified as negative under the 32-bit ABI (was positive before because upper bits were ones; will be positive in new truncated form unless we cast through i32). This pre-emptively addresses PPCBUG-020 for these specific opcodes; the catch-all sweep in batch 6 covers the remaining sites. Tests: - nego_sets_ov_only_on_int_min: updated from i64::MIN → 0x80000000 (32-bit). - test_subfze_carry_only_when_ra_zero_and_ca_one: result expectations updated from u64::MAX → 0xFFFFFFFF (low 32 bits, upper 32 zero). - New: neg_clean_input_no_upper_bits (PPCBUG-006 regression). - New: norx_not_simplified_keeps_upper_bits_clean (PPCBUG-029 regression). - New: eqvx_self_self_self_sets_low32_to_all_ones (PPCBUG-031 regression). - New: andcx_bit_clear_keeps_upper_clean (PPCBUG-033 regression). - New: subfex_clean_inputs_no_upper_bits (PPCBUG-008 regression). - New: subfmex_ra_max_ca_zero_clears_ca (PPCBUG-019 always-true CA fix). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -266,65 +266,71 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::subfex => {
|
||||
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 rb > ra || (rb == ra && ca != 0) { 1 } else { 0 };
|
||||
ctx.gpr[instr.rd()] = result;
|
||||
// PPCBUG-008: 32-bit ABI. Compute in u32 space — `!ra` on u64 always
|
||||
// pollutes the upper 32 bits, making this an active poisoner.
|
||||
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 rb32 > ra32 || (rb32 == ra32 && ca != 0) { 1 } else { 0 };
|
||||
ctx.gpr[instr.rd()] = result32 as u64;
|
||||
if instr.oe() {
|
||||
// RT <- !RA + RB + CA == RB - RA - 1 + CA
|
||||
let true_sum = (rb as i64 as i128) - (ra as i64 as i128) - 1 + (ca as i128);
|
||||
overflow::apply(ctx, overflow::sum_overflow_64(true_sum, result));
|
||||
// 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));
|
||||
}
|
||||
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::subfzex => {
|
||||
let ra = ctx.gpr[instr.ra()];
|
||||
let ca = ctx.xer_ca as u64;
|
||||
let result = (!ra).wrapping_add(ca);
|
||||
// RT <- !RA + CA (no -1 term). 64-bit carry-out only when
|
||||
// !RA = u64::MAX (i.e. RA = 0) AND CA = 1.
|
||||
ctx.xer_ca = if ra == 0 && ca != 0 { 1 } else { 0 };
|
||||
ctx.gpr[instr.rd()] = result;
|
||||
// PPCBUG-018: same active-poisoning shape as subfex; operate in u32.
|
||||
let ra32 = ctx.gpr[instr.ra()] as u32;
|
||||
let ca = ctx.xer_ca as u32;
|
||||
let result32 = (!ra32).wrapping_add(ca);
|
||||
// RT <- !RA + CA (no -1 term). 32-bit carry-out only when
|
||||
// !ra32 = u32::MAX (i.e. ra32 = 0) AND ca = 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 == -RA - 1 + CA
|
||||
let true_sum = -(ra as i64 as i128) - 1 + (ca as i128);
|
||||
overflow::apply(ctx, overflow::sum_overflow_64(true_sum, result));
|
||||
let true_sum = -(ra32 as i32 as i128) - 1 + (ca as i128);
|
||||
overflow::apply(ctx, overflow::sum_overflow_64(true_sum, 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;
|
||||
}
|
||||
PpcOpcode::subfmex => {
|
||||
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-019: also fixes the always-true CA edge — `!ra` on u64
|
||||
// is non-zero when ra32==0xFFFFFFFF and ca==0, so CA was stuck at 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) == -RA - 2 + CA
|
||||
let true_sum = -(ra as i64 as i128) - 2 + (ca as i128);
|
||||
overflow::apply(ctx, overflow::sum_overflow_64(true_sum, result));
|
||||
let true_sum = -(ra32 as i32 as i128) - 2 + (ca as i128);
|
||||
overflow::apply(ctx, overflow::sum_overflow_64(true_sum, 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;
|
||||
}
|
||||
PpcOpcode::negx => {
|
||||
let ra = ctx.gpr[instr.ra()];
|
||||
let result = (!ra).wrapping_add(1);
|
||||
ctx.gpr[instr.rd()] = result;
|
||||
// PPCBUG-006: 32-bit ABI. `(!ra).wrapping_add(1)` on u64 always
|
||||
// sets upper 32 bits — every neg poisoned the GPR. neg_ov also
|
||||
// checks at 64-bit INT_MIN; should be 32-bit INT_MIN.
|
||||
let ra32 = ctx.gpr[instr.ra()] as u32;
|
||||
let result32 = (!ra32).wrapping_add(1);
|
||||
ctx.gpr[instr.rd()] = result32 as u64;
|
||||
if instr.oe() {
|
||||
overflow::apply(ctx, overflow::neg_ov_64(ra));
|
||||
overflow::apply(ctx, ra32 == 0x8000_0000);
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -497,8 +503,11 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::andcx => {
|
||||
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); }
|
||||
// PPCBUG-033: !rb on u64 flips upper 32 bits — active poisoning.
|
||||
let rs32 = ctx.gpr[instr.rs()] as u32;
|
||||
let rb32 = ctx.gpr[instr.rb()] as u32;
|
||||
ctx.gpr[instr.ra()] = (rs32 & !rb32) as u64;
|
||||
if instr.rc_bit() { ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as u32 as i32 as i64); }
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::orx => {
|
||||
@@ -507,8 +516,11 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::orcx => {
|
||||
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); }
|
||||
// PPCBUG-028: same shape as andcx — operate in u32.
|
||||
let rs32 = ctx.gpr[instr.rs()] as u32;
|
||||
let rb32 = ctx.gpr[instr.rb()] as u32;
|
||||
ctx.gpr[instr.ra()] = (rs32 | !rb32) as u64;
|
||||
if instr.rc_bit() { ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as u32 as i32 as i64); }
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::xorx => {
|
||||
@@ -517,18 +529,28 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::norx => {
|
||||
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); }
|
||||
// PPCBUG-029: `not` simplified mnemonic — every `not` poisoned the GPR.
|
||||
let rs32 = ctx.gpr[instr.rs()] as u32;
|
||||
let rb32 = ctx.gpr[instr.rb()] as u32;
|
||||
ctx.gpr[instr.ra()] = (!(rs32 | rb32)) as u64;
|
||||
if instr.rc_bit() { ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as u32 as i32 as i64); }
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::nandx => {
|
||||
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); }
|
||||
// PPCBUG-030: same shape — operate in u32.
|
||||
let rs32 = ctx.gpr[instr.rs()] as u32;
|
||||
let rb32 = ctx.gpr[instr.rb()] as u32;
|
||||
ctx.gpr[instr.ra()] = (!(rs32 & rb32)) as u64;
|
||||
if instr.rc_bit() { ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as u32 as i32 as i64); }
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::eqvx => {
|
||||
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); }
|
||||
// PPCBUG-031: `eqv rA, rA, rA` is a common "set to all-ones" idiom;
|
||||
// 64-bit form gave 0xFFFFFFFFFFFFFFFF but 32-bit ABI expects 0x00000000FFFFFFFF.
|
||||
let rs32 = ctx.gpr[instr.rs()] as u32;
|
||||
let rb32 = ctx.gpr[instr.rb()] as u32;
|
||||
ctx.gpr[instr.ra()] = (!(rs32 ^ rb32)) as u64;
|
||||
if instr.rc_bit() { ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as u32 as i32 as i64); }
|
||||
ctx.pc += 4;
|
||||
}
|
||||
|
||||
@@ -5067,17 +5089,115 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn nego_sets_ov_only_on_int_min() {
|
||||
// PPCBUG-006: 32-bit ABI. INT_MIN is 0x80000000 (low 32), not 0x8000000000000000.
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
// nego r5, r3 (XO=104, OE=1)
|
||||
ctx.gpr[3] = i64::MIN as u64;
|
||||
ctx.gpr[3] = 0x8000_0000;
|
||||
let raw = (31 << 26) | (5 << 21) | (3 << 16) | (1 << 10) | (104 << 1);
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert_eq!(ctx.xer_ov, 1);
|
||||
// -INT_MIN wraps to INT_MIN
|
||||
assert_eq!(ctx.gpr[5], i64::MIN as u64);
|
||||
// -INT_MIN wraps to INT_MIN (low 32 bits) with upper 32 bits zero.
|
||||
assert_eq!(ctx.gpr[5], 0x0000_0000_8000_0000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn neg_clean_input_no_upper_bits() {
|
||||
// PPCBUG-006 regression: neg r3=5 must produce 0x00000000_FFFFFFFB,
|
||||
// not 0xFFFFFFFF_FFFFFFFB (the 64-bit !ra-then-add-1 result).
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.gpr[3] = 5;
|
||||
let raw = (31u32 << 26) | (5 << 21) | (3 << 16) | (104 << 1);
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert_eq!(ctx.gpr[5], 0x0000_0000_FFFF_FFFB);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn norx_not_simplified_keeps_upper_bits_clean() {
|
||||
// PPCBUG-029: `not rA, rB` (norx with rs==rb) is the canonical not
|
||||
// simplified mnemonic. 64-bit !val poisons upper 32 bits of every
|
||||
// execution; under the 32-bit ABI we must truncate.
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.gpr[3] = 0x0000_0000_0000_00FF;
|
||||
// norx r5, r3, r3 (XO=124)
|
||||
let raw = (31u32 << 26) | (3 << 21) | (5 << 16) | (3 << 11) | (124 << 1);
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert_eq!(ctx.gpr[5], 0x0000_0000_FFFF_FF00, "upper 32 bits must be zero");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eqvx_self_self_self_sets_low32_to_all_ones() {
|
||||
// PPCBUG-031: `eqv rA, rA, rA` is a common "set-to-all-ones" idiom.
|
||||
// 64-bit !(0^0) gives u64::MAX (0xFFFFFFFF_FFFFFFFF); 32-bit ABI
|
||||
// expects 0x00000000_FFFFFFFF.
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.gpr[3] = 0;
|
||||
// eqvx r3, r3, r3 (XO=284)
|
||||
let raw = (31u32 << 26) | (3 << 21) | (3 << 16) | (3 << 11) | (284 << 1);
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert_eq!(ctx.gpr[3], 0x0000_0000_FFFF_FFFF);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn andcx_bit_clear_keeps_upper_clean() {
|
||||
// PPCBUG-033: `andc rA, rS, rB` = rS & !rB. 64-bit !rB poisons.
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.gpr[3] = 0xFFFF_FFFF; // rS
|
||||
ctx.gpr[4] = 0x000F; // rB (low bits to clear)
|
||||
// andcx r5, r3, r4 (XO=60)
|
||||
let raw = (31u32 << 26) | (3 << 21) | (5 << 16) | (4 << 11) | (60 << 1);
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert_eq!(ctx.gpr[5], 0x0000_0000_FFFF_FFF0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subfex_clean_inputs_no_upper_bits() {
|
||||
// PPCBUG-008: 32-bit ABI. RT = !RA + RB + CA. RA=5, RB=10, CA=1
|
||||
// → !5u32 = 0xFFFFFFFA, +10 = 0x100000004, +1 = 0x100000005, low32 = 5.
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.gpr[3] = 5;
|
||||
ctx.gpr[4] = 10;
|
||||
ctx.xer_ca = 1;
|
||||
// subfex r5, r3, r4 (XO=136)
|
||||
let raw = (31u32 << 26) | (5 << 21) | (3 << 16) | (4 << 11) | (136 << 1);
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert_eq!(ctx.gpr[5], 5);
|
||||
assert_eq!(ctx.xer_ca, 1, "rb>=ra → CA=1 (10 > 5)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subfmex_ra_max_ca_zero_clears_ca() {
|
||||
// PPCBUG-019: `subfme` with RA=u32::MAX and CA=0 should set CA=0
|
||||
// (because !u32::MAX = 0). The buggy code's `!ra != 0` predicate
|
||||
// on u64 was always true (because !u64-cast-of-u32::MAX has high
|
||||
// bits flipped non-zero), wrongly setting CA=1.
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.gpr[3] = 0xFFFF_FFFFu64;
|
||||
ctx.xer_ca = 0;
|
||||
// subfmex r5, r3 (XO=232)
|
||||
let raw = (31u32 << 26) | (5 << 21) | (3 << 16) | (232 << 1);
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert_eq!(ctx.xer_ca, 0, "RA=u32::MAX, CA=0 → !RA32==0, CA=0");
|
||||
}
|
||||
|
||||
// ---------- Phase 2 fixes: trap TO-field ----------
|
||||
@@ -6086,7 +6206,8 @@ mod tests {
|
||||
ctx.xer_ca = 0;
|
||||
step(&mut ctx, &mem);
|
||||
assert_eq!(ctx.xer_ca, 0, "ra=0, ca=0 should produce CA=0");
|
||||
assert_eq!(ctx.gpr[3], u64::MAX, "result = !0 + 0 = u64::MAX");
|
||||
// PPCBUG-018: 32-bit ABI. !0u32 + 0 = u32::MAX, with upper 32 bits zero.
|
||||
assert_eq!(ctx.gpr[3], 0xFFFF_FFFFu64, "result = !0u32 + 0 = u32::MAX");
|
||||
}
|
||||
// Case 3: ra=1, ca=0 → CA=0 (old buggy code reported CA=1)
|
||||
{
|
||||
@@ -6098,21 +6219,20 @@ mod tests {
|
||||
ctx.xer_ca = 0;
|
||||
step(&mut ctx, &mem);
|
||||
assert_eq!(ctx.xer_ca, 0, "ra=1, ca=0 should produce CA=0");
|
||||
assert_eq!(ctx.gpr[3], u64::MAX - 1, "result = !1 + 0 = u64::MAX - 1");
|
||||
// PPCBUG-018: 32-bit ABI. !1u32 + 0 = u32::MAX - 1, with upper 32 bits zero.
|
||||
assert_eq!(ctx.gpr[3], 0xFFFF_FFFEu64, "result = !1u32 + 0 = u32::MAX - 1");
|
||||
}
|
||||
// Case 4: ra=u64::MAX, ca=0 → CA=0 (old buggy code reported CA=1
|
||||
// because !ra == 0 only here, which the buggy `!ra != 0` predicate
|
||||
// happened to handle right; flip ca=1 to exercise the other arm)
|
||||
// Case 4: ra=u32::MAX, ca=1 → CA=0; result = !u32::MAX + 1 = 1.
|
||||
{
|
||||
let mut ctx = PpcContext::new();
|
||||
let mem = TestMem::new();
|
||||
write_instr(&mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
ctx.gpr[4] = u64::MAX;
|
||||
ctx.gpr[4] = 0xFFFF_FFFFu64;
|
||||
ctx.xer_ca = 1;
|
||||
step(&mut ctx, &mem);
|
||||
assert_eq!(ctx.xer_ca, 0, "ra=u64::MAX, ca=1 should produce CA=0");
|
||||
assert_eq!(ctx.gpr[3], 1, "result = !u64::MAX + 1 = 1");
|
||||
assert_eq!(ctx.xer_ca, 0, "ra=u32::MAX, ca=1 should produce CA=0");
|
||||
assert_eq!(ctx.gpr[3], 1, "result = !u32::MAX + 1 = 1");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user