Split the monolithic interpreter into cohesive modules: dedicated decoder (decoder.rs) producing 8-byte DecodedInstr; opcode tables (opcode.rs); explicit traps (trap.rs); FPSCR helpers (fpscr.rs); overflow/carry helpers (overflow.rs); a 4 KiB-page-versioned decode cache and basic-block cache (block_cache.rs); and a full VMX/VMX128 implementation (vmx.rs) covering AltiVec + Xenon's 128-bit extensions. Add the parallel-execution substrate behind --parallel: a 7-party phaser (phaser.rs) for round-based barrier sync, ReservationTable (reservation.rs) for guest LL/SC, and the per-HW-thread scheduler core (scheduler.rs) that owns ThreadRefs, runqueues, and pending IRQs. Disassembler is now the single source of truth: disasm.rs gains the full base + extended + VMX128 mnemonic set, with golden JSON fixtures and a disasm_goldens test suite. Add a criterion-style interpreter bench. context.rs grows the per-thread state the new modules need (reservation slot, FPSCR, vector regs). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
281 lines
10 KiB
Rust
281 lines
10 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
|
|
)
|
|
}
|
|
|
|
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());
|
|
}
|
|
}
|