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