use superh::SystemReg;
use superh::{
AccessWidth, AddressingMode, Architecture, DecodeOptions, DecodeResult, EffectContext, Effects,
Ins, MemoryAccessKind, Reg, Resource, ResourceSet, StatusBit, decode,
};
#[cfg(feature = "sh4")]
use superh::{DReg, FpscrState, FpuResource};
fn instruction(word: u16) -> Ins {
decode(word, &DecodeOptions::default()).instruction().copied().expect("known instruction")
}
#[test]
fn arithmetic_effects_are_unique_and_directional() {
let effects = instruction(0x321e).effects(EffectContext::default()); assert!(effects.must_write().contains(Resource::Gp(Reg::R2)));
assert!(effects.must_write().contains(Resource::Status(StatusBit::T)));
assert!(effects.must_read().contains(Resource::Gp(Reg::R1)));
assert!(effects.must_read().contains(Resource::Gp(Reg::R2)));
assert!(effects.must_read().contains(Resource::Status(StatusBit::T)));
}
#[test]
fn effects_storage_is_compact_with_exhaustive_headroom() {
assert_eq!(core::mem::size_of::<ResourceSet>(), 33);
assert_eq!(core::mem::size_of::<Effects>(), 145);
let architectures = [
#[cfg(feature = "sh1")]
Architecture::Sh1,
#[cfg(feature = "sh2")]
Architecture::Sh2,
#[cfg(feature = "sh3")]
Architecture::Sh3,
#[cfg(feature = "sh4")]
Architecture::Sh4,
];
let mut maximum = 0;
for architecture in architectures {
let options = DecodeOptions::new(architecture);
for word in 0_u16..=u16::MAX {
let DecodeResult::Instruction(instruction) = decode(word, &options) else {
continue;
};
let effects = instruction.effects(EffectContext::new(architecture));
maximum = maximum.max(
[
effects.must_read().len(),
effects.may_read().len(),
effects.must_write().len(),
effects.may_write().len(),
]
.into_iter()
.max()
.expect("four resource sets"),
);
}
}
assert!(maximum <= 9, "resource occupancy grew to {maximum}");
}
#[test]
fn division_models_q_and_m_state() {
let div0s = instruction(0x2127).effects(EffectContext::default());
for bit in [StatusBit::T, StatusBit::Q, StatusBit::M] {
assert!(div0s.must_write().contains(Resource::Status(bit)));
}
let div1 = instruction(0x3124).effects(EffectContext::default());
assert!(div1.must_read().contains(Resource::Status(StatusBit::Q)));
assert!(div1.must_read().contains(Resource::Status(StatusBit::M)));
assert!(div1.must_write().contains(Resource::Status(StatusBit::Q)));
}
#[test]
#[cfg(feature = "sh3")]
fn s_bit_instructions_touch_only_s() {
let effects = instruction(0x0048).effects(EffectContext::new(Architecture::Sh3));
assert!(effects.must_write().contains(Resource::Status(StatusBit::S)));
assert!(!effects.must_write().contains(Resource::System(SystemReg::Sr)));
}
#[test]
fn rte_effects_depend_on_architecture() {
let sh1 = instruction(0x002b).effects(EffectContext::new(Architecture::Sh1));
assert!(sh1.must_read().contains(Resource::Gp(Reg::R15)));
assert_eq!(sh1.memory().count(), 2);
#[cfg(feature = "sh3")]
{
let sh3 = instruction(0x002b).effects(EffectContext::new(Architecture::Sh3));
assert!(sh3.must_read().contains(Resource::System(SystemReg::Ssr)));
assert!(sh3.must_read().contains(Resource::System(SystemReg::Spc)));
assert_eq!(sh3.memory().count(), 0);
}
}
#[test]
fn memory_effects_include_direction_width_and_addressing() {
let store = instruction(0x2122).effects(EffectContext::default()); let access = store.memory().next().expect("memory write");
assert_eq!(access.kind, MemoryAccessKind::Write);
assert_eq!(access.width, AccessWidth::Long);
assert_eq!(access.addressing, AddressingMode::Indirect);
}
#[test]
#[cfg(feature = "sh4")]
fn unknown_fr_state_produces_may_effects() {
use superh::FReg;
let instruction = instruction(0xf020); let unknown = instruction.effects(EffectContext::new(Architecture::Sh4));
assert!(unknown.may_write().contains(Resource::Fpu(FpuResource::Fr(FReg::Fr0))));
assert!(unknown.may_write().contains(Resource::Fpu(FpuResource::Xf(FReg::Fr0))));
assert!(unknown.may_write().contains(Resource::Fpu(FpuResource::Dr(DReg::Dr0))));
assert!(unknown.may_write().contains(Resource::Fpu(FpuResource::Xd(DReg::Dr0))));
assert!(!unknown.must_write().contains(Resource::Fpu(FpuResource::Fr(FReg::Fr0))));
let known =
instruction.effects(EffectContext::new(Architecture::Sh4).with_fpscr(FpscrState::new(
Some(false),
Some(false),
Some(false),
)));
assert!(known.must_write().contains(Resource::Fpu(FpuResource::Fr(FReg::Fr0))));
let double =
instruction.effects(EffectContext::new(Architecture::Sh4).with_fpscr(FpscrState::new(
Some(true),
Some(false),
Some(false),
)));
assert!(double.must_write().contains(Resource::Fpu(FpuResource::Dr(DReg::Dr0))));
assert!(double.must_read().contains(Resource::Fpu(FpuResource::Dr(DReg::Dr2))));
}
#[test]
#[cfg(feature = "sh4")]
fn known_sz_resolves_fmov_width() {
let instruction = instruction(0xf028); let long = instruction.effects(
EffectContext::new(Architecture::Sh4).with_fpscr(FpscrState::new(None, Some(false), None)),
);
assert_eq!(long.memory().next().expect("memory access").width, AccessWidth::Long);
let quad = instruction.effects(
EffectContext::new(Architecture::Sh4).with_fpscr(FpscrState::new(None, Some(true), None)),
);
assert_eq!(quad.memory().next().expect("memory access").width, AccessWidth::Quad);
}
#[test]
#[cfg(feature = "sh4")]
fn fmov_odd_transfer_field_selects_xd_register() {
let load = instruction(0xf128); let bank_zero =
load.effects(EffectContext::new(Architecture::Sh4).with_fpscr(FpscrState::new(
None,
Some(true),
Some(false),
)));
assert!(bank_zero.must_write().contains(Resource::Fpu(FpuResource::Xd(DReg::Dr0))));
assert!(!bank_zero.must_write().contains(Resource::Fpu(FpuResource::Dr(DReg::Dr0))));
let bank_one = load.effects(EffectContext::new(Architecture::Sh4).with_fpscr(FpscrState::new(
None,
Some(true),
Some(true),
)));
assert!(bank_one.must_write().contains(Resource::Fpu(FpuResource::Dr(DReg::Dr0))));
assert!(!bank_one.must_write().contains(Resource::Fpu(FpuResource::Xd(DReg::Dr0))));
let store = instruction(0xf21a); let store_effects =
store.effects(EffectContext::new(Architecture::Sh4).with_fpscr(FpscrState::new(
None,
Some(true),
Some(false),
)));
assert!(store_effects.must_read().contains(Resource::Fpu(FpuResource::Xd(DReg::Dr0))));
}
#[test]
#[cfg(feature = "sh4")]
fn fsts_and_flds_read_fpscr_for_register_bank_selection() {
let fpscr = Resource::System(SystemReg::Fpscr);
assert!(
instruction(0xf00d)
.effects(EffectContext::new(Architecture::Sh4))
.must_read()
.contains(fpscr)
);
assert!(
instruction(0xf01d)
.effects(EffectContext::new(Architecture::Sh4))
.must_read()
.contains(fpscr)
);
}
#[test]
#[cfg(feature = "sh4")]
fn ftrv_reads_matrix_resource() {
use superh::FpuResource;
let effects = instruction(0xf1fd).effects(EffectContext::new(Architecture::Sh4));
assert!(effects.must_read().contains(Resource::Fpu(FpuResource::Matrix)));
}
#[cfg(feature = "sh1")]
fn assert_stack_trapa_effects(architecture: Architecture) {
let effects = instruction(0xc320).effects(EffectContext::new(architecture)); for resource in [
Resource::System(SystemReg::Sr),
Resource::Status(StatusBit::T),
Resource::System(SystemReg::Vbr),
Resource::Gp(Reg::R15),
] {
assert!(
effects.must_read().contains(resource),
"{architecture:?} trapa must read {resource:?}"
);
}
assert!(effects.must_write().contains(Resource::Gp(Reg::R15)));
let accesses: Vec<_> = effects.memory().collect();
assert_eq!(accesses.len(), 3, "two stack pushes and one vector fetch");
for push in &accesses[..2] {
assert_eq!(push.kind, MemoryAccessKind::Write);
assert_eq!(push.width, AccessWidth::Long);
assert_eq!(push.addressing, AddressingMode::PreDecrement);
}
assert_eq!(accesses[2].kind, MemoryAccessKind::Read);
assert_eq!(accesses[2].width, AccessWidth::Long);
assert_eq!(accesses[2].addressing, AddressingMode::Displacement);
}
#[test]
#[cfg(feature = "sh1")]
fn trapa_models_sh1_stack_entry() {
assert_stack_trapa_effects(Architecture::Sh1);
}
#[test]
#[cfg(feature = "sh2")]
fn trapa_models_sh2_stack_entry() {
assert_stack_trapa_effects(Architecture::Sh2);
}
#[test]
#[cfg(feature = "sh3")]
fn trapa_models_sh3_register_entry_without_sgr() {
let effects = instruction(0xc320).effects(EffectContext::new(Architecture::Sh3)); for resource in [
Resource::System(SystemReg::Sr),
Resource::Status(StatusBit::T),
Resource::System(SystemReg::Vbr),
] {
assert!(effects.must_read().contains(resource), "sh3 trapa must read {resource:?}");
}
for resource in [
Resource::System(SystemReg::Ssr),
Resource::System(SystemReg::Spc),
Resource::System(SystemReg::Tra),
Resource::System(SystemReg::Expevt),
Resource::System(SystemReg::Sr),
] {
assert!(effects.must_write().contains(resource), "sh3 trapa must write {resource:?}");
}
assert!(!effects.may_read().contains(Resource::Gp(Reg::R15)));
assert!(!effects.may_write().contains(Resource::System(SystemReg::Sgr)));
assert!(!effects.may_write().contains(Resource::Status(StatusBit::T)));
assert_eq!(effects.memory().count(), 0);
}
#[test]
#[cfg(feature = "sh4")]
fn trapa_models_sh4_register_entry_with_sgr() {
let effects = instruction(0xc320).effects(EffectContext::new(Architecture::Sh4)); assert!(effects.must_read().contains(Resource::Gp(Reg::R15)));
assert!(effects.must_write().contains(Resource::System(SystemReg::Sgr)));
assert!(effects.must_write().contains(Resource::System(SystemReg::Tra)));
assert!(effects.must_write().contains(Resource::System(SystemReg::Expevt)));
assert_eq!(effects.memory().count(), 0);
}