Split the monolithic interpreter into cohesive modules: dedicated decoder (decoder.rs) producing 8-byte DecodedInstr; opcode tables (opcode.rs); explicit traps (trap.rs); FPSCR helpers (fpscr.rs); overflow/carry helpers (overflow.rs); a 4 KiB-page-versioned decode cache and basic-block cache (block_cache.rs); and a full VMX/VMX128 implementation (vmx.rs) covering AltiVec + Xenon's 128-bit extensions. Add the parallel-execution substrate behind --parallel: a 7-party phaser (phaser.rs) for round-based barrier sync, ReservationTable (reservation.rs) for guest LL/SC, and the per-HW-thread scheduler core (scheduler.rs) that owns ThreadRefs, runqueues, and pending IRQs. Disassembler is now the single source of truth: disasm.rs gains the full base + extended + VMX128 mnemonic set, with golden JSON fixtures and a disasm_goldens test suite. Add a criterion-style interpreter bench. context.rs grows the per-thread state the new modules need (reservation slot, FPSCR, vector regs). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1868 lines
76 KiB
Rust
1868 lines
76 KiB
Rust
//! PowerPC (Xbox 360 Xenon) text disassembler.
|
|
//!
|
|
//! Single source of truth for assembly text formatting. Sits on top of the
|
|
//! canonical decoder in [`crate::decoder`] and consumes [`DecodedInstr`]
|
|
//! (8-byte `Copy`, no allocations) so the interpreter's decode cache stays
|
|
//! lean — formatting allocates, but only when a sink calls [`format`].
|
|
//!
|
|
//! [`format`] returns a [`DisasmText`] carrying both base and extended
|
|
//! (simplified) mnemonic forms. Callers (text printer, JSON sink, DuckDB
|
|
//! row writer) consume the fields directly instead of re-parsing.
|
|
|
|
use crate::decoder::{DecodedInstr, extract_vx128_uimm5};
|
|
use crate::opcode::PpcOpcode;
|
|
|
|
/// Formatted disassembly of a single instruction.
|
|
///
|
|
/// Owns its strings. `mnemonic`/`operands` are the structured base form
|
|
/// (e.g. `"addi"`, `"r3, r1, 16"`); `disasm` is the legacy padded display
|
|
/// form (e.g. `"addi r3, r1, 16"`). The `ext_*` triple is `Some` when
|
|
/// a simplified/extended mnemonic applies (e.g. `addi r3,0,imm` →
|
|
/// `li r3, imm`). `branch_target` is the resolved absolute target for
|
|
/// direct branches (`b`/`bl`/`bc`/`bcl`); `None` for indirect branches
|
|
/// and non-branches.
|
|
#[derive(Debug, Clone)]
|
|
pub struct DisasmText {
|
|
pub mnemonic: String,
|
|
pub operands: String,
|
|
pub disasm: String,
|
|
pub ext_mnemonic: Option<String>,
|
|
pub ext_operands: Option<String>,
|
|
pub ext_disasm: Option<String>,
|
|
pub branch_target: Option<u32>,
|
|
}
|
|
|
|
impl DisasmText {
|
|
/// Preferred display form: extended if present, else base.
|
|
#[inline]
|
|
pub fn display(&self) -> &str {
|
|
self.ext_disasm.as_deref().unwrap_or(&self.disasm)
|
|
}
|
|
}
|
|
|
|
// ── Internal builders ───────────────────────────────────────────────────────
|
|
|
|
#[inline]
|
|
fn pad_into(mnem: &str, operands: &str, width: usize) -> String {
|
|
if width <= mnem.len() + 1 {
|
|
// No padding fits — fall back to single-space join.
|
|
if operands.is_empty() { mnem.to_string() }
|
|
else { format!("{mnem} {operands}") }
|
|
} else {
|
|
format!("{:<width$}{}", mnem, operands, width = width)
|
|
}
|
|
}
|
|
|
|
fn base(mnem: &str, operands: String, pad: usize) -> DisasmText {
|
|
let disasm = pad_into(mnem, &operands, pad);
|
|
DisasmText {
|
|
mnemonic: mnem.to_string(),
|
|
operands,
|
|
disasm,
|
|
ext_mnemonic: None,
|
|
ext_operands: None,
|
|
ext_disasm: None,
|
|
branch_target: None,
|
|
}
|
|
}
|
|
|
|
fn with_ext(
|
|
base_mnem: &str, base_ops: String, base_pad: usize,
|
|
ext_mnem: &str, ext_ops: String, ext_pad: usize,
|
|
) -> DisasmText {
|
|
let disasm = pad_into(base_mnem, &base_ops, base_pad);
|
|
let ext_disasm = pad_into(ext_mnem, &ext_ops, ext_pad);
|
|
DisasmText {
|
|
mnemonic: base_mnem.to_string(),
|
|
operands: base_ops,
|
|
disasm,
|
|
ext_mnemonic: Some(ext_mnem.to_string()),
|
|
ext_operands: Some(ext_ops),
|
|
ext_disasm: Some(ext_disasm),
|
|
branch_target: None,
|
|
}
|
|
}
|
|
|
|
fn with_target(mut t: DisasmText, target: u32) -> DisasmText {
|
|
t.branch_target = Some(target);
|
|
t
|
|
}
|
|
|
|
fn long_word(raw: u32) -> DisasmText {
|
|
let operands = format!("0x{raw:08X}");
|
|
base(".long", operands, 8)
|
|
}
|
|
|
|
// ── Helpers (register names, sign extension, condition decoding) ────────────
|
|
|
|
#[inline] fn gpr(r: usize) -> String { format!("r{r}") }
|
|
#[inline] fn fpr(r: usize) -> String { format!("f{r}") }
|
|
#[inline] fn vr(r: usize) -> String { format!("v{r}") }
|
|
|
|
fn crb(b: u32) -> String {
|
|
let cr = b / 4;
|
|
let bit = b % 4;
|
|
let bit_name = ["lt", "gt", "eq", "so"][bit as usize];
|
|
if cr == 0 { bit_name.to_string() } else { format!("4*cr{cr}+{bit_name}") }
|
|
}
|
|
|
|
fn spr_name(spr: u32) -> String {
|
|
match spr {
|
|
1 => "XER".into(),
|
|
8 => "LR".into(),
|
|
9 => "CTR".into(),
|
|
_ => format!("spr{spr}"),
|
|
}
|
|
}
|
|
|
|
#[inline] fn sign_ext(val: u32, bits: u32) -> i32 {
|
|
let shift = 32 - bits;
|
|
((val << shift) as i32) >> shift
|
|
}
|
|
|
|
/// Map trap TO field to condition suffix (e.g. 16 → "lt", 4 → "eq").
|
|
/// Unsigned variants (`lgt`/`llt`/`lge`/`lle`) cover bits 1-3 of the TO
|
|
/// encoding which `tw`/`td` use for logical-compare conditions.
|
|
fn trap_cond(to: u32) -> Option<&'static str> {
|
|
match to {
|
|
1 => Some("lgt"),
|
|
2 => Some("llt"),
|
|
4 => Some("eq"),
|
|
5 => Some("lge"),
|
|
6 => Some("lle"),
|
|
8 => Some("gt"),
|
|
12 => Some("ge"),
|
|
16 => Some("lt"),
|
|
20 => Some("le"),
|
|
24 => Some("ne"),
|
|
31 => Some(""), // unconditional
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// For non-decrementing conditional branches: returns Some((cond_name, cr_prefix))
|
|
/// where cr_prefix is e.g. "" or "cr2, ".
|
|
fn cond_branch_ext(bo: u32, bi: u32) -> Option<(&'static str, String)> {
|
|
let cond_true = bo & 0x08 != 0;
|
|
let no_cond = bo & 0x10 != 0;
|
|
let decr = bo & 0x04 == 0;
|
|
if no_cond || decr { return None; }
|
|
|
|
let cr_field = bi / 4;
|
|
let cr_bit = bi % 4;
|
|
let cond_name = match (cr_bit, cond_true) {
|
|
(0, true) => "lt", (0, false) => "ge",
|
|
(1, true) => "gt", (1, false) => "le",
|
|
(2, true) => "eq", (2, false) => "ne",
|
|
(3, true) => "so", (3, false) => "ns",
|
|
_ => return None,
|
|
};
|
|
let cr = if cr_field == 0 { String::new() } else { format!("cr{cr_field}, ") };
|
|
Some((cond_name, cr))
|
|
}
|
|
|
|
#[inline] fn rc_dot(instr: &DecodedInstr) -> &'static str {
|
|
if instr.rc_bit() { "." } else { "" }
|
|
}
|
|
|
|
// ── Public entrypoints ──────────────────────────────────────────────────────
|
|
|
|
/// Format a decoded instruction into structured disassembly text.
|
|
pub fn format(instr: &DecodedInstr) -> DisasmText {
|
|
match instr.opcode {
|
|
// ── Branch ──────────────────────────────────────────────────────────
|
|
PpcOpcode::bx => fmt_b(instr),
|
|
PpcOpcode::bcx => fmt_bc(instr),
|
|
PpcOpcode::bclrx => fmt_bclr(instr),
|
|
PpcOpcode::bcctrx => fmt_bcctr(instr),
|
|
PpcOpcode::sc => base("sc", String::new(), 0),
|
|
|
|
// ── Trap ────────────────────────────────────────────────────────────
|
|
PpcOpcode::tdi => fmt_trap_imm(instr, "tdi", "td"),
|
|
PpcOpcode::twi => fmt_trap_imm(instr, "twi", "tw"),
|
|
PpcOpcode::td => fmt_trap_reg(instr, "td"),
|
|
PpcOpcode::tw => fmt_trap_reg(instr, "tw"),
|
|
|
|
// ── D-form ALU/logical ──────────────────────────────────────────────
|
|
PpcOpcode::addi => fmt_addi(instr),
|
|
PpcOpcode::addis => fmt_addis(instr),
|
|
PpcOpcode::addic => fmt_d_add(instr, "addic"),
|
|
PpcOpcode::addicx => fmt_d_add(instr, "addic."),
|
|
PpcOpcode::subficx => fmt_d_imm_simple(instr, "subfic"),
|
|
PpcOpcode::mulli => fmt_d_imm_simple(instr, "mulli"),
|
|
PpcOpcode::cmpi => fmt_cmp_imm(instr, "cmpi", true),
|
|
PpcOpcode::cmpli => fmt_cmp_imm(instr, "cmpli", false),
|
|
PpcOpcode::ori => fmt_ori(instr),
|
|
PpcOpcode::oris => fmt_d_logic(instr, "oris"),
|
|
PpcOpcode::xori => fmt_d_logic(instr, "xori"),
|
|
PpcOpcode::xoris => fmt_d_logic(instr, "xoris"),
|
|
PpcOpcode::andix => fmt_d_logic(instr, "andi."),
|
|
PpcOpcode::andisx => fmt_d_logic(instr, "andis."),
|
|
|
|
// ── D-form load/store ───────────────────────────────────────────────
|
|
PpcOpcode::lwz => fmt_ld(instr, "lwz", false),
|
|
PpcOpcode::lwzu => fmt_ld(instr, "lwzu", false),
|
|
PpcOpcode::lbz => fmt_ld(instr, "lbz", false),
|
|
PpcOpcode::lbzu => fmt_ld(instr, "lbzu", false),
|
|
PpcOpcode::lhz => fmt_ld(instr, "lhz", false),
|
|
PpcOpcode::lhzu => fmt_ld(instr, "lhzu", false),
|
|
PpcOpcode::lha => fmt_ld(instr, "lha", false),
|
|
PpcOpcode::lhau => fmt_ld(instr, "lhau", false),
|
|
PpcOpcode::lmw => fmt_ld(instr, "lmw", false),
|
|
PpcOpcode::lfs => fmt_ld(instr, "lfs", true),
|
|
PpcOpcode::lfsu => fmt_ld(instr, "lfsu", true),
|
|
PpcOpcode::lfd => fmt_ld(instr, "lfd", true),
|
|
PpcOpcode::lfdu => fmt_ld(instr, "lfdu", true),
|
|
PpcOpcode::stw => fmt_st(instr, "stw", false),
|
|
PpcOpcode::stwu => fmt_st(instr, "stwu", false),
|
|
PpcOpcode::stb => fmt_st(instr, "stb", false),
|
|
PpcOpcode::stbu => fmt_st(instr, "stbu", false),
|
|
PpcOpcode::sth => fmt_st(instr, "sth", false),
|
|
PpcOpcode::sthu => fmt_st(instr, "sthu", false),
|
|
PpcOpcode::stmw => fmt_st(instr, "stmw", false),
|
|
PpcOpcode::stfs => fmt_st(instr, "stfs", true),
|
|
PpcOpcode::stfsu => fmt_st(instr, "stfsu", true),
|
|
PpcOpcode::stfd => fmt_st(instr, "stfd", true),
|
|
PpcOpcode::stfdu => fmt_st(instr, "stfdu", true),
|
|
|
|
// ── DS-form load/store ──────────────────────────────────────────────
|
|
PpcOpcode::ld => fmt_ds(instr, "ld"),
|
|
PpcOpcode::ldu => fmt_ds(instr, "ldu"),
|
|
PpcOpcode::lwa => fmt_ds(instr, "lwa"),
|
|
PpcOpcode::std => fmt_ds(instr, "std"),
|
|
PpcOpcode::stdu => fmt_ds(instr, "stdu"),
|
|
|
|
// ── Rotate ─────────────────────────────────────────────────────────
|
|
PpcOpcode::rlwimix => fmt_rlwimi(instr),
|
|
PpcOpcode::rlwinmx => fmt_rlwinm(instr),
|
|
PpcOpcode::rlwnmx => fmt_rlwnm(instr),
|
|
PpcOpcode::rldiclx => fmt_rldicl(instr),
|
|
PpcOpcode::rldicrx => fmt_rldicr(instr),
|
|
PpcOpcode::rldicx => fmt_rldic(instr),
|
|
PpcOpcode::rldimix => fmt_rldimi(instr),
|
|
PpcOpcode::rldclx => fmt_rldcl(instr),
|
|
PpcOpcode::rldcrx => fmt_rldcr(instr),
|
|
|
|
// ── Compare (X-form) ───────────────────────────────────────────────
|
|
PpcOpcode::cmp => fmt_cmp_reg(instr, "cmp"),
|
|
PpcOpcode::cmpl => fmt_cmp_reg(instr, "cmpl"),
|
|
|
|
// ── X-form ALU (3-register) with OE/Rc ─────────────────────────────
|
|
PpcOpcode::addx => fmt_xo_3op(instr, "add"),
|
|
PpcOpcode::addcx => fmt_xo_3op(instr, "addc"),
|
|
PpcOpcode::addex => fmt_xo_3op(instr, "adde"),
|
|
PpcOpcode::addmex => fmt_xo_2op(instr, "addme"),
|
|
PpcOpcode::addzex => fmt_xo_2op(instr, "addze"),
|
|
PpcOpcode::subfx => fmt_subf(instr, "subf", "sub"),
|
|
PpcOpcode::subfcx => fmt_subf(instr, "subfc", "subc"),
|
|
PpcOpcode::subfex => fmt_xo_3op(instr, "subfe"),
|
|
PpcOpcode::subfmex => fmt_xo_2op(instr, "subfme"),
|
|
PpcOpcode::subfzex => fmt_xo_2op(instr, "subfze"),
|
|
PpcOpcode::negx => fmt_xo_2op(instr, "neg"),
|
|
PpcOpcode::mullwx => fmt_xo_3op(instr, "mullw"),
|
|
PpcOpcode::mulhwx => fmt_xo_3op_no_oe(instr, "mulhw"),
|
|
PpcOpcode::mulhwux => fmt_xo_3op_rc_only(instr, "mulhwu"),
|
|
PpcOpcode::divwx => fmt_xo_3op(instr, "divw"),
|
|
PpcOpcode::divwux => fmt_xo_3op(instr, "divwu"),
|
|
PpcOpcode::mulldx => fmt_xo_3op(instr, "mulld"),
|
|
PpcOpcode::mulhdx => fmt_xo_3op_rc_only(instr, "mulhd"),
|
|
PpcOpcode::mulhdux => fmt_xo_3op_rc_only(instr, "mulhdu"),
|
|
PpcOpcode::divdx => fmt_xo_3op(instr, "divd"),
|
|
PpcOpcode::divdux => fmt_xo_3op(instr, "divdu"),
|
|
|
|
// ── X-form logical (Rc) ────────────────────────────────────────────
|
|
PpcOpcode::andx => fmt_logic_and(instr),
|
|
PpcOpcode::andcx => fmt_x_logic(instr, "andc"),
|
|
PpcOpcode::orx => fmt_logic_or(instr),
|
|
PpcOpcode::orcx => fmt_x_logic(instr, "orc"),
|
|
PpcOpcode::xorx => fmt_x_logic(instr, "xor"),
|
|
PpcOpcode::norx => fmt_logic_nor(instr),
|
|
PpcOpcode::nandx => fmt_x_logic(instr, "nand"),
|
|
PpcOpcode::eqvx => fmt_x_logic(instr, "eqv"),
|
|
PpcOpcode::extsbx => fmt_x_unary_rc(instr, "extsb"),
|
|
PpcOpcode::extshx => fmt_x_unary_rc(instr, "extsh"),
|
|
PpcOpcode::extswx => fmt_x_unary_rc(instr, "extsw"),
|
|
PpcOpcode::cntlzwx => fmt_x_unary_rc(instr, "cntlzw"),
|
|
PpcOpcode::cntlzdx => fmt_x_unary_rc(instr, "cntlzd"),
|
|
|
|
// ── Shift (32 / 64) ─────────────────────────────────────────────────
|
|
PpcOpcode::slwx => fmt_x_logic(instr, "slw"),
|
|
PpcOpcode::srwx => fmt_x_logic(instr, "srw"),
|
|
PpcOpcode::srawx => fmt_x_logic(instr, "sraw"),
|
|
PpcOpcode::sldx => fmt_x_logic(instr, "sld"),
|
|
PpcOpcode::srdx => fmt_x_logic(instr, "srd"),
|
|
PpcOpcode::sradx => fmt_x_logic(instr, "srad"),
|
|
PpcOpcode::srawix => fmt_srawi(instr),
|
|
PpcOpcode::sradix => fmt_sradi(instr),
|
|
|
|
// ── Special register moves ─────────────────────────────────────────
|
|
PpcOpcode::mfspr => fmt_mfspr(instr),
|
|
PpcOpcode::mtspr => fmt_mtspr(instr),
|
|
PpcOpcode::mfcr => base("mfcr", gpr(instr.rd()), 8),
|
|
PpcOpcode::mtcrf => fmt_mtcrf(instr),
|
|
PpcOpcode::mfmsr => base("mfmsr", gpr(instr.rd()), 8),
|
|
PpcOpcode::mtmsr => base("mtmsr", gpr(instr.rs()), 8),
|
|
PpcOpcode::mtmsrd => base("mtmsrd", gpr(instr.rs()), 8),
|
|
PpcOpcode::mftb => fmt_mftb(instr),
|
|
PpcOpcode::mcrxr => base("mcrxr", format!("cr{}", instr.crfd()), 8),
|
|
PpcOpcode::mcrf => base("mcrf", format!("cr{}, cr{}", instr.crfd(), instr.crfs()), 8),
|
|
|
|
// ── X-form indexed load/store ──────────────────────────────────────
|
|
PpcOpcode::lwzx => fmt_x_load(instr, "lwzx", false),
|
|
PpcOpcode::lwzux => fmt_x_load(instr, "lwzux", false),
|
|
PpcOpcode::lbzx => fmt_x_load(instr, "lbzx", false),
|
|
PpcOpcode::lbzux => fmt_x_load(instr, "lbzux", false),
|
|
PpcOpcode::lhzx => fmt_x_load(instr, "lhzx", false),
|
|
PpcOpcode::lhzux => fmt_x_load(instr, "lhzux", false),
|
|
PpcOpcode::lhax => fmt_x_load(instr, "lhax", false),
|
|
PpcOpcode::lhaux => fmt_x_load(instr, "lhaux", false),
|
|
PpcOpcode::lwax => fmt_x_load(instr, "lwax", false),
|
|
PpcOpcode::lwaux => fmt_x_load(instr, "lwaux", false),
|
|
PpcOpcode::ldx => fmt_x_load(instr, "ldx", false),
|
|
PpcOpcode::ldux => fmt_x_load(instr, "ldux", false),
|
|
PpcOpcode::lwbrx => fmt_x_load(instr, "lwbrx", false),
|
|
PpcOpcode::lhbrx => fmt_x_load(instr, "lhbrx", false),
|
|
PpcOpcode::ldbrx => fmt_x_load(instr, "ldbrx", false),
|
|
PpcOpcode::lwarx => fmt_x_load(instr, "lwarx", false),
|
|
PpcOpcode::ldarx => fmt_x_load(instr, "ldarx", false),
|
|
PpcOpcode::lswx => fmt_x_load(instr, "lswx", false),
|
|
PpcOpcode::lswi => fmt_lswi_stswi(instr, "lswi"),
|
|
PpcOpcode::lfsx => fmt_x_load(instr, "lfsx", true),
|
|
PpcOpcode::lfsux => fmt_x_load(instr, "lfsux", true),
|
|
PpcOpcode::lfdx => fmt_x_load(instr, "lfdx", true),
|
|
PpcOpcode::lfdux => fmt_x_load(instr, "lfdux", true),
|
|
PpcOpcode::stwx => fmt_x_store(instr, "stwx", false),
|
|
PpcOpcode::stwux => fmt_x_store(instr, "stwux", false),
|
|
PpcOpcode::stbx => fmt_x_store(instr, "stbx", false),
|
|
PpcOpcode::stbux => fmt_x_store(instr, "stbux", false),
|
|
PpcOpcode::sthx => fmt_x_store(instr, "sthx", false),
|
|
PpcOpcode::sthux => fmt_x_store(instr, "sthux", false),
|
|
PpcOpcode::stdx => fmt_x_store(instr, "stdx", false),
|
|
PpcOpcode::stdux => fmt_x_store(instr, "stdux", false),
|
|
PpcOpcode::stwbrx => fmt_x_store(instr, "stwbrx", false),
|
|
PpcOpcode::sthbrx => fmt_x_store(instr, "sthbrx", false),
|
|
PpcOpcode::stdbrx => fmt_x_store(instr, "stdbrx", false),
|
|
PpcOpcode::stwcx => fmt_x_store(instr, "stwcx.", false),
|
|
PpcOpcode::stdcx => fmt_x_store(instr, "stdcx.", false),
|
|
PpcOpcode::stswx => fmt_x_store(instr, "stswx", false),
|
|
PpcOpcode::stswi => fmt_lswi_stswi(instr, "stswi"),
|
|
PpcOpcode::stfsx => fmt_x_store(instr, "stfsx", true),
|
|
PpcOpcode::stfsux => fmt_x_store(instr, "stfsux", true),
|
|
PpcOpcode::stfdx => fmt_x_store(instr, "stfdx", true),
|
|
PpcOpcode::stfdux => fmt_x_store(instr, "stfdux", true),
|
|
PpcOpcode::stfiwx => fmt_x_store(instr, "stfiwx", true),
|
|
|
|
// ── Cache / sync ────────────────────────────────────────────────────
|
|
PpcOpcode::dcbf => fmt_cache(instr, "dcbf"),
|
|
PpcOpcode::dcbi => fmt_cache(instr, "dcbi"),
|
|
PpcOpcode::dcbst => fmt_cache(instr, "dcbst"),
|
|
PpcOpcode::dcbt => fmt_cache(instr, "dcbt"),
|
|
PpcOpcode::dcbtst => fmt_cache(instr, "dcbtst"),
|
|
PpcOpcode::dcbz => fmt_cache(instr, "dcbz"),
|
|
PpcOpcode::dcbz128 => fmt_cache(instr, "dcbz128"),
|
|
PpcOpcode::icbi => fmt_cache(instr, "icbi"),
|
|
PpcOpcode::sync => base("sync", String::new(), 0),
|
|
PpcOpcode::eieio => base("eieio", String::new(), 0),
|
|
PpcOpcode::isync => base("isync", String::new(), 0),
|
|
|
|
// ── CR logical ──────────────────────────────────────────────────────
|
|
PpcOpcode::crand => fmt_cr_logic(instr, "crand"),
|
|
PpcOpcode::crandc => fmt_cr_logic(instr, "crandc"),
|
|
PpcOpcode::creqv => fmt_creqv(instr),
|
|
PpcOpcode::crnand => fmt_cr_logic(instr, "crnand"),
|
|
PpcOpcode::crnor => fmt_crnor(instr),
|
|
PpcOpcode::cror => fmt_cror(instr),
|
|
PpcOpcode::crorc => fmt_cr_logic(instr, "crorc"),
|
|
PpcOpcode::crxor => fmt_crxor(instr),
|
|
|
|
// ── FPU (op59 / op63) ──────────────────────────────────────────────
|
|
PpcOpcode::fdivsx => fmt_a_3op(instr, "fdivs", false),
|
|
PpcOpcode::fsubsx => fmt_a_3op(instr, "fsubs", false),
|
|
PpcOpcode::faddsx => fmt_a_3op(instr, "fadds", false),
|
|
PpcOpcode::fsqrtsx => fmt_a_unary(instr, "fsqrts"),
|
|
PpcOpcode::fresx => fmt_a_unary(instr, "fres"),
|
|
PpcOpcode::fmulsx => fmt_a_3op(instr, "fmuls", true),
|
|
PpcOpcode::fmsubsx => fmt_a_4op(instr, "fmsubs"),
|
|
PpcOpcode::fmaddsx => fmt_a_4op(instr, "fmadds"),
|
|
PpcOpcode::fnmsubsx => fmt_a_4op(instr, "fnmsubs"),
|
|
PpcOpcode::fnmaddsx => fmt_a_4op(instr, "fnmadds"),
|
|
|
|
PpcOpcode::fdivx => fmt_a_3op(instr, "fdiv", false),
|
|
PpcOpcode::fsubx => fmt_a_3op(instr, "fsub", false),
|
|
PpcOpcode::faddx => fmt_a_3op(instr, "fadd", false),
|
|
PpcOpcode::fsqrtx => fmt_a_unary(instr, "fsqrt"),
|
|
PpcOpcode::fselx => fmt_a_4op(instr, "fsel"),
|
|
PpcOpcode::fmulx => fmt_a_3op(instr, "fmul", true),
|
|
PpcOpcode::frsqrtex => fmt_a_unary(instr, "frsqrte"),
|
|
PpcOpcode::fmsubx => fmt_a_4op(instr, "fmsub"),
|
|
PpcOpcode::fmaddx => fmt_a_4op(instr, "fmadd"),
|
|
PpcOpcode::fnmsubx => fmt_a_4op(instr, "fnmsub"),
|
|
PpcOpcode::fnmaddx => fmt_a_4op(instr, "fnmadd"),
|
|
|
|
PpcOpcode::fcmpu => fmt_fcmp(instr, "fcmpu"),
|
|
PpcOpcode::fcmpo => fmt_fcmp(instr, "fcmpo"),
|
|
PpcOpcode::frspx => fmt_x_fpu_unary(instr, "frsp"),
|
|
PpcOpcode::fctiwx => fmt_x_fpu_unary(instr, "fctiw"),
|
|
PpcOpcode::fctiwzx => fmt_x_fpu_unary(instr, "fctiwz"),
|
|
PpcOpcode::fnegx => fmt_x_fpu_unary(instr, "fneg"),
|
|
PpcOpcode::fmrx => fmt_x_fpu_unary(instr, "fmr"),
|
|
PpcOpcode::fnabsx => fmt_x_fpu_unary(instr, "fnabs"),
|
|
PpcOpcode::fabsx => fmt_x_fpu_unary(instr, "fabs"),
|
|
PpcOpcode::fctidx => fmt_x_fpu_unary(instr, "fctid"),
|
|
PpcOpcode::fctidzx => fmt_x_fpu_unary(instr, "fctidz"),
|
|
PpcOpcode::fcfidx => fmt_x_fpu_unary(instr, "fcfid"),
|
|
PpcOpcode::mffsx => {
|
|
let rc = rc_dot(instr);
|
|
base(&format!("mffs{rc}"), fpr(instr.rd()), 8)
|
|
}
|
|
PpcOpcode::mtfsfx => {
|
|
let rc = rc_dot(instr);
|
|
let fxm = (instr.raw >> 17) & 0xFF;
|
|
let frb = (instr.raw >> 11) & 0x1F;
|
|
base(&format!("mtfsf{rc}"), format!("0x{fxm:02X}, {}", fpr(frb as usize)), 8)
|
|
}
|
|
PpcOpcode::mtfsb1x => fmt_mtfsb(instr, "mtfsb1"),
|
|
PpcOpcode::mtfsb0x => fmt_mtfsb(instr, "mtfsb0"),
|
|
PpcOpcode::mtfsfix => {
|
|
let rc = rc_dot(instr);
|
|
let bf = instr.crfd();
|
|
let imm = (instr.raw >> 12) & 0xF;
|
|
base(&format!("mtfsfi{rc}"), format!("cr{bf}, {imm}"), 8)
|
|
}
|
|
PpcOpcode::mcrfs => base("mcrfs", format!("cr{}, cr{}", instr.crfd(), instr.crfs()), 8),
|
|
|
|
// ── Standard VMX (5-bit registers) ────────────────────────────────
|
|
// 3-operand VD, VA, VB
|
|
PpcOpcode::vaddubm | PpcOpcode::vmaxub | PpcOpcode::vrlb | PpcOpcode::vmuloub |
|
|
PpcOpcode::vaddfp | PpcOpcode::vmrghb | PpcOpcode::vpkuhum |
|
|
PpcOpcode::vadduhm | PpcOpcode::vmaxuh | PpcOpcode::vrlh | PpcOpcode::vmulouh |
|
|
PpcOpcode::vsubfp | PpcOpcode::vmrghh | PpcOpcode::vpkuwum |
|
|
PpcOpcode::vadduwm | PpcOpcode::vmaxuw | PpcOpcode::vrlw | PpcOpcode::vmrghw |
|
|
PpcOpcode::vpkuhus | PpcOpcode::vpkuwus |
|
|
PpcOpcode::vmaxsb | PpcOpcode::vslb | PpcOpcode::vmulosb | PpcOpcode::vmrglb |
|
|
PpcOpcode::vpkshus | PpcOpcode::vmaxsh | PpcOpcode::vslh | PpcOpcode::vmulosh |
|
|
PpcOpcode::vmrglh | PpcOpcode::vpkswus | PpcOpcode::vaddcuw | PpcOpcode::vmaxsw |
|
|
PpcOpcode::vslw | PpcOpcode::vmrglw | PpcOpcode::vpkshss | PpcOpcode::vsl |
|
|
PpcOpcode::vpkswss | PpcOpcode::vaddubs | PpcOpcode::vminub | PpcOpcode::vsrb |
|
|
PpcOpcode::vmuleub | PpcOpcode::vadduhs | PpcOpcode::vminuh | PpcOpcode::vsrh |
|
|
PpcOpcode::vmuleuh | PpcOpcode::vadduws | PpcOpcode::vminuw | PpcOpcode::vsrw |
|
|
PpcOpcode::vsr | PpcOpcode::vaddsbs | PpcOpcode::vminsb | PpcOpcode::vsrab |
|
|
PpcOpcode::vmulesb | PpcOpcode::vpkpx | PpcOpcode::vaddshs | PpcOpcode::vminsh |
|
|
PpcOpcode::vsrah | PpcOpcode::vmulesh | PpcOpcode::vaddsws | PpcOpcode::vminsw |
|
|
PpcOpcode::vsraw | PpcOpcode::vsububm | PpcOpcode::vavgub | PpcOpcode::vand |
|
|
PpcOpcode::vmaxfp | PpcOpcode::vslo | PpcOpcode::vsubuhm | PpcOpcode::vavguh |
|
|
PpcOpcode::vandc | PpcOpcode::vminfp | PpcOpcode::vsro | PpcOpcode::vsubuwm |
|
|
PpcOpcode::vavguw | PpcOpcode::vor | PpcOpcode::vxor | PpcOpcode::vavgsb |
|
|
PpcOpcode::vnor | PpcOpcode::vavgsh | PpcOpcode::vsubcuw | PpcOpcode::vavgsw |
|
|
PpcOpcode::vsububs | PpcOpcode::vsum4ubs| PpcOpcode::vsubuhs | PpcOpcode::vsum4shs |
|
|
PpcOpcode::vsubuws | PpcOpcode::vsum2sws| PpcOpcode::vsubsbs | PpcOpcode::vsum4sbs |
|
|
PpcOpcode::vsubshs | PpcOpcode::vsubsws | PpcOpcode::vsumsws => {
|
|
fmt_vmx_3op(instr, opcode_name(instr.opcode))
|
|
}
|
|
|
|
// VMX unary VD, VB
|
|
PpcOpcode::vrefp | PpcOpcode::vrsqrtefp | PpcOpcode::vexptefp |
|
|
PpcOpcode::vlogefp | PpcOpcode::vrfin | PpcOpcode::vrfiz |
|
|
PpcOpcode::vrfip | PpcOpcode::vrfim | PpcOpcode::vupkhsb |
|
|
PpcOpcode::vupkhsh | PpcOpcode::vupklsb | PpcOpcode::vupklsh |
|
|
PpcOpcode::vupkhpx | PpcOpcode::vupklpx => {
|
|
fmt_vmx_unary(instr, opcode_name(instr.opcode))
|
|
}
|
|
|
|
// VMX VD, VB, UIMM (VA = uimm field)
|
|
PpcOpcode::vspltb | PpcOpcode::vsplth | PpcOpcode::vspltw |
|
|
PpcOpcode::vcfux | PpcOpcode::vcfsx |
|
|
PpcOpcode::vctuxs | PpcOpcode::vctsxs => {
|
|
fmt_vmx_uimm(instr, opcode_name(instr.opcode))
|
|
}
|
|
|
|
// VMX VD, SIMM (VA field as 5-bit signed immediate)
|
|
PpcOpcode::vspltisb => fmt_vmx_simm(instr, "vspltisb"),
|
|
PpcOpcode::vspltish => fmt_vmx_simm(instr, "vspltish"),
|
|
PpcOpcode::vspltisw => fmt_vmx_simm(instr, "vspltisw"),
|
|
|
|
PpcOpcode::mfvscr => base("mfvscr", vr(instr.rd()), 8),
|
|
PpcOpcode::mtvscr => base("mtvscr", vr(instr.rb()), 8),
|
|
|
|
// VMX compare (Rc bit at bit 21)
|
|
PpcOpcode::vcmpequb | PpcOpcode::vcmpequh | PpcOpcode::vcmpequw |
|
|
PpcOpcode::vcmpeqfp | PpcOpcode::vcmpgefp | PpcOpcode::vcmpgtub |
|
|
PpcOpcode::vcmpgtuh | PpcOpcode::vcmpgtuw | PpcOpcode::vcmpgtfp |
|
|
PpcOpcode::vcmpgtsb | PpcOpcode::vcmpgtsh | PpcOpcode::vcmpgtsw |
|
|
PpcOpcode::vcmpbfp => fmt_vmx_cmp(instr, opcode_name(instr.opcode)),
|
|
|
|
// VMX 4-operand VD, VA, VB, VC
|
|
PpcOpcode::vmhaddshs | PpcOpcode::vmhraddshs | PpcOpcode::vmladduhm |
|
|
PpcOpcode::vmsumubm | PpcOpcode::vmsummbm | PpcOpcode::vmsumuhm |
|
|
PpcOpcode::vmsumuhs | PpcOpcode::vmsumshm | PpcOpcode::vmsumshs |
|
|
PpcOpcode::vsel | PpcOpcode::vperm => {
|
|
fmt_vmx_4op(instr, opcode_name(instr.opcode))
|
|
}
|
|
|
|
PpcOpcode::vsldoi => fmt_vsldoi(instr),
|
|
PpcOpcode::vmaddfp => fmt_vmx_4op_swap(instr, "vmaddfp"),
|
|
PpcOpcode::vnmsubfp => fmt_vmx_4op_swap(instr, "vnmsubfp"),
|
|
|
|
// ── VMX128 load/store (uses GPR addressing + vd128 dest) ───────────
|
|
PpcOpcode::lvsl128 => fmt_vmx128_ls(instr, "lvsl128"),
|
|
PpcOpcode::lvsr128 => fmt_vmx128_ls(instr, "lvsr128"),
|
|
PpcOpcode::lvewx128 => fmt_vmx128_ls(instr, "lvewx128"),
|
|
PpcOpcode::lvx128 => fmt_vmx128_ls(instr, "lvx128"),
|
|
PpcOpcode::lvxl128 => fmt_vmx128_ls(instr, "lvxl128"),
|
|
PpcOpcode::lvlx128 => fmt_vmx128_ls(instr, "lvlx128"),
|
|
PpcOpcode::lvrx128 => fmt_vmx128_ls(instr, "lvrx128"),
|
|
PpcOpcode::lvlxl128 => fmt_vmx128_ls(instr, "lvlxl128"),
|
|
PpcOpcode::lvrxl128 => fmt_vmx128_ls(instr, "lvrxl128"),
|
|
PpcOpcode::stvewx128 => fmt_vmx128_ls(instr, "stvewx128"),
|
|
PpcOpcode::stvx128 => fmt_vmx128_ls(instr, "stvx128"),
|
|
PpcOpcode::stvxl128 => fmt_vmx128_ls(instr, "stvxl128"),
|
|
PpcOpcode::stvlx128 => fmt_vmx128_ls(instr, "stvlx128"),
|
|
PpcOpcode::stvrx128 => fmt_vmx128_ls(instr, "stvrx128"),
|
|
PpcOpcode::stvlxl128 => fmt_vmx128_ls(instr, "stvlxl128"),
|
|
PpcOpcode::stvrxl128 => fmt_vmx128_ls(instr, "stvrxl128"),
|
|
|
|
// Standard AltiVec load/store indexed (5-bit vr0-vr31)
|
|
PpcOpcode::lvsl => fmt_vmx_ls(instr, "lvsl"),
|
|
PpcOpcode::lvsr => fmt_vmx_ls(instr, "lvsr"),
|
|
PpcOpcode::lvebx => fmt_vmx_ls(instr, "lvebx"),
|
|
PpcOpcode::lvehx => fmt_vmx_ls(instr, "lvehx"),
|
|
PpcOpcode::lvewx => fmt_vmx_ls(instr, "lvewx"),
|
|
PpcOpcode::lvx => fmt_vmx_ls(instr, "lvx"),
|
|
PpcOpcode::lvxl => fmt_vmx_ls(instr, "lvxl"),
|
|
PpcOpcode::lvlx => fmt_vmx_ls(instr, "lvlx"),
|
|
PpcOpcode::lvrx => fmt_vmx_ls(instr, "lvrx"),
|
|
PpcOpcode::lvlxl => fmt_vmx_ls(instr, "lvlxl"),
|
|
PpcOpcode::lvrxl => fmt_vmx_ls(instr, "lvrxl"),
|
|
PpcOpcode::stvebx => fmt_vmx_ls(instr, "stvebx"),
|
|
PpcOpcode::stvehx => fmt_vmx_ls(instr, "stvehx"),
|
|
PpcOpcode::stvewx => fmt_vmx_ls(instr, "stvewx"),
|
|
PpcOpcode::stvx => fmt_vmx_ls(instr, "stvx"),
|
|
PpcOpcode::stvxl => fmt_vmx_ls(instr, "stvxl"),
|
|
PpcOpcode::stvlx => fmt_vmx_ls(instr, "stvlx"),
|
|
PpcOpcode::stvrx => fmt_vmx_ls(instr, "stvrx"),
|
|
PpcOpcode::stvlxl => fmt_vmx_ls(instr, "stvlxl"),
|
|
PpcOpcode::stvrxl => fmt_vmx_ls(instr, "stvrxl"),
|
|
|
|
// ── VMX128 op5 (3-op and 4-op fp/pack/logic) ───────────────────────
|
|
PpcOpcode::vaddfp128 => fmt_vmx128_3op(instr, "vaddfp128"),
|
|
PpcOpcode::vsubfp128 => fmt_vmx128_3op(instr, "vsubfp128"),
|
|
PpcOpcode::vmulfp128 => fmt_vmx128_3op(instr, "vmulfp128"),
|
|
PpcOpcode::vmsum3fp128 => fmt_vmx128_3op(instr, "vmsum3fp128"),
|
|
PpcOpcode::vmsum4fp128 => fmt_vmx128_3op(instr, "vmsum4fp128"),
|
|
PpcOpcode::vpkshss128 => fmt_vmx128_3op(instr, "vpkshss128"),
|
|
PpcOpcode::vpkshus128 => fmt_vmx128_3op(instr, "vpkshus128"),
|
|
PpcOpcode::vpkswss128 => fmt_vmx128_3op(instr, "vpkswss128"),
|
|
PpcOpcode::vpkswus128 => fmt_vmx128_3op(instr, "vpkswus128"),
|
|
PpcOpcode::vpkuhum128 => fmt_vmx128_3op(instr, "vpkuhum128"),
|
|
PpcOpcode::vpkuhus128 => fmt_vmx128_3op(instr, "vpkuhus128"),
|
|
PpcOpcode::vpkuwum128 => fmt_vmx128_3op(instr, "vpkuwum128"),
|
|
PpcOpcode::vpkuwus128 => fmt_vmx128_3op(instr, "vpkuwus128"),
|
|
PpcOpcode::vand128 => fmt_vmx128_3op(instr, "vand128"),
|
|
PpcOpcode::vandc128 => fmt_vmx128_3op(instr, "vandc128"),
|
|
PpcOpcode::vnor128 => fmt_vmx128_3op(instr, "vnor128"),
|
|
PpcOpcode::vor128 => fmt_vmx128_3op(instr, "vor128"),
|
|
PpcOpcode::vxor128 => fmt_vmx128_3op(instr, "vxor128"),
|
|
PpcOpcode::vsel128 => fmt_vmx128_3op(instr, "vsel128"),
|
|
PpcOpcode::vslo128 => fmt_vmx128_3op(instr, "vslo128"),
|
|
PpcOpcode::vsro128 => fmt_vmx128_3op(instr, "vsro128"),
|
|
|
|
PpcOpcode::vmaddfp128 => fmt_vmaddfp128(instr),
|
|
PpcOpcode::vmaddcfp128 => fmt_vmx128_madd_vd_vb(instr, "vmaddcfp128"),
|
|
PpcOpcode::vnmsubfp128 => fmt_vmx128_madd_vd_vb(instr, "vnmsubfp128"),
|
|
|
|
PpcOpcode::vperm128 => fmt_vperm128(instr),
|
|
PpcOpcode::vsldoi128 => fmt_vsldoi128(instr),
|
|
PpcOpcode::vpermwi128 => fmt_vpermwi128(instr),
|
|
|
|
// ── VMX128 op6 special ─────────────────────────────────────────────
|
|
PpcOpcode::vpkd3d128 => fmt_vmx128_pack_d3d(instr, "vpkd3d128"),
|
|
PpcOpcode::vrlimi128 => fmt_vmx128_pack_d3d(instr, "vrlimi128"),
|
|
PpcOpcode::vrfim128 => fmt_vmx128_unary(instr, "vrfim128"),
|
|
PpcOpcode::vrfin128 => fmt_vmx128_unary(instr, "vrfin128"),
|
|
PpcOpcode::vrfip128 => fmt_vmx128_unary(instr, "vrfip128"),
|
|
PpcOpcode::vrfiz128 => fmt_vmx128_unary(instr, "vrfiz128"),
|
|
PpcOpcode::vrefp128 => fmt_vmx128_unary(instr, "vrefp128"),
|
|
PpcOpcode::vrsqrtefp128 => fmt_vmx128_unary(instr, "vrsqrtefp128"),
|
|
PpcOpcode::vexptefp128 => fmt_vmx128_unary(instr, "vexptefp128"),
|
|
PpcOpcode::vlogefp128 => fmt_vmx128_unary(instr, "vlogefp128"),
|
|
PpcOpcode::vcfpsxws128 => fmt_vmx128_uimm(instr, "vcfpsxws128"),
|
|
PpcOpcode::vcfpuxws128 => fmt_vmx128_uimm(instr, "vcfpuxws128"),
|
|
PpcOpcode::vcsxwfp128 => fmt_vmx128_uimm(instr, "vcsxwfp128"),
|
|
PpcOpcode::vcuxwfp128 => fmt_vmx128_uimm(instr, "vcuxwfp128"),
|
|
PpcOpcode::vspltw128 => fmt_vmx128_uimm(instr, "vspltw128"),
|
|
PpcOpcode::vupkd3d128 => fmt_vmx128_uimm(instr, "vupkd3d128"),
|
|
PpcOpcode::vspltisw128 => {
|
|
let vd = instr.vd128();
|
|
let simm = sign_ext(extract_vx128_uimm5(instr.raw), 5);
|
|
base("vspltisw128", format!("{}, {simm}", vr(vd)), 14)
|
|
}
|
|
PpcOpcode::vcmpeqfp128 => fmt_vmx128_cmp(instr, "vcmpeqfp128"),
|
|
PpcOpcode::vcmpgefp128 => fmt_vmx128_cmp(instr, "vcmpgefp128"),
|
|
PpcOpcode::vcmpgtfp128 => fmt_vmx128_cmp(instr, "vcmpgtfp128"),
|
|
PpcOpcode::vcmpbfp128 => fmt_vmx128_cmp(instr, "vcmpbfp128"),
|
|
PpcOpcode::vcmpequw128 => fmt_vmx128_cmp(instr, "vcmpequw128"),
|
|
PpcOpcode::vrlw128 => fmt_vmx128_3op(instr, "vrlw128"),
|
|
PpcOpcode::vslw128 => fmt_vmx128_3op(instr, "vslw128"),
|
|
PpcOpcode::vsraw128 => fmt_vmx128_3op(instr, "vsraw128"),
|
|
PpcOpcode::vsrw128 => fmt_vmx128_3op(instr, "vsrw128"),
|
|
PpcOpcode::vmaxfp128 => fmt_vmx128_3op(instr, "vmaxfp128"),
|
|
PpcOpcode::vminfp128 => fmt_vmx128_3op(instr, "vminfp128"),
|
|
PpcOpcode::vmrghw128 => fmt_vmx128_3op(instr, "vmrghw128"),
|
|
PpcOpcode::vmrglw128 => fmt_vmx128_3op(instr, "vmrglw128"),
|
|
PpcOpcode::vupkhsb128 => fmt_vmx128_3op(instr, "vupkhsb128"),
|
|
PpcOpcode::vupklsb128 => fmt_vmx128_3op(instr, "vupklsb128"),
|
|
|
|
PpcOpcode::Invalid => long_word(instr.raw),
|
|
}
|
|
}
|
|
|
|
/// Disassemble a decoded instruction into PPC assembly text.
|
|
///
|
|
/// Back-compat entry point: returns the same single-string the legacy
|
|
/// formatter produced, preferring the extended form when present.
|
|
pub fn disassemble(instr: &DecodedInstr) -> String {
|
|
format(instr).display().to_string()
|
|
}
|
|
|
|
/// Disassemble a range of instructions from a byte slice.
|
|
pub fn disassemble_block(data: &[u8], base_addr: u32, count: usize) -> Vec<(u32, String)> {
|
|
let mut result = Vec::new();
|
|
for i in 0..count {
|
|
let offset = i * 4;
|
|
if offset + 4 > data.len() {
|
|
break;
|
|
}
|
|
let raw = u32::from_be_bytes([
|
|
data[offset], data[offset + 1], data[offset + 2], data[offset + 3],
|
|
]);
|
|
let addr = base_addr + offset as u32;
|
|
let instr = crate::decoder::decode(raw, addr);
|
|
let text = disassemble(&instr);
|
|
result.push((addr, text));
|
|
}
|
|
result
|
|
}
|
|
|
|
/// One yielded instruction from [`iter_disasm`]. Carries the absolute VA,
|
|
/// raw word, decoded opcode and the formatted text — everything a sink
|
|
/// needs to render or persist a single row without re-parsing.
|
|
#[derive(Debug, Clone)]
|
|
pub struct DisasmItem {
|
|
pub addr: u32,
|
|
pub raw: u32,
|
|
pub opcode: PpcOpcode,
|
|
pub text: DisasmText,
|
|
}
|
|
|
|
/// Iterate over instructions in the VA range `[va_start, va_end)` of an
|
|
/// image-mapped byte slice. `image[rva]` must hold the byte at absolute VA
|
|
/// `image_base + rva` (the layout produced by [`xenia_xex::loader`]).
|
|
///
|
|
/// Stops on a truncated tail (less than 4 bytes remaining at the cursor).
|
|
/// Yields nothing if `va_start >= va_end` or the start RVA is beyond the
|
|
/// image.
|
|
pub fn iter_disasm(
|
|
image: &[u8],
|
|
image_base: u32,
|
|
va_start: u32,
|
|
va_end: u32,
|
|
) -> impl Iterator<Item = DisasmItem> + '_ {
|
|
DisasmIter { image, image_base, va: va_start, end: va_end }
|
|
}
|
|
|
|
struct DisasmIter<'a> {
|
|
image: &'a [u8],
|
|
image_base: u32,
|
|
va: u32,
|
|
end: u32,
|
|
}
|
|
|
|
impl Iterator for DisasmIter<'_> {
|
|
type Item = DisasmItem;
|
|
#[inline]
|
|
fn next(&mut self) -> Option<DisasmItem> {
|
|
if self.va >= self.end {
|
|
return None;
|
|
}
|
|
let rva = self.va.wrapping_sub(self.image_base) as usize;
|
|
if rva + 4 > self.image.len() {
|
|
return None;
|
|
}
|
|
let raw = u32::from_be_bytes([
|
|
self.image[rva],
|
|
self.image[rva + 1],
|
|
self.image[rva + 2],
|
|
self.image[rva + 3],
|
|
]);
|
|
let abs = self.va;
|
|
let decoded = crate::decoder::decode(raw, abs);
|
|
let text = format(&decoded);
|
|
self.va = self.va.wrapping_add(4);
|
|
Some(DisasmItem { addr: abs, raw, opcode: decoded.opcode, text })
|
|
}
|
|
}
|
|
|
|
// ── Per-class formatters ───────────────────────────────────────────────────
|
|
|
|
fn opcode_name(op: PpcOpcode) -> &'static str {
|
|
// Used for VMX where the enum variant name matches the canonical mnemonic.
|
|
// For ALU/FPU variants ending in "x", use hardcoded strings instead.
|
|
match op {
|
|
PpcOpcode::vaddubm => "vaddubm", PpcOpcode::vmaxub => "vmaxub", PpcOpcode::vrlb => "vrlb",
|
|
PpcOpcode::vmuloub => "vmuloub", PpcOpcode::vaddfp => "vaddfp", PpcOpcode::vmrghb => "vmrghb",
|
|
PpcOpcode::vpkuhum => "vpkuhum", PpcOpcode::vadduhm => "vadduhm", PpcOpcode::vmaxuh => "vmaxuh",
|
|
PpcOpcode::vrlh => "vrlh", PpcOpcode::vmulouh => "vmulouh", PpcOpcode::vsubfp => "vsubfp",
|
|
PpcOpcode::vmrghh => "vmrghh", PpcOpcode::vpkuwum => "vpkuwum",
|
|
PpcOpcode::vadduwm => "vadduwm", PpcOpcode::vmaxuw => "vmaxuw", PpcOpcode::vrlw => "vrlw",
|
|
PpcOpcode::vmrghw => "vmrghw", PpcOpcode::vpkuhus => "vpkuhus", PpcOpcode::vpkuwus => "vpkuwus",
|
|
PpcOpcode::vmaxsb => "vmaxsb", PpcOpcode::vslb => "vslb", PpcOpcode::vmulosb => "vmulosb",
|
|
PpcOpcode::vmrglb => "vmrglb", PpcOpcode::vpkshus => "vpkshus", PpcOpcode::vmaxsh => "vmaxsh",
|
|
PpcOpcode::vslh => "vslh", PpcOpcode::vmulosh => "vmulosh", PpcOpcode::vmrglh => "vmrglh",
|
|
PpcOpcode::vpkswus => "vpkswus", PpcOpcode::vaddcuw => "vaddcuw", PpcOpcode::vmaxsw => "vmaxsw",
|
|
PpcOpcode::vslw => "vslw", PpcOpcode::vmrglw => "vmrglw", PpcOpcode::vpkshss => "vpkshss",
|
|
PpcOpcode::vsl => "vsl", PpcOpcode::vpkswss => "vpkswss",
|
|
PpcOpcode::vaddubs => "vaddubs", PpcOpcode::vminub => "vminub", PpcOpcode::vsrb => "vsrb",
|
|
PpcOpcode::vmuleub => "vmuleub", PpcOpcode::vadduhs => "vadduhs", PpcOpcode::vminuh => "vminuh",
|
|
PpcOpcode::vsrh => "vsrh", PpcOpcode::vmuleuh => "vmuleuh",
|
|
PpcOpcode::vadduws => "vadduws", PpcOpcode::vminuw => "vminuw", PpcOpcode::vsrw => "vsrw",
|
|
PpcOpcode::vsr => "vsr",
|
|
PpcOpcode::vaddsbs => "vaddsbs", PpcOpcode::vminsb => "vminsb", PpcOpcode::vsrab => "vsrab",
|
|
PpcOpcode::vmulesb => "vmulesb", PpcOpcode::vpkpx => "vpkpx",
|
|
PpcOpcode::vaddshs => "vaddshs", PpcOpcode::vminsh => "vminsh", PpcOpcode::vsrah => "vsrah",
|
|
PpcOpcode::vmulesh => "vmulesh",
|
|
PpcOpcode::vaddsws => "vaddsws", PpcOpcode::vminsw => "vminsw", PpcOpcode::vsraw => "vsraw",
|
|
PpcOpcode::vsububm => "vsububm", PpcOpcode::vavgub => "vavgub", PpcOpcode::vand => "vand",
|
|
PpcOpcode::vmaxfp => "vmaxfp", PpcOpcode::vslo => "vslo",
|
|
PpcOpcode::vsubuhm => "vsubuhm", PpcOpcode::vavguh => "vavguh", PpcOpcode::vandc => "vandc",
|
|
PpcOpcode::vminfp => "vminfp", PpcOpcode::vsro => "vsro",
|
|
PpcOpcode::vsubuwm => "vsubuwm", PpcOpcode::vavguw => "vavguw", PpcOpcode::vor => "vor",
|
|
PpcOpcode::vxor => "vxor", PpcOpcode::vavgsb => "vavgsb", PpcOpcode::vnor => "vnor",
|
|
PpcOpcode::vavgsh => "vavgsh", PpcOpcode::vsubcuw => "vsubcuw", PpcOpcode::vavgsw => "vavgsw",
|
|
PpcOpcode::vsububs => "vsububs", PpcOpcode::vsum4ubs => "vsum4ubs",
|
|
PpcOpcode::vsubuhs => "vsubuhs", PpcOpcode::vsum4shs => "vsum4shs",
|
|
PpcOpcode::vsubuws => "vsubuws", PpcOpcode::vsum2sws => "vsum2sws",
|
|
PpcOpcode::vsubsbs => "vsubsbs", PpcOpcode::vsum4sbs => "vsum4sbs",
|
|
PpcOpcode::vsubshs => "vsubshs", PpcOpcode::vsubsws => "vsubsws",
|
|
PpcOpcode::vsumsws => "vsumsws",
|
|
|
|
PpcOpcode::vrefp => "vrefp", PpcOpcode::vrsqrtefp => "vrsqrtefp",
|
|
PpcOpcode::vexptefp => "vexptefp", PpcOpcode::vlogefp => "vlogefp",
|
|
PpcOpcode::vrfin => "vrfin", PpcOpcode::vrfiz => "vrfiz",
|
|
PpcOpcode::vrfip => "vrfip", PpcOpcode::vrfim => "vrfim",
|
|
PpcOpcode::vupkhsb => "vupkhsb", PpcOpcode::vupkhsh => "vupkhsh",
|
|
PpcOpcode::vupklsb => "vupklsb", PpcOpcode::vupklsh => "vupklsh",
|
|
PpcOpcode::vupkhpx => "vupkhpx", PpcOpcode::vupklpx => "vupklpx",
|
|
|
|
PpcOpcode::vspltb => "vspltb", PpcOpcode::vsplth => "vsplth", PpcOpcode::vspltw => "vspltw",
|
|
PpcOpcode::vcfux => "vcfux", PpcOpcode::vcfsx => "vcfsx",
|
|
PpcOpcode::vctuxs => "vctuxs", PpcOpcode::vctsxs => "vctsxs",
|
|
|
|
PpcOpcode::vcmpequb => "vcmpequb", PpcOpcode::vcmpequh => "vcmpequh",
|
|
PpcOpcode::vcmpequw => "vcmpequw", PpcOpcode::vcmpeqfp => "vcmpeqfp",
|
|
PpcOpcode::vcmpgefp => "vcmpgefp", PpcOpcode::vcmpgtub => "vcmpgtub",
|
|
PpcOpcode::vcmpgtuh => "vcmpgtuh", PpcOpcode::vcmpgtuw => "vcmpgtuw",
|
|
PpcOpcode::vcmpgtfp => "vcmpgtfp", PpcOpcode::vcmpgtsb => "vcmpgtsb",
|
|
PpcOpcode::vcmpgtsh => "vcmpgtsh", PpcOpcode::vcmpgtsw => "vcmpgtsw",
|
|
PpcOpcode::vcmpbfp => "vcmpbfp",
|
|
|
|
PpcOpcode::vmhaddshs => "vmhaddshs", PpcOpcode::vmhraddshs => "vmhraddshs",
|
|
PpcOpcode::vmladduhm => "vmladduhm",
|
|
PpcOpcode::vmsumubm => "vmsumubm", PpcOpcode::vmsummbm => "vmsummbm",
|
|
PpcOpcode::vmsumuhm => "vmsumuhm", PpcOpcode::vmsumuhs => "vmsumuhs",
|
|
PpcOpcode::vmsumshm => "vmsumshm", PpcOpcode::vmsumshs => "vmsumshs",
|
|
PpcOpcode::vsel => "vsel", PpcOpcode::vperm => "vperm",
|
|
_ => "?",
|
|
}
|
|
}
|
|
|
|
// Branches (I-form: b/bl/ba/bla) — produces base + extended forms.
|
|
fn fmt_b(instr: &DecodedInstr) -> DisasmText {
|
|
let aa = instr.aa();
|
|
let lk = instr.lk();
|
|
let target = if aa { instr.li() as u32 }
|
|
else { instr.addr.wrapping_add(instr.li() as u32) };
|
|
let mnem = match (aa, lk) {
|
|
(false, false) => "b",
|
|
(false, true) => "bl",
|
|
(true, false) => "ba",
|
|
(true, true) => "bla",
|
|
};
|
|
let ops = format!("0x{target:08X}");
|
|
with_target(base(mnem, ops, 8), target)
|
|
}
|
|
|
|
fn fmt_bc(instr: &DecodedInstr) -> DisasmText {
|
|
let bo = instr.bo();
|
|
let bi = instr.bi();
|
|
let aa = instr.aa();
|
|
let lk = instr.lk();
|
|
let target = if aa { instr.bd() as u32 }
|
|
else { instr.addr.wrapping_add(instr.bd() as u32) };
|
|
|
|
let a = if aa { "a" } else { "" };
|
|
let l = if lk { "l" } else { "" };
|
|
let base_mnem = format!("bc{a}{l}");
|
|
let base_ops = format!("{bo}, {}, 0x{target:08X}", crb(bi));
|
|
|
|
// Extended forms.
|
|
let cr_field = bi / 4;
|
|
let cr_bit = bi % 4;
|
|
let decr = bo & 0x04 == 0;
|
|
let uncond = bo & 0x10 != 0;
|
|
|
|
let result = if uncond && !decr {
|
|
// Unconditional branch.
|
|
let ext_mnem = format!("b{a}{l}");
|
|
let ext_ops = format!("0x{target:08X}");
|
|
with_ext(&base_mnem, base_ops, 8, &ext_mnem, ext_ops, 8)
|
|
} else {
|
|
let cond_true = bo & 0x08 != 0;
|
|
let cond_name_opt: Option<&'static str> = match (cr_bit, cond_true) {
|
|
(0, true) => Some("lt"), (0, false) => Some("ge"),
|
|
(1, true) => Some("gt"), (1, false) => Some("le"),
|
|
(2, true) => Some("eq"), (2, false) => Some("ne"),
|
|
(3, true) => Some("so"), (3, false) => Some("ns"),
|
|
_ => None,
|
|
};
|
|
let cr = if cr_field == 0 { String::new() } else { format!("cr{cr_field}, ") };
|
|
|
|
if decr {
|
|
let z = if bo & 0x02 != 0 { "z" } else { "nz" };
|
|
let cond_str = cond_name_opt.unwrap_or("");
|
|
let ext_mnem = format!("bd{z}{cond_str}{a}{l}");
|
|
let ext_ops = format!("{cr}0x{target:08X}");
|
|
with_ext(&base_mnem, base_ops, 8, &ext_mnem, ext_ops, 8)
|
|
} else if let Some(cond_name) = cond_name_opt {
|
|
let ext_mnem = format!("b{cond_name}{a}{l}");
|
|
let ext_ops = format!("{cr}0x{target:08X}");
|
|
with_ext(&base_mnem, base_ops, 8, &ext_mnem, ext_ops, 8)
|
|
} else {
|
|
base(&base_mnem, base_ops, 8)
|
|
}
|
|
};
|
|
with_target(result, target)
|
|
}
|
|
|
|
fn fmt_bclr(instr: &DecodedInstr) -> DisasmText {
|
|
let bo = instr.bo();
|
|
let bi = instr.bi();
|
|
let lk = instr.lk();
|
|
let l = if lk { "l" } else { "" };
|
|
let base_mnem = format!("bclr{l}");
|
|
let base_ops = format!("{bo}, {}", crb(bi));
|
|
|
|
// BO=20 (binary 10100) sets both "ignore CTR" and "ignore CR" bits, making
|
|
// the branch unconditional regardless of BI. BI is don't-care by spec, so
|
|
// the simplified `blr`/`blrl` form applies for any BI value.
|
|
if bo == 20 {
|
|
let ext = if lk { "blrl" } else { "blr" };
|
|
return with_ext(&base_mnem, base_ops, 8, ext, String::new(), 0);
|
|
}
|
|
if let Some((cond, cr)) = cond_branch_ext(bo, bi) {
|
|
let cr_no_comma = cr.trim_end_matches(", ");
|
|
let ext_mnem = format!("b{cond}lr{l}");
|
|
if cr_no_comma.is_empty() {
|
|
return with_ext(&base_mnem, base_ops, 8, &ext_mnem, String::new(), 0);
|
|
} else {
|
|
return with_ext(&base_mnem, base_ops, 8, &ext_mnem, cr_no_comma.to_string(), 8);
|
|
}
|
|
}
|
|
let decr = bo & 0x04 == 0;
|
|
let uncond = bo & 0x10 != 0;
|
|
if decr && uncond {
|
|
let z = if bo & 0x02 != 0 { "z" } else { "nz" };
|
|
let ext_mnem = format!("bd{z}lr{l}");
|
|
return with_ext(&base_mnem, base_ops, 8, &ext_mnem, String::new(), 0);
|
|
}
|
|
base(&base_mnem, base_ops, 8)
|
|
}
|
|
|
|
fn fmt_bcctr(instr: &DecodedInstr) -> DisasmText {
|
|
let bo = instr.bo();
|
|
let bi = instr.bi();
|
|
let lk = instr.lk();
|
|
let l = if lk { "l" } else { "" };
|
|
let base_mnem = format!("bcctr{l}");
|
|
let base_ops = format!("{bo}, {}", crb(bi));
|
|
|
|
// BO=20 unconditional pattern: BI is don't-care (see fmt_bclr).
|
|
if bo == 20 {
|
|
let ext = if lk { "bctrl" } else { "bctr" };
|
|
return with_ext(&base_mnem, base_ops, 8, ext, String::new(), 0);
|
|
}
|
|
if let Some((cond, cr)) = cond_branch_ext(bo, bi) {
|
|
let cr_no_comma = cr.trim_end_matches(", ");
|
|
let ext_mnem = format!("b{cond}ctr{l}");
|
|
if cr_no_comma.is_empty() {
|
|
return with_ext(&base_mnem, base_ops, 8, &ext_mnem, String::new(), 0);
|
|
} else {
|
|
return with_ext(&base_mnem, base_ops, 8, &ext_mnem, cr_no_comma.to_string(), 8);
|
|
}
|
|
}
|
|
base(&base_mnem, base_ops, 8)
|
|
}
|
|
|
|
// Trap immediate / register
|
|
fn fmt_trap_imm(instr: &DecodedInstr, mnem: &str, simplified_prefix: &str) -> DisasmText {
|
|
let to = instr.to();
|
|
let ra = instr.ra();
|
|
let imm = instr.simm16() as i32;
|
|
let base_ops = format!("{to}, {}, {imm}", gpr(ra));
|
|
if let Some(cond) = trap_cond(to) {
|
|
if cond.is_empty() {
|
|
base(mnem, base_ops, 8)
|
|
} else {
|
|
let ext_mnem = format!("{simplified_prefix}{cond}i");
|
|
let ext_ops = format!("{}, {imm}", gpr(ra));
|
|
with_ext(mnem, base_ops, 8, &ext_mnem, ext_ops, 8)
|
|
}
|
|
} else {
|
|
base(mnem, base_ops, 8)
|
|
}
|
|
}
|
|
|
|
fn fmt_trap_reg(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let to = instr.to();
|
|
let ra = instr.ra();
|
|
let rb = instr.rb();
|
|
let base_ops = format!("{to}, {}, {}", gpr(ra), gpr(rb));
|
|
if to == 31 && ra == 0 && rb == 0 {
|
|
return with_ext(mnem, base_ops, 8, "trap", String::new(), 0);
|
|
}
|
|
if let Some(cond) = trap_cond(to)
|
|
&& !cond.is_empty()
|
|
{
|
|
let ext_mnem = format!("{mnem}{cond}");
|
|
let ext_ops = format!("{}, {}", gpr(ra), gpr(rb));
|
|
return with_ext(mnem, base_ops, 8, &ext_mnem, ext_ops, 8);
|
|
}
|
|
base(mnem, base_ops, 8)
|
|
}
|
|
|
|
// D-form ALU
|
|
fn fmt_addi(instr: &DecodedInstr) -> DisasmText {
|
|
let rt = instr.rd();
|
|
let ra = instr.ra();
|
|
let imm = instr.simm16() as i32;
|
|
let base_ops = format!("{}, {}, {imm}", gpr(rt), gpr(ra));
|
|
if ra == 0 {
|
|
with_ext("addi", base_ops, 8, "li", format!("{}, {imm}", gpr(rt)), 8)
|
|
} else if imm < 0 {
|
|
with_ext("addi", base_ops, 8, "subi", format!("{}, {}, {}", gpr(rt), gpr(ra), -imm), 8)
|
|
} else {
|
|
base("addi", base_ops, 8)
|
|
}
|
|
}
|
|
|
|
fn fmt_addis(instr: &DecodedInstr) -> DisasmText {
|
|
let rt = instr.rd();
|
|
let ra = instr.ra();
|
|
let imm = instr.simm16() as i32;
|
|
let imm_u = imm as u16 as u32;
|
|
let base_ops = format!("{}, {}, 0x{imm_u:X}", gpr(rt), gpr(ra));
|
|
if ra == 0 {
|
|
with_ext("addis", base_ops, 8, "lis", format!("{}, 0x{imm_u:X}", gpr(rt)), 8)
|
|
} else if imm < 0 {
|
|
let neg = (-imm) as u16 as u32;
|
|
with_ext("addis", base_ops, 8, "subis", format!("{}, {}, 0x{neg:X}", gpr(rt), gpr(ra)), 8)
|
|
} else {
|
|
base("addis", base_ops, 8)
|
|
}
|
|
}
|
|
|
|
fn fmt_d_add(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let rt = instr.rd();
|
|
let ra = instr.ra();
|
|
let imm = instr.simm16() as i32;
|
|
let base_ops = format!("{}, {}, {imm}", gpr(rt), gpr(ra));
|
|
if imm < 0 {
|
|
let ext_mnem = mnem.replace("addic", "subic");
|
|
with_ext(mnem, base_ops, 8, &ext_mnem, format!("{}, {}, {}", gpr(rt), gpr(ra), -imm), 8)
|
|
} else {
|
|
base(mnem, base_ops, 8)
|
|
}
|
|
}
|
|
|
|
fn fmt_d_imm_simple(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let rt = instr.rd();
|
|
let ra = instr.ra();
|
|
let imm = instr.simm16() as i32;
|
|
base(mnem, format!("{}, {}, {imm}", gpr(rt), gpr(ra)), 8)
|
|
}
|
|
|
|
fn fmt_cmp_imm(instr: &DecodedInstr, mnem: &str, signed: bool) -> DisasmText {
|
|
let bf = instr.crfd();
|
|
let l_bit = if instr.l() { 1 } else { 0 };
|
|
let ra = instr.ra();
|
|
let imm_str = if signed {
|
|
format!("{}", instr.simm16() as i32)
|
|
} else {
|
|
format!("0x{:X}", instr.uimm16())
|
|
};
|
|
let cr = if bf == 0 { String::new() } else { format!("cr{bf}, ") };
|
|
let base_ops = format!("{cr}{l_bit}, {}, {imm_str}", gpr(ra));
|
|
|
|
let size = if l_bit == 0 { "w" } else { "d" };
|
|
let ext_mnem = if mnem == "cmpi" {
|
|
format!("cmp{size}i")
|
|
} else {
|
|
format!("cmpl{size}i")
|
|
};
|
|
let ext_ops = format!("{cr}{}, {imm_str}", gpr(ra));
|
|
with_ext(mnem, base_ops, 8, &ext_mnem, ext_ops, 8)
|
|
}
|
|
|
|
fn fmt_cmp_reg(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let bf = instr.crfd();
|
|
let l_bit = if instr.l() { 1 } else { 0 };
|
|
let ra = instr.ra();
|
|
let rb = instr.rb();
|
|
let cr = if bf == 0 { String::new() } else { format!("cr{bf}, ") };
|
|
let base_ops = format!("{cr}{l_bit}, {}, {}", gpr(ra), gpr(rb));
|
|
let size = if l_bit == 0 { "w" } else { "d" };
|
|
let ext_mnem = format!("{mnem}{size}");
|
|
let ext_ops = format!("{cr}{}, {}", gpr(ra), gpr(rb));
|
|
with_ext(mnem, base_ops, 8, &ext_mnem, ext_ops, 8)
|
|
}
|
|
|
|
fn fmt_ori(instr: &DecodedInstr) -> DisasmText {
|
|
let rs = instr.rs();
|
|
let ra = instr.ra();
|
|
let uimm = instr.uimm16() as u32;
|
|
let base_ops = format!("{}, {}, 0x{uimm:X}", gpr(ra), gpr(rs));
|
|
if rs == 0 && ra == 0 && uimm == 0 {
|
|
with_ext("ori", base_ops, 8, "nop", String::new(), 0)
|
|
} else {
|
|
base("ori", base_ops, 8)
|
|
}
|
|
}
|
|
|
|
fn fmt_d_logic(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let rs = instr.rs();
|
|
let ra = instr.ra();
|
|
let uimm = instr.uimm16() as u32;
|
|
base(mnem, format!("{}, {}, 0x{uimm:X}", gpr(ra), gpr(rs)), 8)
|
|
}
|
|
|
|
// D-form load/store. `is_fpr` selects between fX and rX for the data register.
|
|
fn fmt_ld(instr: &DecodedInstr, mnem: &str, is_fpr: bool) -> DisasmText {
|
|
let rt = instr.rd();
|
|
let ra = instr.ra();
|
|
let d = instr.d();
|
|
let rn = if is_fpr { fpr(rt) } else { gpr(rt) };
|
|
base(mnem, format!("{rn}, {d}({})", gpr(ra)), 8)
|
|
}
|
|
|
|
fn fmt_st(instr: &DecodedInstr, mnem: &str, is_fpr: bool) -> DisasmText {
|
|
let rs = instr.rs();
|
|
let ra = instr.ra();
|
|
let d = instr.d();
|
|
let rn = if is_fpr { fpr(rs) } else { gpr(rs) };
|
|
base(mnem, format!("{rn}, {d}({})", gpr(ra)), 8)
|
|
}
|
|
|
|
// DS-form load/store.
|
|
fn fmt_ds(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let r = instr.rd();
|
|
let ra = instr.ra();
|
|
let ds = instr.ds();
|
|
base(mnem, format!("{}, {ds}({})", gpr(r), gpr(ra)), 8)
|
|
}
|
|
|
|
// Rotate (32-bit).
|
|
fn fmt_rlwimi(instr: &DecodedInstr) -> DisasmText {
|
|
let rs = instr.rs();
|
|
let ra = instr.ra();
|
|
let sh = instr.sh();
|
|
let mb = instr.mb();
|
|
let me = instr.me();
|
|
let rc = rc_dot(instr);
|
|
let mnem = format!("rlwimi{rc}");
|
|
let base_ops = format!("{}, {}, {sh}, {mb}, {me}", gpr(ra), gpr(rs));
|
|
// inslwi rA, rS, n, b = rlwimi rA, rS, 32-b, b, b+n-1
|
|
if mb <= me && sh == (32u32.wrapping_sub(mb)) % 32 && sh != 31u32.wrapping_sub(me) {
|
|
let n = me - mb + 1;
|
|
let b = mb;
|
|
let ext_mnem = format!("inslwi{rc}");
|
|
return with_ext(&mnem, base_ops, 8, &ext_mnem, format!("{}, {}, {n}, {b}", gpr(ra), gpr(rs)), 8);
|
|
}
|
|
// insrwi rA, rS, n, b = rlwimi rA, rS, 32-(b+n), b, b+n-1
|
|
if mb <= me && sh == 31u32.wrapping_sub(me) % 32 {
|
|
let n = me - mb + 1;
|
|
let b = mb;
|
|
let ext_mnem = format!("insrwi{rc}");
|
|
return with_ext(&mnem, base_ops, 8, &ext_mnem, format!("{}, {}, {n}, {b}", gpr(ra), gpr(rs)), 8);
|
|
}
|
|
base(&mnem, base_ops, 8)
|
|
}
|
|
|
|
fn fmt_rlwinm(instr: &DecodedInstr) -> DisasmText {
|
|
let rs = instr.rs();
|
|
let ra = instr.ra();
|
|
let sh = instr.sh();
|
|
let mb = instr.mb();
|
|
let me = instr.me();
|
|
let rc = rc_dot(instr);
|
|
let mnem = format!("rlwinm{rc}");
|
|
let base_ops = format!("{}, {}, {sh}, {mb}, {me}", gpr(ra), gpr(rs));
|
|
|
|
// Priority-ordered simplified forms.
|
|
if sh > 0 && mb == 0 && me == 31 - sh {
|
|
let ext = format!("slwi{rc}");
|
|
return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {sh}", gpr(ra), gpr(rs)), 8);
|
|
}
|
|
if sh > 0 && me == 31 && sh + mb == 32 {
|
|
let ext = format!("srwi{rc}");
|
|
return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {}", gpr(ra), gpr(rs), 32 - sh), 8);
|
|
}
|
|
if sh > 0 && mb == 0 && me == 31 {
|
|
let ext = format!("rotlwi{rc}");
|
|
return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {sh}", gpr(ra), gpr(rs)), 8);
|
|
}
|
|
if sh == 0 && me == 31 && mb > 0 {
|
|
let ext = format!("clrlwi{rc}");
|
|
return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {mb}", gpr(ra), gpr(rs)), 8);
|
|
}
|
|
if sh == 0 && mb == 0 && me < 31 {
|
|
let ext = format!("clrrwi{rc}");
|
|
return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {}", gpr(ra), gpr(rs), 31 - me), 8);
|
|
}
|
|
if mb == 0 && sh > 0 && me < 31 {
|
|
let n = me + 1;
|
|
let ext = format!("extlwi{rc}");
|
|
return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {n}, {sh}", gpr(ra), gpr(rs)), 8);
|
|
}
|
|
if me == 31 && mb > 0 && sh > 0 {
|
|
let n = 32 - mb;
|
|
let b = sh.wrapping_sub(n) % 32;
|
|
let ext = format!("extrwi{rc}");
|
|
return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {n}, {b}", gpr(ra), gpr(rs)), 8);
|
|
}
|
|
base(&mnem, base_ops, 8)
|
|
}
|
|
|
|
fn fmt_rlwnm(instr: &DecodedInstr) -> DisasmText {
|
|
let rs = instr.rs();
|
|
let ra = instr.ra();
|
|
let rb = instr.rb();
|
|
let mb = instr.mb();
|
|
let me = instr.me();
|
|
let rc = rc_dot(instr);
|
|
let mnem = format!("rlwnm{rc}");
|
|
let base_ops = format!("{}, {}, {}, {mb}, {me}", gpr(ra), gpr(rs), gpr(rb));
|
|
if mb == 0 && me == 31 {
|
|
let ext = format!("rotlw{rc}");
|
|
return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {}", gpr(ra), gpr(rs), gpr(rb)), 8);
|
|
}
|
|
base(&mnem, base_ops, 8)
|
|
}
|
|
|
|
// 64-bit MD/MDS-form rotate.
|
|
fn fmt_rldicl(instr: &DecodedInstr) -> DisasmText {
|
|
let rs = instr.rs();
|
|
let ra = instr.ra();
|
|
let rc = rc_dot(instr);
|
|
let sh = instr.sh64();
|
|
let mb = mb_md(instr.raw);
|
|
let mnem = format!("rldicl{rc}");
|
|
let base_ops = format!("{}, {}, {sh}, {mb}", gpr(ra), gpr(rs));
|
|
if sh == 0 && mb > 0 {
|
|
let ext = format!("clrldi{rc}");
|
|
return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {mb}", gpr(ra), gpr(rs)), 8);
|
|
}
|
|
if mb > 0 && sh == (64u32.wrapping_sub(mb)) & 63 {
|
|
let ext = format!("srdi{rc}");
|
|
return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {mb}", gpr(ra), gpr(rs)), 8);
|
|
}
|
|
if sh > 0 && mb == 0 {
|
|
let ext = format!("rotldi{rc}");
|
|
return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {sh}", gpr(ra), gpr(rs)), 8);
|
|
}
|
|
base(&mnem, base_ops, 8)
|
|
}
|
|
|
|
fn fmt_rldicr(instr: &DecodedInstr) -> DisasmText {
|
|
let rs = instr.rs();
|
|
let ra = instr.ra();
|
|
let rc = rc_dot(instr);
|
|
let sh = instr.sh64();
|
|
let me = mb_md(instr.raw);
|
|
let mnem = format!("rldicr{rc}");
|
|
let base_ops = format!("{}, {}, {sh}, {me}", gpr(ra), gpr(rs));
|
|
if sh > 0 && me == (63u32.wrapping_sub(sh)) & 63 {
|
|
let ext = format!("sldi{rc}");
|
|
return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {sh}", gpr(ra), gpr(rs)), 8);
|
|
}
|
|
if sh == 0 && me < 63 {
|
|
let ext = format!("clrrdi{rc}");
|
|
return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {}", gpr(ra), gpr(rs), 63 - me), 8);
|
|
}
|
|
base(&mnem, base_ops, 8)
|
|
}
|
|
|
|
fn fmt_rldic(instr: &DecodedInstr) -> DisasmText {
|
|
let rs = instr.rs();
|
|
let ra = instr.ra();
|
|
let rc = rc_dot(instr);
|
|
let sh = instr.sh64();
|
|
let mb = mb_md(instr.raw);
|
|
base(&format!("rldic{rc}"), format!("{}, {}, {sh}, {mb}", gpr(ra), gpr(rs)), 8)
|
|
}
|
|
|
|
fn fmt_rldimi(instr: &DecodedInstr) -> DisasmText {
|
|
let rs = instr.rs();
|
|
let ra = instr.ra();
|
|
let rc = rc_dot(instr);
|
|
let sh = instr.sh64();
|
|
let mb = mb_md(instr.raw);
|
|
let mnem = format!("rldimi{rc}");
|
|
let base_ops = format!("{}, {}, {sh}, {mb}", gpr(ra), gpr(rs));
|
|
if mb > 0 {
|
|
let n = (64u32.wrapping_sub(sh).wrapping_sub(mb)) & 63;
|
|
if n > 0 {
|
|
let ext = format!("insrdi{rc}");
|
|
return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {n}, {mb}", gpr(ra), gpr(rs)), 8);
|
|
}
|
|
}
|
|
base(&mnem, base_ops, 8)
|
|
}
|
|
|
|
fn fmt_rldcl(instr: &DecodedInstr) -> DisasmText {
|
|
let rs = instr.rs();
|
|
let ra = instr.ra();
|
|
let rb = instr.rb();
|
|
let rc = rc_dot(instr);
|
|
let mb = mb_md(instr.raw);
|
|
let mnem = format!("rldcl{rc}");
|
|
let base_ops = format!("{}, {}, {}, {mb}", gpr(ra), gpr(rs), gpr(rb));
|
|
if mb == 0 {
|
|
let ext = format!("rotld{rc}");
|
|
return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {}", gpr(ra), gpr(rs), gpr(rb)), 8);
|
|
}
|
|
base(&mnem, base_ops, 8)
|
|
}
|
|
|
|
fn fmt_rldcr(instr: &DecodedInstr) -> DisasmText {
|
|
let rs = instr.rs();
|
|
let ra = instr.ra();
|
|
let rb = instr.rb();
|
|
let rc = rc_dot(instr);
|
|
let me = mb_md(instr.raw);
|
|
base(&format!("rldcr{rc}"), format!("{}, {}, {}, {me}", gpr(ra), gpr(rs), gpr(rb)), 8)
|
|
}
|
|
|
|
/// MD/MDS-form mb/me field: 6 bits packed as bits 21-25 + bit 26 (low bit).
|
|
#[inline]
|
|
fn mb_md(raw: u32) -> u32 {
|
|
let lo5 = (raw >> 6) & 0x1F; // bits 21-25
|
|
let hi = (raw >> 5) & 0x1; // bit 26
|
|
lo5 | (hi << 5)
|
|
}
|
|
|
|
// XO-form ALU
|
|
fn fmt_xo_3op(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let rt = instr.rd();
|
|
let ra = instr.ra();
|
|
let rb = instr.rb();
|
|
let rc = rc_dot(instr);
|
|
let oe = if instr.oe() { "o" } else { "" };
|
|
let full = format!("{mnem}{oe}{rc}");
|
|
base(&full, format!("{}, {}, {}", gpr(rt), gpr(ra), gpr(rb)), 8)
|
|
}
|
|
|
|
fn fmt_xo_2op(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let rt = instr.rd();
|
|
let ra = instr.ra();
|
|
let rc = rc_dot(instr);
|
|
let oe = if instr.oe() { "o" } else { "" };
|
|
let full = format!("{mnem}{oe}{rc}");
|
|
base(&full, format!("{}, {}", gpr(rt), gpr(ra)), 8)
|
|
}
|
|
|
|
fn fmt_xo_3op_no_oe(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let rt = instr.rd();
|
|
let ra = instr.ra();
|
|
let rb = instr.rb();
|
|
let rc = rc_dot(instr);
|
|
let oe = if instr.oe() { "o" } else { "" };
|
|
let full = format!("{mnem}{oe}{rc}");
|
|
base(&full, format!("{}, {}, {}", gpr(rt), gpr(ra), gpr(rb)), 8)
|
|
}
|
|
|
|
fn fmt_xo_3op_rc_only(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let rt = instr.rd();
|
|
let ra = instr.ra();
|
|
let rb = instr.rb();
|
|
let rc = rc_dot(instr);
|
|
let full = format!("{mnem}{rc}");
|
|
base(&full, format!("{}, {}, {}", gpr(rt), gpr(ra), gpr(rb)), 8)
|
|
}
|
|
|
|
fn fmt_subf(instr: &DecodedInstr, base_mnem: &str, ext_mnem: &str) -> DisasmText {
|
|
let rt = instr.rd();
|
|
let ra = instr.ra();
|
|
let rb = instr.rb();
|
|
let rc = rc_dot(instr);
|
|
let oe = if instr.oe() { "o" } else { "" };
|
|
let bm = format!("{base_mnem}{oe}{rc}");
|
|
let em = format!("{ext_mnem}{oe}{rc}");
|
|
let bo = format!("{}, {}, {}", gpr(rt), gpr(ra), gpr(rb));
|
|
let eo = format!("{}, {}, {}", gpr(rt), gpr(rb), gpr(ra));
|
|
with_ext(&bm, bo, 8, &em, eo, 8)
|
|
}
|
|
|
|
// X-form logical
|
|
fn fmt_x_logic(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let rs = instr.rs();
|
|
let ra = instr.ra();
|
|
let rb = instr.rb();
|
|
let rc = rc_dot(instr);
|
|
let full = format!("{mnem}{rc}");
|
|
base(&full, format!("{}, {}, {}", gpr(ra), gpr(rs), gpr(rb)), 8)
|
|
}
|
|
|
|
fn fmt_x_unary_rc(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let rs = instr.rs();
|
|
let ra = instr.ra();
|
|
let rc = rc_dot(instr);
|
|
let full = format!("{mnem}{rc}");
|
|
base(&full, format!("{}, {}", gpr(ra), gpr(rs)), 8)
|
|
}
|
|
|
|
fn fmt_logic_and(instr: &DecodedInstr) -> DisasmText {
|
|
let rs = instr.rs();
|
|
let ra = instr.ra();
|
|
let rb = instr.rb();
|
|
let rc = rc_dot(instr);
|
|
let bm = format!("and{rc}");
|
|
let bo = format!("{}, {}, {}", gpr(ra), gpr(rs), gpr(rb));
|
|
if rs == rb {
|
|
let em = format!("mr{rc}");
|
|
with_ext(&bm, bo, 8, &em, format!("{}, {}", gpr(ra), gpr(rs)), 8)
|
|
} else {
|
|
base(&bm, bo, 8)
|
|
}
|
|
}
|
|
|
|
fn fmt_logic_or(instr: &DecodedInstr) -> DisasmText {
|
|
let rs = instr.rs();
|
|
let ra = instr.ra();
|
|
let rb = instr.rb();
|
|
let rc = rc_dot(instr);
|
|
let bm = format!("or{rc}");
|
|
let bo = format!("{}, {}, {}", gpr(ra), gpr(rs), gpr(rb));
|
|
if rs == rb {
|
|
let em = format!("mr{rc}");
|
|
with_ext(&bm, bo, 8, &em, format!("{}, {}", gpr(ra), gpr(rs)), 8)
|
|
} else {
|
|
base(&bm, bo, 8)
|
|
}
|
|
}
|
|
|
|
fn fmt_logic_nor(instr: &DecodedInstr) -> DisasmText {
|
|
let rs = instr.rs();
|
|
let ra = instr.ra();
|
|
let rb = instr.rb();
|
|
let rc = rc_dot(instr);
|
|
let bm = format!("nor{rc}");
|
|
let bo = format!("{}, {}, {}", gpr(ra), gpr(rs), gpr(rb));
|
|
if rs == rb {
|
|
let em = format!("not{rc}");
|
|
with_ext(&bm, bo, 8, &em, format!("{}, {}", gpr(ra), gpr(rs)), 8)
|
|
} else {
|
|
base(&bm, bo, 8)
|
|
}
|
|
}
|
|
|
|
// Shift immediate
|
|
fn fmt_srawi(instr: &DecodedInstr) -> DisasmText {
|
|
let rs = instr.rs();
|
|
let ra = instr.ra();
|
|
let sh = instr.sh();
|
|
let rc = rc_dot(instr);
|
|
base(&format!("srawi{rc}"), format!("{}, {}, {sh}", gpr(ra), gpr(rs)), 8)
|
|
}
|
|
|
|
fn fmt_sradi(instr: &DecodedInstr) -> DisasmText {
|
|
let rs = instr.rs();
|
|
let ra = instr.ra();
|
|
let sh = instr.sh64();
|
|
let rc = rc_dot(instr);
|
|
base(&format!("sradi{rc}"), format!("{}, {}, {sh}", gpr(ra), gpr(rs)), 8)
|
|
}
|
|
|
|
// Special-purpose register moves
|
|
fn fmt_mfspr(instr: &DecodedInstr) -> DisasmText {
|
|
let rd = instr.rd();
|
|
let spr = instr.spr();
|
|
let base_ops = format!("{}, {}", gpr(rd), spr_name(spr));
|
|
let ext = match spr {
|
|
8 => Some(("mflr", format!("{}", gpr(rd)))),
|
|
9 => Some(("mfctr", format!("{}", gpr(rd)))),
|
|
1 => Some(("mfxer", format!("{}", gpr(rd)))),
|
|
_ => None,
|
|
};
|
|
match ext {
|
|
Some((em, eo)) => with_ext("mfspr", base_ops, 8, em, eo, 8),
|
|
None => base("mfspr", base_ops, 8),
|
|
}
|
|
}
|
|
|
|
fn fmt_mtspr(instr: &DecodedInstr) -> DisasmText {
|
|
let rs = instr.rs();
|
|
let spr = instr.spr();
|
|
let base_ops = format!("{}, {}", spr_name(spr), gpr(rs));
|
|
let ext = match spr {
|
|
8 => Some(("mtlr", format!("{}", gpr(rs)))),
|
|
9 => Some(("mtctr", format!("{}", gpr(rs)))),
|
|
1 => Some(("mtxer", format!("{}", gpr(rs)))),
|
|
_ => None,
|
|
};
|
|
match ext {
|
|
Some((em, eo)) => with_ext("mtspr", base_ops, 8, em, eo, 8),
|
|
None => base("mtspr", base_ops, 8),
|
|
}
|
|
}
|
|
|
|
fn fmt_mtcrf(instr: &DecodedInstr) -> DisasmText {
|
|
let rs = instr.rs();
|
|
let fxm = (instr.raw >> 12) & 0xFF;
|
|
let bo = format!("0x{fxm:02X}, {}", gpr(rs));
|
|
if fxm == 0xFF {
|
|
with_ext("mtcrf", bo, 8, "mtcr", gpr(rs), 8)
|
|
} else {
|
|
base("mtcrf", bo, 8)
|
|
}
|
|
}
|
|
|
|
fn fmt_mftb(instr: &DecodedInstr) -> DisasmText {
|
|
let rd = instr.rd();
|
|
let tbr = instr.spr();
|
|
let base_ops = format!("{}, {tbr}", gpr(rd));
|
|
match tbr {
|
|
268 => with_ext("mftb", base_ops, 8, "mftb", gpr(rd), 8),
|
|
269 => with_ext("mftb", base_ops, 8, "mftbu", gpr(rd), 8),
|
|
_ => base("mftb", base_ops, 8),
|
|
}
|
|
}
|
|
|
|
// X-form indexed load/store.
|
|
fn fmt_x_load(instr: &DecodedInstr, mnem: &str, is_fpr: bool) -> DisasmText {
|
|
let rt = instr.rd();
|
|
let ra = instr.ra();
|
|
let rb = instr.rb();
|
|
let rn = if is_fpr { fpr(rt) } else { gpr(rt) };
|
|
base(mnem, format!("{rn}, {}, {}", gpr(ra), gpr(rb)), 8)
|
|
}
|
|
|
|
fn fmt_x_store(instr: &DecodedInstr, mnem: &str, is_fpr: bool) -> DisasmText {
|
|
let rs = instr.rs();
|
|
let ra = instr.ra();
|
|
let rb = instr.rb();
|
|
let rn = if is_fpr { fpr(rs) } else { gpr(rs) };
|
|
base(mnem, format!("{rn}, {}, {}", gpr(ra), gpr(rb)), 8)
|
|
}
|
|
|
|
fn fmt_lswi_stswi(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let rt = instr.rd();
|
|
let ra = instr.ra();
|
|
let nb = instr.nb();
|
|
base(mnem, format!("{}, {}, {nb}", gpr(rt), gpr(ra)), 8)
|
|
}
|
|
|
|
fn fmt_cache(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let ra = instr.ra();
|
|
let rb = instr.rb();
|
|
base(mnem, format!("{}, {}", gpr(ra), gpr(rb)), 8)
|
|
}
|
|
|
|
// CR logical
|
|
fn fmt_cr_logic(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let bt = instr.crbd();
|
|
let ba = instr.crba();
|
|
let bb = instr.crbb();
|
|
base(mnem, format!("{}, {}, {}", crb(bt), crb(ba), crb(bb)), 8)
|
|
}
|
|
|
|
fn fmt_crnor(instr: &DecodedInstr) -> DisasmText {
|
|
let bt = instr.crbd();
|
|
let ba = instr.crba();
|
|
let bb = instr.crbb();
|
|
let bo = format!("{}, {}, {}", crb(bt), crb(ba), crb(bb));
|
|
if ba == bb {
|
|
with_ext("crnor", bo, 8, "crnot", format!("{}, {}", crb(bt), crb(ba)), 8)
|
|
} else {
|
|
base("crnor", bo, 8)
|
|
}
|
|
}
|
|
|
|
fn fmt_crxor(instr: &DecodedInstr) -> DisasmText {
|
|
let bt = instr.crbd();
|
|
let ba = instr.crba();
|
|
let bb = instr.crbb();
|
|
let bo = format!("{}, {}, {}", crb(bt), crb(ba), crb(bb));
|
|
if bt == ba && ba == bb {
|
|
with_ext("crxor", bo, 8, "crclr", crb(bt), 8)
|
|
} else {
|
|
base("crxor", bo, 8)
|
|
}
|
|
}
|
|
|
|
fn fmt_creqv(instr: &DecodedInstr) -> DisasmText {
|
|
let bt = instr.crbd();
|
|
let ba = instr.crba();
|
|
let bb = instr.crbb();
|
|
let bo = format!("{}, {}, {}", crb(bt), crb(ba), crb(bb));
|
|
if bt == ba && ba == bb {
|
|
with_ext("creqv", bo, 8, "crset", crb(bt), 8)
|
|
} else {
|
|
base("creqv", bo, 8)
|
|
}
|
|
}
|
|
|
|
fn fmt_cror(instr: &DecodedInstr) -> DisasmText {
|
|
let bt = instr.crbd();
|
|
let ba = instr.crba();
|
|
let bb = instr.crbb();
|
|
let bo = format!("{}, {}, {}", crb(bt), crb(ba), crb(bb));
|
|
if ba == bb {
|
|
with_ext("cror", bo, 8, "crmove", format!("{}, {}", crb(bt), crb(ba)), 8)
|
|
} else {
|
|
base("cror", bo, 8)
|
|
}
|
|
}
|
|
|
|
// FPU
|
|
fn fmt_a_3op(instr: &DecodedInstr, mnem: &str, use_frc: bool) -> DisasmText {
|
|
let frt = instr.rd();
|
|
let fra = instr.ra();
|
|
let frb = instr.rb();
|
|
let frc = instr.rc();
|
|
let rc = rc_dot(instr);
|
|
let full = format!("{mnem}{rc}");
|
|
let ops = if use_frc {
|
|
format!("{}, {}, {}", fpr(frt), fpr(fra), fpr(frc))
|
|
} else {
|
|
format!("{}, {}, {}", fpr(frt), fpr(fra), fpr(frb))
|
|
};
|
|
base(&full, ops, 8)
|
|
}
|
|
|
|
fn fmt_a_unary(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let frt = instr.rd();
|
|
let frb = instr.rb();
|
|
let rc = rc_dot(instr);
|
|
base(&format!("{mnem}{rc}"), format!("{}, {}", fpr(frt), fpr(frb)), 8)
|
|
}
|
|
|
|
fn fmt_a_4op(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let frt = instr.rd();
|
|
let fra = instr.ra();
|
|
let frb = instr.rb();
|
|
let frc = instr.rc();
|
|
let rc = rc_dot(instr);
|
|
base(&format!("{mnem}{rc}"),
|
|
format!("{}, {}, {}, {}", fpr(frt), fpr(fra), fpr(frc), fpr(frb)), 8)
|
|
}
|
|
|
|
fn fmt_fcmp(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let bf = instr.crfd();
|
|
let fra = instr.ra();
|
|
let frb = instr.rb();
|
|
base(mnem, format!("cr{bf}, {}, {}", fpr(fra), fpr(frb)), 8)
|
|
}
|
|
|
|
fn fmt_x_fpu_unary(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let frt = instr.rd();
|
|
let frb = instr.rb();
|
|
let rc = rc_dot(instr);
|
|
base(&format!("{mnem}{rc}"), format!("{}, {}", fpr(frt), fpr(frb)), 8)
|
|
}
|
|
|
|
fn fmt_mtfsb(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let bt = instr.crbd();
|
|
let rc = rc_dot(instr);
|
|
base(&format!("{mnem}{rc}"), format!("{bt}"), 8)
|
|
}
|
|
|
|
// VMX (5-bit registers).
|
|
fn fmt_vmx_3op(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let vd = instr.rd();
|
|
let va = instr.ra();
|
|
let vb = instr.rb();
|
|
base(mnem, format!("{}, {}, {}", vr(vd), vr(va), vr(vb)), 8)
|
|
}
|
|
|
|
fn fmt_vmx_unary(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let vd = instr.rd();
|
|
let vb = instr.rb();
|
|
base(mnem, format!("{}, {}", vr(vd), vr(vb)), 8)
|
|
}
|
|
|
|
fn fmt_vmx_uimm(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let vd = instr.rd();
|
|
let vb = instr.rb();
|
|
let uimm = instr.ra() as u32;
|
|
base(mnem, format!("{}, {}, {uimm}", vr(vd), vr(vb)), 8)
|
|
}
|
|
|
|
fn fmt_vmx_simm(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let vd = instr.rd();
|
|
let simm = sign_ext(instr.ra() as u32, 5);
|
|
base(mnem, format!("{}, {simm}", vr(vd)), 9)
|
|
}
|
|
|
|
fn fmt_vmx_cmp(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let vd = instr.rd();
|
|
let va = instr.ra();
|
|
let vb = instr.rb();
|
|
// Rc bit at position 22 (0-indexed from MSB)
|
|
let rc = if (instr.raw >> 10) & 1 != 0 { "." } else { "" };
|
|
let full = format!("{mnem}{rc}");
|
|
base(&full, format!("{}, {}, {}", vr(vd), vr(va), vr(vb)), 12)
|
|
}
|
|
|
|
fn fmt_vmx_4op(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let vd = instr.rd();
|
|
let va = instr.ra();
|
|
let vb = instr.rb();
|
|
let vc = instr.rc();
|
|
base(mnem, format!("{}, {}, {}, {}", vr(vd), vr(va), vr(vb), vr(vc)), 12)
|
|
}
|
|
|
|
fn fmt_vmx_4op_swap(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let vd = instr.rd();
|
|
let va = instr.ra();
|
|
let vb = instr.rb();
|
|
let vc = instr.rc();
|
|
base(mnem, format!("{}, {}, {}, {}", vr(vd), vr(va), vr(vc), vr(vb)), 9)
|
|
}
|
|
|
|
fn fmt_vsldoi(instr: &DecodedInstr) -> DisasmText {
|
|
let vd = instr.rd();
|
|
let va = instr.ra();
|
|
let vb = instr.rb();
|
|
let sh = (instr.raw >> 6) & 0xF;
|
|
base("vsldoi", format!("{}, {}, {}, {sh}", vr(vd), vr(va), vr(vb)), 8)
|
|
}
|
|
|
|
fn fmt_vmx_ls(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let vd = instr.rd();
|
|
let ra = instr.ra();
|
|
let rb = instr.rb();
|
|
base(mnem, format!("{}, {}, {}", vr(vd), gpr(ra), gpr(rb)), 8)
|
|
}
|
|
|
|
// VMX128 — uses canonical va128/vb128/vd128 accessors from decoder.rs.
|
|
// (Silently fixes the prior ppc.rs bug where these used wrong bit positions.)
|
|
fn fmt_vmx128_ls(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let vd = instr.vd128();
|
|
let ra = instr.ra();
|
|
let rb = instr.rb();
|
|
base(mnem, format!("{}, {}, {}", vr(vd), gpr(ra), gpr(rb)), 12)
|
|
}
|
|
|
|
fn fmt_vmx128_3op(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let vd = instr.vd128();
|
|
let va = instr.va128();
|
|
let vb = instr.vb128();
|
|
base(mnem, format!("{}, {}, {}", vr(vd), vr(va), vr(vb)), 12)
|
|
}
|
|
|
|
// VMX128 multiply-add forms (VX128_2): the addend is the VD register
|
|
// re-used, not a separate VC field. Operand order differs between
|
|
// `vmaddfp128` (VD, VA, VB, VD) and the `vmaddcfp128`/`vnmsubfp128`
|
|
// pair (VD, VA, VD, VB), per canary's authoritative formatters in
|
|
// xenia-canary/src/xenia/cpu/ppc/ppc_opcode_disasm_gen.cc.
|
|
fn fmt_vmaddfp128(instr: &DecodedInstr) -> DisasmText {
|
|
let vd = instr.vd128();
|
|
let va = instr.va128();
|
|
let vb = instr.vb128();
|
|
base(
|
|
"vmaddfp128",
|
|
format!("{}, {}, {}, {}", vr(vd), vr(va), vr(vb), vr(vd)),
|
|
12,
|
|
)
|
|
}
|
|
|
|
fn fmt_vmx128_madd_vd_vb(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let vd = instr.vd128();
|
|
let va = instr.va128();
|
|
let vb = instr.vb128();
|
|
base(
|
|
mnem,
|
|
format!("{}, {}, {}, {}", vr(vd), vr(va), vr(vd), vr(vb)),
|
|
12,
|
|
)
|
|
}
|
|
|
|
fn fmt_vperm128(instr: &DecodedInstr) -> DisasmText {
|
|
let vd = instr.vd128();
|
|
let va = instr.va128();
|
|
let vb = instr.vb128();
|
|
let vc = (instr.raw >> 6) & 0x7;
|
|
base("vperm128", format!("{}, {}, {}, {vc}", vr(vd), vr(va), vr(vb)), 9)
|
|
}
|
|
|
|
fn fmt_vsldoi128(instr: &DecodedInstr) -> DisasmText {
|
|
let vd = instr.vd128();
|
|
let va = instr.va128();
|
|
let vb = instr.vb128();
|
|
let sh = (instr.raw >> 6) & 0xF;
|
|
base("vsldoi128", format!("{}, {}, {}, {sh}", vr(vd), vr(va), vr(vb)), 10)
|
|
}
|
|
|
|
fn fmt_vpermwi128(instr: &DecodedInstr) -> DisasmText {
|
|
let vd = instr.vd128();
|
|
let vb = instr.vb128();
|
|
// UIMM combines bits 11-15 (low 5) with bits 23-25 (upper 3).
|
|
let lo = (instr.raw >> 16) & 0x1F;
|
|
let hi = (instr.raw >> 6) & 0x7;
|
|
let uimm = lo | (hi << 5);
|
|
base("vpermwi128", format!("{}, {}, 0x{uimm:X}", vr(vd), vr(vb)), 11)
|
|
}
|
|
|
|
fn fmt_vmx128_pack_d3d(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let vd = instr.vd128();
|
|
let vb = instr.vb128();
|
|
let imm = (instr.raw >> 16) & 0x1F;
|
|
let z = (instr.raw >> 6) & 0x3;
|
|
base(mnem, format!("{}, {}, {imm}, {z}", vr(vd), vr(vb)), 10)
|
|
}
|
|
|
|
fn fmt_vmx128_unary(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let vd = instr.vd128();
|
|
let vb = instr.vb128();
|
|
base(mnem, format!("{}, {}", vr(vd), vr(vb)), 12)
|
|
}
|
|
|
|
fn fmt_vmx128_uimm(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let vd = instr.vd128();
|
|
let vb = instr.vb128();
|
|
let uimm = extract_vx128_uimm5(instr.raw);
|
|
base(mnem, format!("{}, {}, {uimm}", vr(vd), vr(vb)), 12)
|
|
}
|
|
|
|
fn fmt_vmx128_cmp(instr: &DecodedInstr, mnem: &str) -> DisasmText {
|
|
let vd = instr.vd128();
|
|
let va = instr.va128();
|
|
let vb = instr.vb128();
|
|
// Rc bit at position 25 in VMX128 cmp form.
|
|
let rc = if (instr.raw >> 6) & 1 != 0 { "." } else { "" };
|
|
let full = format!("{mnem}{rc}");
|
|
base(&full, format!("{}, {}, {}", vr(vd), vr(va), vr(vb)), 14)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::decoder::decode;
|
|
|
|
#[test]
|
|
fn nop_collapses_via_extended() {
|
|
let instr = decode(0x60000000, 0);
|
|
let t = format(&instr);
|
|
assert_eq!(t.mnemonic, "ori");
|
|
assert_eq!(t.ext_mnemonic.as_deref(), Some("nop"));
|
|
assert_eq!(t.display(), "nop");
|
|
}
|
|
|
|
#[test]
|
|
fn addi_to_li_when_ra_zero() {
|
|
// addi r3, r0, 16
|
|
let raw = (14u32 << 26) | (3 << 21) | (0 << 16) | 16;
|
|
let instr = decode(raw, 0);
|
|
let t = format(&instr);
|
|
assert_eq!(t.mnemonic, "addi");
|
|
assert_eq!(t.ext_mnemonic.as_deref(), Some("li"));
|
|
assert_eq!(t.ext_operands.as_deref(), Some("r3, 16"));
|
|
}
|
|
|
|
#[test]
|
|
fn rlwinm_dot_preserves_record_bit() {
|
|
// Same pattern as the Sylpheed graphics-callback test:
|
|
// rlwinm. r11, r11, 0, 31, 31 with Rc=1
|
|
let raw = (21u32 << 26) | (11 << 21) | (11 << 16)
|
|
| (0 << 11) | (31 << 6) | (31 << 1) | 1;
|
|
let instr = decode(raw, 0);
|
|
let t = format(&instr);
|
|
assert!(t.disasm.starts_with("rlwinm."), "got: {}", t.disasm);
|
|
}
|
|
|
|
#[test]
|
|
fn rlwinm_no_dot_when_rc_unset() {
|
|
let raw = (21u32 << 26) | (11 << 21) | (11 << 16)
|
|
| (0 << 11) | (31 << 6) | (31 << 1);
|
|
let instr = decode(raw, 0);
|
|
let t = format(&instr);
|
|
assert_eq!(t.mnemonic, "rlwinm");
|
|
assert!(!t.mnemonic.ends_with('.'));
|
|
}
|
|
|
|
#[test]
|
|
fn or_with_same_source_is_mr() {
|
|
// or r3, r4, r4 → mr r3, r4
|
|
let raw = (31u32 << 26) | (4 << 21) | (3 << 16) | (4 << 11) | (444 << 1);
|
|
let instr = decode(raw, 0);
|
|
let t = format(&instr);
|
|
assert_eq!(t.ext_mnemonic.as_deref(), Some("mr"));
|
|
assert_eq!(t.ext_operands.as_deref(), Some("r3, r4"));
|
|
}
|
|
|
|
#[test]
|
|
fn unconditional_branch_resolves_target() {
|
|
// b +0x100 with addr=0x82000000
|
|
let raw = (18u32 << 26) | (0x40 << 2);
|
|
let instr = decode(raw, 0x82000000);
|
|
let t = format(&instr);
|
|
assert_eq!(t.mnemonic, "b");
|
|
assert_eq!(t.branch_target, Some(0x82000100));
|
|
assert_eq!(t.operands, "0x82000100");
|
|
}
|
|
|
|
#[test]
|
|
fn bclr_unconditional_is_blr() {
|
|
// bclr 20, 0
|
|
let raw = (19u32 << 26) | (20 << 21) | (0 << 16) | (16 << 1);
|
|
let instr = decode(raw, 0);
|
|
let t = format(&instr);
|
|
assert_eq!(t.ext_mnemonic.as_deref(), Some("blr"));
|
|
}
|
|
|
|
#[test]
|
|
fn back_compat_disassemble_returns_display() {
|
|
let instr = decode(0x60000000, 0);
|
|
assert_eq!(disassemble(&instr), "nop");
|
|
}
|
|
|
|
#[test]
|
|
fn iter_disasm_walks_byte_slice_in_order() {
|
|
// Three instructions at 0x82000000: nop, addi r3,r0,16, b +0x100.
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&0x60000000u32.to_be_bytes()); // nop
|
|
bytes.extend_from_slice(&((14u32 << 26) | (3 << 21) | (0 << 16) | 16).to_be_bytes()); // addi
|
|
bytes.extend_from_slice(&((18u32 << 26) | (0x40 << 2)).to_be_bytes()); // b +0x100
|
|
|
|
let items: Vec<_> = super::iter_disasm(&bytes, 0x82000000, 0x82000000, 0x82000000 + 12)
|
|
.collect();
|
|
assert_eq!(items.len(), 3);
|
|
assert_eq!(items[0].addr, 0x82000000);
|
|
assert_eq!(items[0].text.ext_mnemonic.as_deref(), Some("nop"));
|
|
assert_eq!(items[1].addr, 0x82000004);
|
|
assert_eq!(items[1].text.ext_mnemonic.as_deref(), Some("li"));
|
|
assert_eq!(items[2].addr, 0x82000008);
|
|
assert_eq!(items[2].text.branch_target, Some(0x82000108));
|
|
}
|
|
|
|
#[test]
|
|
fn iter_disasm_stops_on_truncated_tail() {
|
|
// 6 bytes — one full instruction + 2 dangling. Iterator must yield exactly 1.
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&0x60000000u32.to_be_bytes());
|
|
bytes.push(0x60); bytes.push(0x00);
|
|
|
|
let items: Vec<_> = super::iter_disasm(&bytes, 0, 0, 6).collect();
|
|
assert_eq!(items.len(), 1);
|
|
}
|
|
}
|