Files
xenia-rs/crates/xenia-cpu/src/trap.rs
MechaCat02 c36cca14f9 xenia-cpu: VMX128, FPSCR, decoder split, scheduler, decode/block caches
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>
2026-05-01 16:27:43 +02:00

96 lines
3.1 KiB
Rust

//! TO-field evaluation for `tw`, `twi`, `td`, `tdi`.
//!
//! The TO field (5 bits) encodes which comparison outcomes trigger a trap:
//!
//! | bit | condition |
//! |-----|-----------|
//! | 0 | a < b (signed) |
//! | 1 | a > b (signed) |
//! | 2 | a == b |
//! | 3 | a < b (unsigned) |
//! | 4 | a > b (unsigned) |
//!
//! The bit numbering matches PowerISA ("MSB is bit 0"): TO[0] corresponds to
//! the high bit of the 5-bit field, i.e. (to >> 4) & 1.
//!
//! `tw` / `twi` compare the low 32 bits of the operands (sign-extended back to
//! 64 for the signed comparison); `td` / `tdi` compare the full 64 bits.
#[derive(Clone, Copy, Debug)]
pub enum TrapWidth {
Word, // tw, twi: 32-bit
Doubleword, // td, tdi: 64-bit
}
const TO_SLT: u32 = 1 << 4; // a < b signed
const TO_SGT: u32 = 1 << 3; // a > b signed
const TO_EQ: u32 = 1 << 2; // a == b
const TO_ULT: u32 = 1 << 1; // a < b unsigned
const TO_UGT: u32 = 1 << 0; // a > b unsigned
/// Returns true when the trap should fire.
pub fn evaluate(to: u32, a: u64, b: u64, width: TrapWidth) -> bool {
let (sa, sb, ua, ub): (i64, i64, u64, u64) = match width {
TrapWidth::Word => (
a as i32 as i64,
b as i32 as i64,
a as u32 as u64,
b as u32 as u64,
),
TrapWidth::Doubleword => (a as i64, b as i64, a, b),
};
if (to & TO_SLT) != 0 && sa < sb { return true; }
if (to & TO_SGT) != 0 && sa > sb { return true; }
if (to & TO_EQ) != 0 && ua == ub { return true; }
if (to & TO_ULT) != 0 && ua < ub { return true; }
if (to & TO_UGT) != 0 && ua > ub { return true; }
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn to_zero_never_traps() {
assert!(!evaluate(0, 0, 0, TrapWidth::Doubleword));
assert!(!evaluate(0, 5, 3, TrapWidth::Doubleword));
assert!(!evaluate(0, !0, 0, TrapWidth::Doubleword));
}
#[test]
fn to_31_always_traps_when_any_condition_holds() {
// 31 = 0b11111 = all conditions enabled
assert!(evaluate(31, 1, 2, TrapWidth::Doubleword)); // slt+ult
assert!(evaluate(31, 2, 1, TrapWidth::Doubleword)); // sgt+ugt
assert!(evaluate(31, 7, 7, TrapWidth::Doubleword)); // eq
}
#[test]
fn to_eq_only() {
// TO[2] = 0b00100 = 4
assert!(evaluate(4, 5, 5, TrapWidth::Doubleword));
assert!(!evaluate(4, 5, 6, TrapWidth::Doubleword));
}
#[test]
fn to_signed_vs_unsigned_on_negative() {
// a=-1 (as u64 = all-ones). TO[0]=slt enabled = 0b10000 = 16
// Signed: -1 < 0 → true
let neg1 = (-1i64) as u64;
assert!(evaluate(16, neg1, 0, TrapWidth::Doubleword));
// TO[3]=ult enabled = 0b00010 = 2 → unsigned: all-ones < 0 is false
assert!(!evaluate(2, neg1, 0, TrapWidth::Doubleword));
}
#[test]
fn word_width_ignores_high_32_bits() {
// a's low 32 = 1, high 32 = different; b = 1. With TO=eq, should trap.
let a = 0xDEAD_BEEF_0000_0001u64;
assert!(evaluate(4, a, 1, TrapWidth::Word));
// In doubleword, different.
assert!(!evaluate(4, a, 1, TrapWidth::Doubleword));
}
}