rustfmt, then clippy -D warnings across the three new crates. Mechanical,
except three decisions that are stated rather than silently allowed:
* lzx.rs gets file-scoped needless_range_loop/explicit_counter_loop allows.
Index arithmetic IS the algorithm -- LZX is defined over symbol indices,
Huffman slots and window positions, and a decompressor that is merely
idiomatic is worth nothing if it is not bit-exact.
* sylpheed-xexdb gets crate-scoped allows for needless_range_loop (nine
sites index reg[r] where r is the PowerPC register number -- the index is
the meaning), too_many_arguments and type_complexity. This code arrived
whole from a retired repository; a refactor here would be an unreviewed
edit dressed as a lint fix.
* Everything else clippy asked for is FIXED, including all 14 doc-indent
sites, the let-else, and a Prepared type alias in the binary.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2599 lines
91 KiB
Rust
2599 lines
91 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"),
|
|
3 => Some("lne"),
|
|
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 => fmt_mfcr(instr),
|
|
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 => {
|
|
// L-field at PPC bit 10 (host bit 21) selects lwsync (L=1), the
|
|
// acquire barrier in every Xbox 360 spinlock. PPCBUG-641.
|
|
if (instr.raw >> 21) & 1 == 1 {
|
|
with_ext("sync", String::new(), 0, "lwsync", String::new(), 0)
|
|
} else {
|
|
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
|
|
// `vor vD,vA,vA` is the canonical vector register move, and
|
|
// `vnor vD,vA,vA` the canonical vector complement. Both are extremely
|
|
// common (1,535 and 9 sites here) and both read as noise in base form.
|
|
PpcOpcode::vor if instr.ra() == instr.rb() => fmt_vmx_move(instr, "vor", "vmr"),
|
|
PpcOpcode::vnor if instr.ra() == instr.rb() => fmt_vmx_move(instr, "vnor", "vnot"),
|
|
|
|
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 [`sylpheed_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)
|
|
}
|
|
|
|
/// Static branch-prediction hint suffix for a `BO` field.
|
|
///
|
|
/// PowerISA gives several `BO` encodings an `at` pair — `001at`, `011at`,
|
|
/// `1a00t`, `1a01t` — where `at=0b10` means "unlikely" (`-`) and `0b11` means
|
|
/// "likely" (`+`); `0b00` is "no hint" and `0b01` is reserved. The forms whose
|
|
/// low bit is the reserved `z` (`0000z`, `0001z`, `0100z`, `0101z`) carry no
|
|
/// hint at all. Dropping the suffix loses the compiler's static prediction,
|
|
/// which is the only place it is recorded.
|
|
fn hint_suffix(bo: u32) -> &'static str {
|
|
let b = |i: u32| (bo >> (4 - i)) & 1; // b(0) is the MSB of the 5-bit field
|
|
let at = match (b(0), b(2), b(3)) {
|
|
// 1z1zz — branch always, no hint.
|
|
(1, 1, _) => return "",
|
|
// 1a00t / 1a01t — the `a` bit is b1.
|
|
(1, 0, _) => (b(1) << 1) | b(4),
|
|
// 001at / 011at — the `a` bit is b3.
|
|
(0, 1, _) => (b(3) << 1) | b(4),
|
|
// 0000z / 0001z / 0100z / 0101z — low bit reserved, no hint.
|
|
_ => return "",
|
|
};
|
|
match at {
|
|
0b10 => "-",
|
|
0b11 => "+",
|
|
_ => "",
|
|
}
|
|
}
|
|
|
|
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 hint = hint_suffix(bo);
|
|
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" };
|
|
if uncond {
|
|
// BO bit 4 set means CR is ignored — a pure CTR-decrement
|
|
// branch. Without this guard bdnz/bdz would emit a spurious
|
|
// `ge` suffix derived from the don't-care BI=0 /
|
|
// cond_true=false pair (PPCBUG-640).
|
|
let ext_mnem = format!("bd{z}{a}{l}{hint}");
|
|
let ext_ops = format!("0x{target:08X}");
|
|
with_ext(&base_mnem, base_ops, 8, &ext_mnem, ext_ops, 8)
|
|
} else {
|
|
// Combined CTR + condition. PowerISA names these `bdnzt` /
|
|
// `bdnzf` / `bdzt` / `bdzf` with the CR bit as an operand —
|
|
// not a condition-suffixed `bdnzne`, which is an invention no
|
|
// assembler accepts.
|
|
let t = if cond_true { "t" } else { "f" };
|
|
let ext_mnem = format!("bd{z}{t}{a}{l}{hint}");
|
|
let ext_ops = format!("{}, 0x{target:08X}", crb(bi));
|
|
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}{hint}");
|
|
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));
|
|
let hint = hint_suffix(bo);
|
|
|
|
// 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}{hint}");
|
|
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}{hint}");
|
|
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));
|
|
let hint = hint_suffix(bo);
|
|
|
|
// 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}{hint}");
|
|
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() {
|
|
// TO=31 traps unconditionally. The register form has `trap`; the
|
|
// immediate form's counterpart is `twui`/`tdui` (binutils), which
|
|
// is what every other disassembler prints for these 16 sites.
|
|
let ext_mnem = format!("{simplified_prefix}ui");
|
|
let ext_ops = format!("{}, {imm}", gpr(ra));
|
|
with_ext(mnem, base_ops, 8, &ext_mnem, ext_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.
|
|
//
|
|
// `slwi` is deliberately not gated on `sh > 0`: `rlwinm rA,rS,0,0,31` is a
|
|
// rotate-by-zero under a full mask, which the ISA's table still names
|
|
// `slwi rA,rS,0` (and which LLVM/capstone print that way). It is the single
|
|
// most common `rlwinm` encoding in this binary — 3,720 sites — so gating it
|
|
// away left the largest simplified-mnemonic gap we had.
|
|
if 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", gpr(rd).to_string())),
|
|
9 => Some(("mfctr", gpr(rd).to_string())),
|
|
1 => Some(("mfxer", gpr(rd).to_string())),
|
|
_ => 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", gpr(rs).to_string())),
|
|
9 => Some(("mtctr", gpr(rs).to_string())),
|
|
1 => Some(("mtxer", gpr(rs).to_string())),
|
|
_ => None,
|
|
};
|
|
match ext {
|
|
Some((em, eo)) => with_ext("mtspr", base_ops, 8, em, eo, 8),
|
|
None => base("mtspr", base_ops, 8),
|
|
}
|
|
}
|
|
|
|
/// `mfcr` and `mfocrf` share XO=19 and are told apart by bit 11.
|
|
///
|
|
/// With bit 11 clear the instruction copies the whole CR into `rD`; with it set
|
|
/// this is `mfocrf`, which copies only the single CR field named by `FXM` and
|
|
/// leaves the rest of `rD` undefined. Printing the latter as a bare `mfcr rD`
|
|
/// loses which field was read — and on this title 163 of 165 sites are the
|
|
/// one-field form. (The reference emulator folds both into one handler because
|
|
/// the wide read is a safe superset at runtime; a disassembler cannot.)
|
|
fn fmt_mfcr(instr: &DecodedInstr) -> DisasmText {
|
|
let rd = instr.rd();
|
|
if instr.raw & (1 << 20) != 0 {
|
|
let fxm = (instr.raw >> 12) & 0xFF;
|
|
base("mfocrf", format!("{}, 0x{fxm:02X}", gpr(rd)), 8)
|
|
} else {
|
|
base("mfcr", gpr(rd), 8)
|
|
}
|
|
}
|
|
|
|
/// `mtcrf` and `mtocrf` share XO=144, told apart by bit 11 exactly as
|
|
/// [`fmt_mfcr`] describes.
|
|
fn fmt_mtcrf(instr: &DecodedInstr) -> DisasmText {
|
|
let rs = instr.rs();
|
|
let fxm = (instr.raw >> 12) & 0xFF;
|
|
if instr.raw & (1 << 20) != 0 {
|
|
return base("mtocrf", format!("0x{fxm:02X}, {}", gpr(rs)), 8);
|
|
}
|
|
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).
|
|
/// A VX-form op whose two sources are the same register, so it degenerates to
|
|
/// a move/complement: emit the base form plus the two-operand simplified one.
|
|
fn fmt_vmx_move(instr: &DecodedInstr, base_mnem: &str, ext_mnem: &str) -> DisasmText {
|
|
let vd = instr.rd();
|
|
let va = instr.ra();
|
|
let vb = instr.rb();
|
|
with_ext(
|
|
base_mnem,
|
|
format!("{}, {}, {}", vr(vd), vr(va), vr(vb)),
|
|
8,
|
|
ext_mnem,
|
|
format!("{}, {}", vr(vd), vr(va)),
|
|
8,
|
|
)
|
|
}
|
|
|
|
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)) | 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)) | (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)) | (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)) | (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)) | 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);
|
|
}
|
|
/// `mfocrf` shares XO=19 with `mfcr`, differing only in bit 11. Printing it
|
|
/// as `mfcr` drops the FXM field naming which CR field is actually read —
|
|
/// 163 of 165 sites in the reference title are this form.
|
|
#[test]
|
|
fn mfocrf_is_distinguished_from_mfcr() {
|
|
// 0x7d502026: mfocrf r10, 0x02 (bit 11 set)
|
|
let d = crate::decoder::decode(0x7d50_2026, 0x8200_0000);
|
|
let t = format(&d);
|
|
assert_eq!(t.mnemonic, "mfocrf");
|
|
assert_eq!(t.operands, "r10, 0x02");
|
|
|
|
// Same encoding with bit 11 clear is a plain whole-CR read.
|
|
let d = crate::decoder::decode(0x7d50_2026 & !(1 << 20), 0x8200_0000);
|
|
let t = format(&d);
|
|
assert_eq!(t.mnemonic, "mfcr");
|
|
assert_eq!(t.operands, "r10");
|
|
}
|
|
|
|
/// The mirror case on the write side.
|
|
#[test]
|
|
fn mtocrf_is_distinguished_from_mtcrf() {
|
|
let base_word = 0x7c10_1120u32; // mtcrf-form, XO=144
|
|
let d = crate::decoder::decode(base_word | (1 << 20), 0x8200_0000);
|
|
assert_eq!(format(&d).mnemonic, "mtocrf");
|
|
let d = crate::decoder::decode(base_word & !(1 << 20), 0x8200_0000);
|
|
assert_eq!(format(&d).mnemonic, "mtcrf");
|
|
}
|
|
|
|
/// `rlwinm rA,rS,0,0,31` is a rotate-by-zero under a full mask. It is the
|
|
/// most common `rlwinm` encoding in the reference title (3,720 sites) and
|
|
/// was falling through to the base form because every simplified branch was
|
|
/// gated on `sh > 0`.
|
|
#[test]
|
|
fn rlwinm_shift_zero_still_simplifies() {
|
|
// 0x5548003e: rlwinm r8, r10, 0, 0, 31
|
|
let d = crate::decoder::decode(0x5548_003e, 0x8200_0000);
|
|
let t = format(&d);
|
|
assert_eq!(t.mnemonic, "rlwinm");
|
|
assert_eq!(t.ext_mnemonic.as_deref(), Some("slwi"));
|
|
assert_eq!(t.ext_operands.as_deref(), Some("r8, r10, 0"));
|
|
|
|
// The record-bit form keeps its dot.
|
|
let d = crate::decoder::decode(0x5569_003f, 0x8200_0000);
|
|
assert_eq!(format(&d).ext_mnemonic.as_deref(), Some("slwi."));
|
|
}
|
|
|
|
/// A genuine bit-extraction has no short name and must stay in base form.
|
|
#[test]
|
|
fn rlwinm_bit_extract_has_no_simplified_form() {
|
|
// rlwinm rA,rS,0,30,30 — extracts one bit; not a shift or clear.
|
|
let word = 0x5548_0000 | (30 << 6) | (30 << 1);
|
|
let t = format(&crate::decoder::decode(word, 0x8200_0000));
|
|
assert_eq!(t.mnemonic, "rlwinm");
|
|
assert_eq!(t.ext_mnemonic, None);
|
|
}
|
|
|
|
/// `vor vD,vA,vA` is the vector register move; `vnor vD,vA,vA` the vector
|
|
/// complement. Both only apply when the two sources are the same register.
|
|
#[test]
|
|
fn vor_and_vnor_simplify_only_when_sources_match() {
|
|
// 0x11800484: vor v12, v0, v0
|
|
let t = format(&crate::decoder::decode(0x1180_0484, 0x8200_0000));
|
|
assert_eq!(t.mnemonic, "vor");
|
|
assert_eq!(t.ext_mnemonic.as_deref(), Some("vmr"));
|
|
assert_eq!(t.ext_operands.as_deref(), Some("v12, v0"));
|
|
|
|
// 0x10000504: vnor v0, v0, v0
|
|
let t = format(&crate::decoder::decode(0x1000_0504, 0x8200_0000));
|
|
assert_eq!(t.ext_mnemonic.as_deref(), Some("vnot"));
|
|
|
|
// Distinct sources: a real bitwise OR, no simplification.
|
|
let t = format(&crate::decoder::decode(0x1180_1484, 0x8200_0000));
|
|
assert_eq!(t.mnemonic, "vor");
|
|
assert_eq!(t.ext_mnemonic, None);
|
|
}
|
|
|
|
/// PowerISA names the combined CTR+condition branches `bdnzt`/`bdnzf`
|
|
/// (and `bdzt`/`bdzf`) with the CR bit as an operand. We used to synthesise
|
|
/// `bdnzne` by gluing on a condition suffix — readable, but not a mnemonic
|
|
/// any assembler accepts.
|
|
#[test]
|
|
fn bdnz_with_condition_uses_the_isa_t_f_form() {
|
|
// 0x4002fff8: BO=00000 (dec CTR, branch if CTR!=0 and CR[BI]==0), BI=eq
|
|
let t = format(&crate::decoder::decode(0x4002_fff8, 0x8200_0000));
|
|
assert_eq!(t.ext_mnemonic.as_deref(), Some("bdnzf"));
|
|
assert!(t.ext_operands.as_deref().unwrap().starts_with("eq,"));
|
|
}
|
|
|
|
/// The `at` hint bits are the only record of the compiler's static branch
|
|
/// prediction, so they must survive into the text.
|
|
#[test]
|
|
fn branch_prediction_hints_are_preserved() {
|
|
// 0x4320fff0: bdnz with at=0b11 -> "+"
|
|
assert_eq!(
|
|
format(&crate::decoder::decode(0x4320_fff0, 0x8200_0000))
|
|
.ext_mnemonic
|
|
.as_deref(),
|
|
Some("bdnz+")
|
|
);
|
|
// 0x41c20024: beq with at=0b10 -> "-"
|
|
assert_eq!(
|
|
format(&crate::decoder::decode(0x41c2_0024, 0x8200_0000))
|
|
.ext_mnemonic
|
|
.as_deref(),
|
|
Some("beq-")
|
|
);
|
|
// 0x4de20020: beqlr with at=0b11 -> "+"
|
|
assert_eq!(
|
|
format(&crate::decoder::decode(0x4de2_0020, 0x8200_0000))
|
|
.ext_mnemonic
|
|
.as_deref(),
|
|
Some("beqlr+")
|
|
);
|
|
}
|
|
|
|
/// A branch with no hint bits set must stay unsuffixed, and `blr` (BO=20,
|
|
/// the branch-always form) never takes a hint at all.
|
|
#[test]
|
|
fn unhinted_branches_gain_no_suffix() {
|
|
// 0x4182000c: beq, at=0b00
|
|
let t = format(&crate::decoder::decode(0x4182_000c, 0x8200_0000));
|
|
assert_eq!(t.ext_mnemonic.as_deref(), Some("beq"));
|
|
// 0x4e800020: blr
|
|
let t = format(&crate::decoder::decode(0x4e80_0020, 0x8200_0000));
|
|
assert_eq!(t.ext_mnemonic.as_deref(), Some("blr"));
|
|
}
|
|
|
|
/// The whole trap family, checked against the reference table: TO=31 is
|
|
/// unconditional, which the register form calls `trap` and the immediate
|
|
/// form `twui` — the latter was the one gap.
|
|
#[test]
|
|
fn trap_extended_mnemonics_cover_the_table() {
|
|
// 0x0fe00016: twi 31, r0, 22 -> unconditional
|
|
let t = format(&crate::decoder::decode(0x0fe0_0016, 0x8200_0000));
|
|
assert_eq!(t.mnemonic, "twi");
|
|
assert_eq!(t.ext_mnemonic.as_deref(), Some("twui"));
|
|
|
|
// TO=6 is "logically less than or equal" — the divide-by-zero guard
|
|
// MSVC emits, and the most common trap in the reference title.
|
|
let word = 0x0c00_0000 | (6 << 21) | (3 << 16);
|
|
assert_eq!(
|
|
format(&crate::decoder::decode(word, 0x8200_0000))
|
|
.ext_mnemonic
|
|
.as_deref(),
|
|
Some("twllei")
|
|
);
|
|
// TO=5 is "logically greater than or equal".
|
|
let word = 0x0c00_0000 | (5 << 21) | (3 << 16);
|
|
assert_eq!(
|
|
format(&crate::decoder::decode(word, 0x8200_0000))
|
|
.ext_mnemonic
|
|
.as_deref(),
|
|
Some("twlgei")
|
|
);
|
|
// tw 31,0,0 stays the register-form `trap`.
|
|
let t = format(&crate::decoder::decode(0x7fe0_0008, 0x8200_0000));
|
|
assert_eq!(t.ext_mnemonic.as_deref(), Some("trap"));
|
|
}
|
|
}
|