use crate::float::x87::{F80, Precision};
use crate::float::{Env, Flags, Round};
pub mod cw {
pub const IM: u16 = 1 << 0;
pub const DM: u16 = 1 << 1;
pub const ZM: u16 = 1 << 2;
pub const OM: u16 = 1 << 3;
pub const UM: u16 = 1 << 4;
pub const PM: u16 = 1 << 5;
pub const MASKS: u16 = IM | DM | ZM | OM | UM | PM;
pub const PC: u16 = 0x0300;
pub const PC_SHIFT: u32 = 8;
pub const RC: u16 = 0x0c00;
pub const RC_SHIFT: u32 = 10;
pub const IC: u16 = 1 << 12;
pub const RESET: u16 = 0x037f;
}
pub mod sw {
pub const IE: u16 = 1 << 0;
pub const DE: u16 = 1 << 1;
pub const ZE: u16 = 1 << 2;
pub const OE: u16 = 1 << 3;
pub const UE: u16 = 1 << 4;
pub const PE: u16 = 1 << 5;
pub const SF: u16 = 1 << 6;
pub const ES: u16 = 1 << 7;
pub const C0: u16 = 1 << 8;
pub const C1: u16 = 1 << 9;
pub const C2: u16 = 1 << 10;
pub const TOP: u16 = 0x3800;
pub const TOP_SHIFT: u32 = 11;
pub const C3: u16 = 1 << 14;
pub const B: u16 = 1 << 15;
pub const EXCEPTIONS: u16 = IE | DE | ZE | OE | UE | PE;
pub const CONDITION: u16 = C0 | C1 | C2 | C3;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Tag {
Valid,
Zero,
Special,
Empty,
}
impl Tag {
#[must_use]
pub const fn bits(self) -> u16 {
match self {
Tag::Valid => 0,
Tag::Zero => 1,
Tag::Special => 2,
Tag::Empty => 3,
}
}
#[must_use]
pub const fn from_bits(bits: u16) -> Tag {
match bits & 3 {
0 => Tag::Valid,
1 => Tag::Zero,
2 => Tag::Special,
_ => Tag::Empty,
}
}
#[must_use]
pub const fn of(value: F80) -> Tag {
if value.exp_field() == 0 {
if value.sig == 0 {
return Tag::Zero;
}
return Tag::Special;
}
if value.exp_field() == 0x7fff || value.sig & (1 << 63) == 0 {
return Tag::Special;
}
Tag::Valid
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct X87 {
pub regs: [F80; 8],
pub control: u16,
pub status: u16,
pub tag: u16,
pub last_ip: u64,
pub last_cs: u16,
pub last_dp: u64,
pub last_ds: u16,
pub last_op: u16,
}
impl Default for X87 {
fn default() -> X87 {
X87::new()
}
}
impl X87 {
#[must_use]
pub const fn new() -> X87 {
X87 {
regs: [F80::ZERO; 8],
control: cw::RESET,
status: 0,
tag: 0xffff,
last_ip: 0,
last_cs: 0,
last_dp: 0,
last_ds: 0,
last_op: 0,
}
}
pub const fn init(&mut self) {
*self = X87::new();
}
#[inline]
#[must_use]
pub const fn top(&self) -> u8 {
((self.status & sw::TOP) >> sw::TOP_SHIFT) as u8
}
#[inline]
pub const fn set_top(&mut self, top: u8) {
self.status = (self.status & !sw::TOP) | (((top & 7) as u16) << sw::TOP_SHIFT);
}
#[inline]
#[must_use]
pub const fn phys(&self, i: u8) -> u8 {
(self.top() + i) & 7
}
#[inline]
#[must_use]
pub const fn tag_at(&self, phys: u8) -> Tag {
Tag::from_bits(self.tag >> (2 * (phys as u32 & 7)))
}
#[inline]
pub const fn set_tag_at(&mut self, phys: u8, tag: Tag) {
let shift = 2 * (phys as u32 & 7);
self.tag = (self.tag & !(3 << shift)) | (tag.bits() << shift);
}
#[inline]
#[must_use]
pub const fn occupied(&self, i: u8) -> bool {
!matches!(self.tag_at(self.phys(i)), Tag::Empty)
}
#[inline]
#[must_use]
pub const fn raw(&self, i: u8) -> F80 {
self.regs[self.phys(i) as usize]
}
#[inline]
pub fn set(&mut self, i: u8, value: F80) {
let p = self.phys(i);
self.regs[p as usize] = value;
self.set_tag_at(p, Tag::of(value));
}
#[inline]
pub const fn free(&mut self, i: u8) {
let p = self.phys(i);
self.set_tag_at(p, Tag::Empty);
}
pub const fn pop(&mut self) {
let p = self.phys(0);
self.set_tag_at(p, Tag::Empty);
self.set_top((self.top() + 1) & 7);
}
#[inline]
pub const fn dec_top(&mut self) {
self.set_top((self.top() + 7) & 7);
}
#[inline]
pub const fn inc_top(&mut self) {
self.set_top((self.top() + 1) & 7);
}
#[inline]
#[must_use]
pub const fn round(&self) -> Round {
Round::from_x86_rc(((self.control & cw::RC) >> cw::RC_SHIFT) as u32)
}
#[inline]
#[must_use]
pub const fn precision(&self) -> Precision {
match Precision::from_pc(((self.control & cw::PC) >> cw::PC_SHIFT) as u32) {
Some(p) => p,
None => Precision::Extended,
}
}
#[inline]
#[must_use]
pub const fn env(&self) -> Env {
Env::X87.round(self.round())
}
#[inline]
#[must_use]
pub const fn unmasked(&self) -> u16 {
!self.control & cw::MASKS
}
pub const fn raise(&mut self, flags: Flags) -> bool {
let bits = (flags.to_x87_status() as u16) & sw::EXCEPTIONS;
self.status |= bits;
let unmasked = bits & self.unmasked();
if unmasked != 0 {
self.status |= sw::ES | sw::B;
}
unmasked != 0
}
pub const fn stack_fault(&mut self, overflow: bool) -> bool {
self.status |= sw::SF;
if overflow {
self.status |= sw::C1;
} else {
self.status &= !sw::C1;
}
self.raise(Flags::INVALID)
}
pub const fn clear_exceptions(&mut self) {
self.status &= !(sw::EXCEPTIONS | sw::SF | sw::ES | sw::B);
}
#[inline]
#[must_use]
pub const fn pending(&self) -> bool {
self.status & sw::ES != 0
}
pub const fn set_condition(&mut self, c0: bool, c1: bool, c2: bool, c3: bool) {
let mut s = self.status & !sw::CONDITION;
if c0 {
s |= sw::C0;
}
if c1 {
s |= sw::C1;
}
if c2 {
s |= sw::C2;
}
if c3 {
s |= sw::C3;
}
self.status = s;
}
#[must_use]
pub const fn abridged_tag(&self) -> u8 {
let mut out = 0u8;
let mut i = 0u8;
while i < 8 {
if !matches!(self.tag_at(i), Tag::Empty) {
out |= 1 << i;
}
i += 1;
}
out
}
pub const fn set_abridged_tag(&mut self, bits: u8) {
let mut i = 0u8;
while i < 8 {
let tag = if bits & (1 << i) == 0 {
Tag::Empty
} else {
Tag::of(self.regs[i as usize])
};
self.set_tag_at(i, tag);
i += 1;
}
}
}
pub mod mxcsr {
pub const IE: u32 = 1 << 0;
pub const DE: u32 = 1 << 1;
pub const ZE: u32 = 1 << 2;
pub const OE: u32 = 1 << 3;
pub const UE: u32 = 1 << 4;
pub const PE: u32 = 1 << 5;
pub const DAZ: u32 = 1 << 6;
pub const IM: u32 = 1 << 7;
pub const DM: u32 = 1 << 8;
pub const ZM: u32 = 1 << 9;
pub const OM: u32 = 1 << 10;
pub const UM: u32 = 1 << 11;
pub const PM: u32 = 1 << 12;
pub const RC: u32 = 0x6000;
pub const RC_SHIFT: u32 = 13;
pub const FTZ: u32 = 1 << 15;
pub const EXCEPTIONS: u32 = IE | DE | ZE | OE | UE | PE;
pub const MASKS: u32 = IM | DM | ZM | OM | UM | PM;
pub const MASK_SHIFT: u32 = 7;
pub const WRITABLE: u32 = EXCEPTIONS | DAZ | MASKS | RC | FTZ;
pub const RESET: u32 = MASKS;
pub const SUPPORTED: u32 = WRITABLE;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Sse {
pub xmm: [[u64; 2]; 16],
pub mxcsr: u32,
}
impl Default for Sse {
fn default() -> Sse {
Sse::new()
}
}
impl Sse {
#[must_use]
pub const fn new() -> Sse {
Sse {
xmm: [[0; 2]; 16],
mxcsr: mxcsr::RESET,
}
}
#[inline]
#[must_use]
pub const fn low(&self, index: u8) -> u64 {
self.xmm[(index & 15) as usize][0]
}
#[inline]
#[must_use]
pub const fn high(&self, index: u8) -> u64 {
self.xmm[(index & 15) as usize][1]
}
#[inline]
pub const fn set_low(&mut self, index: u8, value: u64) {
self.xmm[(index & 15) as usize][0] = value;
}
#[inline]
pub const fn set_high(&mut self, index: u8, value: u64) {
self.xmm[(index & 15) as usize][1] = value;
}
#[inline]
pub const fn set(&mut self, index: u8, value: [u64; 2]) {
self.xmm[(index & 15) as usize] = value;
}
#[inline]
#[must_use]
pub const fn get(&self, index: u8) -> [u64; 2] {
self.xmm[(index & 15) as usize]
}
#[inline]
#[must_use]
pub const fn round(&self) -> Round {
Round::from_x86_rc((self.mxcsr & mxcsr::RC) >> mxcsr::RC_SHIFT)
}
#[inline]
#[must_use]
pub const fn env(&self) -> Env {
Env::X86_SSE
.round(self.round())
.daz(self.mxcsr & mxcsr::DAZ != 0)
.ftz(self.mxcsr & mxcsr::FTZ != 0)
}
#[inline]
#[must_use]
pub const fn unmasked(&self) -> u32 {
(!self.mxcsr >> mxcsr::MASK_SHIFT) & mxcsr::EXCEPTIONS
}
pub const fn raise(&mut self, flags: Flags) -> bool {
let bits = flags.to_mxcsr() & mxcsr::EXCEPTIONS;
self.mxcsr |= bits;
bits & self.unmasked() != 0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_fresh_stack_is_entirely_empty() {
let f = X87::new();
assert_eq!(f.tag, 0xffff);
for i in 0..8 {
assert_eq!(f.tag_at(i), Tag::Empty);
assert!(!f.occupied(i));
}
assert_eq!(f.control, 0x037f);
assert_eq!(f.top(), 0);
}
#[test]
fn st_i_rotates_with_top() {
let mut f = X87::new();
f.set_top(5);
assert_eq!(f.phys(0), 5);
assert_eq!(f.phys(3), 0);
assert_eq!(f.phys(4), 1);
}
#[test]
fn a_zero_tags_as_zero_and_a_nan_as_special() {
let mut f = X87::new();
f.set(0, F80::ZERO);
assert_eq!(f.tag_at(f.phys(0)), Tag::Zero);
f.set(0, F80::INDEFINITE);
assert_eq!(f.tag_at(f.phys(0)), Tag::Special);
f.set(0, F80::new(0x3fff, 1 << 63));
assert_eq!(f.tag_at(f.phys(0)), Tag::Valid);
}
#[test]
fn pop_frees_the_register_it_leaves_behind() {
let mut f = X87::new();
f.dec_top();
f.set(0, F80::ZERO);
let was = f.phys(0);
f.pop();
assert_eq!(f.tag_at(was), Tag::Empty);
assert_eq!(f.top(), 0);
}
#[test]
fn incstp_is_not_a_pop() {
let mut f = X87::new();
f.dec_top();
f.set(0, F80::ZERO);
let was = f.phys(0);
f.inc_top();
assert_eq!(f.tag_at(was), Tag::Zero);
}
#[test]
fn a_masked_exception_is_sticky_and_does_not_summarise() {
let mut f = X87::new();
assert!(!f.raise(Flags::INVALID | Flags::INEXACT));
assert_eq!(f.status & sw::EXCEPTIONS, sw::IE | sw::PE);
assert_eq!(f.status & sw::ES, 0);
assert!(!f.pending());
}
#[test]
fn an_unmasked_exception_sets_the_summary_and_the_busy_bit() {
let mut f = X87::new();
f.control &= !cw::IM;
assert!(f.raise(Flags::INVALID));
assert!(f.pending());
assert_ne!(f.status & sw::B, 0);
f.clear_exceptions();
assert!(!f.pending());
assert_eq!(f.status & sw::B, 0);
}
#[test]
fn a_stack_fault_says_which_direction_it_was() {
let mut f = X87::new();
f.stack_fault(true);
assert_ne!(f.status & sw::C1, 0);
assert_ne!(f.status & sw::SF, 0);
f.status &= !(sw::C1 | sw::SF);
f.stack_fault(false);
assert_eq!(f.status & sw::C1, 0);
assert_ne!(f.status & sw::SF, 0);
}
#[test]
fn precision_control_selects_the_three_precisions() {
let mut f = X87::new();
for (bits, want) in [
(0u16, Precision::Single),
(1, Precision::Extended),
(2, Precision::Double),
(3, Precision::Extended),
] {
f.control = (f.control & !cw::PC) | (bits << cw::PC_SHIFT);
assert_eq!(f.precision(), want, "PC = {bits:02b}");
}
}
#[test]
fn rounding_control_maps_onto_the_four_directions() {
let mut f = X87::new();
for (bits, want) in [
(0u16, Round::TiesEven),
(1, Round::TowardNegative),
(2, Round::TowardPositive),
(3, Round::TowardZero),
] {
f.control = (f.control & !cw::RC) | (bits << cw::RC_SHIFT);
assert_eq!(f.round(), want);
assert_eq!(f.env().round, want);
}
}
#[test]
fn the_abridged_tag_word_round_trips_through_a_save() {
let mut f = X87::new();
f.dec_top();
f.set(0, F80::ZERO);
f.dec_top();
f.set(0, F80::INDEFINITE);
assert_eq!(f.top(), 6);
let bits = f.abridged_tag();
assert_eq!(bits, 0b1100_0000, "R6 and R7 are occupied, not R0 and R1");
let images: [F80; 8] = core::array::from_fn(|i| f.raw(i as u8));
let mut g = X87::new();
g.set_top(f.top());
for (i, value) in images.into_iter().enumerate() {
let p = g.phys(i as u8);
g.regs[p as usize] = value;
}
g.set_abridged_tag(bits);
assert_eq!(g.tag, f.tag);
assert_eq!(g.regs, f.regs);
}
#[test]
fn a_denormal_tags_special_and_so_does_a_pseudo_denormal() {
let mut f = X87::new();
f.set(0, F80::new(0, 1));
assert_eq!(f.tag_at(f.phys(0)), Tag::Special, "the smallest subnormal");
f.set(0, F80::new(0, 1 << 63));
assert_eq!(f.tag_at(f.phys(0)), Tag::Special, "a pseudo-denormal");
f.set(0, F80::new(0x4000, 1));
assert_eq!(f.tag_at(f.phys(0)), Tag::Special, "an unnormal");
f.set(0, F80::new(0x4000, 1 << 63));
assert_eq!(f.tag_at(f.phys(0)), Tag::Valid, "and an ordinary 2.0");
}
#[test]
fn an_unmasked_sse_exception_still_records_which_one_it_was() {
let mut s = Sse::new();
s.mxcsr &= !mxcsr::ZM;
assert!(s.raise(Flags::DIV_BY_ZERO));
assert_ne!(s.mxcsr & mxcsr::ZE, 0, "the cause is legible");
let cause = (!s.mxcsr >> mxcsr::MASK_SHIFT) & s.mxcsr & mxcsr::EXCEPTIONS;
assert_eq!(cause, mxcsr::ZE);
}
#[test]
fn mxcsr_resets_with_everything_masked() {
let s = Sse::new();
assert_eq!(s.mxcsr, 0x1f80);
assert_eq!(s.round(), Round::TiesEven);
assert_eq!(s.unmasked(), 0);
}
#[test]
fn mxcsr_flush_and_denormal_bits_reach_the_environment() {
let mut s = Sse::new();
s.mxcsr |= mxcsr::FTZ | mxcsr::DAZ;
let env = s.env();
assert!(env.flush_outputs);
assert!(env.subnormal_inputs.flushes());
assert!(!env.subnormal_inputs.reports());
}
#[test]
fn a_masked_sse_exception_is_recorded_and_does_not_fault() {
let mut s = Sse::new();
assert!(!s.raise(Flags::INEXACT));
assert_ne!(s.mxcsr & mxcsr::PE, 0);
assert_eq!(s.unmasked(), 0);
}
#[test]
fn a_scalar_write_leaves_the_high_half_alone() {
let mut s = Sse::new();
s.set(1, [0x1111_1111_1111_1111, 0x2222_2222_2222_2222]);
s.set_low(1, 0xdead);
assert_eq!(s.get(1), [0xdead, 0x2222_2222_2222_2222]);
}
}