fix(cpu): PPCBUG-275 276 420 421 422 423 562 600 fix vcmp Rc bit + decode dot forms
PPCBUG-562: Add vc_rc_bit() (PPC bit 21) and vx128r_rc_bit() (PPC bit 27) to decoder.rs. The generic rc_bit() reads bit 0 (PPC bit 31); all vcmp XO values are even so bit 0 is always 0, making CR6 permanently dead. PPCBUG-275/276/420/421: Replace rc_bit() with vc_rc_bit() at all 8 pure VC-form vcmp arms (vcmpequb, vcmpequh, vcmpgtub, vcmpgtsb, vcmpgtuh, vcmpgtsh, vcmpgtuw, vcmpgtsw) and with the correct per-form accessor at the 4 combined arms (vcmpeqfp|128, vcmpgefp|128, vcmpgtfp|128, vcmpequw|128) and vcmpbfp|128. PPCBUG-422: VX128_R-form 128-variants in combined arms now use vx128r_rc_bit() instead of vc_rc_bit(). PPCBUG-423/600: Add 5 dot-form key entries to decode_op6 so vcmp*fp128./vcmpequw128. decode as the correct opcode instead of Invalid. Uses a 5-bit key (bits22-24 + bit25 + bit27) for dot-forms to avoid aliasing against the shift/merge group (which sets bit25=1 when bit27=1). Interpreter uses vx128r_rc_bit() to conditionally update CR6. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2036,7 +2036,8 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
let mut r = [0u32; 4];
|
||||
for i in 0..4 { r[i] = if a[i] == b[i] { 0xFFFF_FFFF } else { 0 }; }
|
||||
ctx.vr[vd] = xenia_types::Vec128::from_u32x4_array(r);
|
||||
if instr.rc_bit() { update_cr6_from_vmask(&r, ctx); }
|
||||
let rc = if matches!(instr.opcode, PpcOpcode::vcmpeqfp128) { instr.vx128r_rc_bit() } else { instr.vc_rc_bit() };
|
||||
if rc { update_cr6_from_vmask(&r, ctx); }
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::vcmpgefp | PpcOpcode::vcmpgefp128 => {
|
||||
@@ -2046,7 +2047,8 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
let mut r = [0u32; 4];
|
||||
for i in 0..4 { r[i] = if a[i] >= b[i] { 0xFFFF_FFFF } else { 0 }; }
|
||||
ctx.vr[vd] = xenia_types::Vec128::from_u32x4_array(r);
|
||||
if instr.rc_bit() { update_cr6_from_vmask(&r, ctx); }
|
||||
let rc = if matches!(instr.opcode, PpcOpcode::vcmpgefp128) { instr.vx128r_rc_bit() } else { instr.vc_rc_bit() };
|
||||
if rc { update_cr6_from_vmask(&r, ctx); }
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::vcmpgtfp | PpcOpcode::vcmpgtfp128 => {
|
||||
@@ -2056,7 +2058,8 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
let mut r = [0u32; 4];
|
||||
for i in 0..4 { r[i] = if a[i] > b[i] { 0xFFFF_FFFF } else { 0 }; }
|
||||
ctx.vr[vd] = xenia_types::Vec128::from_u32x4_array(r);
|
||||
if instr.rc_bit() { update_cr6_from_vmask(&r, ctx); }
|
||||
let rc = if matches!(instr.opcode, PpcOpcode::vcmpgtfp128) { instr.vx128r_rc_bit() } else { instr.vc_rc_bit() };
|
||||
if rc { update_cr6_from_vmask(&r, ctx); }
|
||||
ctx.pc += 4;
|
||||
}
|
||||
|
||||
@@ -2398,7 +2401,8 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
let mut r = [0u32; 4];
|
||||
for i in 0..4 { r[i] = if a[i] == b[i] { 0xFFFF_FFFF } else { 0 }; }
|
||||
ctx.vr[vd] = xenia_types::Vec128::from_u32x4_array(r);
|
||||
if instr.rc_bit() { update_cr6_from_vmask(&r, ctx); }
|
||||
let rc = if matches!(instr.opcode, PpcOpcode::vcmpequw128) { instr.vx128r_rc_bit() } else { instr.vc_rc_bit() };
|
||||
if rc { update_cr6_from_vmask(&r, ctx); }
|
||||
ctx.pc += 4;
|
||||
}
|
||||
|
||||
@@ -3528,7 +3532,7 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
let mut r = [0u8; 16];
|
||||
for i in 0..16 { r[i] = if a[i] == b[i] { 0xFF } else { 0 }; }
|
||||
let v = xenia_types::Vec128::from_bytes(r);
|
||||
if instr.rc_bit() {
|
||||
if instr.vc_rc_bit() {
|
||||
let (t, f) = crate::vmx::cr6_flags_from_mask(v);
|
||||
ctx.cr[6] = crate::context::CrField { lt: t, gt: false, eq: f, so: false };
|
||||
}
|
||||
@@ -3541,7 +3545,7 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
let mut r = [0u16; 8];
|
||||
for i in 0..8 { r[i] = if a[i] == b[i] { 0xFFFF } else { 0 }; }
|
||||
let v = xenia_types::Vec128::from_u16x8_array(r);
|
||||
if instr.rc_bit() {
|
||||
if instr.vc_rc_bit() {
|
||||
let (t, f) = crate::vmx::cr6_flags_from_mask(v);
|
||||
ctx.cr[6] = crate::context::CrField { lt: t, gt: false, eq: f, so: false };
|
||||
}
|
||||
@@ -3554,7 +3558,7 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
let mut r = [0u8; 16];
|
||||
for i in 0..16 { r[i] = if a[i] > b[i] { 0xFF } else { 0 }; }
|
||||
let v = xenia_types::Vec128::from_bytes(r);
|
||||
if instr.rc_bit() {
|
||||
if instr.vc_rc_bit() {
|
||||
let (t, f) = crate::vmx::cr6_flags_from_mask(v);
|
||||
ctx.cr[6] = crate::context::CrField { lt: t, gt: false, eq: f, so: false };
|
||||
}
|
||||
@@ -3567,7 +3571,7 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
let mut r = [0u8; 16];
|
||||
for i in 0..16 { r[i] = if a[i] > b[i] { 0xFF } else { 0 }; }
|
||||
let v = xenia_types::Vec128::from_bytes(r);
|
||||
if instr.rc_bit() {
|
||||
if instr.vc_rc_bit() {
|
||||
let (t, f) = crate::vmx::cr6_flags_from_mask(v);
|
||||
ctx.cr[6] = crate::context::CrField { lt: t, gt: false, eq: f, so: false };
|
||||
}
|
||||
@@ -3580,7 +3584,7 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
let mut r = [0u16; 8];
|
||||
for i in 0..8 { r[i] = if a[i] > b[i] { 0xFFFF } else { 0 }; }
|
||||
let v = xenia_types::Vec128::from_u16x8_array(r);
|
||||
if instr.rc_bit() {
|
||||
if instr.vc_rc_bit() {
|
||||
let (t, f) = crate::vmx::cr6_flags_from_mask(v);
|
||||
ctx.cr[6] = crate::context::CrField { lt: t, gt: false, eq: f, so: false };
|
||||
}
|
||||
@@ -3593,7 +3597,7 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
let mut r = [0u16; 8];
|
||||
for i in 0..8 { r[i] = if a[i] > b[i] { 0xFFFF } else { 0 }; }
|
||||
let v = xenia_types::Vec128::from_u16x8_array(r);
|
||||
if instr.rc_bit() {
|
||||
if instr.vc_rc_bit() {
|
||||
let (t, f) = crate::vmx::cr6_flags_from_mask(v);
|
||||
ctx.cr[6] = crate::context::CrField { lt: t, gt: false, eq: f, so: false };
|
||||
}
|
||||
@@ -3606,7 +3610,7 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
let mut r = [0u32; 4];
|
||||
for i in 0..4 { r[i] = if a[i] > b[i] { 0xFFFFFFFF } else { 0 }; }
|
||||
let v = xenia_types::Vec128::from_u32x4_array(r);
|
||||
if instr.rc_bit() { update_cr6_from_vmask(&r, ctx); }
|
||||
if instr.vc_rc_bit() { update_cr6_from_vmask(&r, ctx); }
|
||||
ctx.vr[instr.rd()] = v;
|
||||
ctx.pc += 4;
|
||||
}
|
||||
@@ -3616,7 +3620,7 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
let mut r = [0u32; 4];
|
||||
for i in 0..4 { r[i] = if a[i] > b[i] { 0xFFFFFFFF } else { 0 }; }
|
||||
let v = xenia_types::Vec128::from_u32x4_array(r);
|
||||
if instr.rc_bit() { update_cr6_from_vmask(&r, ctx); }
|
||||
if instr.vc_rc_bit() { update_cr6_from_vmask(&r, ctx); }
|
||||
ctx.vr[instr.rd()] = v;
|
||||
ctx.pc += 4;
|
||||
}
|
||||
@@ -3638,7 +3642,8 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
if a[i].is_nan() || b[i].is_nan() || a[i] < -b[i] { lane |= 0x4000_0000; any_out = true; }
|
||||
r[i] = lane;
|
||||
}
|
||||
if instr.rc_bit() {
|
||||
let rc = if is_128 { instr.vx128r_rc_bit() } else { instr.vc_rc_bit() };
|
||||
if rc {
|
||||
ctx.cr[6] = crate::context::CrField {
|
||||
lt: false, gt: false, eq: !any_out, so: false,
|
||||
};
|
||||
@@ -6296,4 +6301,61 @@ mod tests {
|
||||
step(&mut ctx, &mem);
|
||||
assert_eq!(ctx.gpr[6], 0x0000_0000_1234_5678_u64);
|
||||
}
|
||||
|
||||
// ===== PPCBUG-275/276/562: vc_rc_bit fix for VC-form vcmpequb =====
|
||||
|
||||
/// VC-form: opcode=4 (VMX), vD at 6-10, vA at 11-15, vB at 16-20, Rc at PPC bit 21 = host bit 10, XO=6.
|
||||
/// vcmpequb.: (4<<26)|(vD<<21)|(vA<<16)|(vB<<11)|(1<<10)|6
|
||||
fn encode_vcmpequb_dot(vd: u32, va: u32, vb: u32) -> u32 {
|
||||
(4 << 26) | (vd << 21) | (va << 16) | (vb << 11) | (1 << 10) | 6
|
||||
}
|
||||
/// vcmpequb (no dot form): same but Rc=0
|
||||
fn encode_vcmpequb(vd: u32, va: u32, vb: u32) -> u32 {
|
||||
(4 << 26) | (vd << 21) | (va << 16) | (vb << 11) | 6
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vcmpequb_dot_all_true_sets_cr6_lt() {
|
||||
// All bytes equal → all lanes 0xFF → CR6.LT=1 (all-true), CR6.EQ=0
|
||||
let mut ctx = PpcContext::new();
|
||||
let mem = TestMem::new();
|
||||
let v = xenia_types::Vec128::from_bytes([0xAAu8; 16]);
|
||||
ctx.vr[1] = v;
|
||||
ctx.vr[2] = v;
|
||||
write_instr(&mem, 0x100, encode_vcmpequb_dot(0, 1, 2));
|
||||
ctx.pc = 0x100;
|
||||
step(&mut ctx, &mem);
|
||||
assert!(ctx.cr[6].lt, "all-true: CR6.LT must be 1");
|
||||
assert!(!ctx.cr[6].eq, "all-true: CR6.EQ must be 0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vcmpequb_no_dot_does_not_update_cr6() {
|
||||
// Without dot form, CR6 must be unchanged
|
||||
let mut ctx = PpcContext::new();
|
||||
let mem = TestMem::new();
|
||||
ctx.cr[6] = crate::context::CrField { lt: true, gt: false, eq: true, so: false };
|
||||
let v = xenia_types::Vec128::from_bytes([0xAAu8; 16]);
|
||||
ctx.vr[1] = v;
|
||||
ctx.vr[2] = v;
|
||||
write_instr(&mem, 0x100, encode_vcmpequb(0, 1, 2));
|
||||
ctx.pc = 0x100;
|
||||
step(&mut ctx, &mem);
|
||||
// CR6 unchanged: no dot form
|
||||
assert!(ctx.cr[6].lt && ctx.cr[6].eq, "CR6 must be unchanged without dot");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vcmpequb_dot_all_false_sets_cr6_eq() {
|
||||
// No bytes equal → all lanes 0x00 → CR6.LT=0, CR6.EQ=1 (all-false)
|
||||
let mut ctx = PpcContext::new();
|
||||
let mem = TestMem::new();
|
||||
ctx.vr[1] = xenia_types::Vec128::from_bytes([0xAAu8; 16]);
|
||||
ctx.vr[2] = xenia_types::Vec128::from_bytes([0xBBu8; 16]);
|
||||
write_instr(&mem, 0x100, encode_vcmpequb_dot(0, 1, 2));
|
||||
ctx.pc = 0x100;
|
||||
step(&mut ctx, &mem);
|
||||
assert!(!ctx.cr[6].lt, "all-false: CR6.LT must be 0");
|
||||
assert!(ctx.cr[6].eq, "all-false: CR6.EQ must be 1");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user