use crate::opcode::PpcOpcode;

/// Extract bits [a..=b] from a 32-bit value (PPC bit numbering: 0 = MSB).
#[inline(always)]
const fn extract_bits(v: u32, a: u32, b: u32) -> u32 {
    (v >> (32 - 1 - b)) & ((1 << (b - a + 1)) - 1)
}

/// Decoded PPC instruction with extracted operand fields.
#[derive(Debug, Clone, Copy)]
pub struct DecodedInstr {
    pub opcode: PpcOpcode,
    pub raw: u32,
    pub addr: u32,
}

impl DecodedInstr {
    // Common field extractors (PPC bit numbering)

    /// Primary opcode (bits 0-5)
    #[inline] pub fn op(&self) -> u32 { extract_bits(self.raw, 0, 5) }

    /// rD/rS/rT (bits 6-10) - destination/source register
    #[inline] pub fn rd(&self) -> usize { extract_bits(self.raw, 6, 10) as usize }
    #[inline] pub fn rs(&self) -> usize { self.rd() }
    #[inline] pub fn rt(&self) -> usize { self.rd() }

    /// rA (bits 11-15)
    #[inline] pub fn ra(&self) -> usize { extract_bits(self.raw, 11, 15) as usize }

    /// rB (bits 16-20)
    #[inline] pub fn rb(&self) -> usize { extract_bits(self.raw, 16, 20) as usize }

    /// rC (bits 21-25) - for 4-operand instructions
    #[inline] pub fn rc(&self) -> usize { extract_bits(self.raw, 21, 25) as usize }

    /// SIMM/UIMM (bits 16-31) - signed/unsigned immediate
    #[inline] pub fn simm16(&self) -> i16 { (self.raw & 0xFFFF) as i16 }
    #[inline] pub fn uimm16(&self) -> u16 { (self.raw & 0xFFFF) as u16 }

    /// D-form displacement (signed, bits 16-31)
    #[inline] pub fn d(&self) -> i32 { self.simm16() as i32 }

    /// DS-form displacement (signed, bits 16-29, shifted left 2)
    #[inline] pub fn ds(&self) -> i32 { (self.raw & 0xFFFC) as i16 as i32 }

    /// LI field for branch (bits 6-29, sign-extended, shifted left 2)
    #[inline] pub fn li(&self) -> i32 {
        let li = extract_bits(self.raw, 6, 29);
        // Sign-extend from 24 bits, then shift left 2
        let sign_extended = ((li as i32) << 8) >> 8;
        sign_extended << 2
    }

    /// BD field for conditional branch (bits 16-29, sign-extended, shifted left 2)
    #[inline] pub fn bd(&self) -> i32 {
        let bd = extract_bits(self.raw, 16, 29);
        let sign_extended = ((bd as i32) << 18) >> 18;
        sign_extended << 2
    }

    /// BO field (bits 6-10) - branch options
    #[inline] pub fn bo(&self) -> u32 { extract_bits(self.raw, 6, 10) }

    /// BI field (bits 11-15) - branch condition
    #[inline] pub fn bi(&self) -> u32 { extract_bits(self.raw, 11, 15) }

    /// AA bit (bit 30) - absolute address
    #[inline] pub fn aa(&self) -> bool { (self.raw >> 1) & 1 != 0 }

    /// LK bit (bit 31) - link (update LR)
    #[inline] pub fn lk(&self) -> bool { self.raw & 1 != 0 }

    /// Rc bit (bit 31) - record CR0
    #[inline] pub fn rc_bit(&self) -> bool { self.raw & 1 != 0 }

    /// Rc for VC-form vector compare instructions — PPC bit 21 = host bit 10.
    #[inline] pub fn vc_rc_bit(&self) -> bool { (self.raw >> 10) & 1 != 0 }
    /// Rc for VX128_R-form vector compare instructions — PPC bit 27 = host bit 4.
    /// VX128_R Rc bit — PPC bit 25 (host bit 6) per canary's FormatVX128_R
    /// bitfield layout. PPCBUG-700.
    #[inline] pub fn vx128r_rc_bit(&self) -> bool { (self.raw >> 6) & 1 != 0 }

    /// IMM field for VX128_4-form instructions (vrlimi128) — 5-bit blend mask at PPC bits 11-15.
    #[inline] pub fn vx128_4_imm(&self) -> u32 { extract_bits(self.raw, 11, 15) }
    /// z field for VX128_4-form instructions (vrlimi128) — 2-bit rotation index at PPC bits 24-25.
    #[inline] pub fn vx128_4_z(&self) -> u32 { extract_bits(self.raw, 24, 25) }

    /// OE bit (bit 21) - overflow enable
    #[inline] pub fn oe(&self) -> bool { extract_bits(self.raw, 21, 21) != 0 }

    /// TO field (bits 6-10) for tw/twi/td/tdi trap instructions.
    #[inline] pub fn to(&self) -> u32 { extract_bits(self.raw, 6, 10) }

    /// MB, ME fields for rotate instructions
    #[inline] pub fn mb(&self) -> u32 { extract_bits(self.raw, 21, 25) }
    #[inline] pub fn me(&self) -> u32 { extract_bits(self.raw, 26, 30) }

    /// SH field (bits 16-20) for shift instructions
    #[inline] pub fn sh(&self) -> u32 { extract_bits(self.raw, 16, 20) }

    /// SH field for 64-bit shifts (bits 16-20 + bit 30)
    #[inline] pub fn sh64(&self) -> u32 {
        (extract_bits(self.raw, 30, 30) << 5) | extract_bits(self.raw, 16, 20)
    }

    /// MB/ME field for MD-form and MDS-form instructions (6-bit field, split encoding).
    /// MB[4:0] at PPC bits 21-25; MB[5] at PPC bit 26.
    #[inline] pub fn mb_md(&self) -> u32 {
        extract_bits(self.raw, 21, 25) | (extract_bits(self.raw, 26, 26) << 5)
    }

    /// SPR field (bits 11-20, swapped halves)
    #[inline] pub fn spr(&self) -> u32 {
        let spr_raw = extract_bits(self.raw, 11, 20);
        ((spr_raw & 0x1F) << 5) | ((spr_raw >> 5) & 0x1F)
    }

    /// CRM field (bits 12-19) for mtcrf
    #[inline] pub fn crm(&self) -> u32 { extract_bits(self.raw, 12, 19) }

    /// crfD (bits 6-8) - condition register field destination
    #[inline] pub fn crfd(&self) -> usize { extract_bits(self.raw, 6, 8) as usize }

    /// crfS (bits 11-13)
    #[inline] pub fn crfs(&self) -> usize { extract_bits(self.raw, 11, 13) as usize }

    /// L bit (bit 10) - 64-bit compare
    #[inline] pub fn l(&self) -> bool { extract_bits(self.raw, 10, 10) != 0 }

    /// crbD (bits 6-10)
    #[inline] pub fn crbd(&self) -> u32 { extract_bits(self.raw, 6, 10) }
    /// crbA (bits 11-15)
    #[inline] pub fn crba(&self) -> u32 { extract_bits(self.raw, 11, 15) }
    /// crbB (bits 16-20)
    #[inline] pub fn crbb(&self) -> u32 { extract_bits(self.raw, 16, 20) }

    // VMX128 field extractors — bit positions match canary's
    // FormatVX128/VX128_2/VX128_4/VX128_5/VX128_R bitfield layout
    // (xenia-canary `ppc_decode_data.h:484-663`, LSB-first packed). PPCBUG-700.

    /// VA128 = VA128l(5) | VA128h(1) << 5 | VA128H(1) << 6.
    /// Canonical 7-bit register selector: PPC 11-15 (low), PPC 26 (mid), PPC 21 (high).
    #[inline] pub fn va128(&self) -> usize {
        (extract_bits(self.raw, 11, 15)
            | (extract_bits(self.raw, 26, 26) << 5)
            | (extract_bits(self.raw, 21, 21) << 6)) as usize
    }

    /// VB128 = VB128l(5) | VB128h(2) << 5. Canary's VB128h is a 2-bit
    /// contiguous field at PPC 30-31 (host bits 0-1).
    #[inline] pub fn vb128(&self) -> usize {
        (extract_bits(self.raw, 16, 20)
            | (extract_bits(self.raw, 30, 31) << 5)) as usize
    }

    /// VD128 = VD128l(5) | VD128h(2) << 5. Canary's VD128h is a 2-bit
    /// contiguous field at PPC 28-29 (host bits 2-3).
    #[inline] pub fn vd128(&self) -> usize {
        (extract_bits(self.raw, 6, 10)
            | (extract_bits(self.raw, 28, 29) << 5)) as usize
    }

    /// VS128 - same encoding as VD128
    #[inline] pub fn vs128(&self) -> usize { self.vd128() }

    /// VC register for VX128_2-form instructions (vperm128) — 3-bit at PPC bits 23-25.
    #[inline] pub fn vc128_2(&self) -> usize { extract_bits(self.raw, 23, 25) as usize }

    /// NB field (bits 16-20) for lswi/stswi
    #[inline] pub fn nb(&self) -> u32 { extract_bits(self.raw, 16, 20) }

    /// PERM field for VX128_P-form instructions (vpermwi128) — 8-bit split encoding.
    /// PERMl (5 bits) at PPC bits 11-15; PERMh (3 bits) at PPC bits 23-25.
    #[inline] pub fn vx128_p_perm(&self) -> u32 {
        extract_bits(self.raw, 11, 15) | (extract_bits(self.raw, 23, 25) << 5)
    }

    /// SH field for VX128_5-form instructions (vsldoi128) — 4-bit shift at PPC bits 22-25.
    #[inline] pub fn vx128_5_sh(&self) -> u32 { extract_bits(self.raw, 22, 25) }
}

/// Extract the 5-bit `UIMM` (`VX128_3`) / `IMM` (`VX128_4`) field. Canary
/// packs both formats with LSB-bits 16-20 holding the field, which is
/// MSB bits 11-15 in our `extract_bits` convention. For `vpkd3d128` /
/// `vupkd3d128` the decoded selector is `type = UIMM >> 2` (3 bits; valid
/// values 0-6 per [`crate::vmx::D3dPackType`], 7 is undocumented /
/// undefined in canary) and `pack = UIMM & 0x3` (output-slot layout for
/// `vpkd3d128` only, `vupkd3d128` ignores it).
///
/// First-Pixels M3: the interpreter previously used a hand-rolled
/// `(instr.raw >> 6) & 0x7` that was **LSB-numbered** and extracted
/// bits from a completely different part of the word (the
/// secondary-opcode region). Centralizing the extractor here matches
/// canary's `FormatVX128_{3,4}::{UIMM,IMM}` field semantics exactly.
#[inline]
pub fn extract_vx128_uimm5(raw: u32) -> u32 {
    extract_bits(raw, 11, 15)
}

/// Decode a 32-bit PPC instruction into its opcode.
/// Direct translation of the C++ LookupOpcode from ppc_opcode_lookup_gen.cc.
pub fn decode(raw: u32, addr: u32) -> DecodedInstr {
    let opcode = lookup_opcode(raw);
    DecodedInstr { opcode, raw, addr }
}

// Perf tier-2 — direct-mapped PC-keyed decode cache.
//
// The interpreter hot path spends ~15-25% of its time in `decode()`
// parsing the raw u32 and walking the primary+secondary opcode tables.
// For non-self-modifying guest code — the common case past the XEX
// loader — `decode(raw, pc)` is purely a function of `(raw, pc)` and
// the output is `Copy + 16B`. A direct-mapped cache indexed by
// `(pc >> 2) & MASK` gives the interpreter a 1-comparison fast path,
// at the cost of one branch and a 1.5 MiB region of memory.
//
// Invalidation piggybacks on `xenia_memory::GuestMemory::page_version`
// (P5 texture-cache invalidation): every cache entry carries the page
// version that was active at decode time; on lookup we compare against
// the current version of the containing 4 KiB page. Any write to the
// page bumps the counter, so the next decode on that PC is a miss that
// refills.

/// Number of direct-mapped entries. 2^16 = 65,536 slots, one PPC
/// instruction address per slot — enough for every hot code path in a
/// typical Xbox 360 title to stay resident without collision.
const DECODE_CACHE_SIZE: usize = 1 << 16;
const DECODE_CACHE_MASK: u32 = (DECODE_CACHE_SIZE - 1) as u32;

#[derive(Clone, Copy)]
struct DecodeCacheEntry {
    /// Guest PC this entry was decoded at. Used as the tag on lookup; a
    /// mismatch means the slot was last populated by a different PC that
    /// shares the same low-16 index.
    pc: u32,
    /// Page version at decode time (from `GuestMemory::page_version(pc)`).
    /// Zero means "unused slot" since real page versions start at 1.
    page_version: u64,
    decoded: DecodedInstr,
}

impl DecodeCacheEntry {
    const fn empty() -> Self {
        // `Invalid` is the decoder's "unrecognized opcode" sentinel; we
        // use it here as the empty-slot marker. Real misses compare `pc`,
        // not the opcode, so the sentinel choice is cosmetic.
        Self {
            pc: 0,
            page_version: 0,
            decoded: DecodedInstr {
                opcode: PpcOpcode::Invalid,
                raw: 0,
                addr: 0,
            },
        }
    }
}

/// Direct-mapped PC-keyed decode cache. One instance shared across all
/// HW threads (PC is thread-independent; entries are read-only once
/// filled). Not thread-safe — the single scheduler thread owns it.
pub struct DecodeCache {
    slots: Box<[DecodeCacheEntry]>,
    hits: u64,
    misses: u64,
    invalidations: u64,
}

impl Default for DecodeCache {
    fn default() -> Self {
        Self::new()
    }
}

impl DecodeCache {
    pub fn new() -> Self {
        Self {
            slots: vec![DecodeCacheEntry::empty(); DECODE_CACHE_SIZE].into_boxed_slice(),
            hits: 0,
            misses: 0,
            invalidations: 0,
        }
    }

    /// Look up (or fill) the decoded form of the instruction at `pc`.
    /// `raw` is the fetched instruction word; `current_page_version` is
    /// `mem.page_version(pc)` — the caller has it cheaper than we do,
    /// since they're already touching `mem` to fetch `raw`.
    #[inline]
    pub fn lookup(&mut self, pc: u32, raw: u32, current_page_version: u64) -> DecodedInstr {
        let idx = ((pc >> 2) & DECODE_CACHE_MASK) as usize;
        // Safety: `idx` is masked into `[0, DECODE_CACHE_SIZE)` so the
        // slice access is always in-bounds. Opt-out of the bounds check
        // for the hot path.
        let entry = unsafe { self.slots.get_unchecked_mut(idx) };
        if entry.pc == pc && entry.page_version == current_page_version {
            self.hits += 1;
            return entry.decoded;
        }
        if entry.pc == pc && entry.page_version != current_page_version {
            self.invalidations += 1;
        }
        self.misses += 1;
        let decoded = decode(raw, pc);
        *entry = DecodeCacheEntry {
            pc,
            page_version: current_page_version,
            decoded,
        };
        decoded
    }

    pub fn hits(&self) -> u64 {
        self.hits
    }
    pub fn misses(&self) -> u64 {
        self.misses
    }
    pub fn invalidations(&self) -> u64 {
        self.invalidations
    }
}

fn lookup_opcode(code: u32) -> PpcOpcode {
    match extract_bits(code, 0, 5) {
        2 => PpcOpcode::tdi,
        3 => PpcOpcode::twi,
        4 => decode_op4(code),
        5 => decode_op5(code),
        6 => decode_op6(code),
        7 => PpcOpcode::mulli,
        8 => PpcOpcode::subficx,
        10 => PpcOpcode::cmpli,
        11 => PpcOpcode::cmpi,
        12 => PpcOpcode::addic,
        13 => PpcOpcode::addicx,
        14 => PpcOpcode::addi,
        15 => PpcOpcode::addis,
        16 => PpcOpcode::bcx,
        17 => PpcOpcode::sc,
        18 => PpcOpcode::bx,
        19 => decode_op19(code),
        20 => PpcOpcode::rlwimix,
        21 => PpcOpcode::rlwinmx,
        23 => PpcOpcode::rlwnmx,
        24 => PpcOpcode::ori,
        25 => PpcOpcode::oris,
        26 => PpcOpcode::xori,
        27 => PpcOpcode::xoris,
        28 => PpcOpcode::andix,
        29 => PpcOpcode::andisx,
        30 => decode_op30(code),
        31 => decode_op31(code),
        32 => PpcOpcode::lwz,
        33 => PpcOpcode::lwzu,
        34 => PpcOpcode::lbz,
        35 => PpcOpcode::lbzu,
        36 => PpcOpcode::stw,
        37 => PpcOpcode::stwu,
        38 => PpcOpcode::stb,
        39 => PpcOpcode::stbu,
        40 => PpcOpcode::lhz,
        41 => PpcOpcode::lhzu,
        42 => PpcOpcode::lha,
        43 => PpcOpcode::lhau,
        44 => PpcOpcode::sth,
        45 => PpcOpcode::sthu,
        46 => PpcOpcode::lmw,
        47 => PpcOpcode::stmw,
        48 => PpcOpcode::lfs,
        49 => PpcOpcode::lfsu,
        50 => PpcOpcode::lfd,
        51 => PpcOpcode::lfdu,
        52 => PpcOpcode::stfs,
        53 => PpcOpcode::stfsu,
        54 => PpcOpcode::stfd,
        55 => PpcOpcode::stfdu,
        58 => match extract_bits(code, 30, 31) {
            0b00 => PpcOpcode::ld,
            0b01 => PpcOpcode::ldu,
            0b10 => PpcOpcode::lwa,
            _ => PpcOpcode::Invalid,
        },
        59 => match extract_bits(code, 26, 30) {
            0b10010 => PpcOpcode::fdivsx,
            0b10100 => PpcOpcode::fsubsx,
            0b10101 => PpcOpcode::faddsx,
            0b10110 => PpcOpcode::fsqrtsx,
            0b11000 => PpcOpcode::fresx,
            0b11001 => PpcOpcode::fmulsx,
            0b11100 => PpcOpcode::fmsubsx,
            0b11101 => PpcOpcode::fmaddsx,
            0b11110 => PpcOpcode::fnmsubsx,
            0b11111 => PpcOpcode::fnmaddsx,
            _ => PpcOpcode::Invalid,
        },
        62 => match extract_bits(code, 30, 31) {
            0b00 => PpcOpcode::std,
            0b01 => PpcOpcode::stdu,
            _ => PpcOpcode::Invalid,
        },
        63 => decode_op63(code),
        _ => PpcOpcode::Invalid,
    }
}

fn decode_op4(code: u32) -> PpcOpcode {
    // VMX128 load/store (op=4, bits 21-27 << 4 | bits 30-31)
    let key1 = (extract_bits(code, 21, 27) << 4) | extract_bits(code, 30, 31);
    match key1 {
        0b00000000011 => return PpcOpcode::lvsl128,
        0b00001000011 => return PpcOpcode::lvsr128,
        0b00010000011 => return PpcOpcode::lvewx128,
        0b00011000011 => return PpcOpcode::lvx128,
        0b00110000011 => return PpcOpcode::stvewx128,
        0b00111000011 => return PpcOpcode::stvx128,
        0b01011000011 => return PpcOpcode::lvxl128,
        0b01111000011 => return PpcOpcode::stvxl128,
        0b10000000011 => return PpcOpcode::lvlx128,
        0b10001000011 => return PpcOpcode::lvrx128,
        0b10100000011 => return PpcOpcode::stvlx128,
        0b10101000011 => return PpcOpcode::stvrx128,
        0b11000000011 => return PpcOpcode::lvlxl128,
        0b11001000011 => return PpcOpcode::lvrxl128,
        0b11100000011 => return PpcOpcode::stvlxl128,
        0b11101000011 => return PpcOpcode::stvrxl128,
        _ => {}
    }

    // Standard VMX (op=4, bits 21-31)
    let key2 = extract_bits(code, 21, 31);
    match key2 {
        0b00000000000 => return PpcOpcode::vaddubm,
        0b00000000010 => return PpcOpcode::vmaxub,
        0b00000000100 => return PpcOpcode::vrlb,
        0b00000001000 => return PpcOpcode::vmuloub,
        0b00000001010 => return PpcOpcode::vaddfp,
        0b00000001100 => return PpcOpcode::vmrghb,
        0b00000001110 => return PpcOpcode::vpkuhum,
        0b00001000000 => return PpcOpcode::vadduhm,
        0b00001000010 => return PpcOpcode::vmaxuh,
        0b00001000100 => return PpcOpcode::vrlh,
        0b00001001000 => return PpcOpcode::vmulouh,
        0b00001001010 => return PpcOpcode::vsubfp,
        0b00001001100 => return PpcOpcode::vmrghh,
        0b00001001110 => return PpcOpcode::vpkuwum,
        0b00010000000 => return PpcOpcode::vadduwm,
        0b00010000010 => return PpcOpcode::vmaxuw,
        0b00010000100 => return PpcOpcode::vrlw,
        0b00010001100 => return PpcOpcode::vmrghw,
        0b00010001110 => return PpcOpcode::vpkuhus,
        0b00011001110 => return PpcOpcode::vpkuwus,
        0b00100000010 => return PpcOpcode::vmaxsb,
        0b00100000100 => return PpcOpcode::vslb,
        0b00100001000 => return PpcOpcode::vmulosb,
        0b00100001010 => return PpcOpcode::vrefp,
        0b00100001100 => return PpcOpcode::vmrglb,
        0b00100001110 => return PpcOpcode::vpkshus,
        0b00101000010 => return PpcOpcode::vmaxsh,
        0b00101000100 => return PpcOpcode::vslh,
        0b00101001000 => return PpcOpcode::vmulosh,
        0b00101001010 => return PpcOpcode::vrsqrtefp,
        0b00101001100 => return PpcOpcode::vmrglh,
        0b00101001110 => return PpcOpcode::vpkswus,
        0b00110000000 => return PpcOpcode::vaddcuw,
        0b00110000010 => return PpcOpcode::vmaxsw,
        0b00110000100 => return PpcOpcode::vslw,
        0b00110001010 => return PpcOpcode::vexptefp,
        0b00110001100 => return PpcOpcode::vmrglw,
        0b00110001110 => return PpcOpcode::vpkshss,
        0b00111000100 => return PpcOpcode::vsl,
        0b00111001010 => return PpcOpcode::vlogefp,
        0b00111001110 => return PpcOpcode::vpkswss,
        0b01000000000 => return PpcOpcode::vaddubs,
        0b01000000010 => return PpcOpcode::vminub,
        0b01000000100 => return PpcOpcode::vsrb,
        0b01000001000 => return PpcOpcode::vmuleub,
        0b01000001010 => return PpcOpcode::vrfin,
        0b01000001100 => return PpcOpcode::vspltb,
        0b01000001110 => return PpcOpcode::vupkhsb,
        0b01001000000 => return PpcOpcode::vadduhs,
        0b01001000010 => return PpcOpcode::vminuh,
        0b01001000100 => return PpcOpcode::vsrh,
        0b01001001000 => return PpcOpcode::vmuleuh,
        0b01001001010 => return PpcOpcode::vrfiz,
        0b01001001100 => return PpcOpcode::vsplth,
        0b01001001110 => return PpcOpcode::vupkhsh,
        0b01010000000 => return PpcOpcode::vadduws,
        0b01010000010 => return PpcOpcode::vminuw,
        0b01010000100 => return PpcOpcode::vsrw,
        0b01010001010 => return PpcOpcode::vrfip,
        0b01010001100 => return PpcOpcode::vspltw,
        0b01010001110 => return PpcOpcode::vupklsb,
        0b01011000100 => return PpcOpcode::vsr,
        0b01011001010 => return PpcOpcode::vrfim,
        0b01011001110 => return PpcOpcode::vupklsh,
        0b01100000000 => return PpcOpcode::vaddsbs,
        0b01100000010 => return PpcOpcode::vminsb,
        0b01100000100 => return PpcOpcode::vsrab,
        0b01100001000 => return PpcOpcode::vmulesb,
        0b01100001010 => return PpcOpcode::vcfux,
        0b01100001100 => return PpcOpcode::vspltisb,
        0b01100001110 => return PpcOpcode::vpkpx,
        0b01101000000 => return PpcOpcode::vaddshs,
        0b01101000010 => return PpcOpcode::vminsh,
        0b01101000100 => return PpcOpcode::vsrah,
        0b01101001000 => return PpcOpcode::vmulesh,
        0b01101001010 => return PpcOpcode::vcfsx,
        0b01101001100 => return PpcOpcode::vspltish,
        0b01101001110 => return PpcOpcode::vupkhpx,
        0b01110000000 => return PpcOpcode::vaddsws,
        0b01110000010 => return PpcOpcode::vminsw,
        0b01110000100 => return PpcOpcode::vsraw,
        0b01110001010 => return PpcOpcode::vctuxs,
        0b01110001100 => return PpcOpcode::vspltisw,
        0b01111001010 => return PpcOpcode::vctsxs,
        0b01111001110 => return PpcOpcode::vupklpx,
        0b10000000000 => return PpcOpcode::vsububm,
        0b10000000010 => return PpcOpcode::vavgub,
        0b10000000100 => return PpcOpcode::vand,
        0b10000001010 => return PpcOpcode::vmaxfp,
        0b10000001100 => return PpcOpcode::vslo,
        0b10001000000 => return PpcOpcode::vsubuhm,
        0b10001000010 => return PpcOpcode::vavguh,
        0b10001000100 => return PpcOpcode::vandc,
        0b10001001010 => return PpcOpcode::vminfp,
        0b10001001100 => return PpcOpcode::vsro,
        0b10010000000 => return PpcOpcode::vsubuwm,
        0b10010000010 => return PpcOpcode::vavguw,
        0b10010000100 => return PpcOpcode::vor,
        0b10011000100 => return PpcOpcode::vxor,
        0b10100000010 => return PpcOpcode::vavgsb,
        0b10100000100 => return PpcOpcode::vnor,
        0b10101000010 => return PpcOpcode::vavgsh,
        0b10110000000 => return PpcOpcode::vsubcuw,
        0b10110000010 => return PpcOpcode::vavgsw,
        0b11000000000 => return PpcOpcode::vsububs,
        0b11000000100 => return PpcOpcode::mfvscr,
        0b11000001000 => return PpcOpcode::vsum4ubs,
        0b11001000000 => return PpcOpcode::vsubuhs,
        0b11001000100 => return PpcOpcode::mtvscr,
        0b11001001000 => return PpcOpcode::vsum4shs,
        0b11010000000 => return PpcOpcode::vsubuws,
        0b11010001000 => return PpcOpcode::vsum2sws,
        0b11100000000 => return PpcOpcode::vsubsbs,
        0b11100001000 => return PpcOpcode::vsum4sbs,
        0b11101000000 => return PpcOpcode::vsubshs,
        0b11110000000 => return PpcOpcode::vsubsws,
        0b11110001000 => return PpcOpcode::vsumsws,
        _ => {}
    }

    // VMX compare (op=4, bits 22-31)
    let key3 = extract_bits(code, 22, 31);
    match key3 {
        0b0000000110 => return PpcOpcode::vcmpequb,
        0b0001000110 => return PpcOpcode::vcmpequh,
        0b0010000110 => return PpcOpcode::vcmpequw,
        0b0011000110 => return PpcOpcode::vcmpeqfp,
        0b0111000110 => return PpcOpcode::vcmpgefp,
        0b1000000110 => return PpcOpcode::vcmpgtub,
        0b1001000110 => return PpcOpcode::vcmpgtuh,
        0b1010000110 => return PpcOpcode::vcmpgtuw,
        0b1011000110 => return PpcOpcode::vcmpgtfp,
        0b1100000110 => return PpcOpcode::vcmpgtsb,
        0b1101000110 => return PpcOpcode::vcmpgtsh,
        0b1110000110 => return PpcOpcode::vcmpgtsw,
        0b1111000110 => return PpcOpcode::vcmpbfp,
        _ => {}
    }

    // VMX 4-operand (op=4, bits 26-31)
    let key4 = extract_bits(code, 26, 31);
    match key4 {
        0b100000 => return PpcOpcode::vmhaddshs,
        0b100001 => return PpcOpcode::vmhraddshs,
        0b100010 => return PpcOpcode::vmladduhm,
        0b100100 => return PpcOpcode::vmsumubm,
        0b100101 => return PpcOpcode::vmsummbm,
        0b100110 => return PpcOpcode::vmsumuhm,
        0b100111 => return PpcOpcode::vmsumuhs,
        0b101000 => return PpcOpcode::vmsumshm,
        0b101001 => return PpcOpcode::vmsumshs,
        0b101010 => return PpcOpcode::vsel,
        0b101011 => return PpcOpcode::vperm,
        0b101100 => return PpcOpcode::vsldoi,
        0b101110 => return PpcOpcode::vmaddfp,
        0b101111 => return PpcOpcode::vnmsubfp,
        _ => {}
    }

    // vsldoi128 (op=4, bit 27)
    if extract_bits(code, 27, 27) == 1 {
        return PpcOpcode::vsldoi128;
    }

    PpcOpcode::Invalid
}

fn decode_op5(code: u32) -> PpcOpcode {
    // vperm128 (op=5, bits 22,27)
    let key1 = (extract_bits(code, 22, 22) << 5) | extract_bits(code, 27, 27);
    if key1 == 0b000000 {
        return PpcOpcode::vperm128;
    }

    let key2 = (extract_bits(code, 22, 25) << 2) | extract_bits(code, 27, 27);
    match key2 {
        0b000001 => PpcOpcode::vaddfp128,
        0b000101 => PpcOpcode::vsubfp128,
        0b001001 => PpcOpcode::vmulfp128,
        0b001101 => PpcOpcode::vmaddfp128,
        0b010001 => PpcOpcode::vmaddcfp128,
        0b010101 => PpcOpcode::vnmsubfp128,
        0b011001 => PpcOpcode::vmsum3fp128,
        0b011101 => PpcOpcode::vmsum4fp128,
        0b100000 => PpcOpcode::vpkshss128,
        0b100001 => PpcOpcode::vand128,
        0b100100 => PpcOpcode::vpkshus128,
        0b100101 => PpcOpcode::vandc128,
        0b101000 => PpcOpcode::vpkswss128,
        0b101001 => PpcOpcode::vnor128,
        0b101100 => PpcOpcode::vpkswus128,
        0b101101 => PpcOpcode::vor128,
        0b110000 => PpcOpcode::vpkuhum128,
        0b110001 => PpcOpcode::vxor128,
        0b110100 => PpcOpcode::vpkuhus128,
        0b110101 => PpcOpcode::vsel128,
        0b111000 => PpcOpcode::vpkuwum128,
        0b111001 => PpcOpcode::vslo128,
        0b111100 => PpcOpcode::vpkuwus128,
        0b111101 => PpcOpcode::vsro128,
        _ => PpcOpcode::Invalid,
    }
}

fn decode_op6(code: u32) -> PpcOpcode {
    // vpermwi128
    let key1 = (extract_bits(code, 21, 22) << 5) | extract_bits(code, 26, 27);
    if key1 == 0b0100001 {
        return PpcOpcode::vpermwi128;
    }

    // vpkd3d128, vrlimi128
    let key2 = (extract_bits(code, 21, 23) << 4) | extract_bits(code, 26, 27);
    match key2 {
        0b1100001 => return PpcOpcode::vpkd3d128,
        0b1110001 => return PpcOpcode::vrlimi128,
        _ => {}
    }

    // Unary VMX128 ops
    let key3 = extract_bits(code, 21, 27);
    match key3 {
        0b0100011 => return PpcOpcode::vcfpsxws128,
        0b0100111 => return PpcOpcode::vcfpuxws128,
        0b0101011 => return PpcOpcode::vcsxwfp128,
        0b0101111 => return PpcOpcode::vcuxwfp128,
        0b0110011 => return PpcOpcode::vrfim128,
        0b0110111 => return PpcOpcode::vrfin128,
        0b0111011 => return PpcOpcode::vrfip128,
        0b0111111 => return PpcOpcode::vrfiz128,
        0b1100011 => return PpcOpcode::vrefp128,
        0b1100111 => return PpcOpcode::vrsqrtefp128,
        0b1101011 => return PpcOpcode::vexptefp128,
        0b1101111 => return PpcOpcode::vlogefp128,
        0b1110011 => return PpcOpcode::vspltw128,
        0b1110111 => return PpcOpcode::vspltisw128,
        0b1111111 => return PpcOpcode::vupkd3d128,
        _ => {}
    }

    // VMX128 compare (VX128_R form). Single dispatch path: bit 27 = 0 always
    // for these opcodes per canary's table (`ppc_opcode_table_gen.cc:295-305`).
    // The Rc bit is at PPC 25 (host bit 6) per the FormatVX128_R bitfield —
    // it's a runtime modifier read by the interpreter, NOT part of the
    // secondary-opcode discrimination. PPCBUG-700.
    let key4_nd = (extract_bits(code, 22, 24) << 3) | extract_bits(code, 27, 27);
    match key4_nd {
        0b000000 => return PpcOpcode::vcmpeqfp128,
        0b001000 => return PpcOpcode::vcmpgefp128,
        0b010000 => return PpcOpcode::vcmpgtfp128,
        0b011000 => return PpcOpcode::vcmpbfp128,
        0b100000 => return PpcOpcode::vcmpequw128,
        _ => {}
    }

    // VMX128 shift/merge
    let key5 = (extract_bits(code, 22, 25) << 2) | extract_bits(code, 27, 27);
    match key5 {
        0b000101 => return PpcOpcode::vrlw128,
        0b001101 => return PpcOpcode::vslw128,
        0b010101 => return PpcOpcode::vsraw128,
        0b011101 => return PpcOpcode::vsrw128,
        0b101000 => return PpcOpcode::vmaxfp128,
        0b101100 => return PpcOpcode::vminfp128,
        0b110000 => return PpcOpcode::vmrghw128,
        0b110100 => return PpcOpcode::vmrglw128,
        0b111000 => return PpcOpcode::vupkhsb128,
        0b111100 => return PpcOpcode::vupklsb128,
        _ => {}
    }

    PpcOpcode::Invalid
}

fn decode_op19(code: u32) -> PpcOpcode {
    match extract_bits(code, 21, 30) {
        0b0000000000 => PpcOpcode::mcrf,
        0b0000010000 => PpcOpcode::bclrx,
        0b0000100001 => PpcOpcode::crnor,
        0b0010000001 => PpcOpcode::crandc,
        0b0010010110 => PpcOpcode::isync,
        0b0011000001 => PpcOpcode::crxor,
        0b0011100001 => PpcOpcode::crnand,
        0b0100000001 => PpcOpcode::crand,
        0b0100100001 => PpcOpcode::creqv,
        0b0110100001 => PpcOpcode::crorc,
        0b0111000001 => PpcOpcode::cror,
        0b1000010000 => PpcOpcode::bcctrx,
        _ => PpcOpcode::Invalid,
    }
}

fn decode_op30(code: u32) -> PpcOpcode {
    match extract_bits(code, 27, 29) {
        0b000 => PpcOpcode::rldiclx,
        0b001 => PpcOpcode::rldicrx,
        0b010 => PpcOpcode::rldicx,
        0b011 => PpcOpcode::rldimix,
        _ => match extract_bits(code, 27, 30) {
            0b1000 => PpcOpcode::rldclx,
            0b1001 => PpcOpcode::rldcrx,
            _ => PpcOpcode::Invalid,
        },
    }
}

fn decode_op31(code: u32) -> PpcOpcode {
    // sradix has a unique 10-bit key (bits 21-29)
    if extract_bits(code, 21, 29) == 0b110011101 {
        return PpcOpcode::sradix;
    }

    // Main op31 table (bits 21-30)
    let key = extract_bits(code, 21, 30);
    match key {
        0b0000000000 => return PpcOpcode::cmp,
        0b0000000100 => return PpcOpcode::tw,
        0b0000000110 => return PpcOpcode::lvsl,
        0b0000000111 => return PpcOpcode::lvebx,
        0b0000010011 => return PpcOpcode::mfcr,
        0b0000010100 => return PpcOpcode::lwarx,
        0b0000010101 => return PpcOpcode::ldx,
        0b0000010111 => return PpcOpcode::lwzx,
        0b0000011000 => return PpcOpcode::slwx,
        0b0000011010 => return PpcOpcode::cntlzwx,
        0b0000011011 => return PpcOpcode::sldx,
        0b0000011100 => return PpcOpcode::andx,
        0b0000100000 => return PpcOpcode::cmpl,
        0b0000100110 => return PpcOpcode::lvsr,
        0b0000100111 => return PpcOpcode::lvehx,
        0b0000110101 => return PpcOpcode::ldux,
        0b0000110110 => return PpcOpcode::dcbst,
        0b0000110111 => return PpcOpcode::lwzux,
        0b0000111010 => return PpcOpcode::cntlzdx,
        0b0000111100 => return PpcOpcode::andcx,
        0b0001000100 => return PpcOpcode::td,
        0b0001000111 => return PpcOpcode::lvewx,
        0b0001010011 => return PpcOpcode::mfmsr,
        0b0001010100 => return PpcOpcode::ldarx,
        0b0001010110 => return PpcOpcode::dcbf,
        0b0001010111 => return PpcOpcode::lbzx,
        0b0001100111 => return PpcOpcode::lvx,
        0b0001110111 => return PpcOpcode::lbzux,
        0b0001111100 => return PpcOpcode::norx,
        0b0010000111 => return PpcOpcode::stvebx,
        0b0010010000 => return PpcOpcode::mtcrf,
        0b0010010010 => return PpcOpcode::mtmsr,
        0b0010010101 => return PpcOpcode::stdx,
        0b0010010110 => return PpcOpcode::stwcx,
        0b0010010111 => return PpcOpcode::stwx,
        0b0010100111 => return PpcOpcode::stvehx,
        0b0010110010 => return PpcOpcode::mtmsrd,
        0b0010110101 => return PpcOpcode::stdux,
        0b0010110111 => return PpcOpcode::stwux,
        0b0011000111 => return PpcOpcode::stvewx,
        0b0011010110 => return PpcOpcode::stdcx,
        0b0011010111 => return PpcOpcode::stbx,
        0b0011100111 => return PpcOpcode::stvx,
        0b0011110110 => return PpcOpcode::dcbtst,
        0b0011110111 => return PpcOpcode::stbux,
        0b0100010110 => return PpcOpcode::dcbt,
        0b0100010111 => return PpcOpcode::lhzx,
        0b0100011100 => return PpcOpcode::eqvx,
        0b0100110111 => return PpcOpcode::lhzux,
        0b0100111100 => return PpcOpcode::xorx,
        0b0101010011 => return PpcOpcode::mfspr,
        0b0101010101 => return PpcOpcode::lwax,
        0b0101010111 => return PpcOpcode::lhax,
        0b0101100111 => return PpcOpcode::lvxl,
        0b0101110011 => return PpcOpcode::mftb,
        0b0101110101 => return PpcOpcode::lwaux,
        0b0101110111 => return PpcOpcode::lhaux,
        0b0110010111 => return PpcOpcode::sthx,
        0b0110011100 => return PpcOpcode::orcx,
        0b0110110111 => return PpcOpcode::sthux,
        0b0110111100 => return PpcOpcode::orx,
        0b0111010011 => return PpcOpcode::mtspr,
        0b0111010110 => return PpcOpcode::dcbi,
        0b0111011100 => return PpcOpcode::nandx,
        0b0111100111 => return PpcOpcode::stvxl,
        0b1000000000 => return PpcOpcode::mcrxr,
        0b1000000111 => return PpcOpcode::lvlx,
        0b1000010100 => return PpcOpcode::ldbrx,
        0b1000010101 => return PpcOpcode::lswx,
        0b1000010110 => return PpcOpcode::lwbrx,
        0b1000010111 => return PpcOpcode::lfsx,
        0b1000011000 => return PpcOpcode::srwx,
        0b1000011011 => return PpcOpcode::srdx,
        0b1000100111 => return PpcOpcode::lvrx,
        0b1000110111 => return PpcOpcode::lfsux,
        0b1001010101 => return PpcOpcode::lswi,
        0b1001010110 => return PpcOpcode::sync,
        0b1001010111 => return PpcOpcode::lfdx,
        0b1001110111 => return PpcOpcode::lfdux,
        0b1010000111 => return PpcOpcode::stvlx,
        0b1010010100 => return PpcOpcode::stdbrx,
        0b1010010101 => return PpcOpcode::stswx,
        0b1010010110 => return PpcOpcode::stwbrx,
        0b1010010111 => return PpcOpcode::stfsx,
        0b1010100111 => return PpcOpcode::stvrx,
        0b1010110111 => return PpcOpcode::stfsux,
        0b1011010101 => return PpcOpcode::stswi,
        0b1011010111 => return PpcOpcode::stfdx,
        0b1011110111 => return PpcOpcode::stfdux,
        0b1100000111 => return PpcOpcode::lvlxl,
        0b1100010110 => return PpcOpcode::lhbrx,
        0b1100011000 => return PpcOpcode::srawx,
        0b1100011010 => return PpcOpcode::sradx,
        0b1100100111 => return PpcOpcode::lvrxl,
        0b1100111000 => return PpcOpcode::srawix,
        0b1101010110 => return PpcOpcode::eieio,
        0b1110000111 => return PpcOpcode::stvlxl,
        0b1110010110 => return PpcOpcode::sthbrx,
        0b1110011010 => return PpcOpcode::extshx,
        0b1110100111 => return PpcOpcode::stvrxl,
        0b1110111010 => return PpcOpcode::extsbx,
        0b1111010110 => return PpcOpcode::icbi,
        0b1111010111 => return PpcOpcode::stfiwx,
        0b1111011010 => return PpcOpcode::extswx,
        _ => {}
    }

    // Arithmetic op31 (bits 22-30)
    let key2 = extract_bits(code, 22, 30);
    match key2 {
        0b000001000 => return PpcOpcode::subfcx,
        0b000001001 => return PpcOpcode::mulhdux,
        0b000001010 => return PpcOpcode::addcx,
        0b000001011 => return PpcOpcode::mulhwux,
        0b000101000 => return PpcOpcode::subfx,
        0b001001001 => return PpcOpcode::mulhdx,
        0b001001011 => return PpcOpcode::mulhwx,
        0b001101000 => return PpcOpcode::negx,
        0b010001000 => return PpcOpcode::subfex,
        0b010001010 => return PpcOpcode::addex,
        0b011001000 => return PpcOpcode::subfzex,
        0b011001010 => return PpcOpcode::addzex,
        0b011101000 => return PpcOpcode::subfmex,
        0b011101001 => return PpcOpcode::mulldx,
        0b011101010 => return PpcOpcode::addmex,
        0b011101011 => return PpcOpcode::mullwx,
        0b100001010 => return PpcOpcode::addx,
        0b111001001 => return PpcOpcode::divdux,
        0b111001011 => return PpcOpcode::divwux,
        0b111101001 => return PpcOpcode::divdx,
        0b111101011 => return PpcOpcode::divwx,
        _ => {}
    }

    // dcbz/dcbz128 special case
    let key3 = (extract_bits(code, 6, 10) << 20) | (extract_bits(code, 21, 30));
    match key3 {
        0b0000000000000001111110110 => return PpcOpcode::dcbz,
        0b0000100000000001111110110 => return PpcOpcode::dcbz128,
        _ => {}
    }

    PpcOpcode::Invalid
}

fn decode_op63(code: u32) -> PpcOpcode {
    // Primary op63 table (bits 21-30)
    match extract_bits(code, 21, 30) {
        0b0000000000 => return PpcOpcode::fcmpu,
        0b0000001100 => return PpcOpcode::frspx,
        0b0000001110 => return PpcOpcode::fctiwx,
        0b0000001111 => return PpcOpcode::fctiwzx,
        0b0000100000 => return PpcOpcode::fcmpo,
        0b0000100110 => return PpcOpcode::mtfsb1x,
        0b0000101000 => return PpcOpcode::fnegx,
        0b0001000000 => return PpcOpcode::mcrfs,
        0b0001000110 => return PpcOpcode::mtfsb0x,
        0b0001001000 => return PpcOpcode::fmrx,
        0b0010000110 => return PpcOpcode::mtfsfix,
        0b0010001000 => return PpcOpcode::fnabsx,
        0b0100001000 => return PpcOpcode::fabsx,
        0b1001000111 => return PpcOpcode::mffsx,
        0b1011000111 => return PpcOpcode::mtfsfx,
        0b1100101110 => return PpcOpcode::fctidx,
        0b1100101111 => return PpcOpcode::fctidzx,
        0b1101001110 => return PpcOpcode::fcfidx,
        _ => {}
    }

    // FPU arithmetic (bits 26-30)
    match extract_bits(code, 26, 30) {
        0b10010 => PpcOpcode::fdivx,
        0b10100 => PpcOpcode::fsubx,
        0b10101 => PpcOpcode::faddx,
        0b10110 => PpcOpcode::fsqrtx,
        0b10111 => PpcOpcode::fselx,
        0b11001 => PpcOpcode::fmulx,
        0b11010 => PpcOpcode::frsqrtex,
        0b11100 => PpcOpcode::fmsubx,
        0b11101 => PpcOpcode::fmaddx,
        0b11110 => PpcOpcode::fnmsubx,
        0b11111 => PpcOpcode::fnmaddx,
        _ => PpcOpcode::Invalid,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_decode_addi() {
        // addi r3, r1, 0x10 => opcode 14, rD=3, rA=1, SIMM=0x10
        let raw: u32 = (14 << 26) | (3 << 21) | (1 << 16) | 0x10;
        let instr = decode(raw, 0);
        assert_eq!(instr.opcode, PpcOpcode::addi);
        assert_eq!(instr.rd(), 3);
        assert_eq!(instr.ra(), 1);
        assert_eq!(instr.simm16(), 0x10);
    }

    #[test]
    fn test_decode_lwz() {
        // lwz r5, 0x20(r1) => opcode 32
        let raw: u32 = (32 << 26) | (5 << 21) | (1 << 16) | 0x20;
        let instr = decode(raw, 0);
        assert_eq!(instr.opcode, PpcOpcode::lwz);
        assert_eq!(instr.rd(), 5);
        assert_eq!(instr.ra(), 1);
        assert_eq!(instr.d(), 0x20);
    }

    #[test]
    fn decode_cache_miss_fills_then_hit() {
        let mut cache = DecodeCache::new();
        let raw: u32 = (14 << 26) | (3 << 21) | (1 << 16) | 0x10;
        let pc = 0x8200_0000u32;
        let first = cache.lookup(pc, raw, 1);
        assert_eq!(first.opcode, PpcOpcode::addi);
        assert_eq!(cache.hits(), 0);
        assert_eq!(cache.misses(), 1);
        // Same pc, same version → cache hit, no new decode.
        let second = cache.lookup(pc, raw, 1);
        assert_eq!(second.opcode, PpcOpcode::addi);
        assert_eq!(cache.hits(), 1);
        assert_eq!(cache.misses(), 1);
    }

    #[test]
    fn decode_cache_stale_version_refills() {
        let mut cache = DecodeCache::new();
        // First fill with an `addi`.
        let raw_addi: u32 = (14 << 26) | (3 << 21) | (1 << 16) | 0x10;
        let pc = 0x8200_0000u32;
        cache.lookup(pc, raw_addi, 1);
        // Guest rewrote the page: same pc, different raw + bumped version.
        // Cache must refill — not return the stale `addi`.
        let raw_lwz: u32 = (32 << 26) | (5 << 21) | (1 << 16) | 0x20;
        let refreshed = cache.lookup(pc, raw_lwz, 2);
        assert_eq!(refreshed.opcode, PpcOpcode::lwz);
        assert_eq!(cache.invalidations(), 1);
        assert_eq!(cache.misses(), 2);
    }

    #[test]
    fn decode_cache_pc_collision_refills() {
        // Two PCs that hash to the same slot (pc >> 2 low 16 bits equal)
        // must not alias. Slot index = ((pc >> 2) & 0xFFFF) — pick two
        // PCs 4 * 2^16 bytes apart.
        let mut cache = DecodeCache::new();
        let pc_a = 0x8200_0000u32;
        let pc_b = pc_a.wrapping_add(0x0004_0000u32); // (>> 2) differs by 2^16
        let raw_addi: u32 = (14 << 26) | (3 << 21) | (1 << 16) | 0x10;
        let raw_lwz: u32 = (32 << 26) | (5 << 21) | (1 << 16) | 0x20;
        cache.lookup(pc_a, raw_addi, 1);
        // Different pc but same slot → miss + refill.
        cache.lookup(pc_b, raw_lwz, 1);
        // First pc comes back → miss + refill (slot was taken by pc_b).
        let back = cache.lookup(pc_a, raw_addi, 1);
        assert_eq!(back.opcode, PpcOpcode::addi);
        assert_eq!(cache.misses(), 3);
    }

    #[test]
    fn test_decode_branch() {
        // b +0x100 => opcode 18, LI=0x40 (shifted left 2 = 0x100), AA=0, LK=0
        let raw: u32 = (18 << 26) | (0x40 << 2);
        let instr = decode(raw, 0);
        assert_eq!(instr.opcode, PpcOpcode::bx);
        assert_eq!(instr.li(), 0x100);
        assert!(!instr.aa());
        assert!(!instr.lk());
    }

    #[test]
    fn test_decode_stw() {
        // stw r7, 0x8(r2)
        let raw: u32 = (36 << 26) | (7 << 21) | (2 << 16) | 0x8;
        let instr = decode(raw, 0);
        assert_eq!(instr.opcode, PpcOpcode::stw);
        assert_eq!(instr.rs(), 7);
        assert_eq!(instr.ra(), 2);
    }

    #[test]
    fn test_decode_ori_nop() {
        // ori r0, r0, 0 = NOP
        let raw: u32 = 24 << 26;
        let instr = decode(raw, 0);
        assert_eq!(instr.opcode, PpcOpcode::ori);
    }

    #[test]
    fn test_extract_bits() {
        assert_eq!(extract_bits(0xFFFF_FFFF, 0, 5), 0x3F);
        assert_eq!(extract_bits(0x8000_0000, 0, 0), 1);
        assert_eq!(extract_bits(0x0000_0001, 31, 31), 1);
    }

    // VMX128 register-name extraction. Locks the canonical bit positions
    // (decoder.rs is the single source of truth — the analysis crate's
    // old `ppc.rs` had different positions, which produced wrong printed
    // register names; the bug was silent because the interpreter never
    // used those extractors). Each test poke-bits exactly the slots the
    // accessor reads and asserts the assembled register number.

    /// Build a VMX128 test word for the canary-compliant register layout.
    /// `vd128 = vd_lo | (vd_hi << 5)` where vd_lo is 5 bits (PPC 6-10) and
    /// vd_hi is 2 bits (PPC 28-29). Same shape for vb128 (vb_lo at PPC 16-20,
    /// vb_hi 2 bits at PPC 30-31). va128 = va_lo | (va_h26<<5) | (va_h21<<6)
    /// per canary's 7-bit VA selector.
    fn vmx128_test_word(vd_lo: u32, vd_hi: u32, va_lo: u32, va_h26: u32, va_h21: u32,
                       vb_lo: u32, vb_hi: u32) -> u32 {
        // PPC bit i -> host bit (31-i).
        (vd_lo << (31 - 10))           // VD128l: PPC 6-10 = host 21-25
            | (vd_hi << (31 - 29))      // VD128h: PPC 28-29 = host 2-3 (LSB at host 2)
            | (va_lo << (31 - 15))      // VA128l: PPC 11-15 = host 16-20
            | (va_h26 << (31 - 26))     // VA128h: PPC 26 = host 5
            | (va_h21 << (31 - 21))     // VA128H: PPC 21 = host 10
            | (vb_lo << (31 - 20))      // VB128l: PPC 16-20 = host 11-15
            | (vb_hi << (31 - 31))      // VB128h: PPC 30-31 = host 0-1 (LSB at host 0)
    }

    #[test]
    fn vmx128_vd128_low_5_bits_only() {
        // vd_lo = 0..31, vd_hi = 0 → vd128 = vd_lo
        for r in 0..32u32 {
            let raw = (r as u32) << (31 - 10);
            let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 };
            assert_eq!(d.vd128(), r as usize, "vd_lo={r}");
        }
    }

    #[test]
    fn vmx128_vd128_high_low_bit_adds_32() {
        // vd_lo = 0, VD128h = 0b01 (LSB only at host bit 2 = PPC 29) → vd128 = 32
        let raw = (1u32 << (31 - 29));
        let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 };
        assert_eq!(d.vd128(), 32);
    }

    #[test]
    fn vmx128_vd128_high_high_bit_adds_64() {
        // vd_lo = 0, VD128h = 0b10 (MSB only at host bit 3 = PPC 28) → vd128 = 64
        let raw = (1u32 << (31 - 28));
        let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 };
        assert_eq!(d.vd128(), 64);
    }

    #[test]
    fn vmx128_vd128_full_127() {
        // vd_lo = 31, VD128h = 0b11 → vd128 = 127
        let raw = (31u32 << (31 - 10))
            | (1u32 << (31 - 28))
            | (1u32 << (31 - 29));
        let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 };
        assert_eq!(d.vd128(), 127);
    }

    #[test]
    fn vmx128_va128_canary_layout() {
        // va_lo = 7 at PPC 11-15, VA128h = 1 at PPC 26 → va128 = 7 | 32 = 39
        let raw = (7u32 << (31 - 15)) | (1u32 << (31 - 26));
        let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 };
        assert_eq!(d.va128(), 39);
        // VA128H = 1 at PPC 21 → va128 += 64 = 103
        let raw = raw | (1u32 << (31 - 21));
        let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 };
        assert_eq!(d.va128(), 7 | 32 | 64);
    }

    #[test]
    fn vmx128_vb128_uses_bits30_31() {
        // vb_lo = 5 at PPC 16-20. VB128h = 0b01 (LSB at PPC 31 = host 0) → +32.
        // VB128h = 0b11 → +96.
        let raw = (5u32 << (31 - 20)) | (1u32 << (31 - 31));
        let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 };
        assert_eq!(d.vb128(), 5 | 32);
        let raw = raw | (1u32 << (31 - 30));
        let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 };
        assert_eq!(d.vb128(), 5 | 32 | 64);
    }

    #[test]
    fn vmx128_vs128_aliases_vd128() {
        // vs128 must always equal vd128.
        for r in [0u32, 31, 32, 64, 96, 127] {
            let lo = r & 0x1F;
            let hi = (r >> 5) & 0x3;
            let raw = (lo << (31 - 10))
                | (hi << (31 - 29));
            let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 };
            assert_eq!(d.vd128(), r as usize, "vd128 mismatch for r={r}");
            assert_eq!(d.vs128(), r as usize, "vs128 mismatch for r={r}");
            assert_eq!(d.vd128(), d.vs128());
        }
    }

    #[test]
    #[allow(dead_code)]
    fn _vmx128_test_word_helper_compiles() {
        // Keep the helper validated against the real accessor.
        // vd_lo=5, vd_hi=0b11 → vd128 = 5 | 96 = 101
        let raw = vmx128_test_word(5, 3, 0, 0, 0, 0, 0);
        let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 };
        assert_eq!(d.vd128(), 5 | 32 | 64);
    }

    #[test]
    fn vx128_5_sh_bit_positions() {
        // SH=8 (binary 1000): bit 3 = 1, bits 0-2 = 0.
        // Host bit 9 = 1 (PPC bit 22), host bits 6-8 = 0.
        // So raw bit 9 set = raw |= 1 << 9 = 0x200
        let raw = 0x200u32; // host bit 9 set only
        let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 };
        assert_eq!(d.vx128_5_sh(), 8, "SH=8: MSB at PPC bit 22");

        // SH=1 (binary 0001): host bit 6 set = raw |= 1 << 6 = 0x40
        let raw = 0x40u32;
        let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 };
        assert_eq!(d.vx128_5_sh(), 1, "SH=1: LSB at PPC bit 25");

        // SH=15 (binary 1111): host bits 6-9 all set = raw |= 0xF << 6 = 0x3C0
        let raw = 0x3C0u32;
        let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 };
        assert_eq!(d.vx128_5_sh(), 15, "SH=15: all 4 bits set");

        // SH=0: raw=0
        let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw: 0, addr: 0 };
        assert_eq!(d.vx128_5_sh(), 0, "SH=0");
    }

    #[test]
    fn vx128_4_accessors_correct_bit_positions() {
        // z=3 (binary 11) at PPC bits 24-25 = host bits 6-7
        let raw = 0b11u32 << 6;
        let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 };
        assert_eq!(d.vx128_4_z(), 3, "z=3 from host bits 6-7");

        // IMM=0x15 (binary 10101) at PPC bits 11-15 = host bits 16-20
        let raw2 = 0x15u32 << 16;
        let d2 = DecodedInstr { opcode: PpcOpcode::Invalid, raw: raw2, addr: 0 };
        assert_eq!(d2.vx128_4_imm(), 0x15, "IMM=0x15 from host bits 16-20");

        // Combined: z=1, IMM=0xA — fields must not bleed into each other
        let raw3 = (0x1u32 << 6) | (0xAu32 << 16);
        let d3 = DecodedInstr { opcode: PpcOpcode::Invalid, raw: raw3, addr: 0 };
        assert_eq!(d3.vx128_4_z(), 1, "z=1 combined");
        assert_eq!(d3.vx128_4_imm(), 0xA, "IMM=0xA combined");

        // z=2, IMM=0xF — max 4-bit blend mask, exercises the full lower nibble
        let raw4 = (0b10u32 << 6) | (0xFu32 << 16);
        let d4 = DecodedInstr { opcode: PpcOpcode::Invalid, raw: raw4, addr: 0 };
        assert_eq!(d4.vx128_4_z(), 2, "z=2 from binary 10");
        assert_eq!(d4.vx128_4_imm(), 0xF, "IMM=0xF all-ones nibble");
    }

    #[test]
    fn vc128_2_extracts_ppc_bits_23_25() {
        // VC=5 (binary 101) at PPC bits 23-25 = host bits 6-8
        // extract_bits(raw, 23, 25) = (raw >> (31-25)) & 0x7 = (raw >> 6) & 0x7
        let raw = 5u32 << 6; // host bits 6-8 = 5
        let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 };
        assert_eq!(d.vc128_2(), 5);

        let d0 = DecodedInstr { opcode: PpcOpcode::Invalid, raw: 0, addr: 0 };
        assert_eq!(d0.vc128_2(), 0);

        let d7 = DecodedInstr { opcode: PpcOpcode::Invalid, raw: 7u32 << 6, addr: 0 };
        assert_eq!(d7.vc128_2(), 7);

        let d1 = DecodedInstr { opcode: PpcOpcode::Invalid, raw: 1u32 << 6, addr: 0 };
        assert_eq!(d1.vc128_2(), 1);
    }

    #[test]
    fn vx128_p_perm_assembles_correctly() {
        // PERMl=0x1F (all 5 bits set) at host bits 16-20: raw = 0x1F << 16
        let raw = 0x1Fu32 << 16;
        let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 };
        assert_eq!(d.vx128_p_perm(), 0x1F, "PERMl only");

        // PERMh=0x7 (all 3 bits set) at host bits 6-8: raw = 0x7 << 6 = 0x1C0
        let raw = 0x7u32 << 6;
        let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 };
        assert_eq!(d.vx128_p_perm(), 0x7 << 5, "PERMh only: bits 5-7");

        // PERMl=0xA, PERMh=0x5: raw = (0xA << 16) | (0x5 << 6)
        let raw = (0xAu32 << 16) | (0x5u32 << 6);
        let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 };
        assert_eq!(d.vx128_p_perm(), 0xA | (0x5 << 5));

        // PERMl and PERMh bits must not bleed into each other
        let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw: 0, addr: 0 };
        assert_eq!(d.vx128_p_perm(), 0);
    }
}
