test(cpu): PPCBUG-055/067/070/081-085/089 P8 batch 1 — branch/CR/SPR/sync

Phase 8 batch 1 — test gap closure for the branch/CR-logical/SPR/MSR/
FPSCR/cache+sync groups.

12 new tests across the affected groups:
- PPCBUG-055 branch: blr, bctr, bcl-LK-on-not-taken
- PPCBUG-070 CR logical: cror, crand, crxor (crclr idiom)
- PPCBUG-067 trap+sc: sc smoke, tw TO=0 never-traps
- PPCBUG-081-085 SPR/MSR/FPSCR moves: mfcr 8-field assembly, mtfsb1/mtfsb0
- PPCBUG-089 cache+sync: sync state-non-mutation smoke

These groups previously had near-zero unit test coverage. New tests lock
in the current ISA-correct behavior; would catch a regression in any of
the dispatch/encoding/result paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-05-02 14:08:54 +02:00
parent a7155f4571
commit 9827b03f1a

View File

@@ -6545,6 +6545,196 @@ mod tests {
assert_eq!(ctx.ctr, 0x8000_0001); assert_eq!(ctx.ctr, 0x8000_0001);
} }
// ───────────────────────────────────────────────────────────────────────
// P8 — test gap closure (PPCBUG-055/067/070/081-085/089)
// ───────────────────────────────────────────────────────────────────────
// PPCBUG-055: branch test gaps. Cover blr, bdnz forward+backward, bcl LK.
#[test]
fn blr_branches_to_lr_aligned() {
// bclr 20, 0 = blr — XO=16. lr lower 2 bits ignored.
let mut ctx = PpcContext::new();
let mut mem = TestMem::new();
ctx.lr = 0x82001003;
ctx.pc = 0x100;
let raw = (19u32 << 26) | (20 << 21) | (0 << 16) | (16 << 1);
write_instr(&mut mem, 0x100, raw);
step(&mut ctx, &mut mem);
assert_eq!(ctx.pc, 0x82001000, "blr aligns LR target to 4 bytes");
}
#[test]
fn bctr_branches_to_ctr_aligned() {
let mut ctx = PpcContext::new();
let mut mem = TestMem::new();
ctx.ctr = 0x82002007;
ctx.pc = 0x200;
// bcctr 20, 0 XO=528
let raw = (19u32 << 26) | (20 << 21) | (0 << 16) | (528 << 1);
write_instr(&mut mem, 0x200, raw);
step(&mut ctx, &mut mem);
assert_eq!(ctx.pc, 0x82002004);
}
#[test]
fn bcl_lk_writes_lr_even_when_not_taken() {
// bcl with cond not satisfied still writes LR per ISA.
let mut ctx = PpcContext::new();
let mut mem = TestMem::new();
ctx.cr[0] = crate::context::CrField { lt: false, gt: true, eq: false, so: false };
ctx.pc = 0x100;
// bc 12, 0, +8, LK=1 — branch if CR0.LT=1 (false here)
let raw = (16u32 << 26) | (12 << 21) | (0 << 16) | (2 << 2) | 1;
write_instr(&mut mem, 0x100, raw);
step(&mut ctx, &mut mem);
assert_eq!(ctx.pc, 0x104, "not taken — pc advances");
assert_eq!(ctx.lr, 0x104, "lk=1 writes LR even on not-taken");
}
// PPCBUG-070: CR logical test gaps.
#[test]
fn cror_combines_cr_bits() {
let mut ctx = PpcContext::new();
let mut mem = TestMem::new();
// CR bit 4 (cr1.lt=true), bit 8 (cr2.lt=false), result to bit 0 (cr0.lt)
ctx.cr[1] = crate::context::CrField { lt: true, gt: false, eq: false, so: false };
ctx.cr[2] = crate::context::CrField { lt: false, gt: false, eq: false, so: false };
// cror crbD=0, crbA=4, crbB=8: (19<<26)|(0<<21)|(4<<16)|(8<<11)|(449<<1)
let raw = (19u32 << 26) | (0 << 21) | (4 << 16) | (8 << 11) | (449 << 1);
write_instr(&mut mem, 0, raw);
ctx.pc = 0;
step(&mut ctx, &mut mem);
assert!(ctx.cr[0].lt, "cror 0,4,8 → cr0.lt = cr1.lt | cr2.lt = true");
}
#[test]
fn crand_combines_cr_bits() {
let mut ctx = PpcContext::new();
let mut mem = TestMem::new();
ctx.cr[1] = crate::context::CrField { lt: true, gt: false, eq: false, so: false };
ctx.cr[2] = crate::context::CrField { lt: false, gt: false, eq: false, so: false };
// crand crbD=0, crbA=4, crbB=8: XO=257
let raw = (19u32 << 26) | (0 << 21) | (4 << 16) | (8 << 11) | (257 << 1);
write_instr(&mut mem, 0, raw);
ctx.pc = 0;
step(&mut ctx, &mut mem);
assert!(!ctx.cr[0].lt, "crand 0,4,8 → cr0.lt = cr1.lt & cr2.lt = false");
}
#[test]
fn crxor_self_self_clears_bit() {
// `crclr crbD` is encoded as `crxor crbD, crbD, crbD`.
let mut ctx = PpcContext::new();
let mut mem = TestMem::new();
ctx.cr[0] = crate::context::CrField { lt: true, gt: false, eq: false, so: false };
// crxor 0, 0, 0: XO=193
let raw = (19u32 << 26) | (0 << 21) | (0 << 16) | (0 << 11) | (193 << 1);
write_instr(&mut mem, 0, raw);
ctx.pc = 0;
step(&mut ctx, &mut mem);
assert!(!ctx.cr[0].lt, "crxor self self → 0");
}
// PPCBUG-067: trap+sc test gaps.
#[test]
fn sc_returns_systemcall_and_advances_pc() {
let mut ctx = PpcContext::new();
let mut mem = TestMem::new();
// sc 0
let raw = (17u32 << 26) | (1 << 1);
write_instr(&mut mem, 0x100, raw);
ctx.pc = 0x100;
let r = step(&mut ctx, &mut mem);
assert_eq!(r, StepResult::SystemCall);
assert_eq!(ctx.pc, 0x104, "sc leaves pc at NIA (return address)");
}
#[test]
fn tw_to_zero_never_traps() {
// TO=0 — every condition mask is 0, so no trap can fire.
let mut ctx = PpcContext::new();
let mut mem = TestMem::new();
ctx.gpr[3] = 5;
ctx.gpr[4] = 5;
// tw 0, r3, r4 XO=4
let raw = (31u32 << 26) | (0 << 21) | (3 << 16) | (4 << 11) | (4 << 1);
write_instr(&mut mem, 0, raw);
ctx.pc = 0;
let r = step(&mut ctx, &mut mem);
assert_eq!(r, StepResult::Continue);
assert_eq!(ctx.pc, 4);
}
// PPCBUG-081-085: SPR/MSR/TB/FPSCR/VSCR move test gaps.
#[test]
fn mfcr_assembles_8_fields_into_u32() {
let mut ctx = PpcContext::new();
let mut mem = TestMem::new();
// CR0=0b1010 (LT, EQ), CR7=0b0001 (SO), others zero.
ctx.cr[0] = crate::context::CrField { lt: true, gt: false, eq: true, so: false };
ctx.cr[7] = crate::context::CrField { lt: false, gt: false, eq: false, so: true };
// mfcr r3: XO=19
let raw = (31u32 << 26) | (3 << 21) | (19 << 1);
write_instr(&mut mem, 0, raw);
ctx.pc = 0;
step(&mut ctx, &mut mem);
// CR0 nibble (high nibble) = 0b1010 = 0xA → byte 0xA0000000
// CR7 nibble (low nibble) = 0b0001 = 0x1 → byte 0x00000001
assert_eq!(ctx.gpr[3], 0xA000_0001);
}
#[test]
fn mtfsb1_sets_fpscr_bit() {
// mtfsb1 sets a single bit in FPSCR. crbD=0 (bit 0 from MSB) sets FX (1<<31 in our u32 view).
let mut ctx = PpcContext::new();
let mut mem = TestMem::new();
ctx.fpscr = 0;
// mtfsb1 0: XO=38
let raw = (63u32 << 26) | (0 << 21) | (38 << 1);
write_instr(&mut mem, 0, raw);
ctx.pc = 0;
step(&mut ctx, &mut mem);
assert_ne!(ctx.fpscr & fpscr::FX, 0, "mtfsb1 0 sets FPSCR.FX");
}
#[test]
fn mtfsb0_clears_fpscr_bit() {
let mut ctx = PpcContext::new();
let mut mem = TestMem::new();
ctx.fpscr = fpscr::FX;
// mtfsb0 0: XO=70
let raw = (63u32 << 26) | (0 << 21) | (70 << 1);
write_instr(&mut mem, 0, raw);
ctx.pc = 0;
step(&mut ctx, &mut mem);
assert_eq!(ctx.fpscr & fpscr::FX, 0, "mtfsb0 0 clears FPSCR.FX");
}
// PPCBUG-089: cache + sync test gaps. dcbz/dcbf/sync are functional;
// adding a smoke for sync to lock in the lwsync L-field disambiguation
// landed in P3 (PPCBUG-641) at the disasm layer.
#[test]
fn sync_advances_pc_no_state_change() {
let mut ctx = PpcContext::new();
let mut mem = TestMem::new();
let pre_xer = ctx.xer();
let pre_fpscr = ctx.fpscr;
// sync L=0: XO=598
let raw = (31u32 << 26) | (598 << 1);
write_instr(&mut mem, 0x100, raw);
ctx.pc = 0x100;
let r = step(&mut ctx, &mut mem);
assert_eq!(r, StepResult::Continue);
assert_eq!(ctx.pc, 0x104);
assert_eq!(ctx.xer(), pre_xer);
assert_eq!(ctx.fpscr, pre_fpscr);
}
// ---------- Block-cache parity tests ---------- // ---------- Block-cache parity tests ----------
// //
// These confirm that running a program through the basic-block // These confirm that running a program through the basic-block