Files
Sylpheed/crates/sylpheed-ppc/src/opcode.rs
MechaCat02 9c48c340bd fix(xexdb): clear the lint gate on the imported crates
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>
2026-09-13 20:25:44 +02:00

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());
}
}