use std::sync::OnceLock;
use crate::cpu::{CpuOps, Register};
use crate::memory::{MacMemoryBus, MemoryBus};
use crate::Result;
static TRACE_SANE_NAN: OnceLock<bool> = OnceLock::new();
static TRACE_SANE: OnceLock<bool> = OnceLock::new();
#[inline]
fn trace_sane_nan_enabled() -> bool {
*TRACE_SANE_NAN.get_or_init(|| std::env::var_os("SYSTEMLESS_TRACE_SANE_NAN").is_some())
}
#[inline]
fn trace_sane_enabled() -> bool {
*TRACE_SANE.get_or_init(|| std::env::var_os("SYSTEMLESS_TRACE_SANE").is_some())
}
fn trace_sane_bytes(bus: &MacMemoryBus, addr: u32, len: u32) -> String {
use std::fmt::Write;
let mut out = String::with_capacity(len as usize * 2);
for i in 0..len {
let _ = write!(out, "{:02X}", bus.read_byte(addr + i));
}
out
}
#[inline]
fn sane_op_name(op: u16) -> &'static str {
match op {
0x00 => "FLN",
0x02 => "FLOG2",
0x04 => "FLN1",
0x06 => "FLOG21",
0x08 => "FEXP",
0x0A => "FEXP2",
0x0C => "FEXP1",
0x0E => "FEXP21",
0x18 => "FSIN",
0x1A => "FCOS",
0x1C => "FTAN",
0x1E => "FATAN",
_ => "Felems",
}
}
#[inline]
fn elems_op_name(opcode: u16) -> &'static str {
match (opcode & 0x8000 != 0, opcode & 0x00FF) {
(true, 0x10) => "FXPWRI",
(true, 0x12) => "FXPWRY",
(_, op) => sane_op_name(op),
}
}
#[inline]
fn report_loaded_nan_if_enabled(
pc: u32,
caller: Option<u32>,
addr: u32,
fmt_label: &str,
role: &str,
value: f64,
) {
if !trace_sane_nan_enabled() || value.is_finite() {
return;
}
let caller_str = caller
.map(|c| format!(" caller=${:08X}", c))
.unwrap_or_default();
eprintln!(
"[SANE-NAN] LOAD {} ${:08X} fmt={} = {} ({}) pc=${:08X}{}",
role,
addr,
fmt_label,
value,
if value.is_nan() { "NaN" } else { "inf" },
pc,
caller_str,
);
}
#[inline]
fn report_nan_if_enabled(
pc: u32,
caller: Option<u32>,
op_name: &str,
x: f64,
y: Option<f64>,
result: f64,
) {
if !trace_sane_nan_enabled() || result.is_finite() {
return;
}
let inputs_finite = x.is_finite() && y.map(|v| v.is_finite()).unwrap_or(true);
let tag = if inputs_finite { "NEW" } else { "PROP" };
let kind = if result.is_nan() { "NaN" } else { "inf" };
let caller_str = caller
.map(|c| format!(" caller=${:08X}", c))
.unwrap_or_default();
match y {
Some(v) => eprintln!(
"[SANE-NAN] {} {}({}, {}) → {} ({}) pc=${:08X}{}",
tag, op_name, x, v, result, kind, pc, caller_str,
),
None => eprintln!(
"[SANE-NAN] {} {}({}) → {} ({}) pc=${:08X}{}",
tag, op_name, x, result, kind, pc, caller_str,
),
}
}
impl super::TrapDispatcher {
pub(crate) fn dispatch_sane<C: CpuOps>(
&mut self,
is_tool: bool,
trap_num: u16,
cpu: &mut C,
bus: &mut MacMemoryBus,
) -> Option<Result<()>> {
let result = match (is_tool, trap_num) {
(true, 0x1EB) => {
let sp = cpu.read_reg(Register::A7);
let opcode = bus.read_word(sp);
self.handle_fp68k(opcode, sp, cpu, bus)
}
(true, 0x1EC) => {
let sp = cpu.read_reg(Register::A7);
let opcode = bus.read_word(sp);
self.handle_elems68k(opcode, sp, cpu, bus)
}
_ => return None,
};
Some(result)
}
fn handle_fp68k<C: CpuOps>(
&mut self,
opcode: u16,
sp: u32,
cpu: &mut C,
bus: &mut MacMemoryBus,
) -> Result<()> {
let op = opcode & 0x00FF; let fmt = (opcode >> 11) & 0x07;
let is_two_addr = matches!(
op,
0x00 | 0x02 | 0x04 | 0x06 | 0x08 | 0x0A | 0x0C | 0x0E | 0x10 | 0x18 | 0x1C );
let trap_pc = cpu.read_reg(Register::PC).wrapping_sub(2);
let trap_caller = self.current_trap_caller;
let trace_sane = trace_sane_enabled();
use super::extended80::Extended80;
if is_two_addr {
let dst_ptr = bus.read_long(sp + 2);
let src_ptr = bus.read_long(sp + 6);
cpu.write_reg(Register::A7, sp + 10);
let src_ext = Extended80::read_format(bus, src_ptr, fmt);
let dst_ext = Extended80::read_from_bus(bus, dst_ptr);
let src_f = f64::from(src_ext);
let dst_f = f64::from(dst_ext);
if trace_sane {
eprintln!(
"[SANE] FP68K pc=${:08X} caller={} opcode=0x{:04X} op=0x{:02X} fmt={} two=1 sp=${:08X} dst=${:08X} src=${:08X} dst={} src={} dst_raw={} src_raw={}",
trap_pc,
trap_caller
.map(|c| format!("${:08X}", c))
.unwrap_or_else(|| "-".to_string()),
opcode,
op,
fmt,
sp,
dst_ptr,
src_ptr,
dst_f,
src_f,
trace_sane_bytes(bus, dst_ptr, 10),
trace_sane_bytes(bus, src_ptr, if fmt == 0 { 10 } else { 8 }),
);
}
report_loaded_nan_if_enabled(
trap_pc,
trap_caller,
src_ptr,
&format!("{}", fmt),
"src",
src_f,
);
report_loaded_nan_if_enabled(trap_pc, trap_caller, dst_ptr, "ext", "dst", dst_f);
let fp_result = |op_name: &str, r: f64| {
report_nan_if_enabled(trap_pc, trap_caller, op_name, dst_f, Some(src_f), r);
Some(Extended80::from(r))
};
let result = match op {
0x00 => fp_result("FADD", dst_f + src_f),
0x02 => fp_result("FSUB", dst_f - src_f),
0x04 => fp_result("FMUL", dst_f * src_f),
0x06 => fp_result("FDIV", dst_f / src_f),
0x08 | 0x0A => {
let ccr = if dst_f.is_nan() || src_f.is_nan() {
0x02 } else if dst_f == src_f {
0x04 } else if dst_f < src_f {
0x19 } else {
0x00
};
cpu.set_ccr(ccr);
return Ok(());
}
0x0C => {
let rem = dst_f - (dst_f / src_f).round() * src_f;
Some(Extended80::from(rem))
}
0x0E => {
src_ext.write_to_bus(bus, dst_ptr);
return Ok(());
}
0x10 => {
let ext_val = Extended80::read_from_bus(bus, src_ptr);
ext_val.write_format(bus, dst_ptr, fmt);
return Ok(());
}
0x18 => {
let n = bus.read_word(src_ptr) as i16;
Some(Extended80::from(libm::scalbn(dst_f, n as i32)))
}
0x1C => {
bus.write_word(dst_ptr, src_ext.classify() as u16);
return Ok(());
}
_ => None,
};
if let Some(r) = result {
if trace_sane {
eprintln!(
"[SANE] FP68K result pc=${:08X} opcode=0x{:04X} dst=${:08X} result={}",
trap_pc,
opcode,
dst_ptr,
f64::from(r)
);
}
r.write_to_bus(bus, dst_ptr);
}
} else {
let dst_ptr = bus.read_long(sp + 2);
cpu.write_reg(Register::A7, sp + 6);
let dst_ext = Extended80::read_from_bus(bus, dst_ptr);
let dst_f = f64::from(dst_ext);
if trace_sane {
eprintln!(
"[SANE] FP68K pc=${:08X} caller={} opcode=0x{:04X} op=0x{:02X} fmt={} two=0 sp=${:08X} dst=${:08X} dst={} dst_raw={}",
trap_pc,
trap_caller
.map(|c| format!("${:08X}", c))
.unwrap_or_else(|| "-".to_string()),
opcode,
op,
fmt,
sp,
dst_ptr,
dst_f,
trace_sane_bytes(bus, dst_ptr, 10),
);
}
report_loaded_nan_if_enabled(trap_pc, trap_caller, dst_ptr, "ext", "dst", dst_f);
let fp1_result = |op_name: &str, r: f64| {
report_nan_if_enabled(trap_pc, trap_caller, op_name, dst_f, None, r);
Some(Extended80::from(r))
};
let result = match op {
0x0D => fp1_result("FNEG", -dst_f),
0x0F => fp1_result("FABS", dst_f.abs()),
0x12 => fp1_result("FSQRT", libm::sqrt(dst_f)),
0x14 => fp1_result("FRTI", libm::rint(dst_f)),
0x16 => fp1_result("FTTI", libm::trunc(dst_f)),
0x1A => fp1_result("FLOGB", libm::ilogb(dst_f) as f64),
0x01 | 0x17 => {
bus.write_word(dst_ptr, 0);
return Ok(());
} 0x03 | 0x09 | 0x19 | 0x1E => {
return Ok(());
} 0x05 => {
bus.write_word(dst_ptr, 0);
return Ok(());
} 0x0B => {
bus.write_word(dst_ptr, 0);
return Ok(());
} _ => None,
};
if let Some(r) = result {
if trace_sane {
eprintln!(
"[SANE] FP68K result pc=${:08X} opcode=0x{:04X} dst=${:08X} result={}",
trap_pc,
opcode,
dst_ptr,
f64::from(r)
);
}
r.write_to_bus(bus, dst_ptr);
}
}
Ok(())
}
fn handle_elems68k<C: CpuOps>(
&mut self,
opcode: u16,
sp: u32,
cpu: &mut C,
bus: &mut MacMemoryBus,
) -> Result<()> {
let op = opcode & 0x00FF;
let fmt = (opcode >> 11) & 0x07;
static ELEMS_LOG_COUNT: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
let log_count = ELEMS_LOG_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
if log_count < 20 {
eprintln!(
"[SANE] Elems68K opcode=0x{:04X} op=0x{:02X} fmt={} SP=${:08X}",
opcode, op, fmt, sp
);
}
let trap_pc = cpu.read_reg(Register::PC).wrapping_sub(2);
let trap_caller = self.current_trap_caller;
let trace_sane = trace_sane_enabled();
use super::extended80::Extended80;
let is_two_addr_elem = (opcode & 0x8000) != 0;
if is_two_addr_elem {
let dst_ptr = bus.read_long(sp + 2);
let src_ptr = bus.read_long(sp + 6);
cpu.write_reg(Register::A7, sp + 10);
let src_val = Extended80::read_format(bus, src_ptr, fmt);
let dst_val = Extended80::read_from_bus(bus, dst_ptr);
if trace_sane {
eprintln!(
"[SANE] Elems68K pc=${:08X} caller={} opcode=0x{:04X} op={} fmt={} two=1 sp=${:08X} dst=${:08X} src=${:08X} dst={} src={} dst_raw={} src_raw={}",
trap_pc,
trap_caller
.map(|c| format!("${:08X}", c))
.unwrap_or_else(|| "-".to_string()),
opcode,
elems_op_name(opcode),
fmt,
sp,
dst_ptr,
src_ptr,
f64::from(dst_val),
f64::from(src_val),
trace_sane_bytes(bus, dst_ptr, 10),
trace_sane_bytes(bus, src_ptr, if fmt == 0 { 10 } else { 8 }),
);
}
report_loaded_nan_if_enabled(
trap_pc,
trap_caller,
src_ptr,
&format!("{}", fmt),
"src",
f64::from(src_val),
);
report_loaded_nan_if_enabled(
trap_pc,
trap_caller,
dst_ptr,
"ext",
"dst",
f64::from(dst_val),
);
let result = match op {
0x10 if (opcode & 0x8000) != 0 => {
let base = f64::from(dst_val);
let i = f64::from(src_val) as i32;
let r = libm::pow(base, i as f64);
report_nan_if_enabled(trap_pc, trap_caller, "FXPWRI", base, Some(i as f64), r);
Extended80::from(r)
}
0x12 if (opcode & 0x8000) != 0 => {
cpu.set_ccr(0x08);
dst_val
}
_ => Self::apply_elems_op(trap_pc, trap_caller, op, src_val),
};
if trace_sane {
eprintln!(
"[SANE] Elems68K result pc=${:08X} opcode=0x{:04X} op={} dst=${:08X} result={}",
trap_pc,
opcode,
elems_op_name(opcode),
dst_ptr,
f64::from(result)
);
}
result.write_to_bus(bus, dst_ptr);
return Ok(());
}
let dst_ptr = bus.read_long(sp + 2);
cpu.write_reg(Register::A7, sp + 6);
let val = Extended80::read_from_bus(bus, dst_ptr);
if trace_sane {
eprintln!(
"[SANE] Elems68K pc=${:08X} caller={} opcode=0x{:04X} op={} fmt={} two=0 sp=${:08X} dst=${:08X} val={} raw={}",
trap_pc,
trap_caller
.map(|c| format!("${:08X}", c))
.unwrap_or_else(|| "-".to_string()),
opcode,
elems_op_name(opcode),
fmt,
sp,
dst_ptr,
f64::from(val),
trace_sane_bytes(bus, dst_ptr, 10),
);
}
report_loaded_nan_if_enabled(trap_pc, trap_caller, dst_ptr, "ext", "dst", f64::from(val));
let result = Self::apply_elems_op(trap_pc, trap_caller, op, val);
if trace_sane {
eprintln!(
"[SANE] Elems68K result pc=${:08X} opcode=0x{:04X} op={} dst=${:08X} result={}",
trap_pc,
opcode,
elems_op_name(opcode),
dst_ptr,
f64::from(result)
);
}
result.write_to_bus(bus, dst_ptr);
Ok(())
}
fn apply_elems_op(
pc: u32,
caller: Option<u32>,
op: u16,
val: super::extended80::Extended80,
) -> super::extended80::Extended80 {
use super::extended80::Extended80;
let x = f64::from(val);
let result = match op {
0x00 => libm::log(x),
0x02 => libm::log2(x),
0x04 => libm::log(1.0 + x),
0x06 => libm::log2(1.0 + x),
0x08 => libm::exp(x),
0x0A => libm::exp2(x),
0x0C => libm::exp(x) - 1.0,
0x0E => libm::exp2(x) - 1.0,
0x18 => libm::sin(x),
0x1A => libm::cos(x),
0x1C => libm::tan(x),
0x1E => libm::atan(x),
_ => return val,
};
report_nan_if_enabled(pc, caller, sane_op_name(op), x, None, result);
Extended80::from(result)
}
#[allow(dead_code)]
fn read_fp_value(&self, bus: &MacMemoryBus, addr: u32, fmt: u16) -> f64 {
match fmt {
0 => self.read_fp_extended(bus, addr),
1 => self.read_fp_double(bus, addr),
2 => self.read_fp_single(bus, addr),
4 => bus.read_word(addr) as i16 as f64,
5 => bus.read_long(addr) as i32 as f64,
6 => self.read_fp_comp(bus, addr),
_ => {
eprintln!("[SANE] Unknown format {} at ${:08X}", fmt, addr);
0.0
}
}
}
#[allow(dead_code)]
fn write_fp_value(&self, bus: &mut MacMemoryBus, addr: u32, fmt: u16, val: f64) {
match fmt {
0 => self.write_fp_extended(bus, addr, val),
1 => self.write_fp_double(bus, addr, val),
2 => self.write_fp_single(bus, addr, val),
4 => bus.write_word(addr, (val as i16) as u16),
5 => bus.write_long(addr, (val as i32) as u32),
6 => self.write_fp_comp(bus, addr, val),
_ => eprintln!("[SANE] Cannot write unknown format {}", fmt),
}
}
fn read_fp_extended(&self, bus: &MacMemoryBus, addr: u32) -> f64 {
let w0 = bus.read_word(addr);
let w1 = bus.read_word(addr + 2);
let w2 = bus.read_word(addr + 4);
let w3 = bus.read_word(addr + 6);
let w4 = bus.read_word(addr + 8);
let sign = (w0 >> 15) & 1;
let exponent = w0 & 0x7FFF;
let significand: u64 =
((w1 as u64) << 48) | ((w2 as u64) << 32) | ((w3 as u64) << 16) | (w4 as u64);
if exponent == 0 && significand == 0 {
return if sign == 1 { -0.0 } else { 0.0 };
}
if exponent == 0x7FFF {
if significand == 0 {
return if sign == 1 {
f64::NEG_INFINITY
} else {
f64::INFINITY
};
}
return f64::NAN;
}
let exp = exponent as i32 - 16383;
let frac = significand as f64 / (1u64 << 63) as f64;
let val = frac * (2.0f64).powi(exp);
if sign == 1 {
-val
} else {
val
}
}
fn write_fp_extended(&self, bus: &mut MacMemoryBus, addr: u32, val: f64) {
if val.is_nan() {
bus.write_word(addr, 0x7FFF);
bus.write_word(addr + 2, 0xFFFF);
bus.write_word(addr + 4, 0xFFFF);
bus.write_word(addr + 6, 0xFFFF);
bus.write_word(addr + 8, 0xFFFF);
return;
}
if val.is_infinite() {
let sign_bit = if val < 0.0 { 0x8000u16 } else { 0u16 };
bus.write_word(addr, sign_bit | 0x7FFF);
bus.write_word(addr + 2, 0x0000);
bus.write_word(addr + 4, 0x0000);
bus.write_word(addr + 6, 0x0000);
bus.write_word(addr + 8, 0x0000);
return;
}
if val == 0.0 {
let sign_bit = if val.is_sign_negative() {
0x8000u16
} else {
0u16
};
bus.write_word(addr, sign_bit);
bus.write_word(addr + 2, 0);
bus.write_word(addr + 4, 0);
bus.write_word(addr + 6, 0);
bus.write_word(addr + 8, 0);
return;
}
let sign = if val < 0.0 { 1u16 } else { 0u16 };
let abs_val = val.abs();
let bits = abs_val.to_bits();
let ieee_exp = ((bits >> 52) & 0x7FF) as i32;
let ieee_frac = bits & 0x000FFFFFFFFFFFFF;
if ieee_exp == 0 {
bus.write_word(addr, sign << 15);
bus.write_word(addr + 2, 0);
bus.write_word(addr + 4, 0);
bus.write_word(addr + 6, 0);
bus.write_word(addr + 8, 0);
return;
}
let ext_exp = (ieee_exp - 1023 + 16383) as u16;
let significand: u64 = (1u64 << 63) | (ieee_frac << 11);
let w0 = (sign << 15) | ext_exp;
let w1 = (significand >> 48) as u16;
let w2 = (significand >> 32) as u16;
let w3 = (significand >> 16) as u16;
let w4 = significand as u16;
bus.write_word(addr, w0);
bus.write_word(addr + 2, w1);
bus.write_word(addr + 4, w2);
bus.write_word(addr + 6, w3);
bus.write_word(addr + 8, w4);
}
fn read_fp_double(&self, bus: &MacMemoryBus, addr: u32) -> f64 {
let hi = bus.read_long(addr) as u64;
let lo = bus.read_long(addr + 4) as u64;
f64::from_bits((hi << 32) | lo)
}
fn write_fp_double(&self, bus: &mut MacMemoryBus, addr: u32, val: f64) {
let bits = val.to_bits();
bus.write_long(addr, (bits >> 32) as u32);
bus.write_long(addr + 4, bits as u32);
}
fn read_fp_single(&self, bus: &MacMemoryBus, addr: u32) -> f64 {
let bits = bus.read_long(addr);
f32::from_bits(bits) as f64
}
fn write_fp_single(&self, bus: &mut MacMemoryBus, addr: u32, val: f64) {
let bits = (val as f32).to_bits();
bus.write_long(addr, bits);
}
fn read_fp_comp(&self, bus: &MacMemoryBus, addr: u32) -> f64 {
let hi = bus.read_long(addr) as u64;
let lo = bus.read_long(addr + 4) as u64;
let val = ((hi << 32) | lo) as i64;
val as f64
}
fn write_fp_comp(&self, bus: &mut MacMemoryBus, addr: u32, val: f64) {
let ival = val as i64;
bus.write_long(addr, (ival >> 32) as u32);
bus.write_long(addr + 4, ival as u32);
}
}
#[cfg(test)]
mod tests {
use super::super::test_helpers::{setup, MockCpu, TEST_SP};
use crate::cpu::{CpuOps, Register};
use crate::memory::MemoryBus;
const DST_ADDR: u32 = 0x300000;
const SRC_ADDR: u32 = 0x300100;
const NON_STACK_REGS: [Register; 15] = [
Register::D0,
Register::D1,
Register::D2,
Register::D3,
Register::D4,
Register::D5,
Register::D6,
Register::D7,
Register::A0,
Register::A1,
Register::A2,
Register::A3,
Register::A4,
Register::A5,
Register::A6,
];
fn seed_non_stack_registers(cpu: &mut MockCpu) -> Vec<(Register, u32)> {
NON_STACK_REGS
.iter()
.enumerate()
.map(|(idx, reg)| {
let value = 0x1111_0000u32.wrapping_add((idx as u32) * 0x101);
cpu.write_reg(*reg, value);
(*reg, value)
})
.collect()
}
fn assert_non_stack_registers_unchanged(cpu: &MockCpu, expected: &[(Register, u32)]) {
for (reg, value) in expected {
assert_eq!(
cpu.read_reg(*reg),
*value,
"register {:?} changed across SANE call",
reg
);
}
}
fn run_fp68k_two_addr(
opcode: u16,
dst_val: f64,
src_val: f64,
) -> (f64, u32, crate::trap::test_helpers::MockCpu) {
let (mut disp, mut cpu, mut bus) = setup();
let sp = TEST_SP;
disp.write_fp_extended(&mut bus, DST_ADDR, dst_val);
disp.write_fp_extended(&mut bus, SRC_ADDR, src_val);
bus.write_word(sp, opcode);
bus.write_long(sp + 2, DST_ADDR);
bus.write_long(sp + 6, SRC_ADDR);
disp.dispatch_sane(true, 0x1EB, &mut cpu, &mut bus);
let result = disp.read_fp_extended(&bus, DST_ADDR);
let new_sp = cpu.read_reg(Register::A7);
(result, new_sp, cpu)
}
fn run_fp68k_one_addr(opcode: u16, dst_val: f64) -> (f64, u32) {
let (mut disp, mut cpu, mut bus) = setup();
let sp = TEST_SP;
disp.write_fp_extended(&mut bus, DST_ADDR, dst_val);
bus.write_word(sp, opcode);
bus.write_long(sp + 2, DST_ADDR);
disp.dispatch_sane(true, 0x1EB, &mut cpu, &mut bus);
let result = disp.read_fp_extended(&bus, DST_ADDR);
let new_sp = cpu.read_reg(Register::A7);
(result, new_sp)
}
fn run_elems_one_addr(opcode: u16, dst_val: f64) -> (f64, u32) {
let (mut disp, mut cpu, mut bus) = setup();
let sp = TEST_SP;
disp.write_fp_extended(&mut bus, DST_ADDR, dst_val);
bus.write_word(sp, opcode);
bus.write_long(sp + 2, DST_ADDR);
disp.dispatch_sane(true, 0x1EC, &mut cpu, &mut bus);
let result = disp.read_fp_extended(&bus, DST_ADDR);
let new_sp = cpu.read_reg(Register::A7);
(result, new_sp)
}
#[test]
fn test_fadd() {
let (result, new_sp, _) = run_fp68k_two_addr(0x0000, 3.0, 2.0);
assert!((result - 5.0).abs() < 1e-10, "expected 5.0, got {}", result);
assert_eq!(new_sp, TEST_SP + 10);
}
#[test]
fn pack4_two_address_call_consumes_opword_and_two_pointers() {
let (result, new_sp, _) = run_fp68k_two_addr(0x0000, 3.0, 2.0);
assert!((result - 5.0).abs() < 1e-10, "expected 5.0, got {}", result);
assert_eq!(new_sp, TEST_SP + 10);
}
#[test]
fn pack4_non_remainder_ops_preserve_non_stack_registers() {
let (mut disp, mut cpu, mut bus) = setup();
let saved = seed_non_stack_registers(&mut cpu);
let sp = TEST_SP;
disp.write_fp_extended(&mut bus, DST_ADDR, 3.0);
disp.write_fp_extended(&mut bus, SRC_ADDR, 2.0);
bus.write_word(sp, 0x0000); bus.write_long(sp + 2, DST_ADDR);
bus.write_long(sp + 6, SRC_ADDR);
disp.dispatch_sane(true, 0x1EB, &mut cpu, &mut bus);
assert_non_stack_registers_unchanged(&cpu, &saved);
assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 10);
}
#[test]
fn test_fsub() {
let (result, new_sp, _) = run_fp68k_two_addr(0x0002, 5.0, 3.0);
assert!((result - 2.0).abs() < 1e-10, "expected 2.0, got {}", result);
assert_eq!(new_sp, TEST_SP + 10);
}
#[test]
fn test_fmul() {
let (result, new_sp, _) = run_fp68k_two_addr(0x0004, 3.0, 4.0);
assert!(
(result - 12.0).abs() < 1e-10,
"expected 12.0, got {}",
result
);
assert_eq!(new_sp, TEST_SP + 10);
}
#[test]
fn test_fdiv() {
let (result, new_sp, _) = run_fp68k_two_addr(0x0006, 10.0, 4.0);
assert!((result - 2.5).abs() < 1e-10, "expected 2.5, got {}", result);
assert_eq!(new_sp, TEST_SP + 10);
}
#[test]
fn test_fdiv_by_zero() {
let (result, new_sp, _) = run_fp68k_two_addr(0x0006, 1.0, 0.0);
assert!(result.is_infinite(), "expected infinity, got {}", result);
assert!(result > 0.0, "expected positive infinity");
assert_eq!(new_sp, TEST_SP + 10);
}
#[test]
fn test_fcmp_equal() {
let (mut disp, mut cpu, mut bus) = setup();
let sp = TEST_SP;
disp.write_fp_extended(&mut bus, DST_ADDR, 5.0);
disp.write_fp_extended(&mut bus, SRC_ADDR, 5.0);
bus.write_word(sp, 0x0008); bus.write_long(sp + 2, DST_ADDR);
bus.write_long(sp + 6, SRC_ADDR);
disp.dispatch_sane(true, 0x1EB, &mut cpu, &mut bus);
assert_eq!(
cpu.ccr, 0x04,
"expected Z flag (0x04), got 0x{:02X}",
cpu.ccr
);
assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 10);
}
#[test]
fn test_fcmp_destination_less_than_source() {
let (mut disp, mut cpu, mut bus) = setup();
let sp = TEST_SP;
disp.write_fp_extended(&mut bus, DST_ADDR, 3.0);
disp.write_fp_extended(&mut bus, SRC_ADDR, 5.0);
bus.write_word(sp, 0x0008);
bus.write_long(sp + 2, DST_ADDR);
bus.write_long(sp + 6, SRC_ADDR);
disp.dispatch_sane(true, 0x1EB, &mut cpu, &mut bus);
assert_eq!(
cpu.ccr, 0x19,
"expected X+N+C flags (0x19), got 0x{:02X}",
cpu.ccr
);
assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 10);
}
#[test]
fn test_fcmp_destination_greater_than_source() {
let (mut disp, mut cpu, mut bus) = setup();
let sp = TEST_SP;
disp.write_fp_extended(&mut bus, DST_ADDR, 7.0);
disp.write_fp_extended(&mut bus, SRC_ADDR, 5.0);
bus.write_word(sp, 0x0008);
bus.write_long(sp + 2, DST_ADDR);
bus.write_long(sp + 6, SRC_ADDR);
disp.dispatch_sane(true, 0x1EB, &mut cpu, &mut bus);
assert_eq!(
cpu.ccr, 0x00,
"expected no flags (0x00), got 0x{:02X}",
cpu.ccr
);
assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 10);
}
#[test]
fn test_frem() {
let (result, new_sp, _) = run_fp68k_two_addr(0x000C, 7.0, 3.0);
assert!((result - 1.0).abs() < 1e-10, "expected 1.0, got {}", result);
assert_eq!(new_sp, TEST_SP + 10);
}
#[test]
fn test_fneg() {
let (result, new_sp) = run_fp68k_one_addr(0x000D, 3.0);
assert!(
(result - (-3.0)).abs() < 1e-10,
"expected -3.0, got {}",
result
);
assert_eq!(new_sp, TEST_SP + 6);
}
#[test]
fn test_fabs() {
let (result, new_sp) = run_fp68k_one_addr(0x000F, -5.0);
assert!((result - 5.0).abs() < 1e-10, "expected 5.0, got {}", result);
assert_eq!(new_sp, TEST_SP + 6);
}
#[test]
fn test_fsqrt() {
let (result, new_sp) = run_fp68k_one_addr(0x0012, 9.0);
assert!((result - 3.0).abs() < 1e-10, "expected 3.0, got {}", result);
assert_eq!(new_sp, TEST_SP + 6);
}
#[test]
fn test_frti() {
let (result, new_sp) = run_fp68k_one_addr(0x0014, 3.7);
assert!((result - 4.0).abs() < 1e-10, "expected 4.0, got {}", result);
assert_eq!(new_sp, TEST_SP + 6);
}
#[test]
fn test_ftti() {
let (result, new_sp) = run_fp68k_one_addr(0x0016, 3.7);
assert!((result - 3.0).abs() < 1e-10, "expected 3.0, got {}", result);
assert_eq!(new_sp, TEST_SP + 6);
}
#[test]
fn test_flogb() {
let (result, new_sp) = run_fp68k_one_addr(0x001A, 8.0);
assert!((result - 3.0).abs() < 1e-10, "expected 3.0, got {}", result);
assert_eq!(new_sp, TEST_SP + 6);
}
#[test]
fn test_fgetenv() {
let (mut disp, mut cpu, mut bus) = setup();
let sp = TEST_SP;
bus.write_word(DST_ADDR, 0xFFFF);
bus.write_word(sp, 0x0001); bus.write_long(sp + 2, DST_ADDR);
disp.dispatch_sane(true, 0x1EB, &mut cpu, &mut bus);
let env_word = bus.read_word(DST_ADDR);
assert_eq!(env_word, 0, "expected environment word 0, got {}", env_word);
assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 6);
}
#[test]
fn test_fsetenv() {
let (mut disp, mut cpu, mut bus) = setup();
let sp = TEST_SP;
disp.write_fp_extended(&mut bus, DST_ADDR, 0.0);
bus.write_word(sp, 0x001E); bus.write_long(sp + 2, DST_ADDR);
disp.dispatch_sane(true, 0x1EB, &mut cpu, &mut bus);
assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 6);
}
#[test]
fn test_fln() {
let e = std::f64::consts::E;
let (result, new_sp) = run_elems_one_addr(0x0000, e);
assert!((result - 1.0).abs() < 1e-10, "expected 1.0, got {}", result);
assert_eq!(new_sp, TEST_SP + 6);
}
#[test]
fn pack5_one_address_call_consumes_opword_and_pointer() {
let (result, new_sp) = run_elems_one_addr(0x0018, 0.0); assert!(result.abs() < 1e-10, "expected 0.0, got {}", result);
assert_eq!(new_sp, TEST_SP + 6);
}
#[test]
fn pack5_high_bit_8012_consumes_two_addresses_and_preserves_destination() {
let (mut disp, mut cpu, mut bus) = setup();
let sp = TEST_SP;
disp.write_fp_extended(&mut bus, DST_ADDR, 2.0);
disp.write_fp_extended(&mut bus, SRC_ADDR, 3.0);
bus.write_word(sp, 0x8012);
bus.write_long(sp + 2, DST_ADDR);
bus.write_long(sp + 6, SRC_ADDR);
disp.dispatch_sane(true, 0x1EC, &mut cpu, &mut bus);
let result = disp.read_fp_extended(&bus, DST_ADDR);
assert!((result - 2.0).abs() < 1e-10, "expected 2.0, got {}", result);
assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 10);
assert_eq!(cpu.ccr, 0x08);
}
#[test]
fn pack5_one_address_0012_is_unimplemented_noop() {
let (result, new_sp) = run_elems_one_addr(0x0012, 81.0);
assert!(
(result - 81.0).abs() < 1e-10,
"expected 81.0, got {}",
result
);
assert_eq!(new_sp, TEST_SP + 6);
}
#[test]
fn pack5_ops_preserve_non_stack_registers() {
let (mut disp, mut cpu, mut bus) = setup();
let saved = seed_non_stack_registers(&mut cpu);
let sp = TEST_SP;
disp.write_fp_extended(&mut bus, DST_ADDR, std::f64::consts::E);
bus.write_word(sp, 0x0000); bus.write_long(sp + 2, DST_ADDR);
disp.dispatch_sane(true, 0x1EC, &mut cpu, &mut bus);
assert_non_stack_registers_unchanged(&cpu, &saved);
assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 6);
}
#[test]
fn test_flog2() {
let (result, new_sp) = run_elems_one_addr(0x0002, 8.0);
assert!((result - 3.0).abs() < 1e-10, "expected 3.0, got {}", result);
assert_eq!(new_sp, TEST_SP + 6);
}
#[test]
fn test_fexp() {
let (result, new_sp) = run_elems_one_addr(0x0008, 1.0);
assert!(
(result - std::f64::consts::E).abs() < 1e-6,
"expected e (~2.71828), got {}",
result
);
assert_eq!(new_sp, TEST_SP + 6);
}
#[test]
fn test_fexp2() {
let (result, new_sp) = run_elems_one_addr(0x000A, 3.0);
assert!((result - 8.0).abs() < 1e-10, "expected 8.0, got {}", result);
assert_eq!(new_sp, TEST_SP + 6);
}
#[test]
fn test_fsin() {
let (result, new_sp) = run_elems_one_addr(0x0018, 0.0);
assert!(result.abs() < 1e-10, "expected 0.0, got {}", result);
assert_eq!(new_sp, TEST_SP + 6);
}
#[test]
fn test_fcos() {
let (result, new_sp) = run_elems_one_addr(0x001A, 0.0);
assert!((result - 1.0).abs() < 1e-10, "expected 1.0, got {}", result);
assert_eq!(new_sp, TEST_SP + 6);
}
#[test]
fn test_ftan() {
let (result, new_sp) = run_elems_one_addr(0x001C, 0.0);
assert!(result.abs() < 1e-10, "expected 0.0, got {}", result);
assert_eq!(new_sp, TEST_SP + 6);
}
#[test]
fn test_fatan() {
let (result, new_sp) = run_elems_one_addr(0x001E, 1.0);
let expected = std::f64::consts::FRAC_PI_4;
assert!(
(result - expected).abs() < 1e-6,
"expected pi/4 (~0.7854), got {}",
result
);
assert_eq!(new_sp, TEST_SP + 6);
}
#[test]
fn test_fexp1() {
let (result, new_sp) = run_elems_one_addr(0x000C, 1.0);
let expected = std::f64::consts::E - 1.0;
assert!(
(result - expected).abs() < 1e-10,
"expected {}, got {}",
expected,
result
);
assert_eq!(new_sp, TEST_SP + 6);
}
#[test]
fn test_fexp21() {
let (result, new_sp) = run_elems_one_addr(0x000E, 3.0);
assert!((result - 7.0).abs() < 1e-10, "expected 7.0, got {}", result);
assert_eq!(new_sp, TEST_SP + 6);
}
#[test]
fn test_write_read_extended_positive() {
let (disp, _, mut bus) = setup();
disp.write_fp_extended(&mut bus, DST_ADDR, 42.5);
let result = disp.read_fp_extended(&bus, DST_ADDR);
assert!(
(result - 42.5).abs() < 1e-10,
"expected 42.5, got {}",
result
);
}
#[test]
fn test_write_read_extended_negative() {
let (disp, _, mut bus) = setup();
disp.write_fp_extended(&mut bus, DST_ADDR, -100.0);
let result = disp.read_fp_extended(&bus, DST_ADDR);
assert!(
(result - (-100.0)).abs() < 1e-10,
"expected -100.0, got {}",
result
);
}
#[test]
fn test_extended_zero() {
let (disp, _, mut bus) = setup();
disp.write_fp_extended(&mut bus, DST_ADDR, 0.0);
let result = disp.read_fp_extended(&bus, DST_ADDR);
assert_eq!(result, 0.0, "expected 0.0, got {}", result);
}
#[test]
fn test_extended_nan() {
let (disp, _, mut bus) = setup();
disp.write_fp_extended(&mut bus, DST_ADDR, f64::NAN);
let result = disp.read_fp_extended(&bus, DST_ADDR);
assert!(result.is_nan(), "expected NaN, got {}", result);
}
#[test]
fn test_extended_infinity() {
let (disp, _, mut bus) = setup();
disp.write_fp_extended(&mut bus, DST_ADDR, f64::INFINITY);
let result = disp.read_fp_extended(&bus, DST_ADDR);
assert!(result.is_infinite(), "expected infinity, got {}", result);
assert!(result > 0.0, "expected positive infinity");
}
#[test]
#[allow(clippy::approx_constant)] fn test_write_read_double() {
let (disp, _, mut bus) = setup();
let val = 3.141592653589793;
disp.write_fp_double(&mut bus, DST_ADDR, val);
let result = disp.read_fp_double(&bus, DST_ADDR);
assert!(
(result - val).abs() < 1e-15,
"expected {}, got {}",
val,
result
);
}
#[test]
fn test_write_read_single() {
let (disp, _, mut bus) = setup();
let val = 2.5;
disp.write_fp_single(&mut bus, DST_ADDR, val);
let result = disp.read_fp_single(&bus, DST_ADDR);
assert!(
(result - val).abs() < 1e-6,
"expected {}, got {}",
val,
result
);
}
#[test]
fn test_write_read_comp() {
let (disp, _, mut bus) = setup();
let val = 123456789.0;
disp.write_fp_comp(&mut bus, DST_ADDR, val);
let result = disp.read_fp_comp(&bus, DST_ADDR);
assert!(
(result - val).abs() < 1e-10,
"expected {}, got {}",
val,
result
);
}
}