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>
741 lines
14 KiB
Rust
741 lines
14 KiB
Rust
/// All PPC opcodes supported by the Xbox 360, including VMX128 extensions.
|
|
/// Directly mirrors the C++ PPCOpcode enum from ppc_opcode.h.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
#[repr(u32)]
|
|
#[allow(non_camel_case_types)]
|
|
pub enum PpcOpcode {
|
|
// ALU
|
|
addcx,
|
|
addex,
|
|
addi,
|
|
addic,
|
|
addicx,
|
|
addis,
|
|
addmex,
|
|
addx,
|
|
addzex,
|
|
andcx,
|
|
andisx,
|
|
andix,
|
|
andx,
|
|
// Branch
|
|
bcctrx,
|
|
bclrx,
|
|
bcx,
|
|
bx,
|
|
// Compare
|
|
cmp,
|
|
cmpi,
|
|
cmpl,
|
|
cmpli,
|
|
// Count leading zeros
|
|
cntlzdx,
|
|
cntlzwx,
|
|
// Condition register
|
|
crand,
|
|
crandc,
|
|
creqv,
|
|
crnand,
|
|
crnor,
|
|
cror,
|
|
crorc,
|
|
crxor,
|
|
// Data cache
|
|
dcbf,
|
|
dcbi,
|
|
dcbst,
|
|
dcbt,
|
|
dcbtst,
|
|
dcbz,
|
|
dcbz128,
|
|
// Division
|
|
divdux,
|
|
divdx,
|
|
divwux,
|
|
divwx,
|
|
// Sync/barrier
|
|
eieio,
|
|
// Logical
|
|
eqvx,
|
|
extsbx,
|
|
extshx,
|
|
extswx,
|
|
// FPU
|
|
fabsx,
|
|
faddsx,
|
|
faddx,
|
|
fcfidx,
|
|
fcmpo,
|
|
fcmpu,
|
|
fctidx,
|
|
fctidzx,
|
|
fctiwx,
|
|
fctiwzx,
|
|
fdivsx,
|
|
fdivx,
|
|
fmaddsx,
|
|
fmaddx,
|
|
fmrx,
|
|
fmsubsx,
|
|
fmsubx,
|
|
fmulsx,
|
|
fmulx,
|
|
fnabsx,
|
|
fnegx,
|
|
fnmaddsx,
|
|
fnmaddx,
|
|
fnmsubsx,
|
|
fnmsubx,
|
|
fresx,
|
|
frspx,
|
|
frsqrtex,
|
|
fselx,
|
|
fsqrtsx,
|
|
fsqrtx,
|
|
fsubsx,
|
|
fsubx,
|
|
// Instruction cache
|
|
icbi,
|
|
isync,
|
|
// Load byte
|
|
lbz,
|
|
lbzu,
|
|
lbzux,
|
|
lbzx,
|
|
// Load doubleword
|
|
ld,
|
|
ldarx,
|
|
ldbrx,
|
|
ldu,
|
|
ldux,
|
|
ldx,
|
|
// Load float
|
|
lfd,
|
|
lfdu,
|
|
lfdux,
|
|
lfdx,
|
|
lfs,
|
|
lfsu,
|
|
lfsux,
|
|
lfsx,
|
|
// Load halfword
|
|
lha,
|
|
lhau,
|
|
lhaux,
|
|
lhax,
|
|
lhbrx,
|
|
lhz,
|
|
lhzu,
|
|
lhzux,
|
|
lhzx,
|
|
// Load multiple/string
|
|
lmw,
|
|
lswi,
|
|
lswx,
|
|
// Load vector
|
|
lvebx,
|
|
lvehx,
|
|
lvewx,
|
|
lvewx128,
|
|
lvlx,
|
|
lvlx128,
|
|
lvlxl,
|
|
lvlxl128,
|
|
lvrx,
|
|
lvrx128,
|
|
lvrxl,
|
|
lvrxl128,
|
|
lvsl,
|
|
lvsl128,
|
|
lvsr,
|
|
lvsr128,
|
|
lvx,
|
|
lvx128,
|
|
lvxl,
|
|
lvxl128,
|
|
// Load word
|
|
lwa,
|
|
lwarx,
|
|
lwaux,
|
|
lwax,
|
|
lwbrx,
|
|
lwz,
|
|
lwzu,
|
|
lwzux,
|
|
lwzx,
|
|
// Move CR
|
|
mcrf,
|
|
mcrfs,
|
|
mcrxr,
|
|
// Move from special
|
|
mfcr,
|
|
mffsx,
|
|
mfmsr,
|
|
mfspr,
|
|
mftb,
|
|
mfvscr,
|
|
// Move to special
|
|
mtcrf,
|
|
mtfsb0x,
|
|
mtfsb1x,
|
|
mtfsfix,
|
|
mtfsfx,
|
|
mtmsr,
|
|
mtmsrd,
|
|
mtspr,
|
|
mtvscr,
|
|
// Multiply
|
|
mulhdux,
|
|
mulhdx,
|
|
mulhwux,
|
|
mulhwx,
|
|
mulldx,
|
|
mulli,
|
|
mullwx,
|
|
// Logical
|
|
nandx,
|
|
negx,
|
|
norx,
|
|
orcx,
|
|
ori,
|
|
oris,
|
|
orx,
|
|
// Rotate
|
|
rldclx,
|
|
rldcrx,
|
|
rldiclx,
|
|
rldicrx,
|
|
rldicx,
|
|
rldimix,
|
|
rlwimix,
|
|
rlwinmx,
|
|
rlwnmx,
|
|
// System call
|
|
sc,
|
|
// Shift
|
|
sldx,
|
|
slwx,
|
|
sradix,
|
|
sradx,
|
|
srawix,
|
|
srawx,
|
|
srdx,
|
|
srwx,
|
|
// Store byte
|
|
stb,
|
|
stbu,
|
|
stbux,
|
|
stbx,
|
|
// Store doubleword
|
|
std,
|
|
stdbrx,
|
|
stdcx,
|
|
stdu,
|
|
stdux,
|
|
stdx,
|
|
// Store float
|
|
stfd,
|
|
stfdu,
|
|
stfdux,
|
|
stfdx,
|
|
stfiwx,
|
|
stfs,
|
|
stfsu,
|
|
stfsux,
|
|
stfsx,
|
|
// Store halfword
|
|
sth,
|
|
sthbrx,
|
|
sthu,
|
|
sthux,
|
|
sthx,
|
|
// Store multiple/string
|
|
stmw,
|
|
stswi,
|
|
stswx,
|
|
// Store vector
|
|
stvebx,
|
|
stvehx,
|
|
stvewx,
|
|
stvewx128,
|
|
stvlx,
|
|
stvlx128,
|
|
stvlxl,
|
|
stvlxl128,
|
|
stvrx,
|
|
stvrx128,
|
|
stvrxl,
|
|
stvrxl128,
|
|
stvx,
|
|
stvx128,
|
|
stvxl,
|
|
stvxl128,
|
|
// Store word
|
|
stw,
|
|
stwbrx,
|
|
stwcx,
|
|
stwu,
|
|
stwux,
|
|
stwx,
|
|
// Subtract
|
|
subfcx,
|
|
subfex,
|
|
subficx,
|
|
subfmex,
|
|
subfx,
|
|
subfzex,
|
|
// Sync
|
|
sync,
|
|
// Trap
|
|
td,
|
|
tdi,
|
|
tw,
|
|
twi,
|
|
// VMX integer
|
|
vaddcuw,
|
|
vaddfp,
|
|
vaddfp128,
|
|
vaddsbs,
|
|
vaddshs,
|
|
vaddsws,
|
|
vaddubm,
|
|
vaddubs,
|
|
vadduhm,
|
|
vadduhs,
|
|
vadduwm,
|
|
vadduws,
|
|
vand,
|
|
vand128,
|
|
vandc,
|
|
vandc128,
|
|
vavgsb,
|
|
vavgsh,
|
|
vavgsw,
|
|
vavgub,
|
|
vavguh,
|
|
vavguw,
|
|
vcfpsxws128,
|
|
vcfpuxws128,
|
|
vcfsx,
|
|
vcfux,
|
|
vcmpbfp,
|
|
vcmpbfp128,
|
|
vcmpeqfp,
|
|
vcmpeqfp128,
|
|
vcmpequb,
|
|
vcmpequh,
|
|
vcmpequw,
|
|
vcmpequw128,
|
|
vcmpgefp,
|
|
vcmpgefp128,
|
|
vcmpgtfp,
|
|
vcmpgtfp128,
|
|
vcmpgtsb,
|
|
vcmpgtsh,
|
|
vcmpgtsw,
|
|
vcmpgtub,
|
|
vcmpgtuh,
|
|
vcmpgtuw,
|
|
vcsxwfp128,
|
|
vctsxs,
|
|
vctuxs,
|
|
vcuxwfp128,
|
|
vexptefp,
|
|
vexptefp128,
|
|
vlogefp,
|
|
vlogefp128,
|
|
vmaddcfp128,
|
|
vmaddfp,
|
|
vmaddfp128,
|
|
vmaxfp,
|
|
vmaxfp128,
|
|
vmaxsb,
|
|
vmaxsh,
|
|
vmaxsw,
|
|
vmaxub,
|
|
vmaxuh,
|
|
vmaxuw,
|
|
vmhaddshs,
|
|
vmhraddshs,
|
|
vminfp,
|
|
vminfp128,
|
|
vminsb,
|
|
vminsh,
|
|
vminsw,
|
|
vminub,
|
|
vminuh,
|
|
vminuw,
|
|
vmladduhm,
|
|
vmrghb,
|
|
vmrghh,
|
|
vmrghw,
|
|
vmrghw128,
|
|
vmrglb,
|
|
vmrglh,
|
|
vmrglw,
|
|
vmrglw128,
|
|
vmsum3fp128,
|
|
vmsum4fp128,
|
|
vmsummbm,
|
|
vmsumshm,
|
|
vmsumshs,
|
|
vmsumubm,
|
|
vmsumuhm,
|
|
vmsumuhs,
|
|
vmulesb,
|
|
vmulesh,
|
|
vmuleub,
|
|
vmuleuh,
|
|
vmulfp128,
|
|
vmulosb,
|
|
vmulosh,
|
|
vmuloub,
|
|
vmulouh,
|
|
vnmsubfp,
|
|
vnmsubfp128,
|
|
vnor,
|
|
vnor128,
|
|
vor,
|
|
vor128,
|
|
vperm,
|
|
vperm128,
|
|
vpermwi128,
|
|
vpkd3d128,
|
|
vpkpx,
|
|
vpkshss,
|
|
vpkshss128,
|
|
vpkshus,
|
|
vpkshus128,
|
|
vpkswss,
|
|
vpkswss128,
|
|
vpkswus,
|
|
vpkswus128,
|
|
vpkuhum,
|
|
vpkuhum128,
|
|
vpkuhus,
|
|
vpkuhus128,
|
|
vpkuwum,
|
|
vpkuwum128,
|
|
vpkuwus,
|
|
vpkuwus128,
|
|
vrefp,
|
|
vrefp128,
|
|
vrfim,
|
|
vrfim128,
|
|
vrfin,
|
|
vrfin128,
|
|
vrfip,
|
|
vrfip128,
|
|
vrfiz,
|
|
vrfiz128,
|
|
vrlb,
|
|
vrlh,
|
|
vrlimi128,
|
|
vrlw,
|
|
vrlw128,
|
|
vrsqrtefp,
|
|
vrsqrtefp128,
|
|
vsel,
|
|
vsel128,
|
|
vsl,
|
|
vslb,
|
|
vsldoi,
|
|
vsldoi128,
|
|
vslh,
|
|
vslo,
|
|
vslo128,
|
|
vslw,
|
|
vslw128,
|
|
vspltb,
|
|
vsplth,
|
|
vspltisb,
|
|
vspltish,
|
|
vspltisw,
|
|
vspltisw128,
|
|
vspltw,
|
|
vspltw128,
|
|
vsr,
|
|
vsrab,
|
|
vsrah,
|
|
vsraw,
|
|
vsraw128,
|
|
vsrb,
|
|
vsrh,
|
|
vsro,
|
|
vsro128,
|
|
vsrw,
|
|
vsrw128,
|
|
vsubcuw,
|
|
vsubfp,
|
|
vsubfp128,
|
|
vsubsbs,
|
|
vsubshs,
|
|
vsubsws,
|
|
vsububm,
|
|
vsububs,
|
|
vsubuhm,
|
|
vsubuhs,
|
|
vsubuwm,
|
|
vsubuws,
|
|
vsum2sws,
|
|
vsum4sbs,
|
|
vsum4shs,
|
|
vsum4ubs,
|
|
vsumsws,
|
|
vupkd3d128,
|
|
vupkhpx,
|
|
vupkhsb,
|
|
vupkhsb128,
|
|
vupkhsh,
|
|
vupklpx,
|
|
vupklsb,
|
|
vupklsb128,
|
|
vupklsh,
|
|
vxor,
|
|
vxor128,
|
|
// XOR immediate
|
|
xori,
|
|
xoris,
|
|
xorx,
|
|
// Invalid
|
|
Invalid,
|
|
}
|
|
|
|
impl PpcOpcode {
|
|
/// Returns true if this opcode is a branch instruction.
|
|
pub fn is_branch(&self) -> bool {
|
|
matches!(self, Self::bx | Self::bcx | Self::bclrx | Self::bcctrx)
|
|
}
|
|
|
|
/// Returns true if this opcode is a system call.
|
|
pub fn is_syscall(&self) -> bool {
|
|
matches!(self, Self::sc)
|
|
}
|
|
|
|
/// Returns true if this opcode unconditionally ends a basic block:
|
|
/// any branch, system call, trap, or `Invalid` (decoder couldn't
|
|
/// recognize the instruction — execution will hit the
|
|
/// `Unimplemented` arm and we don't want to swallow the boundary
|
|
/// inside a cached block).
|
|
///
|
|
/// Notably *not* terminating: `mtmsr`/`mtmsrd`/`isync`/`mfmsr`.
|
|
/// On real hardware these have synchronization semantics (a context
|
|
/// synchronizing event for `isync`, MSR rewrite for the `mt*`s) but
|
|
/// our interpreter has no asynchronous-exception model and no
|
|
/// out-of-order execution — they execute as plain ALU/move ops and
|
|
/// don't change control flow synchronously. Block-cache replay is
|
|
/// still bit-for-bit identical to per-instruction dispatch for
|
|
/// those.
|
|
///
|
|
/// Used by the basic-block cache (`block_cache.rs`) to know when to
|
|
/// stop accumulating instructions during a forward decode walk.
|
|
pub fn terminates_block(&self) -> bool {
|
|
matches!(
|
|
self,
|
|
Self::bx
|
|
| Self::bcx
|
|
| Self::bclrx
|
|
| Self::bcctrx
|
|
| Self::sc
|
|
| Self::td
|
|
| Self::tdi
|
|
| Self::tw
|
|
| Self::twi
|
|
| Self::Invalid
|
|
)
|
|
}
|
|
|
|
/// Returns true if this is a load instruction.
|
|
pub fn is_load(&self) -> bool {
|
|
matches!(
|
|
self,
|
|
Self::lbz
|
|
| Self::lbzu
|
|
| Self::lbzux
|
|
| Self::lbzx
|
|
| Self::lhz
|
|
| Self::lhzu
|
|
| Self::lhzux
|
|
| Self::lhzx
|
|
| Self::lha
|
|
| Self::lhau
|
|
| Self::lhaux
|
|
| Self::lhax
|
|
| Self::lwz
|
|
| Self::lwzu
|
|
| Self::lwzux
|
|
| Self::lwzx
|
|
| Self::lwa
|
|
| Self::lwax
|
|
| Self::lwaux
|
|
| Self::ld
|
|
| Self::ldu
|
|
| Self::ldux
|
|
| Self::ldx
|
|
| Self::lfs
|
|
| Self::lfsu
|
|
| Self::lfsux
|
|
| Self::lfsx
|
|
| Self::lfd
|
|
| Self::lfdu
|
|
| Self::lfdux
|
|
| Self::lfdx
|
|
| Self::lhbrx
|
|
| Self::lwbrx
|
|
| Self::ldbrx
|
|
| Self::lmw
|
|
| Self::lswi
|
|
| Self::lswx
|
|
| Self::lwarx
|
|
| Self::ldarx
|
|
)
|
|
}
|
|
|
|
/// Returns true if this is a store instruction.
|
|
pub fn is_store(&self) -> bool {
|
|
matches!(
|
|
self,
|
|
Self::stb
|
|
| Self::stbu
|
|
| Self::stbux
|
|
| Self::stbx
|
|
| Self::sth
|
|
| Self::sthu
|
|
| Self::sthux
|
|
| Self::sthx
|
|
| Self::stw
|
|
| Self::stwu
|
|
| Self::stwux
|
|
| Self::stwx
|
|
| Self::std
|
|
| Self::stdu
|
|
| Self::stdux
|
|
| Self::stdx
|
|
| Self::stfs
|
|
| Self::stfsu
|
|
| Self::stfsux
|
|
| Self::stfsx
|
|
| Self::stfd
|
|
| Self::stfdu
|
|
| Self::stfdux
|
|
| Self::stfdx
|
|
| Self::sthbrx
|
|
| Self::stwbrx
|
|
| Self::stdbrx
|
|
| Self::stmw
|
|
| Self::stswi
|
|
| Self::stswx
|
|
| Self::stwcx
|
|
| Self::stdcx
|
|
| Self::stfiwx
|
|
)
|
|
}
|
|
|
|
/// Returns true if this opcode is a cross-thread synchronization
|
|
/// point at which the superblock runner MUST yield back to the
|
|
/// round-robin scheduler so the lockstep interleaving stays
|
|
/// fine-grained enough to preserve correct cross-thread ordering:
|
|
///
|
|
/// - reserved load/store (`lwarx`/`ldarx`/`stwcx.`/`stdcx.`): the
|
|
/// atomic primitive other threads race on. Running past one
|
|
/// without returning to the scheduler would let a single slot
|
|
/// win/lose a reservation across many blocks before any peer
|
|
/// observes it.
|
|
/// - memory barriers (`sync`/`eieio`/`isync`): the guest explicitly
|
|
/// demands a global ordering point here; honour it by ending the
|
|
/// superblock so the scheduler re-interleaves.
|
|
///
|
|
/// Purely a function of the opcode (no guest data), so the yield
|
|
/// decision is deterministic and the schedule reproduces byte-identically.
|
|
/// Note: `sc` (syscall) and traps already `terminates_block`, and
|
|
/// import-thunk / halt-sentinel PCs are handled by the per-block
|
|
/// prologue re-check in the superblock loop — they are not listed here.
|
|
#[inline]
|
|
pub fn is_sync_sensitive(&self) -> bool {
|
|
matches!(
|
|
self,
|
|
Self::lwarx
|
|
| Self::ldarx
|
|
| Self::stwcx
|
|
| Self::stdcx
|
|
| Self::sync
|
|
| Self::eieio
|
|
| Self::isync
|
|
)
|
|
}
|
|
|
|
pub fn name(&self) -> &'static str {
|
|
match self {
|
|
Self::Invalid => "invalid",
|
|
_ => {
|
|
// Use debug formatting to get the variant name
|
|
// This is a placeholder - in practice we'd have a lookup table
|
|
"?"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for PpcOpcode {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
std::fmt::Debug::fmt(self, f)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn terminates_block_includes_all_branches() {
|
|
assert!(PpcOpcode::bx.terminates_block());
|
|
assert!(PpcOpcode::bcx.terminates_block());
|
|
assert!(PpcOpcode::bclrx.terminates_block());
|
|
assert!(PpcOpcode::bcctrx.terminates_block());
|
|
}
|
|
|
|
#[test]
|
|
fn terminates_block_includes_sc_and_traps() {
|
|
assert!(PpcOpcode::sc.terminates_block());
|
|
assert!(PpcOpcode::td.terminates_block());
|
|
assert!(PpcOpcode::tdi.terminates_block());
|
|
assert!(PpcOpcode::tw.terminates_block());
|
|
assert!(PpcOpcode::twi.terminates_block());
|
|
}
|
|
|
|
#[test]
|
|
fn terminates_block_includes_invalid() {
|
|
// Decoder failure must end the block — otherwise an unknown
|
|
// opcode would be replayed inside a cached block without going
|
|
// through the per-instruction Unimplemented path.
|
|
assert!(PpcOpcode::Invalid.terminates_block());
|
|
}
|
|
|
|
#[test]
|
|
fn terminates_block_excludes_straight_line_ops() {
|
|
// Common ALU and load/store ops must NOT terminate a block.
|
|
assert!(!PpcOpcode::addi.terminates_block());
|
|
assert!(!PpcOpcode::addis.terminates_block());
|
|
assert!(!PpcOpcode::addx.terminates_block());
|
|
assert!(!PpcOpcode::cmpi.terminates_block());
|
|
assert!(!PpcOpcode::cmp.terminates_block());
|
|
assert!(!PpcOpcode::lwz.terminates_block());
|
|
assert!(!PpcOpcode::stw.terminates_block());
|
|
assert!(!PpcOpcode::lbzx.terminates_block());
|
|
assert!(!PpcOpcode::ori.terminates_block());
|
|
assert!(!PpcOpcode::oris.terminates_block());
|
|
assert!(!PpcOpcode::rlwinmx.terminates_block());
|
|
}
|
|
|
|
#[test]
|
|
fn terminates_block_excludes_msr_and_sync_ops() {
|
|
// Documented decision: synchronizing ops execute as ALU within
|
|
// a block since the interpreter has no async-exception model.
|
|
assert!(!PpcOpcode::mtmsr.terminates_block());
|
|
assert!(!PpcOpcode::mtmsrd.terminates_block());
|
|
assert!(!PpcOpcode::isync.terminates_block());
|
|
assert!(!PpcOpcode::sync.terminates_block());
|
|
assert!(!PpcOpcode::mfmsr.terminates_block());
|
|
}
|
|
}
|