From 9827b03f1a5d4eb4fb1a33731c079e95d2aa3121 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Sat, 2 May 2026 14:08:54 +0200 Subject: [PATCH 1/5] =?UTF-8?q?test(cpu):=20PPCBUG-055/067/070/081-085/089?= =?UTF-8?q?=20P8=20batch=201=20=E2=80=94=20branch/CR/SPR/sync?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- crates/xenia-cpu/src/interpreter.rs | 190 ++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) diff --git a/crates/xenia-cpu/src/interpreter.rs b/crates/xenia-cpu/src/interpreter.rs index 00a6108..b5510b2 100644 --- a/crates/xenia-cpu/src/interpreter.rs +++ b/crates/xenia-cpu/src/interpreter.rs @@ -6545,6 +6545,196 @@ mod tests { 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 ---------- // // These confirm that running a program through the basic-block From 2d223eee69d0e125ecbfa1a26060216e34ac3e50 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Sat, 2 May 2026 14:10:26 +0200 Subject: [PATCH 2/5] =?UTF-8?q?test(cpu):=20PPCBUG-091/100/109-111/118/127?= =?UTF-8?q?/129/132/146-147/153/163/171=20P8=20batch=202=20=E2=80=94=20loa?= =?UTF-8?q?d/store?= 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 From ebfd18a64e8be6bd8592b9af984f33336efbf41c Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Sat, 2 May 2026 14:14:10 +0200 Subject: [PATCH 3/5] =?UTF-8?q?test(cpu):=20PPCBUG-187/208/228/438/439/440?= =?UTF-8?q?=20P8=20batch=203=20=E2=80=94=20FPU=20+=20VMX=20float?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 8 batch 3 — FPU and VMX float test gap closure. 14 new tests: - Single FPU (187): fadds, fmuls - Double FPU (208): fmul, fdiv (zero-numerator), fneg, fabs, fmr - FPU convert/compare (228): fcmpu, fcfid - VMX float compare (438): vcmpeqfp lane mask - VMX rounding (439): vrfip, vrfim, vrfiz - VMX convert (440): vctsxs saturation to INT_MAX/INT_MIN The VMX VX-form encoding nit (XO is 11 bits at PPC 21-31, host bits 10-0, with bit 0 the LSB — not bit 1) was caught by initial test failures and fixed before commit. VC-form (vcmpeqfp) has the same "XO at bit 0" layout. Co-Authored-By: Claude Sonnet 4.6 --- crates/xenia-cpu/src/interpreter.rs | 222 ++++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) diff --git a/crates/xenia-cpu/src/interpreter.rs b/crates/xenia-cpu/src/interpreter.rs index a5751d0..519ed51 100644 --- a/crates/xenia-cpu/src/interpreter.rs +++ b/crates/xenia-cpu/src/interpreter.rs @@ -6976,6 +6976,228 @@ mod tests { assert_eq!(mem.read_u32(0x100), 1.5_f32.to_bits()); } + // ─────────────────────────────────────────────────────────────────────── + // P8 batch 3 — FPU + VMX float test gaps + // (PPCBUG-187/208/228/438/439/440) + // ─────────────────────────────────────────────────────────────────────── + + // PPCBUG-187 single-precision FPU smokes. + #[test] + fn fadds_single_arithmetic() { + let mut ctx = PpcContext::new(); + let mut mem = TestMem::new(); + ctx.fpr[1] = 1.5; + ctx.fpr[2] = 2.5; + // fadds f3, f1, f2: opcode 59, XO=21 + let raw = (59u32 << 26) | (3 << 21) | (1 << 16) | (2 << 11) | (21 << 1); + write_instr(&mut mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mut mem); + assert_eq!(ctx.fpr[3], 4.0); + } + + #[test] + fn fmuls_single_multiply() { + let mut ctx = PpcContext::new(); + let mut mem = TestMem::new(); + ctx.fpr[1] = 2.0; + ctx.fpr[2] = 3.0; + // fmuls f3, f1, f2: XO=25, frC at bits 21-25 (so c is rb encoding slot) + // Standard A-form: (59<<26)|(rd<<21)|(ra<<16)|(0<<11)|(rc<<6)|(25<<1) + let raw = (59u32 << 26) | (3 << 21) | (1 << 16) | (2 << 6) | (25 << 1); + write_instr(&mut mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mut mem); + assert_eq!(ctx.fpr[3], 6.0); + } + + // PPCBUG-208 double-precision FPU smokes. + #[test] + fn fmul_double_multiply() { + let mut ctx = PpcContext::new(); + let mut mem = TestMem::new(); + ctx.fpr[1] = 4.0; + ctx.fpr[2] = 0.25; + // fmul f3, f1, f2: opcode 63, XO=25 + let raw = (63u32 << 26) | (3 << 21) | (1 << 16) | (2 << 6) | (25 << 1); + write_instr(&mut mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mut mem); + assert_eq!(ctx.fpr[3], 1.0); + } + + #[test] + fn fdiv_zero_over_finite() { + let mut ctx = PpcContext::new(); + let mut mem = TestMem::new(); + ctx.fpr[1] = 0.0; + ctx.fpr[2] = 5.0; + // fdiv f3, f1, f2: XO=18 + let raw = (63u32 << 26) | (3 << 21) | (1 << 16) | (2 << 11) | (18 << 1); + write_instr(&mut mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mem); + assert_eq!(ctx.fpr[3], 0.0); + assert_eq!(ctx.fpscr & fpscr::ZX, 0, "0/finite is not divide-by-zero"); + } + + #[test] + fn fneg_flips_sign_bit() { + let mut ctx = PpcContext::new(); + let mut mem = TestMem::new(); + ctx.fpr[1] = 1.0; + // fneg f3, f1: XO=40 + let raw = (63u32 << 26) | (3 << 21) | (0 << 16) | (1 << 11) | (40 << 1); + write_instr(&mut mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mut mem); + assert_eq!(ctx.fpr[3], -1.0); + } + + #[test] + fn fabs_clears_sign_bit() { + let mut ctx = PpcContext::new(); + let mut mem = TestMem::new(); + ctx.fpr[1] = -3.5; + // fabs f3, f1: XO=264 + let raw = (63u32 << 26) | (3 << 21) | (0 << 16) | (1 << 11) | (264 << 1); + write_instr(&mut mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mut mem); + assert_eq!(ctx.fpr[3], 3.5); + } + + #[test] + fn fmr_copies_register() { + let mut ctx = PpcContext::new(); + let mut mem = TestMem::new(); + ctx.fpr[5] = 1.5_f64.copysign(-1.0); + // fmr f3, f5: XO=72 + let raw = (63u32 << 26) | (3 << 21) | (0 << 16) | (5 << 11) | (72 << 1); + write_instr(&mut mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mut mem); + assert_eq!(ctx.fpr[3].to_bits(), ctx.fpr[5].to_bits()); + } + + // PPCBUG-228 fpu convert / fcmp smokes. + #[test] + fn fcmpu_lt_sets_cr_lt() { + let mut ctx = PpcContext::new(); + let mut mem = TestMem::new(); + ctx.fpr[1] = 1.0; + ctx.fpr[2] = 2.0; + // fcmpu cr3, f1, f2: opcode 63, XO=0, BF=3 + let raw = (63u32 << 26) | (3 << 23) | (1 << 16) | (2 << 11) | (0 << 1); + write_instr(&mut mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mut mem); + assert!(ctx.cr[3].lt); + assert!(!ctx.cr[3].gt); + } + + #[test] + fn fcfid_converts_int64_to_double() { + let mut ctx = PpcContext::new(); + let mut mem = TestMem::new(); + ctx.fpr[1] = f64::from_bits(123u64); + // fcfid f3, f1: XO=846 + let raw = (63u32 << 26) | (3 << 21) | (0 << 16) | (1 << 11) | (846 << 1); + write_instr(&mut mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mut mem); + assert_eq!(ctx.fpr[3], 123.0); + } + + // PPCBUG-438 VMX float compares. VC-form: XO at PPC 22-31 (host 9-0), bit 0. + #[test] + fn vcmpeqfp_sets_lanes_to_all_ones_on_eq() { + let mut ctx = PpcContext::new(); + let mut mem = TestMem::new(); + ctx.vr[1] = xenia_types::Vec128::from_f32x4_array([1.0, 2.0, 3.0, 4.0]); + ctx.vr[2] = xenia_types::Vec128::from_f32x4_array([1.0, 0.0, 3.0, 0.0]); + // vcmpeqfp v3, v1, v2: canary base 0x100000c6 → op6=4, XO=198 at bits 0-9. + let raw = (4u32 << 26) | (3 << 21) | (1 << 16) | (2 << 11) | 198; + write_instr(&mut mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mut mem); + let r = ctx.vr[3].as_u32x4(); + assert_eq!(r[0], 0xFFFF_FFFF); // 1.0 == 1.0 → all ones + assert_eq!(r[1], 0); // 2.0 != 0.0 + assert_eq!(r[2], 0xFFFF_FFFF); // 3.0 == 3.0 + assert_eq!(r[3], 0); // 4.0 != 0.0 + } + + // PPCBUG-439 VMX rounding. VX-form XO at bit 0. + #[test] + fn vrfip_rounds_toward_pos_inf() { + let mut ctx = PpcContext::new(); + let mut mem = TestMem::new(); + ctx.vr[1] = xenia_types::Vec128::from_f32x4_array([1.4, 1.5, -1.4, -1.5]); + // vrfip canary base 0x1000028a → XO=650. + let raw = (4u32 << 26) | (3 << 21) | (0 << 16) | (1 << 11) | 650; + write_instr(&mut mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mut mem); + let r = ctx.vr[3].as_f32x4(); + assert_eq!(r[0], 2.0); + assert_eq!(r[1], 2.0); + assert_eq!(r[2], -1.0); + assert_eq!(r[3], -1.0); + } + + #[test] + fn vrfim_rounds_toward_neg_inf() { + let mut ctx = PpcContext::new(); + let mut mem = TestMem::new(); + ctx.vr[1] = xenia_types::Vec128::from_f32x4_array([1.4, 1.5, -1.4, -1.5]); + // vrfim canary base 0x100002ca → XO=714. + let raw = (4u32 << 26) | (3 << 21) | (0 << 16) | (1 << 11) | 714; + write_instr(&mut mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mut mem); + let r = ctx.vr[3].as_f32x4(); + assert_eq!(r[0], 1.0); + assert_eq!(r[1], 1.0); + assert_eq!(r[2], -2.0); + assert_eq!(r[3], -2.0); + } + + #[test] + fn vrfiz_truncates_toward_zero() { + let mut ctx = PpcContext::new(); + let mut mem = TestMem::new(); + ctx.vr[1] = xenia_types::Vec128::from_f32x4_array([1.7, 2.5, -1.7, -2.5]); + // vrfiz canary base 0x1000024a → XO=586. + let raw = (4u32 << 26) | (3 << 21) | (0 << 16) | (1 << 11) | 586; + write_instr(&mut mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mut mem); + let r = ctx.vr[3].as_f32x4(); + assert_eq!(r[0], 1.0); + assert_eq!(r[1], 2.0); + assert_eq!(r[2], -1.0); + assert_eq!(r[3], -2.0); + } + + // PPCBUG-440 VMX convert. + #[test] + fn vctsxs_saturates_max_to_int_max() { + let mut ctx = PpcContext::new(); + let mut mem = TestMem::new(); + ctx.vr[1] = xenia_types::Vec128::from_f32x4_array([1e10, -1e10, 1.5, -1.5]); + // vctsxs canary base 0x100003ca → XO=970, UIMM=0. + let raw = (4u32 << 26) | (3 << 21) | (0 << 16) | (1 << 11) | 970; + write_instr(&mut mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mut mem); + let r = crate::vmx::as_i32x4(ctx.vr[3]); + assert_eq!(r[0], i32::MAX, "1e10 saturates to INT_MAX"); + assert_eq!(r[1], i32::MIN, "-1e10 saturates to INT_MIN"); + assert_eq!(r[2], 1); + assert_eq!(r[3], -1); + } + // ---------- Block-cache parity tests ---------- // // These confirm that running a program through the basic-block From 261480616c2799b4dc07b582fc56f3d566115a19 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Sat, 2 May 2026 14:16:51 +0200 Subject: [PATCH 4/5] =?UTF-8?q?test(cpu):=20PPCBUG-240/277/278/316/321/370?= =?UTF-8?q?/490/517=20P8=20batch=204=20=E2=80=94=20VMX=20integer/permute/l?= =?UTF-8?q?oad-store?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 8 batch 4 — VMX integer + permute/pack + multiply-sum + load/store. 12 new tests: - VMX add/sub (240): vaddubm byte add, vsubuwm word sub. - VMX compare (277): vcmpequb lane mask. - VMX min/max (278): vmaxsw signed lane max. - VMX shift/rotate (316): vsl 128-bit left shift, vsraw arithmetic per-lane. - VMX logical (321): vand lane-wise AND. - VMX permute (370): vsldoi byte concatenation + shift. - VMX multiply-sum (490): vmaddfp lane FMA. - VMX load/store (517): lvx aligned quadword load, stvx aligned store, lvebx byte-lane load. Co-Authored-By: Claude Sonnet 4.6 --- crates/xenia-cpu/src/interpreter.rs | 233 ++++++++++++++++++++++++++++ 1 file changed, 233 insertions(+) diff --git a/crates/xenia-cpu/src/interpreter.rs b/crates/xenia-cpu/src/interpreter.rs index 519ed51..cd7bd1d 100644 --- a/crates/xenia-cpu/src/interpreter.rs +++ b/crates/xenia-cpu/src/interpreter.rs @@ -7198,6 +7198,239 @@ mod tests { assert_eq!(r[3], -1); } + // ─────────────────────────────────────────────────────────────────────── + // P8 batch 4 — VMX integer + permute/pack + multiply-sum + load/store + // (PPCBUG-240/243/277-279/316-325/370-378/490-494/517-519) + // ─────────────────────────────────────────────────────────────────────── + + // PPCBUG-240 VMX integer add/sub. + #[test] + fn vaddubm_lane_wise_byte_add() { + let mut ctx = PpcContext::new(); + let mut mem = TestMem::new(); + ctx.vr[1] = xenia_types::Vec128::from_bytes([0x10; 16]); + ctx.vr[2] = xenia_types::Vec128::from_bytes([0x20; 16]); + // vaddubm canary base 0x10000000 → XO=0 + let raw = (4u32 << 26) | (3 << 21) | (1 << 16) | (2 << 11); + write_instr(&mut mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mut mem); + let r = ctx.vr[3].as_bytes(); + assert_eq!(r[0], 0x30); + assert_eq!(r[15], 0x30); + } + + #[test] + fn vsubuwm_lane_wise_word_sub() { + let mut ctx = PpcContext::new(); + let mut mem = TestMem::new(); + ctx.vr[1] = xenia_types::Vec128::from_u32x4(100, 200, 300, 400); + ctx.vr[2] = xenia_types::Vec128::from_u32x4(40, 30, 20, 10); + // vsubuwm canary base 0x10000480 → XO=1152 + let raw = (4u32 << 26) | (3 << 21) | (1 << 16) | (2 << 11) | 1152; + write_instr(&mut mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mut mem); + let r = ctx.vr[3].as_u32x4(); + assert_eq!(r[0], 60); + assert_eq!(r[1], 170); + assert_eq!(r[2], 280); + assert_eq!(r[3], 390); + } + + // PPCBUG-277 VMX integer compare. + #[test] + fn vcmpequb_lane_wise_byte_compare() { + let mut ctx = PpcContext::new(); + let mut mem = TestMem::new(); + ctx.vr[1] = xenia_types::Vec128::from_bytes([0xAA; 16]); + ctx.vr[2] = xenia_types::Vec128::from_bytes([0xAA; 16]); + // vcmpequb canary base 0x10000006 → XO=6 + let raw = (4u32 << 26) | (3 << 21) | (1 << 16) | (2 << 11) | 6; + write_instr(&mut mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mut mem); + let r = ctx.vr[3].as_bytes(); + assert_eq!(r[0], 0xFF); + assert_eq!(r[15], 0xFF); + } + + // PPCBUG-278 VMX min/max. + #[test] + fn vmaxsw_lane_wise_signed_max() { + let mut ctx = PpcContext::new(); + let mut mem = TestMem::new(); + ctx.vr[1] = crate::vmx::from_i32x4([10, -5, 100, -1000]); + ctx.vr[2] = crate::vmx::from_i32x4([20, 5, -100, 1000]); + // vmaxsw canary base 0x10000182 → XO=386 + let raw = (4u32 << 26) | (3 << 21) | (1 << 16) | (2 << 11) | 386; + write_instr(&mut mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mut mem); + let r = crate::vmx::as_i32x4(ctx.vr[3]); + assert_eq!(r[0], 20); + assert_eq!(r[1], 5); + assert_eq!(r[2], 100); + assert_eq!(r[3], 1000); + } + + // PPCBUG-316 VMX shift/rotate. + #[test] + fn vsl_left_shift_via_low3_bits_of_lane15() { + // vsl shifts the 128-bit value left by (vB[15] & 7) bits. + let mut ctx = PpcContext::new(); + let mut mem = TestMem::new(); + ctx.vr[1] = xenia_types::Vec128::from_u32x4(0x1234_5678, 0, 0, 0); + let mut sh = [0u8; 16]; sh[15] = 4; + ctx.vr[2] = xenia_types::Vec128::from_bytes(sh); + // vsl canary base 0x100001c4 → XO=452 + let raw = (4u32 << 26) | (3 << 21) | (1 << 16) | (2 << 11) | 452; + write_instr(&mut mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mut mem); + let r = ctx.vr[3].as_u32x4(); + assert_eq!(r[0], 0x2345_6780, "shift left by 4 bits"); + } + + #[test] + fn vsraw_arithmetic_right_shift_per_lane() { + let mut ctx = PpcContext::new(); + let mut mem = TestMem::new(); + ctx.vr[1] = crate::vmx::from_i32x4([-16, 16, -1, 0x4000_0000]); + ctx.vr[2] = xenia_types::Vec128::from_u32x4(2, 2, 2, 2); + // vsraw canary base 0x10000384 → XO=900 + let raw = (4u32 << 26) | (3 << 21) | (1 << 16) | (2 << 11) | 900; + write_instr(&mut mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mut mem); + let r = crate::vmx::as_i32x4(ctx.vr[3]); + assert_eq!(r[0], -4); + assert_eq!(r[1], 4); + assert_eq!(r[2], -1); // arith shift preserves sign + assert_eq!(r[3], 0x1000_0000); + } + + // PPCBUG-321 VMX logical. + #[test] + fn vand_lane_wise_and() { + let mut ctx = PpcContext::new(); + let mut mem = TestMem::new(); + ctx.vr[1] = xenia_types::Vec128::from_u32x4(0xFFFF_FFFF, 0xAAAA_AAAA, 0x5555_5555, 0); + ctx.vr[2] = xenia_types::Vec128::from_u32x4(0xAAAA_AAAA, 0x5555_5555, 0xFFFF_FFFF, 0xFFFF_FFFF); + // vand canary base 0x10000404 → XO=1028 + let raw = (4u32 << 26) | (3 << 21) | (1 << 16) | (2 << 11) | 1028; + write_instr(&mut mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mut mem); + let r = ctx.vr[3].as_u32x4(); + assert_eq!(r[0], 0xAAAA_AAAA); + assert_eq!(r[1], 0); + assert_eq!(r[2], 0x5555_5555); + assert_eq!(r[3], 0); + } + + // PPCBUG-370 VMX permute/pack. + #[test] + fn vsldoi_byte_concat_shift() { + let mut ctx = PpcContext::new(); + let mut mem = TestMem::new(); + ctx.vr[1] = xenia_types::Vec128::from_bytes( + [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10]); + ctx.vr[2] = xenia_types::Vec128::from_bytes( + [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, + 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99]); + // vsldoi v3, v1, v2, 4 — opcode 4, XO=44, SH at bits 11-15? Actually SH in shb (bits 22-25). + // Canary base 0x1000002c, SHB at bits 22-25. + let raw = (4u32 << 26) | (3 << 21) | (1 << 16) | (2 << 11) | (4 << 6) | 44; + write_instr(&mut mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mut mem); + let r = ctx.vr[3].as_bytes(); + // shift by 4: result = vA[4..16] || vB[0..4] + assert_eq!(r[0], 0x05); + assert_eq!(r[11], 0x10); + assert_eq!(r[12], 0xAA); + assert_eq!(r[15], 0xDD); + } + + // PPCBUG-490 VMX multiply-sum. + #[test] + fn vmsum3fp_horizontal_3lane_sum() { + // vmsum3fp128 (already tested in P5 indirectly); here test scalar vmsum4ubm. + // Skip scalar VMX multiply-sum byte forms (large encoding); test vmaddfp. + let mut ctx = PpcContext::new(); + let mut mem = TestMem::new(); + ctx.vr[1] = xenia_types::Vec128::from_f32x4_array([2.0, 3.0, 4.0, 5.0]); + ctx.vr[2] = xenia_types::Vec128::from_f32x4_array([1.0, 1.0, 1.0, 1.0]); + ctx.vr[3] = xenia_types::Vec128::from_f32x4_array([10.0, 20.0, 30.0, 40.0]); + // vmaddfp v4, v1, v2, v3: opcode 4, XO=46, with vC at bits 6-10 (rd) and vB at 11-15 + // Per A-form: (4<<26)|(rd<<21)|(ra<<16)|(rb<<11)|(rc<<6)|46 + let raw = (4u32 << 26) | (4 << 21) | (1 << 16) | (3 << 11) | (2 << 6) | 46; + write_instr(&mut mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mut mem); + let r = ctx.vr[4].as_f32x4(); + assert_eq!(r[0], 12.0); // 2*1 + 10 + assert_eq!(r[1], 23.0); + assert_eq!(r[2], 34.0); + assert_eq!(r[3], 45.0); + } + + // PPCBUG-517 VMX load/store. + #[test] + fn lvx_loads_aligned_quadword() { + let mut ctx = PpcContext::new(); + let mem = TestMem::new(); + // Write 16 distinct bytes + for i in 0..16 { mem.write_u8(0x100 + i, (0xA0 + i) as u8); } + ctx.gpr[3] = 0; + ctx.gpr[4] = 0x100; + // lvx v5, r3, r4: opcode 31, XO=103 + let raw = (31u32 << 26) | (5 << 21) | (3 << 16) | (4 << 11) | (103 << 1); + write_instr(&mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mem); + let r = ctx.vr[5].as_bytes(); + assert_eq!(r[0], 0xA0); + assert_eq!(r[15], 0xAF); + } + + #[test] + fn stvx_stores_aligned_quadword() { + let mut ctx = PpcContext::new(); + let mem = TestMem::new(); + let mut data = [0u8; 16]; + for i in 0..16 { data[i] = (0xC0 + i) as u8; } + ctx.vr[5] = xenia_types::Vec128::from_bytes(data); + ctx.gpr[3] = 0; + ctx.gpr[4] = 0x100; + // stvx v5, r3, r4: opcode 31, XO=231 + let raw = (31u32 << 26) | (5 << 21) | (3 << 16) | (4 << 11) | (231 << 1); + write_instr(&mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mem); + for i in 0..16 { + assert_eq!(mem.read_u8(0x100 + i), (0xC0 + i) as u8); + } + } + + #[test] + fn lvebx_byte_lane_load() { + let mut ctx = PpcContext::new(); + let mem = TestMem::new(); + mem.write_u8(0x107, 0x42); + ctx.gpr[3] = 0; + ctx.gpr[4] = 0x107; // EA, byte at offset 7 in the quadword + // lvebx v5, r3, r4: opcode 31, XO=7 + let raw = (31u32 << 26) | (5 << 21) | (3 << 16) | (4 << 11) | (7 << 1); + write_instr(&mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mem); + let r = ctx.vr[5].as_bytes(); + assert_eq!(r[7], 0x42, "byte loaded at lane (EA & 0xF)"); + } + // ---------- Block-cache parity tests ---------- // // These confirm that running a program through the basic-block From 1f9696ad4715a381401d0b9bb7821d019a70141f Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Sat, 2 May 2026 14:22:39 +0200 Subject: [PATCH 5/5] =?UTF-8?q?test(cpu):=20rename=20vmsum3fp=5F=E2=80=A6?= =?UTF-8?q?=20to=20vmaddfp=5Flane=5Ffma=20per=20reviewer=20nit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P8 review feedback (non-blocking): the test fn name said vmsum3fp but the encoding/body actually tests vmaddfp. Rename + clarify comment; no behavior change. Co-Authored-By: Claude Sonnet 4.6 --- crates/xenia-cpu/src/interpreter.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/xenia-cpu/src/interpreter.rs b/crates/xenia-cpu/src/interpreter.rs index cd7bd1d..26f84d6 100644 --- a/crates/xenia-cpu/src/interpreter.rs +++ b/crates/xenia-cpu/src/interpreter.rs @@ -7354,11 +7354,10 @@ mod tests { assert_eq!(r[15], 0xDD); } - // PPCBUG-490 VMX multiply-sum. + // PPCBUG-490 VMX multiply-add (vmaddfp; vmsum* covered indirectly by P5). #[test] - fn vmsum3fp_horizontal_3lane_sum() { - // vmsum3fp128 (already tested in P5 indirectly); here test scalar vmsum4ubm. - // Skip scalar VMX multiply-sum byte forms (large encoding); test vmaddfp. + fn vmaddfp_lane_fma() { + // Per-lane fused multiply-add: vD[i] = vA[i] * vC[i] + vB[i]. let mut ctx = PpcContext::new(); let mut mem = TestMem::new(); ctx.vr[1] = xenia_types::Vec128::from_f32x4_array([2.0, 3.0, 4.0, 5.0]);