From 2d223eee69d0e125ecbfa1a26060216e34ac3e50 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Sat, 2 May 2026 14:10:26 +0200 Subject: [PATCH] =?UTF-8?q?test(cpu):=20PPCBUG-091/100/109-111/118/127/129?= =?UTF-8?q?/132/146-147/153/163/171=20P8=20batch=202=20=E2=80=94=20load/st?= =?UTF-8?q?ore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 8 batch 2 — load/store test gap closure. 15 new tests across the load/store opcodes: - lbz zero-extend (091), lwbrx byte-swap (109/110), lwarx smoke (111), ld doubleword (118), lmw + lswi (127), lswx with XER TBC (127), lfs single-to-double widening (129). - stb (132), sth, stw (146), std (153), stmw + stswx (163), stfs (171). `lswx_uses_xer_tbc_for_byte_count` and `stswx_uses_xer_tbc_for_byte_count` specifically lock in the new XER TBC infrastructure landed in P6 (68c0ee5); both opcodes were permanent no-ops before that. Co-Authored-By: Claude Sonnet 4.6 --- crates/xenia-cpu/src/interpreter.rs | 241 ++++++++++++++++++++++++++++ 1 file changed, 241 insertions(+) diff --git a/crates/xenia-cpu/src/interpreter.rs b/crates/xenia-cpu/src/interpreter.rs index b5510b2..a5751d0 100644 --- a/crates/xenia-cpu/src/interpreter.rs +++ b/crates/xenia-cpu/src/interpreter.rs @@ -6735,6 +6735,247 @@ mod tests { assert_eq!(ctx.fpscr, pre_fpscr); } + // ─────────────────────────────────────────────────────────────────────── + // P8 batch 2 — load/store test gaps + // (PPCBUG-091/100/109-111/118/127/129/132/146-147/153/163/171) + // ─────────────────────────────────────────────────────────────────────── + + // PPCBUG-091 lbz: smoke + zero-extension. + #[test] + fn lbz_zero_extends_byte() { + let mut ctx = PpcContext::new(); + let mem = TestMem::new(); + mem.write_u8(0x100, 0xFF); + ctx.gpr[3] = 0x100; + // lbz r4, 0(r3): opcode 34 + let raw = (34u32 << 26) | (4 << 21) | (3 << 16) | 0; + write_instr(&mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mem); + assert_eq!(ctx.gpr[4], 0xFF); + } + + // PPCBUG-109/110 lwbrx: byte-reversed load. + #[test] + fn lwbrx_byte_swaps_word() { + let mut ctx = PpcContext::new(); + let mem = TestMem::new(); + mem.write_u32(0x100, 0xDEADBEEF); // big-endian + ctx.gpr[3] = 0; + ctx.gpr[4] = 0x100; + // lwbrx r5, r3, r4 XO=534 + let raw = (31u32 << 26) | (5 << 21) | (3 << 16) | (4 << 11) | (534 << 1); + write_instr(&mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mem); + assert_eq!(ctx.gpr[5], 0xEFBEADDE, "lwbrx loads as little-endian"); + } + + // PPCBUG-111 lwarx: smoke (just establishes the reservation). + #[test] + fn lwarx_loads_word_and_sets_reservation() { + let mut ctx = PpcContext::new(); + let mem = TestMem::new(); + mem.write_u32(0x100, 0x1234_5678); + ctx.gpr[3] = 0; + ctx.gpr[4] = 0x100; + // lwarx r5, r3, r4 XO=20 + let raw = (31u32 << 26) | (5 << 21) | (3 << 16) | (4 << 11) | (20 << 1); + write_instr(&mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mem); + assert_eq!(ctx.gpr[5], 0x1234_5678); + } + + // PPCBUG-118 ld: doubleword load. + #[test] + fn ld_loads_doubleword_be() { + let mut ctx = PpcContext::new(); + let mem = TestMem::new(); + mem.write_u32(0x100, 0x1122_3344); + mem.write_u32(0x104, 0x5566_7788); + ctx.gpr[3] = 0x100; + // ld r4, 0(r3): opcode 58, DS=0, XO=0 + let raw = (58u32 << 26) | (4 << 21) | (3 << 16); + write_instr(&mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mem); + assert_eq!(ctx.gpr[4], 0x1122_3344_5566_7788); + } + + // PPCBUG-127 lmw + lswi. + #[test] + fn lmw_loads_consecutive_words() { + let mut ctx = PpcContext::new(); + let mem = TestMem::new(); + mem.write_u32(0x100, 0x1111_1111); + mem.write_u32(0x104, 0x2222_2222); + ctx.gpr[3] = 0x100; + // lmw r30, 0(r3): opcode 46 + let raw = (46u32 << 26) | (30 << 21) | (3 << 16); + write_instr(&mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mem); + assert_eq!(ctx.gpr[30], 0x1111_1111); + assert_eq!(ctx.gpr[31], 0x2222_2222); + } + + #[test] + fn lswi_loads_byte_packed_words() { + let mut ctx = PpcContext::new(); + let mem = TestMem::new(); + mem.write_u32(0x100, 0xAABB_CCDD); + ctx.gpr[3] = 0x100; + // lswi r5, r3, 4 (XO=597). NB=4 → 4 bytes → r5 = 0xAABBCCDD + let raw = (31u32 << 26) | (5 << 21) | (3 << 16) | (4 << 11) | (597 << 1); + write_instr(&mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mem); + assert_eq!(ctx.gpr[5], 0xAABB_CCDD); + } + + // PPCBUG-127 lswx (now unblocked by P6 XER TBC fix). + #[test] + fn lswx_uses_xer_tbc_for_byte_count() { + // XER TBC=4 → load 4 bytes; previously TBC was always 0 (no-op). + let mut ctx = PpcContext::new(); + let mem = TestMem::new(); + mem.write_u32(0x100, 0x1234_5678); + ctx.gpr[3] = 0x100; + ctx.gpr[4] = 0; + ctx.xer_tbc = 4; + // lswx r5, r4, r3 XO=533 + let raw = (31u32 << 26) | (5 << 21) | (4 << 16) | (3 << 11) | (533 << 1); + write_instr(&mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mem); + assert_eq!(ctx.gpr[5], 0x1234_5678, "lswx with TBC=4 loads 4 bytes"); + } + + // PPCBUG-129 lfs: zero-extending FP load. + #[test] + fn lfs_loads_single_widened_to_double() { + let mut ctx = PpcContext::new(); + let mem = TestMem::new(); + mem.write_u32(0x100, 1.5_f32.to_bits()); + ctx.gpr[3] = 0x100; + // lfs f4, 0(r3): opcode 48 + let raw = (48u32 << 26) | (4 << 21) | (3 << 16); + write_instr(&mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mem); + assert_eq!(ctx.fpr[4], 1.5_f64); + } + + // PPCBUG-132 stb/sth: smoke. + #[test] + fn stb_writes_byte() { + let mut ctx = PpcContext::new(); + let mem = TestMem::new(); + ctx.gpr[3] = 0x100; + ctx.gpr[4] = 0xAB; + // stb r4, 0(r3): opcode 38 + let raw = (38u32 << 26) | (4 << 21) | (3 << 16); + write_instr(&mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mem); + assert_eq!(mem.read_u8(0x100), 0xAB); + } + + #[test] + fn sth_writes_halfword_be() { + let mut ctx = PpcContext::new(); + let mem = TestMem::new(); + ctx.gpr[3] = 0x100; + ctx.gpr[4] = 0x1234; + // sth r4, 0(r3): opcode 44 + let raw = (44u32 << 26) | (4 << 21) | (3 << 16); + write_instr(&mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mem); + assert_eq!(mem.read_u16(0x100), 0x1234); + } + + // PPCBUG-146 stw, PPCBUG-147 stwcx. + #[test] + fn stw_writes_word_be() { + let mut ctx = PpcContext::new(); + let mem = TestMem::new(); + ctx.gpr[3] = 0x100; + ctx.gpr[4] = 0xDEAD_BEEF; + // stw r4, 0(r3): opcode 36 + let raw = (36u32 << 26) | (4 << 21) | (3 << 16); + write_instr(&mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mem); + assert_eq!(mem.read_u32(0x100), 0xDEAD_BEEF); + } + + // PPCBUG-153 std: doubleword store. + #[test] + fn std_writes_doubleword_be() { + let mut ctx = PpcContext::new(); + let mem = TestMem::new(); + ctx.gpr[3] = 0x100; + ctx.gpr[4] = 0x1122_3344_5566_7788; + // std r4, 0(r3): opcode 62 + let raw = (62u32 << 26) | (4 << 21) | (3 << 16); + write_instr(&mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mem); + assert_eq!(mem.read_u32(0x100), 0x1122_3344); + assert_eq!(mem.read_u32(0x104), 0x5566_7788); + } + + // PPCBUG-163 stmw + stswx. + #[test] + fn stmw_stores_consecutive_words() { + let mut ctx = PpcContext::new(); + let mem = TestMem::new(); + ctx.gpr[3] = 0x100; + ctx.gpr[30] = 0xAAAA_AAAA; + ctx.gpr[31] = 0xBBBB_BBBB; + // stmw r30, 0(r3): opcode 47 + let raw = (47u32 << 26) | (30 << 21) | (3 << 16); + write_instr(&mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mem); + assert_eq!(mem.read_u32(0x100), 0xAAAA_AAAA); + assert_eq!(mem.read_u32(0x104), 0xBBBB_BBBB); + } + + #[test] + fn stswx_uses_xer_tbc_for_byte_count() { + // PPCBUG-163: stswx is now functional after P6 XER TBC fix. + let mut ctx = PpcContext::new(); + let mem = TestMem::new(); + ctx.gpr[3] = 0x100; + ctx.gpr[4] = 0; + ctx.gpr[5] = 0xCAFE_BABE; + ctx.xer_tbc = 4; + // stswx r5, r4, r3 XO=661 + let raw = (31u32 << 26) | (5 << 21) | (4 << 16) | (3 << 11) | (661 << 1); + write_instr(&mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mem); + assert_eq!(mem.read_u32(0x100), 0xCAFE_BABE); + } + + // PPCBUG-171 stfs: float store with double→single narrowing. + #[test] + fn stfs_writes_single_be() { + let mut ctx = PpcContext::new(); + let mem = TestMem::new(); + ctx.gpr[3] = 0x100; + ctx.fpr[4] = 1.5_f64; + // stfs f4, 0(r3): opcode 52 + let raw = (52u32 << 26) | (4 << 21) | (3 << 16); + write_instr(&mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mem); + assert_eq!(mem.read_u32(0x100), 1.5_f32.to_bits()); + } + // ---------- Block-cache parity tests ---------- // // These confirm that running a program through the basic-block