xenia-cpu: VMX128, FPSCR, decoder split, scheduler, decode/block caches
Split the monolithic interpreter into cohesive modules: dedicated decoder (decoder.rs) producing 8-byte DecodedInstr; opcode tables (opcode.rs); explicit traps (trap.rs); FPSCR helpers (fpscr.rs); overflow/carry helpers (overflow.rs); a 4 KiB-page-versioned decode cache and basic-block cache (block_cache.rs); and a full VMX/VMX128 implementation (vmx.rs) covering AltiVec + Xenon's 128-bit extensions. Add the parallel-execution substrate behind --parallel: a 7-party phaser (phaser.rs) for round-based barrier sync, ReservationTable (reservation.rs) for guest LL/SC, and the per-HW-thread scheduler core (scheduler.rs) that owns ThreadRefs, runqueues, and pending IRQs. Disassembler is now the single source of truth: disasm.rs gains the full base + extended + VMX128 mnemonic set, with golden JSON fixtures and a disasm_goldens test suite. Add a criterion-style interpreter bench. context.rs grows the per-thread state the new modules need (reservation slot, FPSCR, vector regs). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
531
crates/xenia-cpu/tests/disasm_goldens.rs
Normal file
531
crates/xenia-cpu/tests/disasm_goldens.rs
Normal file
@@ -0,0 +1,531 @@
|
||||
//! 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::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<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
ext_operands: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
branch_target: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct GoldenFile {
|
||||
rows: Vec<GoldenRow>,
|
||||
}
|
||||
|
||||
fn fixture_path(name: &str) -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("tests")
|
||||
.join("golden")
|
||||
.join(name)
|
||||
}
|
||||
|
||||
fn build_rows(cases: &[(u32, u32, &str)]) -> Vec<GoldenRow> {
|
||||
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 | <fields>.
|
||||
|
||||
#[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, op30 xo=0. sh split: bits 16-20 (high 5) + bit 30 (low bit).
|
||||
// mb split: bits 21-25 (low 5) + bit 26 (high bit).
|
||||
let sh_hi = (sh >> 1) & 0x1F;
|
||||
let sh_lo = sh & 1;
|
||||
let mb_lo = mb & 0x1F;
|
||||
let mb_hi = (mb >> 5) & 1;
|
||||
(30 << 26)
|
||||
| (rs << 21)
|
||||
| (ra << 16)
|
||||
| (sh_hi << 11)
|
||||
| (mb_lo << 6)
|
||||
| (mb_hi << 5)
|
||||
| (0 << 2)
|
||||
| (sh_lo << 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 — uses vd128/va128/vb128 (7-bit registers, high bits at
|
||||
// 21+22). These are the silent-bug-area encodings; we exercise low
|
||||
// register indices here because the secondary-opcode key for op=5
|
||||
// includes bits 21-22, constraining vd128 high bits to 0 in this form.
|
||||
// High-index examples for vd128 live in the op=6 series below.
|
||||
let vmx128_op5 = [
|
||||
// vaddfp128 v3, v4, v5 : op=5, key2=0b000001
|
||||
((5u32 << 26) | (3 << 21) | (4 << 16) | (5 << 11) | (0 << 6) | (1 << 0), 0x82000000, "vaddfp128 (encoded sloppily)"),
|
||||
];
|
||||
|
||||
// VMX128 op=6 — vrlimi128 has secondary key in bits 23-25 + 26-27, so
|
||||
// bits 21-22 ARE the high bits of vd128 (canonical silent-bug-area).
|
||||
// These instructions exercise vd128 = 32, 64, 96 — covering the bit-21
|
||||
// and bit-22 split that ppc.rs's old extractor (now deleted) miscoded.
|
||||
let vrlimi128 = |vd: u32, vb: u32, imm: u32, z: u32| -> u32 {
|
||||
// op=6, vd128 = bits 6-10 + bit 21 + bit 22, vb128 = bits 16-20 + bits 30+31,
|
||||
// IMM = bits 11-15, Z = bits 24-25, key2 = (bits 23-25 << 4) | bits 26-27 = 0b1110001
|
||||
let vd_lo = vd & 0x1F;
|
||||
let vd_b21 = (vd >> 5) & 1;
|
||||
let vd_b22 = (vd >> 6) & 1;
|
||||
let vb_lo = vb & 0x1F;
|
||||
let vb_b30 = (vb >> 5) & 1;
|
||||
let vb_b31 = (vb >> 6) & 1;
|
||||
// bits 23-25 = 111, bits 26-27 = 00, bit 27 = 1 → key2 lower 4 bits = 0001
|
||||
// Encoded: bits 23-25 = 111, bits 26-27 = 00 are actually overlapping with z field (bits 24-25)
|
||||
// The plan view: (bits 23 << 6) | (bits 24-25 << 4) | (bits 26-27 << 2) but the table uses different.
|
||||
// Easiest: hand-encode known bit pattern matching decoder.rs's match:
|
||||
// key2 = (extract_bits(code, 23, 25) << 4) | extract_bits(code, 26, 27) = 0b1110001
|
||||
// bits 23-25 = 111, bits 26-27 = 01
|
||||
// Bit positions 23-27 = 11101 (5 bits, MSB at 23).
|
||||
// PPC bit 23 (LSB index 8): set
|
||||
// PPC bit 24 (LSB index 7): set -- this is z bit 0
|
||||
// PPC bit 25 (LSB index 6): set -- this is z bit 1
|
||||
// PPC bit 26 (LSB index 5): unset
|
||||
// PPC bit 27 (LSB index 4): set
|
||||
// We let z = bits 24-25 stored with vd128 bits at 21-22.
|
||||
// To preserve key2 = 0b1110001, we need bits 24-25 = 11, bit 26 = 0, bit 27 = 1.
|
||||
// BUT bits 24-25 ARE the z field; if we set them = 11 the z value is 3.
|
||||
// So Z is constrained for vrlimi128. Choose Z = 3 (matches Sylpheed examples).
|
||||
let z3 = z & 0x3;
|
||||
(6u32 << 26)
|
||||
| (vd_lo << 21)
|
||||
| (imm << 16)
|
||||
| (vb_lo << 11)
|
||||
| (vd_b21 << 10) // bit 21 (LSB pos 10)
|
||||
| (vd_b22 << 9) // bit 22 (LSB pos 9)
|
||||
| (1 << 8) // bit 23
|
||||
| (z3 << 6) // bits 24-25
|
||||
| (0 << 5) // bit 26
|
||||
| (1 << 4) // bit 27
|
||||
| (vb_b30 << 1) // bit 30
|
||||
| vb_b31 // bit 31
|
||||
};
|
||||
// Note: VMX128 op6 secondary keys constrain bits 21-23. For
|
||||
// vrlimi128 (key2 = 0b1110001 over bits 21-23 + 26-27) the only
|
||||
// valid vd128 range is 96..=127 — lower values change the secondary
|
||||
// key into some other instruction. The cases below record what the
|
||||
// disassembler emits for the borderline encodings, so a regression
|
||||
// in either the lookup table or the formatter would surface here.
|
||||
let vmx128_high = [
|
||||
// bits 21-22 = 00 → key2 ≠ vrlimi128 → decodes to vsrw128 (key5
|
||||
// branch). Locks current behavior; shows the silent-bug-area
|
||||
// encoding constraint.
|
||||
(vrlimi128(0, 12, 4, 3), 0x82000000, "encoding vd_hi=00: actually vsrw128"),
|
||||
// bits 21-22 = 10 → still not vrlimi128.
|
||||
(vrlimi128(32, 12, 4, 3), 0x82000000, "encoding vd_hi=10: actually vsrw128 v32"),
|
||||
// bits 21-22 = 01 → key1 matches vpermwi128.
|
||||
(vrlimi128(64, 12, 4, 3), 0x82000000, "encoding vd_hi=01: actually vpermwi128"),
|
||||
// bits 21-22 = 11 → key2 matches vrlimi128 with vd128=96.
|
||||
(vrlimi128(96, 12, 4, 3), 0x82000000, "vrlimi128 v96, v12, 4, 3 (real)"),
|
||||
(vrlimi128(127, 127, 4, 3), 0x82000000, "vrlimi128 v127, v127, 4, 3 (real)"),
|
||||
];
|
||||
|
||||
// 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 = [
|
||||
// vmaddfp128: bits 24=1, 25=1, 27=1, bit 29=1 (VA high), VB=5
|
||||
(0x146028D4u32, 0x82000000, "vmaddfp128 v3, v35, v5, v3"),
|
||||
// vmaddcfp128: bits 23=1, 27=1, bit 29=1, VB=5
|
||||
(0x14602914u32, 0x82000000, "vmaddcfp128 v3, v35, v3, v5"),
|
||||
// vnmsubfp128: bits 23=1, 25=1, 27=1, bit 29=1, VB=5
|
||||
(0x14602954u32, 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);
|
||||
}
|
||||
Reference in New Issue
Block a user