use std::{
mem,
f64,
fmt::{Formatter, Debug, Error},
};
use crate::common::data::Data;
use crate::vm::slot::Slot;
pub struct Tagged(u64);
const QNAN: u64 = 0x7ffe_0000_0000_0000;
const P_FLAG: u64 = 0x8000_0000_0000_0000;
const P_MASK: u64 = 0x0000_FFFF_FFFF_FFFF;
const S_FLAG: u64 = 0x0000_0000_0000_0000; const U_FLAG: u64 = 0x0000_0000_0000_0001; const F_FLAG: u64 = 0x0000_0000_0000_0002; const T_FLAG: u64 = 0x0000_0000_0000_0003; const N_FLAG: u64 = 0x0000_0000_0000_0004;
impl Tagged {
pub fn new(slot: Slot) -> Tagged {
match slot {
Slot::Data(Data::Real(f)) => Tagged(f.to_bits()),
Slot::Data(Data::Unit) => Tagged(QNAN | U_FLAG),
Slot::Data(Data::Boolean(false)) => Tagged(QNAN | F_FLAG),
Slot::Data(Data::Boolean(true)) => Tagged(QNAN | T_FLAG),
Slot::Frame => Tagged(QNAN | S_FLAG),
Slot::Data(Data::NotInit) => Tagged(QNAN | N_FLAG),
other @ Slot::Data(_)
| other @ Slot::Suspend { .. }
=> Tagged(P_FLAG | QNAN | (P_MASK & (Box::into_raw(Box::new(other))) as u64)),
}
}
#[inline]
pub fn frame() -> Tagged {
Tagged::new(Slot::Frame)
}
#[inline]
pub fn not_init() -> Tagged {
Tagged::new(Slot::Data(Data::NotInit))
}
unsafe fn extract(&self) -> Result<Slot, Box<Slot>> {
let Tagged(bits) = self;
return match bits {
n if (n & QNAN) != QNAN => Ok(Slot::Data(Data::Real(f64::from_bits(*n)))),
u if u == &(QNAN | U_FLAG) => Ok(Slot::Data(Data::Unit)),
f if f == &(QNAN | F_FLAG) => Ok(Slot::Data(Data::Boolean(false))),
t if t == &(QNAN | T_FLAG) => Ok(Slot::Data(Data::Boolean(true))),
s if s == &(QNAN | S_FLAG) => Ok(Slot::Frame),
n if n == &(QNAN | N_FLAG) => Ok(Slot::Data(Data::NotInit)),
p if (p & P_FLAG) == P_FLAG => Err({
Box::from_raw((bits & P_MASK) as *mut Slot)
}),
_ => unreachable!("Corrupted tagged data"),
}
}
pub fn slot(self) -> Slot {
let d = unsafe {
match self.extract() {
Ok(slot) => slot,
Err(slot) => {
*slot
}
}
};
mem::drop(self.0);
mem::forget(self);
return d;
}
pub fn copy(&self) -> Slot {
unsafe {
match self.extract() {
Ok(slot) => slot.to_owned(),
Err(boxed) => {
let copy = boxed.clone();
Box::leak(boxed);
*copy
},
}
}
}
}
impl Drop for Tagged {
fn drop(&mut self) {
unsafe { mem::drop(self.extract()) };
}
}
impl Debug for Tagged {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
write!(f, "Tagged({:?})", self.copy())
}
}
impl From<Tagged> for u64 {
fn from(tagged: Tagged) -> Self { tagged.0 }
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn reals_eq() {
let positive = 478_329.0;
let negative = -231.0;
let nan = f64::NAN;
let neg_inf = f64::NEG_INFINITY;
for n in &[positive, negative, nan, neg_inf] {
let data = Data::Real(*n);
let wrapped = Tagged::new(Slot::Data(data));
match wrapped.copy().data() {
Data::Real(f) if f.is_nan() => assert!(n.is_nan()),
Data::Real(f) => assert_eq!(*n, f),
_ => panic!("Didn't unwrap to a real"),
}
}
}
#[test]
fn bool_and_back() {
assert_eq!(Data::Boolean(true), Tagged::new(Slot::Data(Data::Boolean(true) )).copy().data());
assert_eq!(Data::Boolean(false), Tagged::new(Slot::Data(Data::Boolean(false))).copy().data());
}
#[test]
fn unit() {
assert_eq!(Data::Unit, Tagged::new(Slot::Data(Data::Unit)).copy().data());
}
#[test]
fn size() {
let data_size = mem::size_of::<Data>();
let tag_size = mem::size_of::<Tagged>();
println!("Data size: {} bytes", data_size);
println!("Tagged size: {} bytes", tag_size);
assert_eq!(tag_size, mem::size_of::<f64>());
assert!(tag_size < data_size);
}
#[test]
fn string_pointer() {
let s = "I just lost the game".to_string();
let three = "Elongated Muskrat".to_string();
let x = "It's kind of a dead giveaway, isn't it?".to_string();
for item in &[s, three, x] {
let data = Data::String(item.clone());
let wrapped = Tagged::new(Slot::Data(data));
match wrapped.copy().data() {
Data::String(s) => { assert_eq!(item, &s) },
_ => {
panic!("Didn't unwrap to a string");
},
}
}
}
#[test]
fn other_tests_eq() {
let tests = vec![
Data::Real(f64::consts::PI),
Data::Real(-2.12),
Data::Real(2.5E10),
Data::Real(2.5e10),
Data::Real(2.5E-10),
Data::Real(0.5),
Data::Real(f64::MAX),
Data::Real(f64::MIN),
Data::Real(f64::INFINITY),
Data::Real(f64::NEG_INFINITY),
Data::Real(f64::NAN),
Data::Boolean(true),
Data::Boolean(false),
Data::Unit,
Data::String("Hello, World!".to_string()),
Data::String("".to_string()),
Data::String("Whoop 😋".to_string()),
];
for test in tests {
let tagged = Tagged::new(Slot::Data(test.clone()));
let untagged = tagged.copy().data();
if let Data::Real(f) = untagged {
if let Data::Real(n) = test {
if n.is_nan() {
assert!(f.is_nan())
} else {
assert_eq!(test, Data::Real(n));
}
}
} else {
assert_eq!(test, untagged);
}
}
}
#[test]
fn no_leak_round() {
let location = "This is a string".to_string();
let tagged = Tagged::new(Slot::Data(Data::String(location.clone())));
let pointer = tagged.0 & P_MASK;
let untagged = tagged.copy().data();
let data = unsafe { Box::from_raw(pointer as *mut Data) };
mem::forget(data);
mem::drop(untagged);
}
#[test]
fn no_leak_tagged() {
let location = "This is a string".to_string();
let tagged = Tagged::new(Slot::Data(Data::String(location.clone())));
let pointer = tagged.0 & P_MASK;
let data = unsafe { Box::from_raw(pointer as *mut Data) };
mem::forget(data);
mem::drop(tagged);
}
}