PPCBUG-563: Add vx128_4_imm() (PPC bits 11-15) and vx128_4_z() (PPC bits 24-25) accessors to decoder.rs for VX128_4-form instructions. PPCBUG-315: vrlimi128 was reading z from host bits 16-17 (a subset of IMM) and mask from host bits 2-5 (a reserved/XO region). Replace with the correct accessors: z selects which word-lane to start the rotation from (0-3); IMM is the 5-bit per-lane blend mask. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1240 lines
49 KiB
Rust
1240 lines
49 KiB
Rust
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.
|
|
#[inline] pub fn vx128r_rc_bit(&self) -> bool { (self.raw >> 4) & 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
|
|
|
|
/// VA128 (bits 6-10, plus bit from 29)
|
|
#[inline] pub fn va128(&self) -> usize {
|
|
(extract_bits(self.raw, 6, 10) | (extract_bits(self.raw, 29, 29) << 5)) as usize
|
|
}
|
|
|
|
/// VB128 (bits 16-20, plus bits from 28, 30)
|
|
#[inline] pub fn vb128(&self) -> usize {
|
|
(extract_bits(self.raw, 16, 20)
|
|
| (extract_bits(self.raw, 28, 28) << 5)
|
|
| (extract_bits(self.raw, 30, 30) << 6)) as usize
|
|
}
|
|
|
|
/// VD128 (bits 6-10, plus bits from 21, 22)
|
|
#[inline] pub fn vd128(&self) -> usize {
|
|
(extract_bits(self.raw, 6, 10)
|
|
| (extract_bits(self.raw, 21, 21) << 5)
|
|
| (extract_bits(self.raw, 22, 22) << 6)) 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 (non-dot and dot forms).
|
|
// Non-dot: bit 27 = 0. Dot: bit 27 = 1, but bit 25 must also be 0 to
|
|
// distinguish from the shift/merge group (which has bit 25 = 1 when bit 27 = 1).
|
|
// key4_nd uses bits 22-24 + bit 27 (same as original, covers non-dot).
|
|
// key4_dt uses bits 22-24 + bit 25 + bit 27 (narrower, covers dot-only).
|
|
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,
|
|
_ => {}
|
|
}
|
|
// Dot forms: bit 27 = 1, bit 25 = 0 (key = bits22-24 + bit25 + bit27, low 3 bits)
|
|
let key4_dt = (extract_bits(code, 22, 24) << 2) | (extract_bits(code, 25, 25) << 1) | extract_bits(code, 27, 27);
|
|
match key4_dt {
|
|
0b00001 => return PpcOpcode::vcmpeqfp128, // bits22-24=000, bit25=0, bit27=1
|
|
0b00101 => return PpcOpcode::vcmpgefp128, // bits22-24=001, bit25=0, bit27=1
|
|
0b01001 => return PpcOpcode::vcmpgtfp128, // bits22-24=010, bit25=0, bit27=1
|
|
0b01101 => return PpcOpcode::vcmpbfp128, // bits22-24=011, bit25=0, bit27=1
|
|
0b10001 => return PpcOpcode::vcmpequw128, // bits22-24=100, bit25=0, bit27=1
|
|
_ => {}
|
|
}
|
|
|
|
// 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.
|
|
|
|
fn vmx128_test_word(va21: u32, vd6_10: u32, vd21: u32, vd22: u32,
|
|
vb16_20: u32, vb28: u32, vb30: u32) -> u32 {
|
|
// PPC bit i -> LSB position 31-i.
|
|
(vd6_10 << (31 - 10))
|
|
| (va21 << (31 - 21)) // va128 high bit at PPC 29 in some forms — kept 0 here
|
|
| (vd21 << (31 - 21))
|
|
| (vd22 << (31 - 22))
|
|
| (vb16_20 << (31 - 20))
|
|
| (vb28 << (31 - 28))
|
|
| (vb30 << (31 - 30))
|
|
}
|
|
|
|
#[test]
|
|
fn vmx128_vd128_low_5_bits_only() {
|
|
// vd_lo = 0..31, vd_b21 = 0, vd_b22 = 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_bit21_adds_32() {
|
|
// vd_lo = 0, vd_b21 = 1, vd_b22 = 0 → vd128 = 32
|
|
let raw = (0u32 << (31 - 10)) | (1u32 << (31 - 21));
|
|
let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 };
|
|
assert_eq!(d.vd128(), 32);
|
|
}
|
|
|
|
#[test]
|
|
fn vmx128_vd128_bit22_adds_64() {
|
|
// vd_lo = 0, vd_b21 = 0, vd_b22 = 1 → vd128 = 64
|
|
let raw = (0u32 << (31 - 10)) | (1u32 << (31 - 22));
|
|
let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 };
|
|
assert_eq!(d.vd128(), 64);
|
|
}
|
|
|
|
#[test]
|
|
fn vmx128_vd128_full_127() {
|
|
// vd_lo = 31, vd_b21 = 1, vd_b22 = 1 → vd128 = 127
|
|
let raw = (31u32 << (31 - 10))
|
|
| (1u32 << (31 - 21))
|
|
| (1u32 << (31 - 22));
|
|
let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 };
|
|
assert_eq!(d.vd128(), 127);
|
|
}
|
|
|
|
#[test]
|
|
fn vmx128_va128_uses_bit29() {
|
|
// va128 = bits 6-10 + bit 29. va_lo = 7, bit 29 = 1 → va128 = 7 | 32 = 39.
|
|
let raw = (7u32 << (31 - 10)) | (1u32 << (31 - 29));
|
|
let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 };
|
|
assert_eq!(d.va128(), 39);
|
|
}
|
|
|
|
#[test]
|
|
fn vmx128_vb128_uses_bits28_and_30() {
|
|
// vb128 = bits 16-20 + bit 28 + bit 30. Low 5 = 5, bit 28 = 1 → +32, bit 30 = 1 → +64.
|
|
let raw = (5u32 << (31 - 20))
|
|
| (1u32 << (31 - 28))
|
|
| (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 b21 = (r >> 5) & 1;
|
|
let b22 = (r >> 6) & 1;
|
|
let raw = (lo << (31 - 10))
|
|
| (b21 << (31 - 21))
|
|
| (b22 << (31 - 22));
|
|
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.
|
|
let raw = vmx128_test_word(0, 5, 1, 1, 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;
|
|
assert_eq!(DecodedInstr::from_raw(raw).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;
|
|
assert_eq!(
|
|
DecodedInstr::from_raw(raw).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);
|
|
assert_eq!(DecodedInstr::from_raw(raw).vx128_p_perm(), 0xA | (0x5 << 5));
|
|
|
|
// PERMl and PERMh bits must not bleed into each other
|
|
let raw = 0u32;
|
|
assert_eq!(DecodedInstr::from_raw(raw).vx128_p_perm(), 0);
|
|
}
|
|
}
|