//! Assert-based goldens for the PPC disassembler. //! //! Each test owns an inline list of `(raw, addr, label)` cases. On a //! normal run, the test reads the corresponding fixture JSON and asserts //! that `format(decode(raw, addr))` reproduces every field exactly. On //! first creation (fixture file missing) or with `REGEN_GOLDENS=1` set, //! the test (re)writes the fixture from `format()` output. //! //! Workflow: //! ```sh //! cargo test -p xenia-cpu --test disasm_goldens # assert //! REGEN_GOLDENS=1 cargo test -p xenia-cpu --test disasm_goldens # regen //! ``` //! //! The hand-encoded test cases below cover the silent-bug regression //! cases that lived in the old println-based `disasm_audit.rs` harness //! (now deleted). use std::path::PathBuf; use serde::{Deserialize, Serialize}; use xenia_cpu::decoder::{DecodedInstr, decode}; use xenia_cpu::disasm::format; #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] struct GoldenRow { label: String, raw: String, addr: String, mnemonic: String, operands: String, #[serde(default, skip_serializing_if = "Option::is_none")] ext_mnemonic: Option, #[serde(default, skip_serializing_if = "Option::is_none")] ext_operands: Option, #[serde(default, skip_serializing_if = "Option::is_none")] branch_target: Option, } #[derive(Debug, Deserialize, Serialize)] struct GoldenFile { rows: Vec, } fn fixture_path(name: &str) -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("tests") .join("golden") .join(name) } /// Encode a VMX128 VX128-form (or VX128_R/_2) instruction with canary's /// 7-bit register layout: VD low at PPC 6-10, high 2 bits at PPC 28-29; /// VA low at PPC 11-15, mid bit at PPC 26, high bit at PPC 21; VB low at /// PPC 16-20, high 2 bits at PPC 30-31. `secondary_bits` carries any /// secondary opcode + VC + Rc + key bits the caller needs. fn encode_vx128(op6: u32, vd: u32, va: u32, vb: u32, secondary_bits: u32) -> u32 { ((op6 & 0x3F) << 26) | ((vd & 0x1F) << 21) | (((vd >> 5) & 0x3) << 2) | ((va & 0x1F) << 16) | (((va >> 5) & 0x1) << 5) | (((va >> 6) & 0x1) << 10) | ((vb & 0x1F) << 11) | (((vb >> 5) & 0x3) << 0) | secondary_bits } fn build_rows(cases: &[(u32, u32, &str)]) -> Vec { cases .iter() .map(|&(raw, addr, label)| { let d = decode(raw, addr); let t = format(&d); GoldenRow { label: label.to_string(), raw: format!("0x{raw:08X}"), addr: format!("0x{addr:08X}"), mnemonic: t.mnemonic, operands: t.operands, ext_mnemonic: t.ext_mnemonic, ext_operands: t.ext_operands, branch_target: t.branch_target.map(|t| format!("0x{t:08X}")), } }) .collect() } /// Compare what `format()` produces against the committed JSON snapshot. /// Set `REGEN_GOLDENS=1` to overwrite the snapshot from current output. /// Missing snapshot is treated as "first creation": writes and panics so /// CI can't accidentally accept blank goldens. fn assert_or_regen(fixture_name: &str, cases: &[(u32, u32, &str)]) { let rows = build_rows(cases); let path = fixture_path(fixture_name); let regen = std::env::var("REGEN_GOLDENS").is_ok(); if regen || !path.exists() { if let Some(parent) = path.parent() { std::fs::create_dir_all(parent).unwrap(); } let serialized = serde_json::to_string_pretty(&GoldenFile { rows }).unwrap(); std::fs::write(&path, serialized + "\n").unwrap(); if !regen { panic!( "Generated fixture {} (was missing). Inspect, commit, then re-run.", path.display() ); } return; } let src = std::fs::read_to_string(&path).unwrap(); let golden: GoldenFile = serde_json::from_str(&src).unwrap(); assert_eq!( rows.len(), golden.rows.len(), "row count differs from {} (live={}, fixture={}). Run with REGEN_GOLDENS=1 if the test cases changed intentionally.", path.display(), rows.len(), golden.rows.len() ); for (i, (got, expected)) in rows.iter().zip(golden.rows.iter()).enumerate() { assert_eq!( got, expected, "row {} ({}) differs in {}\n live: {got:#?}\n fixture: {expected:#?}", i, expected.label, path.display() ); } } // ── Encoding helpers ──────────────────────────────────────────────────────── // PPC bit numbering: bit 0 is MSB, bit 31 is LSB. Most helpers below emit // instructions in canonical hand-readable form: opcode << 26 | . #[allow(clippy::too_many_arguments)] fn xform_xo3(rd: u32, ra: u32, rb: u32, oe: u32, xo: u32, rc: u32) -> u32 { (31 << 26) | (rd << 21) | (ra << 16) | (rb << 11) | (oe << 10) | (xo << 1) | rc } fn xform_logic(rs: u32, ra: u32, rb: u32, xo: u32, rc: u32) -> u32 { (31 << 26) | (rs << 21) | (ra << 16) | (rb << 11) | (xo << 1) | rc } fn dform(op: u32, rt: u32, ra: u32, imm: i16) -> u32 { (op << 26) | (rt << 21) | (ra << 16) | ((imm as u16) as u32) } fn iform_b(target_disp: i32, aa: u32, lk: u32) -> u32 { // I-form: opcode 18 | LI<<2 | AA<<1 | LK let li = (target_disp as u32) & 0x03FF_FFFC; (18 << 26) | li | (aa << 1) | lk } fn bform_bc(bo: u32, bi: u32, target_disp: i32, aa: u32, lk: u32) -> u32 { // B-form: opcode 16 | BO<<21 | BI<<16 | BD<<2 | AA<<1 | LK let bd = (target_disp as u32) & 0x0000_FFFC; (16 << 26) | (bo << 21) | (bi << 16) | bd | (aa << 1) | lk } fn xlform_bclr(bo: u32, bi: u32, lk: u32) -> u32 { // XL-form: opcode 19 | BO<<21 | BI<<16 | XO=16<<1 | LK (19 << 26) | (bo << 21) | (bi << 16) | (16 << 1) | lk } fn xlform_bcctr(bo: u32, bi: u32, lk: u32) -> u32 { (19 << 26) | (bo << 21) | (bi << 16) | (528 << 1) | lk } fn rlwinm(rs: u32, ra: u32, sh: u32, mb: u32, me: u32, rc: u32) -> u32 { (21 << 26) | (rs << 21) | (ra << 16) | (sh << 11) | (mb << 6) | (me << 1) | rc } fn rldicl(rs: u32, ra: u32, sh: u32, mb: u32, rc: u32) -> u32 { // MD-form: sh[4:0] at PPC bits 16-20 (host bits 11-15); sh[5] at PPC bit 30 (host bit 1). // mb[4:0] at PPC bits 21-25 (host bits 6-10); mb[5] at PPC bit 26 (host bit 5). let sh_lo = sh & 0x1F; let sh_hi = (sh >> 5) & 1; let mb_lo = mb & 0x1F; let mb_hi = (mb >> 5) & 1; (30 << 26) | (rs << 21) | (ra << 16) | (sh_lo << 11) | (mb_lo << 6) | (mb_hi << 5) | (0 << 2) | (sh_hi << 1) | rc } fn mfspr(rd: u32, spr: u32) -> u32 { let spr_swapped = ((spr & 0x1F) << 5) | ((spr >> 5) & 0x1F); (31 << 26) | (rd << 21) | (spr_swapped << 11) | (339 << 1) } fn mtspr(rs: u32, spr: u32) -> u32 { let spr_swapped = ((spr & 0x1F) << 5) | ((spr >> 5) & 0x1F); (31 << 26) | (rs << 21) | (spr_swapped << 11) | (467 << 1) } // ── Tests ─────────────────────────────────────────────────────────────────── #[test] fn base_mnemonics() { let cases: &[(u32, u32, &str)] = &[ // X-form ALU (Rc and OE bits) (xform_xo3(3, 4, 5, 0, 266, 0), 0x82000000, "add r3,r4,r5"), (xform_xo3(3, 4, 5, 0, 266, 1), 0x82000000, "add. r3,r4,r5"), (xform_xo3(3, 4, 5, 1, 266, 0), 0x82000000, "addo r3,r4,r5"), (xform_xo3(3, 4, 5, 1, 266, 1), 0x82000000, "addo. r3,r4,r5"), (xform_xo3(3, 4, 0, 0, 104, 0), 0x82000000, "neg r3,r4"), (xform_xo3(3, 4, 5, 0, 235, 0), 0x82000000, "mullw r3,r4,r5"), (xform_xo3(3, 4, 5, 0, 491, 0), 0x82000000, "divw r3,r4,r5"), (xform_xo3(3, 4, 5, 0, 75, 1), 0x82000000, "mulhw. r3,r4,r5"), (xform_xo3(3, 4, 5, 0, 11, 1), 0x82000000, "mulhwu. r3,r4,r5"), (xform_xo3(3, 4, 5, 0, 233, 0), 0x82000000, "mulld r3,r4,r5"), // X-form logical (xform_logic(4, 3, 5, 28, 0), 0x82000000, "and r3,r4,r5"), (xform_logic(4, 3, 5, 444, 0), 0x82000000, "or r3,r4,r5 (non-mr: rs!=rb)"), (xform_logic(4, 3, 5, 316, 0), 0x82000000, "xor r3,r4,r5"), (xform_logic(4, 3, 5, 124, 0), 0x82000000, "nor r3,r4,r5"), (xform_logic(4, 3, 5, 476, 0), 0x82000000, "nand r3,r4,r5"), (xform_logic(4, 3, 5, 284, 0), 0x82000000, "eqv r3,r4,r5"), (xform_logic(4, 3, 5, 60, 0), 0x82000000, "andc r3,r4,r5"), (xform_logic(4, 3, 5, 412, 0), 0x82000000, "orc r3,r4,r5"), // X-form shift (xform_logic(4, 3, 5, 24, 0), 0x82000000, "slw r3,r4,r5"), (xform_logic(4, 3, 5, 536, 0), 0x82000000, "srw r3,r4,r5"), (xform_logic(4, 3, 5, 792, 0), 0x82000000, "sraw r3,r4,r5"), (xform_logic(4, 3, 5, 27, 0), 0x82000000, "sld r3,r4,r5"), (xform_logic(4, 3, 5, 539, 0), 0x82000000, "srd r3,r4,r5"), // srawi / sradi (immediate shifts) ((31 << 26) | (4 << 21) | (3 << 16) | (16 << 11) | (824 << 1), 0x82000000, "srawi r3,r4,16"), // Atomics ((31 << 26) | (3 << 21) | (4 << 16) | (5 << 11) | (150 << 1) | 1, 0x82000000, "stwcx. r3,r4,r5"), ((31 << 26) | (3 << 21) | (4 << 16) | (5 << 11) | (214 << 1) | 1, 0x82000000, "stdcx. r3,r4,r5"), ((31 << 26) | (3 << 21) | (4 << 16) | (5 << 11) | (20 << 1), 0x82000000, "lwarx r3,r4,r5"), ((31 << 26) | (3 << 21) | (4 << 16) | (5 << 11) | (84 << 1), 0x82000000, "ldarx r3,r4,r5"), // Compares (dform(11, 0, 3, 16), 0x82000000, "cmpwi cr0, r3, 16"), (dform(11, 2 << 2, 3, 16), 0x82000000, "cmpwi cr2, r3, 16"), (dform(10, 0, 3, 16), 0x82000000, "cmplwi cr0, r3, 16"), ((31 << 26) | (3 << 16) | (4 << 11), 0x82000000, "cmpw r3,r4 in cr0"), ((31 << 26) | (1 << 21) | (3 << 16) | (4 << 11), 0x82000000, "cmpd r3,r4"), ((31 << 26) | (3 << 16) | (4 << 11) | (32 << 1), 0x82000000, "cmplw r3,r4"), // D-form ALU/load/store (dform(14, 3, 1, 16), 0x82000000, "addi r3, r1, 16"), (dform(15, 3, 1, 0x100), 0x82000000, "addis r3, r1, 0x100 (ra!=0)"), (dform(7, 3, 4, 5), 0x82000000, "mulli r3, r4, 5"), (dform(8, 3, 4, 5), 0x82000000, "subfic r3, r4, 5"), (dform(12, 3, 4, 16), 0x82000000, "addic r3, r4, 16"), (dform(13, 3, 4, 16), 0x82000000, "addic. r3, r4, 16"), (dform(24, 3, 4, 0x10), 0x82000000, "ori r4, r3, 0x10 (non-nop)"), (dform(25, 3, 4, 0x10), 0x82000000, "oris r4, r3, 0x10"), (dform(26, 3, 4, 0x10), 0x82000000, "xori r4, r3, 0x10"), (dform(28, 3, 4, 0x10), 0x82000000, "andi. r4, r3, 0x10"), // Loads/stores D-form (dform(32, 5, 1, 0x20), 0x82000000, "lwz r5, 0x20(r1)"), (dform(36, 5, 1, 0x20), 0x82000000, "stw r5, 0x20(r1)"), (dform(34, 5, 1, 0x20), 0x82000000, "lbz r5, 0x20(r1)"), (dform(40, 5, 1, 0x20), 0x82000000, "lhz r5, 0x20(r1)"), (dform(48, 5, 1, 0x20), 0x82000000, "lfs f5, 0x20(r1)"), (dform(50, 5, 1, 0x20), 0x82000000, "lfd f5, 0x20(r1)"), (dform(54, 5, 1, 0x20), 0x82000000, "stfd f5, 0x20(r1)"), // DS-form 64-bit loads ((58u32 << 26) | (5 << 21) | (1 << 16) | 0x20, 0x82000000, "ld r5, 0x20(r1)"), ((62u32 << 26) | (5 << 21) | (1 << 16) | 0x20, 0x82000000, "std r5, 0x20(r1)"), // Sync / barrier (parameterless) ((31 << 26) | (598 << 1), 0x82000000, "sync 0 (extends to sync)"), ((19 << 26) | (150 << 1), 0x82000000, "isync"), ((31 << 26) | (854 << 1), 0x82000000, "eieio"), // Cache hints ((31 << 26) | (1 << 16) | (2 << 11) | (54 << 1), 0x82000000, "dcbst r1, r2"), ((31 << 26) | (1 << 16) | (2 << 11) | (86 << 1), 0x82000000, "dcbf r1, r2"), ((31 << 26) | (1 << 16) | (2 << 11) | (278 << 1), 0x82000000, "dcbt r1, r2"), ((31 << 26) | (1 << 16) | (2 << 11) | (1014 << 1), 0x82000000, "dcbz r1, r2"), ((31 << 26) | (1 << 21) | (1 << 16) | (2 << 11) | (1014 << 1), 0x82000000, "dcbz128 r1, r2"), // CR logical (without simplification triggers) ((19 << 26) | (4 << 21) | (5 << 16) | (6 << 11) | (33 << 1), 0x82000000, "crnor 4,5,6 (no simplify)"), ((19 << 26) | (4 << 21) | (5 << 16) | (6 << 11) | (257 << 1), 0x82000000, "crand 4,5,6"), ((19 << 26) | (4 << 21) | (5 << 16) | (6 << 11) | (449 << 1), 0x82000000, "cror 4,5,6 (no simplify)"), // Trap (no simplification: TO=11 doesn't match the table) ((31 << 26) | (11 << 21) | (3 << 16) | (4 << 11) | (4 << 1), 0x82000000, "tw 11, r3, r4 (uncommon TO)"), ((2u32 << 26) | (11 << 21) | (3 << 16) | (123u32 & 0xFFFF), 0x82000000, "tdi 11, r3, 123"), // mtcr (extended): mtcrf 0xFF, r5 ((31 << 26) | (5 << 21) | (0xFF << 12) | (144 << 1), 0x82000000, "mtcrf 0xFF, r5 → mtcr"), // mfcr / mfmsr / mtmsr / mtmsrd ((31 << 26) | (5 << 21) | (19 << 1), 0x82000000, "mfcr r5"), ((31 << 26) | (5 << 21) | (83 << 1), 0x82000000, "mfmsr r5"), ((31 << 26) | (5 << 21) | (146 << 1), 0x82000000, "mtmsr r5"), ((31 << 26) | (5 << 21) | (178 << 1), 0x82000000, "mtmsrd r5"), // FPU base ((63u32 << 26) | (3 << 21) | (4 << 16) | (5 << 11) | (21 << 1), 0x82000000, "fadd f3, f4, f5"), ((63u32 << 26) | (3 << 21) | (4 << 16) | (5 << 11) | (20 << 1), 0x82000000, "fsub f3, f4, f5"), ((63u32 << 26) | (3 << 21) | (4 << 16) | (5 << 11) | (18 << 1), 0x82000000, "fdiv f3, f4, f5"), ((63u32 << 26) | (3 << 21) | (5 << 21) | (5 << 11) | (25 << 1), 0x82000000, "fmul f3, f0, f5 (encoded)"), ((63u32 << 26) | (3 << 21) | (4 << 16) | (40 << 1), 0x82000000, "fneg f3, f4"), ((63u32 << 26) | (3 << 21) | (4 << 16) | (72 << 1), 0x82000000, "fmr f3, f4"), // mtfsf — XFL form (Fix 1). FM at LSB bits 17-24 (PPC bits 7-14). // Encoding: opcode 63 | FM<<17 | frB<<11 | XO=711<<1 | Rc. ((63u32 << 26) | (0xFF << 17) | (5 << 11) | (711 << 1), 0x82000000, "mtfsf 0xFF, f5 (Rc=0)"), ((63u32 << 26) | (0xFF << 17) | (5 << 11) | (711 << 1) | 1, 0x82000000, "mtfsf. 0xFF, f5 (Rc=1)"), ]; assert_or_regen("base_mnemonics.json", cases); } #[test] fn extended_mnemonics() { let cases: &[(u32, u32, &str)] = &[ // ori r0, r0, 0 → nop (dform(24, 0, 0, 0), 0x82000000, "nop"), // addi r3, r0, imm → li (dform(14, 3, 0, 16), 0x82000000, "li r3, 16"), (dform(14, 3, 0, -1), 0x82000000, "li r3, -1"), // addi r3, r4, neg → subi (dform(14, 3, 4, -16), 0x82000000, "subi r3, r4, 16"), // addis r3, r0, imm → lis (dform(15, 3, 0, 0x1234), 0x82000000, "lis r3, 0x1234"), // addis r3, r4, neg → subis (dform(15, 3, 4, -1), 0x82000000, "subis r3, r4, 0xFFFF"), // or rA, rS, rS → mr (xform_logic(4, 3, 4, 444, 0), 0x82000000, "mr r3, r4"), (xform_logic(4, 3, 4, 444, 1), 0x82000000, "mr. r3, r4"), // and rA, rS, rS → mr (also) (xform_logic(4, 3, 4, 28, 0), 0x82000000, "mr (via and)"), // nor rA, rS, rS → not (xform_logic(4, 3, 4, 124, 0), 0x82000000, "not r3, r4"), // subf → sub (operand swap) (xform_xo3(3, 4, 5, 0, 40, 0), 0x82000000, "subf → sub r3, r5, r4"), // rlwinm simplifications (rlwinm(4, 3, 4, 0, 31 - 4, 0), 0x82000000, "slwi r3, r4, 4"), (rlwinm(4, 3, 32 - 4, 4, 31, 0), 0x82000000, "srwi r3, r4, 4"), (rlwinm(4, 3, 8, 0, 31, 0), 0x82000000, "rotlwi r3, r4, 8"), (rlwinm(4, 3, 0, 4, 31, 0), 0x82000000, "clrlwi r3, r4, 4"), (rlwinm(4, 3, 0, 0, 27, 0), 0x82000000, "clrrwi r3, r4, 4"), (rlwinm(4, 3, 8, 0, 7, 0), 0x82000000, "extlwi r3, r4, 8, 8"), // rlwinm with Rc (rlwinm(4, 3, 4, 0, 31 - 4, 1), 0x82000000, "slwi. r3, r4, 4"), // rlwinm Sylpheed regression (rlwinm(11, 11, 0, 31, 31, 1), 0x82000000, "rlwinm. r11,r11,0,31,31 (no simplify)"), // rldicl simplifications (rldicl(4, 3, 0, 32, 0), 0x82000000, "clrldi r3, r4, 32"), (rldicl(4, 3, 64u32 - 8, 8, 0), 0x82000000, "srdi r3, r4, 8"), (rldicl(4, 3, 8, 0, 0), 0x82000000, "rotldi r3, r4, 8"), // cmpi / cmpli → cmpwi/cmpdi/cmplwi/cmpldi (dform(11, 0, 3, 16), 0x82000000, "cmpwi cr0, r3, 16"), (dform(11, (1 << 21) | (2 << 23), 3, 16) | (1 << 21), 0x82000000, "cmpdi (L=1) variant"), // bclr 20, 0 → blr (xlform_bclr(20, 0, 0), 0x82000000, "blr"), (xlform_bclr(20, 0, 1), 0x82000000, "blrl"), // bcctr 20, 0 → bctr (xlform_bcctr(20, 0, 0), 0x82000000, "bctr"), (xlform_bcctr(20, 0, 1), 0x82000000, "bctrl"), // bclr conditional (xlform_bclr(12, 2, 0), 0x82000000, "beqlr (BO=12, BI=2 → cr0.eq true)"), (xlform_bclr(4, 2, 0), 0x82000000, "bnelr"), // bc with full BO/BI: branch always (BO=20) (bform_bc(20, 0, 0x40, 0, 0), 0x82000000, "bc → b 0x82000040"), (bform_bc(20, 0, 0x40, 0, 1), 0x82000000, "bc l → bl 0x82000040"), // Conditional bc → beq/bne/etc (bform_bc(12, 2, 0x40, 0, 0), 0x82000000, "bc 12,cr0.eq → beq 0x82000040"), (bform_bc(4, 2, 0x40, 0, 0), 0x82000000, "bc 4,cr0.eq → bne 0x82000040"), (bform_bc(12, 0, 0x40, 0, 0), 0x82000000, "bc 12,cr0.lt → blt 0x82000040"), (bform_bc(4, 0, 0x40, 0, 0), 0x82000000, "bc 4,cr0.lt → bge 0x82000040"), (bform_bc(12, 1, 0x40, 0, 0), 0x82000000, "bc 12,cr0.gt → bgt 0x82000040"), (bform_bc(4, 1, 0x40, 0, 0), 0x82000000, "bc 4,cr0.gt → ble 0x82000040"), // Conditional with non-zero CR field (bform_bc(12, 2 + 8, 0x40, 0, 0), 0x82000000, "bc 12, cr2.eq → beq cr2, 0x...040"), // bdnz / bdz (decrement-CTR branches) (bform_bc(16, 0, 0x40, 0, 0), 0x82000000, "bdnz 0x82000040"), (bform_bc(18, 0, 0x40, 0, 0), 0x82000000, "bdz 0x82000040"), // I-form branches (iform_b(0x40, 0, 0), 0x82000000, "b +0x40 → 0x82000040"), (iform_b(0x40, 0, 1), 0x82000000, "bl +0x40 → 0x82000040"), (iform_b(0x40, 1, 0), 0x82000000, "ba 0x40 absolute"), (iform_b(0x40, 1, 1), 0x82000000, "bla 0x40 absolute"), // Trap immediate simplifications ((2u32 << 26) | (4 << 21) | (3 << 16) | (123u32 & 0xFFFF), 0x82000000, "tdeqi r3, 123"), ((3u32 << 26) | (16 << 21) | (3 << 16) | (123u32 & 0xFFFF), 0x82000000, "twlti r3, 123"), // mfspr → mflr / mfctr / mfxer (mfspr(3, 8), 0x82000000, "mflr r3"), (mfspr(3, 9), 0x82000000, "mfctr r3"), (mfspr(3, 1), 0x82000000, "mfxer r3"), // mtspr → mtlr / mtctr / mtxer (mtspr(3, 8), 0x82000000, "mtlr r3"), (mtspr(3, 9), 0x82000000, "mtctr r3"), (mtspr(3, 1), 0x82000000, "mtxer r3"), // crnor with same source bits → crnot ((19 << 26) | (4 << 21) | (5 << 16) | (5 << 11) | (33 << 1), 0x82000000, "crnot 4, 5"), // crxor with all same → crclr ((19 << 26) | (4 << 21) | (4 << 16) | (4 << 11) | (193 << 1), 0x82000000, "crclr 4"), // creqv with all same → crset ((19 << 26) | (4 << 21) | (4 << 16) | (4 << 11) | (289 << 1), 0x82000000, "crset 4"), // cror with same source bits → crmove ((19 << 26) | (4 << 21) | (5 << 16) | (5 << 11) | (449 << 1), 0x82000000, "crmove 4, 5"), // sync L=1 → lwsync ((31 << 26) | (1 << 21) | (598 << 1), 0x82000000, "lwsync"), // tw 31, 0, 0 → trap ((31 << 26) | (31 << 21) | (4 << 1), 0x82000000, "trap"), // Fix 2: bclr/bcctr with BO=20 and BI≠0 still emits blr/bctr ext. // BO=20 ignores both CTR test and CR test, so BI is don't-care. (xlform_bclr(20, 4, 0), 0x82000000, "blr (BO=20, BI=4 — BI is don't-care)"), (xlform_bclr(20, 7, 1), 0x82000000, "blrl (BO=20, BI=7)"), (xlform_bcctr(20, 4, 0), 0x82000000, "bctr (BO=20, BI=4)"), // Fix 3: trap unsigned simplified mnemonics (TO=1, 2, 5, 6 — logical // compare conditions). Register form (tw/td) and immediate (twi/tdi). ((31u32 << 26) | (2 << 21) | (3 << 16) | (4 << 11) | (4 << 1), 0x82000000, "twllt r3, r4 (TO=2)"), ((31u32 << 26) | (1 << 21) | (3 << 16) | (4 << 11) | (4 << 1), 0x82000000, "twlgt r3, r4 (TO=1)"), ((31u32 << 26) | (5 << 21) | (3 << 16) | (4 << 11) | (68 << 1), 0x82000000, "tdlge r3, r4 (TO=5)"), ((31u32 << 26) | (6 << 21) | (3 << 16) | (4 << 11) | (4 << 1), 0x82000000, "twlle r3, r4 (TO=6)"), ((3u32 << 26) | (2 << 21) | (3 << 16) | (16u32 & 0xFFFF), 0x82000000, "twllti r3, 16"), ((2u32 << 26) | (5 << 21) | (3 << 16) | (16u32 & 0xFFFF), 0x82000000, "tdlgei r3, 16"), ]; assert_or_regen("extended_mnemonics.json", cases); } #[test] fn vmx128_registers() { // Standard VMX (op=4) — 5-bit registers v0..v31. Verifies that the // low-register path renders correctly through the new formatter. let std_vmx = [ // vaddubm v3, v4, v5 : op=4, 3-op key=0 ((4u32 << 26) | (3 << 21) | (4 << 16) | (5 << 11) | 0, 0x82000000, "vaddubm v3, v4, v5"), // vaddfp v3, v4, v5 : op=4, vx=10 ((4u32 << 26) | (3 << 21) | (4 << 16) | (5 << 11) | 10, 0x82000000, "vaddfp v3, v4, v5"), // vand v3, v4, v5 : vx=1028 ((4u32 << 26) | (3 << 21) | (4 << 16) | (5 << 11) | 1028, 0x82000000, "vand v3, v4, v5"), // vor v3, v4, v5 : vx=1156 ((4u32 << 26) | (3 << 21) | (4 << 16) | (5 << 11) | 1156, 0x82000000, "vor v3, v4, v5"), // vxor v3, v4, v5 : vx=1220 ((4u32 << 26) | (3 << 21) | (4 << 16) | (5 << 11) | 1220, 0x82000000, "vxor v3, v4, v5"), // vsel v3, v4, v5, v6 : op=4, va_key=42 (4-op) ((4u32 << 26) | (3 << 21) | (4 << 16) | (5 << 11) | (6 << 6) | 42, 0x82000000, "vsel v3,v4,v5,v6"), // vperm v3, v4, v5, v6 : va_key=43 ((4u32 << 26) | (3 << 21) | (4 << 16) | (5 << 11) | (6 << 6) | 43, 0x82000000, "vperm v3,v4,v5,v6"), // vmaddfp v3, v4, v5, v6 : va_key=46 (operand swap: vd, va, vc, vb) ((4u32 << 26) | (3 << 21) | (4 << 16) | (5 << 11) | (6 << 6) | 46, 0x82000000, "vmaddfp v3, v4, v6, v5 (swap)"), // mfvscr v3 : vx=1540 ((4u32 << 26) | (3 << 21) | 1540, 0x82000000, "mfvscr v3"), // mtvscr v5 : vx=1604, vb=v5 ((4u32 << 26) | (5 << 11) | 1604, 0x82000000, "mtvscr v5"), ]; // VMX128 op=5: vperm128 v3, v4, v5, vc=0. Canary FormatVX128: VD low // at PPC 6-10, VA low at PPC 11-15, VB low at PPC 16-20, VC at PPC 23-25. // key1 = (bit22<<5)|bit27 = 0 selects vperm128. let vmx128_op5 = [ (encode_vx128(5, 3, 4, 5, 0), 0x82000000, "vperm128 v3, v4, v5, 0 (canary)"), ]; // VMX128 op=6 — exercise full 0-127 vd128 range under canary's layout. // VD128h is at PPC 28-29 (host 2-3): no overlap with secondary opcode key, // so vd can be freely 0-127 for any op6 instruction. let vsrw128 = |vd: u32, vb: u32| -> u32 { // vsrw128 secondary: 0x000001D0 (decode_op6 key5 = 0b011101). encode_vx128(6, vd, 0, vb, 0x000001D0) }; let vpermwi128 = |vd: u32, vb: u32, perm: u32| -> u32 { // vpermwi128: PERMl at PPC 11-15, PERMh at PPC 23-25, key1 sets bit 22 + bit 27. let perml = perm & 0x1F; let permh = (perm >> 5) & 0x7; let mut raw = (6u32 << 26) | ((vd & 0x1F) << 21) | (((vd >> 5) & 0x3) << 2) // VD128h | (perml << 16) | ((vb & 0x1F) << 11) | (((vb >> 5) & 0x3) << 0) // VB128h | (permh << 6) // PERMh at PPC 23-25 | (1 << 9) // bit 22 (key1 high) | (1 << 4); // bit 27 (key1 low) raw &= !(1 << 10); // PPC 21 = 0 for vpermwi128 raw }; let vrlimi128 = |vd: u32, vb: u32, imm: u32, z: u32| -> u32 { // vrlimi128: IMM at PPC 11-15, z at PPC 24-25, key2 = 0b1110001 over // bits 21-23 + 26-27 → bits 21,22,23 = 1, bit 26 = 0, bit 27 = 1. (6u32 << 26) | ((vd & 0x1F) << 21) | (((vd >> 5) & 0x3) << 2) // VD128h | ((imm & 0x1F) << 16) | ((vb & 0x1F) << 11) | (((vb >> 5) & 0x3) << 0) // VB128h | ((z & 0x3) << 6) // z at PPC 24-25 = host 6-7 | (1 << 8) // bit 23 (key2) | (1 << 9) // bit 22 (key2) | (1 << 10) // bit 21 (key2) | (1 << 4) // bit 27 (key2) }; let vmx128_high = [ (vsrw128(0, 12), 0x82000000, "vsrw128 v0, v0, v12 (canary, vd_hi=00)"), (vsrw128(32, 12), 0x82000000, "vsrw128 v32, v0, v12 (canary, VD128h=01)"), (vpermwi128(64, 12, 0xE4), 0x82000000, "vpermwi128 v64, v12, 0xE4 (canary, VD128h=10)"), (vrlimi128(96, 12, 4, 3), 0x82000000, "vrlimi128 v96, v12, 4, 3 (canary, VD128h=11)"), (vrlimi128(127, 95, 4, 3), 0x82000000, "vrlimi128 v127, v95, 4, 3 (canary)"), ]; // Fix 4: VMX128 multiply-add 4-operand layouts. Per canary, the addend // is the VD register re-used; operand order differs between the three // mnemonics. Encodings hand-built to satisfy decode_op5's key2 secondary // opcode (vmaddfp128=0b001101, vmaddcfp128=0b010001, vnmsubfp128=0b010101) // with bit 22=0 (forced by key2's high nibble) so vd128 high bit 1 = 0. // vd128 low = 3 (bits 6-10); va128 = 3 | (bit29<<5) = 35; vb128 = 5. // Distinct VD vs VA verifies the layout isn't trivially aliasing VD. // // layout (canary): // vmaddfp128 VD, VA, VB, VD → "v3, v35, v5, v3" // vmaddcfp128 VD, VA, VD, VB → "v3, v35, v3, v5" // vnmsubfp128 VD, VA, VD, VB → "v3, v35, v3, v5" let vmx128_4op = [ // Canary FormatVX128 layout: vd=3 (PPC 6-10), va=35 (low 3 at PPC 11-15 + VA128h=1 at PPC 26), // vb=5 (PPC 16-20), key2 at PPC 22-25 + bit 27. (0x146328F0u32, 0x82000000, "vmaddfp128 v3, v35, v5, v3"), (0x14632930u32, 0x82000000, "vmaddcfp128 v3, v35, v3, v5"), (0x14632970u32, 0x82000000, "vnmsubfp128 v3, v35, v3, v5"), ]; let mut all = Vec::new(); all.extend_from_slice(&std_vmx); all.extend_from_slice(&vmx128_op5); all.extend_from_slice(&vmx128_high); all.extend_from_slice(&vmx128_4op); assert_or_regen("vmx128_registers.json", &all); } #[test] fn sradi_shift_32_decodes_to_32() { // sradi rA, rS, 32: sh=32 → sh[4:0]=0, sh[5]=1 // After PPCBUG-040 fix, sh64() must return 32, not 1. let instr: DecodedInstr = decode(rldicl(3, 4, 32, 63, 0), 0); // rldicl with mb=63 is not sradi, but tests sh64() extraction. assert_eq!(instr.sh64(), 32, "sh64 must return 32 for sh=32 (sh5=1, sh_lo=0)"); } #[test] fn sh64_shift_1_decodes_correctly() { // sh=1: sh[4:0]=1, sh[5]=0 → sh64() must return 1 let instr: DecodedInstr = decode(rldicl(3, 4, 1, 0, 0), 0); assert_eq!(instr.sh64(), 1, "sh64 must return 1 for sh=1"); } #[test] fn sh64_shift_63_decodes_correctly() { // sh=63: sh[4:0]=31=0x1F, sh[5]=1 → sh64() must return 63 let instr: DecodedInstr = decode(rldicl(3, 4, 63, 0, 0), 0); assert_eq!(instr.sh64(), 63, "sh64 must return 63 for sh=63"); }