use super::decode::{resolve_modrm32, Operand};
use super::isa_int::{Cpu, StepOk};
use super::mmu::Mmu;
use super::Trap;
pub const FPU_STACK_DEPTH: usize = 8;
pub const SW_C0: u16 = 1 << 8;
pub const SW_C1: u16 = 1 << 9;
pub const SW_C2: u16 = 1 << 10;
pub const SW_C3: u16 = 1 << 14;
#[derive(Clone, Debug)]
pub struct FpuState {
pub regs: [f64; FPU_STACK_DEPTH],
pub tag_valid: [bool; FPU_STACK_DEPTH],
pub top: u8,
pub sw: u16,
}
impl Default for FpuState {
fn default() -> Self {
Self::new()
}
}
impl FpuState {
pub fn new() -> Self {
FpuState {
regs: [0.0; FPU_STACK_DEPTH],
tag_valid: [false; FPU_STACK_DEPTH],
top: 0,
sw: 0,
}
}
pub fn push(&mut self, v: f64) {
self.top = (self.top.wrapping_sub(1)) & 7;
self.regs[self.top as usize] = v;
self.tag_valid[self.top as usize] = true;
}
pub fn pop(&mut self) -> f64 {
let v = self.regs[self.top as usize];
self.tag_valid[self.top as usize] = false;
self.top = (self.top.wrapping_add(1)) & 7;
v
}
pub fn st(&self, i: u8) -> f64 {
self.regs[((self.top + i) & 7) as usize]
}
pub fn set_st(&mut self, i: u8, v: f64) {
let idx = ((self.top + i) & 7) as usize;
self.regs[idx] = v;
self.tag_valid[idx] = true;
}
pub fn set_cc_from_compare(&mut self, st0: f64, other: f64) {
self.sw &= !(SW_C0 | SW_C1 | SW_C2 | SW_C3);
if st0.is_nan() || other.is_nan() {
self.sw |= SW_C0 | SW_C2 | SW_C3;
} else if st0 > other {
} else if st0 < other {
self.sw |= SW_C0;
} else {
self.sw |= SW_C3;
}
}
}
pub fn dispatch(cpu: &mut Cpu, mmu: &mut Mmu, opcode: u8, entry_eip: u32) -> Result<StepOk, Trap> {
let mr = cpu.fetch_modrm(mmu)?;
let bytes = cpu.peek_after_modrm(mmu, 16)?;
let (op, consumed) = resolve_modrm32(mr, &bytes, &cpu.regs)?;
cpu.regs.eip = cpu.regs.eip.wrapping_add(consumed as u32);
let op = cpu.seg_apply_pub(op);
if mr.mode == 0b11 {
return dispatch_reg_form(cpu, opcode, mr.reg, mr.rm, entry_eip);
}
let addr = match op {
Operand::Mem32(a) => a,
Operand::Reg32(_) => unreachable!(),
};
match opcode {
0xD8 => fpu_d8_mem(cpu, mmu, mr.reg, addr, entry_eip),
0xD9 => fpu_d9_mem(cpu, mmu, mr.reg, addr, entry_eip),
0xDA => fpu_da_mem(cpu, mmu, mr.reg, addr, entry_eip),
0xDB => fpu_db_mem(cpu, mmu, mr.reg, addr, entry_eip),
0xDC => fpu_dc_mem(cpu, mmu, mr.reg, addr, entry_eip),
0xDD => fpu_dd_mem(cpu, mmu, mr.reg, addr, entry_eip),
0xDE => fpu_de_mem(cpu, mmu, mr.reg, addr, entry_eip),
0xDF => fpu_df_mem(cpu, mmu, mr.reg, addr, entry_eip),
_ => unreachable!(),
}
}
fn dispatch_reg_form(
cpu: &mut Cpu,
opcode: u8,
reg: u8,
rm: u8,
entry_eip: u32,
) -> Result<StepOk, Trap> {
match opcode {
0xD8 => {
let st0 = cpu.fpu.st(0);
let sti = cpu.fpu.st(rm);
let r = match reg {
0 => st0 + sti,
1 => st0 * sti,
2 => {
cpu.fpu.set_cc_from_compare(st0, sti);
return Ok(StepOk::Continued);
}
3 => {
cpu.fpu.set_cc_from_compare(st0, sti);
cpu.fpu.pop();
return Ok(StepOk::Continued);
}
4 => st0 - sti,
5 => sti - st0,
6 => st0 / sti,
7 => sti / st0,
_ => unreachable!(),
};
cpu.fpu.set_st(0, r);
Ok(StepOk::Continued)
}
0xD9 => {
match (reg, rm) {
(0, _) => {
let v = cpu.fpu.st(rm);
cpu.fpu.push(v);
Ok(StepOk::Continued)
}
(1, _) => {
let v0 = cpu.fpu.st(0);
let vi = cpu.fpu.st(rm);
cpu.fpu.set_st(0, vi);
cpu.fpu.set_st(rm, v0);
Ok(StepOk::Continued)
}
(2, 0) => {
Ok(StepOk::Continued)
}
(4, 0) => {
let v = cpu.fpu.st(0);
cpu.fpu.set_st(0, -v);
Ok(StepOk::Continued)
}
(4, 1) => {
let v = cpu.fpu.st(0);
cpu.fpu.set_st(0, v.abs());
Ok(StepOk::Continued)
}
(4, 4) => {
let v = cpu.fpu.st(0);
cpu.fpu.set_cc_from_compare(v, 0.0);
Ok(StepOk::Continued)
}
(5, 0) => {
cpu.fpu.push(1.0);
Ok(StepOk::Continued)
}
(5, 1) => {
cpu.fpu.push(std::f64::consts::LOG2_10);
Ok(StepOk::Continued)
}
(5, 2) => {
cpu.fpu.push(std::f64::consts::LOG2_E);
Ok(StepOk::Continued)
}
(5, 3) => {
cpu.fpu.push(std::f64::consts::PI);
Ok(StepOk::Continued)
}
(5, 4) => {
cpu.fpu.push(std::f64::consts::LOG10_2);
Ok(StepOk::Continued)
}
(5, 5) => {
cpu.fpu.push(std::f64::consts::LN_2);
Ok(StepOk::Continued)
}
(5, 6) => {
cpu.fpu.push(0.0);
Ok(StepOk::Continued)
}
(6, 1) => {
let st0 = cpu.fpu.st(0);
let st1 = cpu.fpu.st(1);
let result = st1 * st0.log2();
cpu.fpu.pop();
cpu.fpu.set_st(0, result);
Ok(StepOk::Continued)
}
(7, 0) => {
let st0 = cpu.fpu.st(0);
let st1 = cpu.fpu.st(1);
if st1 != 0.0 {
cpu.fpu.set_st(0, st0 - (st0 / st1).trunc() * st1);
}
Ok(StepOk::Continued)
}
(7, 2) => {
let v = cpu.fpu.st(0);
cpu.fpu.set_st(0, v.sqrt());
Ok(StepOk::Continued)
}
(7, 4) => {
let v = cpu.fpu.st(0);
cpu.fpu.set_st(0, v.round());
Ok(StepOk::Continued)
}
(7, 5) => {
let st0 = cpu.fpu.st(0);
let st1 = cpu.fpu.st(1);
let scale = (st1.trunc() as i32).clamp(-1023, 1023);
let result = st0 * (2f64).powi(scale);
cpu.fpu.set_st(0, result);
Ok(StepOk::Continued)
}
(7, 6) => {
let v = cpu.fpu.st(0);
cpu.fpu.set_st(0, v.sin());
Ok(StepOk::Continued)
}
(7, 7) => {
let v = cpu.fpu.st(0);
cpu.fpu.set_st(0, v.cos());
Ok(StepOk::Continued)
}
_ => Err(Trap::UndefinedOpcode {
eip: entry_eip,
opcode: 0xD900 | (u32::from(reg) << 4) | u32::from(rm),
}),
}
}
0xDA => {
Err(Trap::PrivilegedOpcode {
eip: entry_eip,
mnemonic: "x87 DA reg-form (FCMOVcc / FUCOMPP) — not modelled",
})
}
0xDB => {
match (reg, rm) {
(4, 2) => {
cpu.fpu.sw = 0;
Ok(StepOk::Continued)
}
(4, 3) => {
cpu.fpu = FpuState::new();
cpu.fpu_cw = 0x037F;
Ok(StepOk::Continued)
}
_ => Err(Trap::PrivilegedOpcode {
eip: entry_eip,
mnemonic: "x87 DB reg-form (unimplemented)",
}),
}
}
0xDC => {
let st0 = cpu.fpu.st(0);
let sti = cpu.fpu.st(rm);
let r = match reg {
0 => sti + st0,
1 => sti * st0,
2 => {
cpu.fpu.set_cc_from_compare(st0, sti);
return Ok(StepOk::Continued);
}
3 => {
cpu.fpu.set_cc_from_compare(st0, sti);
cpu.fpu.pop();
return Ok(StepOk::Continued);
}
4 => sti - st0,
5 => st0 - sti,
6 => sti / st0,
7 => st0 / sti,
_ => unreachable!(),
};
cpu.fpu.set_st(rm, r);
Ok(StepOk::Continued)
}
0xDD => {
match reg {
0 => {
let idx = ((cpu.fpu.top + rm) & 7) as usize;
cpu.fpu.tag_valid[idx] = false;
Ok(StepOk::Continued)
}
2 => {
let v = cpu.fpu.st(0);
cpu.fpu.set_st(rm, v);
Ok(StepOk::Continued)
}
3 => {
let v = cpu.fpu.st(0);
cpu.fpu.set_st(rm, v);
cpu.fpu.pop();
Ok(StepOk::Continued)
}
4 => {
let st0 = cpu.fpu.st(0);
let sti = cpu.fpu.st(rm);
cpu.fpu.set_cc_from_compare(st0, sti);
Ok(StepOk::Continued)
}
5 => {
let st0 = cpu.fpu.st(0);
let sti = cpu.fpu.st(rm);
cpu.fpu.set_cc_from_compare(st0, sti);
cpu.fpu.pop();
Ok(StepOk::Continued)
}
_ => Err(Trap::PrivilegedOpcode {
eip: entry_eip,
mnemonic: "x87 DD reg-form (unimplemented)",
}),
}
}
0xDE => {
match (reg, rm) {
(3, 1) => {
let st0 = cpu.fpu.st(0);
let st1 = cpu.fpu.st(1);
cpu.fpu.set_cc_from_compare(st0, st1);
cpu.fpu.pop();
cpu.fpu.pop();
Ok(StepOk::Continued)
}
_ => {
let st0 = cpu.fpu.st(0);
let sti = cpu.fpu.st(rm);
let r = match reg {
0 => sti + st0, 1 => sti * st0, 4 => sti - st0, 5 => st0 - sti, 6 => sti / st0, 7 => st0 / sti, _ => {
return Err(Trap::PrivilegedOpcode {
eip: entry_eip,
mnemonic: "x87 DE reg-form (bad sub-op)",
});
}
};
cpu.fpu.set_st(rm, r);
cpu.fpu.pop();
Ok(StepOk::Continued)
}
}
}
0xDF => {
match (reg, rm) {
(4, 0) => {
let sw = cpu.fpu.sw;
let prev = cpu.regs.get32(super::regs::Reg32::Eax);
let new_eax = (prev & 0xFFFF_0000) | u32::from(sw);
cpu.regs.set32(super::regs::Reg32::Eax, new_eax);
Ok(StepOk::Continued)
}
_ => Err(Trap::PrivilegedOpcode {
eip: entry_eip,
mnemonic: "x87 DF reg-form (unimplemented)",
}),
}
}
_ => unreachable!(),
}
}
fn fpu_d8_mem(
cpu: &mut Cpu,
mmu: &mut Mmu,
sub: u8,
addr: u32,
entry_eip: u32,
) -> Result<StepOk, Trap> {
let bits = mmu.load32(addr)?;
let v = f32::from_bits(bits) as f64;
let st0 = cpu.fpu.st(0);
let r = match sub {
0 => st0 + v,
1 => st0 * v,
2 => {
cpu.fpu.set_cc_from_compare(st0, v);
return Ok(StepOk::Continued);
}
3 => {
cpu.fpu.set_cc_from_compare(st0, v);
cpu.fpu.pop();
return Ok(StepOk::Continued);
}
4 => st0 - v,
5 => v - st0,
6 => st0 / v,
7 => v / st0,
_ => {
return Err(Trap::PrivilegedOpcode {
eip: entry_eip,
mnemonic: "x87 D8 mem (bad sub-op)",
});
}
};
cpu.fpu.set_st(0, r);
Ok(StepOk::Continued)
}
fn fpu_d9_mem(
cpu: &mut Cpu,
mmu: &mut Mmu,
sub: u8,
addr: u32,
entry_eip: u32,
) -> Result<StepOk, Trap> {
match sub {
0 => {
let bits = mmu.load32(addr)?;
let v = f32::from_bits(bits) as f64;
cpu.fpu.push(v);
Ok(StepOk::Continued)
}
2 => {
let v = cpu.fpu.st(0);
mmu.store32(addr, (v as f32).to_bits())?;
Ok(StepOk::Continued)
}
3 => {
let v = cpu.fpu.st(0);
mmu.store32(addr, (v as f32).to_bits())?;
cpu.fpu.pop();
Ok(StepOk::Continued)
}
4 => {
let cw = mmu.load16(addr)?;
cpu.fpu_cw = cw;
Ok(StepOk::Continued)
}
5 => {
cpu.fpu_cw = mmu.load16(addr)?;
Ok(StepOk::Continued)
}
6 => {
mmu.store16(addr, cpu.fpu_cw)?;
for off in 2..28u32 {
mmu.store8(addr.wrapping_add(off), 0)?;
}
Ok(StepOk::Continued)
}
7 => {
mmu.store16(addr, cpu.fpu_cw)?;
Ok(StepOk::Continued)
}
_ => Err(Trap::PrivilegedOpcode {
eip: entry_eip,
mnemonic: "x87 D9 mem (bad sub-op)",
}),
}
}
fn fpu_da_mem(
cpu: &mut Cpu,
mmu: &mut Mmu,
sub: u8,
addr: u32,
entry_eip: u32,
) -> Result<StepOk, Trap> {
let v = mmu.load32(addr)? as i32 as f64;
let st0 = cpu.fpu.st(0);
let r = match sub {
0 => st0 + v,
1 => st0 * v,
2 => {
cpu.fpu.set_cc_from_compare(st0, v);
return Ok(StepOk::Continued);
}
3 => {
cpu.fpu.set_cc_from_compare(st0, v);
cpu.fpu.pop();
return Ok(StepOk::Continued);
}
4 => st0 - v,
5 => v - st0,
6 => st0 / v,
7 => v / st0,
_ => {
return Err(Trap::PrivilegedOpcode {
eip: entry_eip,
mnemonic: "x87 DA mem (bad sub-op)",
});
}
};
cpu.fpu.set_st(0, r);
Ok(StepOk::Continued)
}
fn fpu_db_mem(
cpu: &mut Cpu,
mmu: &mut Mmu,
sub: u8,
addr: u32,
entry_eip: u32,
) -> Result<StepOk, Trap> {
match sub {
0 => {
let v = mmu.load32(addr)? as i32 as f64;
cpu.fpu.push(v);
Ok(StepOk::Continued)
}
2 => {
let v = cpu.fpu.st(0).round() as i32;
mmu.store32(addr, v as u32)?;
Ok(StepOk::Continued)
}
3 => {
let v = cpu.fpu.st(0).round() as i32;
mmu.store32(addr, v as u32)?;
cpu.fpu.pop();
Ok(StepOk::Continued)
}
5 => {
let lo = mmu.load32(addr)?;
let hi = mmu.load32(addr.wrapping_add(4))?;
let m64 = (u64::from(hi) << 32) | u64::from(lo);
let exp = mmu.load16(addr.wrapping_add(8))?;
let sign = (exp >> 15) & 1;
let unbiased = (exp & 0x7FFF) as i32 - 16383;
let mantissa = (m64 as f64) / (1u64 << 63) as f64; let v_abs = mantissa * 2f64.powi(unbiased);
let v = if sign != 0 { -v_abs } else { v_abs };
cpu.fpu.push(v);
Ok(StepOk::Continued)
}
7 => {
let v = cpu.fpu.st(0);
let bits = v.to_bits();
let sign = (bits >> 63) & 1;
let exp64 = ((bits >> 52) & 0x7FF) as i32;
let frac52 = bits & 0x000F_FFFF_FFFF_FFFF;
let unbiased = exp64 - 1023;
let exp80 = (unbiased + 16383) as u16 & 0x7FFF;
let m64 = if exp64 == 0 {
frac52 << 11
} else {
(1u64 << 63) | (frac52 << 11)
};
let lo = (m64 & 0xFFFF_FFFF) as u32;
let hi = (m64 >> 32) as u32;
let exp_word = ((sign as u16) << 15) | exp80;
mmu.store32(addr, lo)?;
mmu.store32(addr.wrapping_add(4), hi)?;
mmu.store16(addr.wrapping_add(8), exp_word)?;
cpu.fpu.pop();
Ok(StepOk::Continued)
}
_ => Err(Trap::PrivilegedOpcode {
eip: entry_eip,
mnemonic: "x87 DB mem (bad sub-op)",
}),
}
}
fn fpu_dc_mem(
cpu: &mut Cpu,
mmu: &mut Mmu,
sub: u8,
addr: u32,
entry_eip: u32,
) -> Result<StepOk, Trap> {
let lo = mmu.load32(addr)?;
let hi = mmu.load32(addr.wrapping_add(4))?;
let bits = (u64::from(hi) << 32) | u64::from(lo);
let v = f64::from_bits(bits);
let st0 = cpu.fpu.st(0);
let r = match sub {
0 => st0 + v,
1 => st0 * v,
2 => {
cpu.fpu.set_cc_from_compare(st0, v);
return Ok(StepOk::Continued);
}
3 => {
cpu.fpu.set_cc_from_compare(st0, v);
cpu.fpu.pop();
return Ok(StepOk::Continued);
}
4 => st0 - v,
5 => v - st0,
6 => st0 / v,
7 => v / st0,
_ => {
return Err(Trap::PrivilegedOpcode {
eip: entry_eip,
mnemonic: "x87 DC mem (bad sub-op)",
});
}
};
cpu.fpu.set_st(0, r);
Ok(StepOk::Continued)
}
fn fpu_dd_mem(
cpu: &mut Cpu,
mmu: &mut Mmu,
sub: u8,
addr: u32,
entry_eip: u32,
) -> Result<StepOk, Trap> {
match sub {
0 => {
let lo = mmu.load32(addr)?;
let hi = mmu.load32(addr.wrapping_add(4))?;
let bits = (u64::from(hi) << 32) | u64::from(lo);
cpu.fpu.push(f64::from_bits(bits));
Ok(StepOk::Continued)
}
2 => {
let bits = cpu.fpu.st(0).to_bits();
mmu.store32(addr, (bits & 0xFFFF_FFFF) as u32)?;
mmu.store32(addr.wrapping_add(4), (bits >> 32) as u32)?;
Ok(StepOk::Continued)
}
3 => {
let bits = cpu.fpu.st(0).to_bits();
mmu.store32(addr, (bits & 0xFFFF_FFFF) as u32)?;
mmu.store32(addr.wrapping_add(4), (bits >> 32) as u32)?;
cpu.fpu.pop();
Ok(StepOk::Continued)
}
7 => {
mmu.store16(addr, cpu.fpu.sw)?;
Ok(StepOk::Continued)
}
_ => Err(Trap::PrivilegedOpcode {
eip: entry_eip,
mnemonic: "x87 DD mem (bad sub-op)",
}),
}
}
fn fpu_de_mem(
cpu: &mut Cpu,
mmu: &mut Mmu,
sub: u8,
addr: u32,
entry_eip: u32,
) -> Result<StepOk, Trap> {
let v = mmu.load16(addr)? as i16 as f64;
let st0 = cpu.fpu.st(0);
let r = match sub {
0 => st0 + v,
1 => st0 * v,
2 => {
cpu.fpu.set_cc_from_compare(st0, v);
return Ok(StepOk::Continued);
}
3 => {
cpu.fpu.set_cc_from_compare(st0, v);
cpu.fpu.pop();
return Ok(StepOk::Continued);
}
4 => st0 - v,
5 => v - st0,
6 => st0 / v,
7 => v / st0,
_ => {
return Err(Trap::PrivilegedOpcode {
eip: entry_eip,
mnemonic: "x87 DE mem (bad sub-op)",
});
}
};
cpu.fpu.set_st(0, r);
Ok(StepOk::Continued)
}
fn fpu_df_mem(
cpu: &mut Cpu,
mmu: &mut Mmu,
sub: u8,
addr: u32,
entry_eip: u32,
) -> Result<StepOk, Trap> {
match sub {
0 => {
let v = mmu.load16(addr)? as i16 as f64;
cpu.fpu.push(v);
Ok(StepOk::Continued)
}
2 => {
let v = cpu.fpu.st(0).round() as i16;
mmu.store16(addr, v as u16)?;
Ok(StepOk::Continued)
}
3 => {
let v = cpu.fpu.st(0).round() as i16;
mmu.store16(addr, v as u16)?;
cpu.fpu.pop();
Ok(StepOk::Continued)
}
5 => {
let lo = mmu.load32(addr)?;
let hi = mmu.load32(addr.wrapping_add(4))?;
let v64 = ((u64::from(hi) << 32) | u64::from(lo)) as i64;
cpu.fpu.push(v64 as f64);
Ok(StepOk::Continued)
}
7 => {
let v = cpu.fpu.st(0).round() as i64;
mmu.store32(addr, (v as u64 & 0xFFFF_FFFF) as u32)?;
mmu.store32(addr.wrapping_add(4), ((v as u64) >> 32) as u32)?;
cpu.fpu.pop();
Ok(StepOk::Continued)
}
_ => Err(Trap::PrivilegedOpcode {
eip: entry_eip,
mnemonic: "x87 DF mem (bad sub-op)",
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fpu_push_pop_roundtrip() {
let mut f = FpuState::new();
assert!(!f.tag_valid[0]);
f.push(3.5);
f.push(7.0);
assert_eq!(f.st(0), 7.0);
assert_eq!(f.st(1), 3.5);
let v = f.pop();
assert_eq!(v, 7.0);
assert_eq!(f.st(0), 3.5);
}
#[test]
fn fpu_compare_sets_condition_codes() {
let mut f = FpuState::new();
f.set_cc_from_compare(2.0, 1.0);
assert_eq!(f.sw & (SW_C0 | SW_C2 | SW_C3), 0);
f.set_cc_from_compare(1.0, 2.0);
assert_eq!(f.sw & SW_C0, SW_C0);
f.set_cc_from_compare(2.0, 2.0);
assert_eq!(f.sw & SW_C3, SW_C3);
f.set_cc_from_compare(f64::NAN, 1.0);
assert_eq!(f.sw & (SW_C0 | SW_C2 | SW_C3), SW_C0 | SW_C2 | SW_C3);
}
#[test]
fn fpu_set_st_does_not_touch_top() {
let mut f = FpuState::new();
f.push(1.0);
f.push(2.0);
f.push(3.0);
let top_before = f.top;
f.set_st(1, 99.0);
assert_eq!(f.top, top_before);
assert_eq!(f.st(1), 99.0);
assert_eq!(f.st(0), 3.0);
}
}