use std::cmp::Ordering;
use std::fmt::{self, Write as _};
use std::hash::Hash;
use crate::descriptor::{
BuiltinTypeId, DynamicHasher, FormatSink, Payload, Tracer, TypeDescriptor, hash_value,
};
use crate::heap::InlineClaimSite;
pub type UnitPayload = ();
pub type BoolPayload = u8;
pub type IntPayload = i64;
pub type BytePayload = u8;
pub type CharPayload = u32;
pub type FloatPayload = f64;
unsafe fn scalar_trace(_: *mut u8, _: &mut dyn Tracer) {}
unsafe fn scalar_drop(_: *mut u8) {}
unsafe fn scalar_equals<P: Copy + PartialEq>(a: *const u8, b: *const u8) -> bool {
unsafe { *(a as *const P) == *(b as *const P) }
}
unsafe fn scalar_hash<P: Copy + Hash>(payload: *const u8, hasher: &mut dyn DynamicHasher) {
let v = unsafe { *(payload as *const P) };
hash_value(hasher, &v);
}
unsafe fn scalar_compare<P: Copy + Ord>(a: *const u8, b: *const u8) -> Ordering {
unsafe { (*(a as *const P)).cmp(&*(b as *const P)) }
}
unsafe fn unit_format(_: *const u8, out: &mut FormatSink<'_>) {
let _ = out.write_str("Unit");
}
unsafe fn unit_equals(_: *const u8, _: *const u8) -> bool {
true
}
unsafe fn unit_hash(_: *const u8, hasher: &mut dyn DynamicHasher) {
hash_value(hasher, &());
}
unsafe fn unit_compare(_: *const u8, _: *const u8) -> Ordering {
Ordering::Equal
}
pub static UNIT: TypeDescriptor = TypeDescriptor::builtin::<UnitPayload>(
BuiltinTypeId::Unit,
"Unit",
scalar_trace,
scalar_drop,
unit_format,
Some(unit_equals),
Some(unit_hash),
Some(unit_compare),
);
pub static UNIT_PAYLOAD: Payload<UnitPayload> = Payload::new(&UNIT);
unsafe fn bool_format(payload: *const u8, out: &mut FormatSink<'_>) {
let v = unsafe { *(payload as *const BoolPayload) };
let _ = out.write_str(if v != 0 { "true" } else { "false" });
}
pub static BOOL: TypeDescriptor = TypeDescriptor::builtin::<BoolPayload>(
BuiltinTypeId::Bool,
"Bool",
scalar_trace,
scalar_drop,
bool_format,
Some(scalar_equals::<BoolPayload>),
Some(scalar_hash::<BoolPayload>),
Some(scalar_compare::<BoolPayload>),
);
pub static BOOL_PAYLOAD: Payload<BoolPayload> = Payload::new(&BOOL);
pub(crate) fn write_int(out: &mut dyn fmt::Write, v: IntPayload) {
let _ = write!(out, "{v}");
}
unsafe fn int_format(payload: *const u8, out: &mut FormatSink<'_>) {
let v = unsafe { *(payload as *const IntPayload) };
write_int(out, v);
}
pub static INT: TypeDescriptor = TypeDescriptor::builtin::<IntPayload>(
BuiltinTypeId::Int,
"Int",
scalar_trace,
scalar_drop,
int_format,
Some(scalar_equals::<IntPayload>),
Some(scalar_hash::<IntPayload>),
Some(scalar_compare::<IntPayload>),
);
pub static INT_PAYLOAD: Payload<IntPayload> = Payload::new(&INT);
pub const INT_CLAIM_SITE: InlineClaimSite = match InlineClaimSite::of(&INT) {
Some(site) => site,
None => panic!("Int has no owned_bytes charge and its block is on the ladder"),
};
unsafe fn byte_format(payload: *const u8, out: &mut FormatSink<'_>) {
let v = unsafe { *(payload as *const BytePayload) };
let _ = write!(out, "{v}");
}
pub static BYTE: TypeDescriptor = TypeDescriptor::builtin::<BytePayload>(
BuiltinTypeId::Byte,
"Byte",
scalar_trace,
scalar_drop,
byte_format,
Some(scalar_equals::<BytePayload>),
Some(scalar_hash::<BytePayload>),
Some(scalar_compare::<BytePayload>),
);
pub static BYTE_PAYLOAD: Payload<BytePayload> = Payload::new(&BYTE);
pub(crate) fn write_char(out: &mut dyn fmt::Write, v: CharPayload) {
match char::from_u32(v) {
Some(c) => {
let _ = write!(out, "{c}");
}
None => {
let _ = out.write_str("\u{FFFD}");
}
}
}
unsafe fn char_format(payload: *const u8, out: &mut FormatSink<'_>) {
let raw = unsafe { *(payload as *const CharPayload) };
write_char(out, raw);
}
pub static CHAR: TypeDescriptor = TypeDescriptor::builtin::<CharPayload>(
BuiltinTypeId::Char,
"Char",
scalar_trace,
scalar_drop,
char_format,
Some(scalar_equals::<CharPayload>),
Some(scalar_hash::<CharPayload>),
Some(scalar_compare::<CharPayload>),
);
pub static CHAR_PAYLOAD: Payload<CharPayload> = Payload::new(&CHAR);
pub(crate) fn write_float(out: &mut dyn fmt::Write, v: FloatPayload) {
let rendered = format!("{v}");
let is_a_float_literal = rendered
.bytes()
.any(|b| b == b'.' || b == b'e' || b == b'E');
if v.is_finite() && !is_a_float_literal {
let _ = write!(out, "{rendered}.0");
} else {
let _ = out.write_str(&rendered);
}
}
unsafe fn float_format(payload: *const u8, out: &mut FormatSink<'_>) {
let v = unsafe { *(payload as *const FloatPayload) };
write_float(out, v);
}
unsafe fn float_equals(a: *const u8, b: *const u8) -> bool {
unsafe { *(a as *const FloatPayload) == *(b as *const FloatPayload) }
}
unsafe fn float_hash(payload: *const u8, hasher: &mut dyn DynamicHasher) {
let v = unsafe { *(payload as *const FloatPayload) };
let bits = if v == 0.0 {
v.to_bits() & 0x7fff_ffff_ffff_ffff
} else {
v.to_bits()
};
hash_value(hasher, &bits);
}
unsafe fn float_compare(a: *const u8, b: *const u8) -> Ordering {
let (x, y) = unsafe { (*(a as *const FloatPayload), *(b as *const FloatPayload)) };
match x.partial_cmp(&y) {
Some(o) => o,
None => match (x.is_nan(), y.is_nan()) {
(true, true) => Ordering::Equal,
(true, false) => Ordering::Greater,
(false, true) => Ordering::Less,
(false, false) => Ordering::Equal,
},
}
}
pub static FLOAT: TypeDescriptor = TypeDescriptor::builtin::<FloatPayload>(
BuiltinTypeId::Float,
"Float",
scalar_trace,
scalar_drop,
float_format,
Some(float_equals),
Some(float_hash),
Some(float_compare),
);
pub static FLOAT_PAYLOAD: Payload<FloatPayload> = Payload::new(&FLOAT);
pub const FLOAT_CLAIM_SITE: InlineClaimSite = match InlineClaimSite::of(&FLOAT) {
Some(site) => site,
None => panic!("Float has no owned_bytes charge and its block is on the ladder"),
};
pub(crate) fn is_valid_char(v: u32) -> bool {
char::from_u32(v).is_some()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::descriptor::StructHasher;
#[test]
fn scalar_descriptors_format_equality_hash() {
use std::ptr;
let mut buf = String::new();
buf.clear();
unsafe { (UNIT.format)(ptr::null(), &mut crate::FormatSink::display(&mut buf)) };
assert_eq!(buf, "Unit");
let t: BoolPayload = 1;
let f: BoolPayload = 0;
buf.clear();
unsafe { (BOOL.format)(ptr::addr_of!(t), &mut crate::FormatSink::display(&mut buf)) };
assert_eq!(buf, "true");
buf.clear();
unsafe { (BOOL.format)(ptr::addr_of!(f), &mut crate::FormatSink::display(&mut buf)) };
assert_eq!(buf, "false");
assert!(unsafe { (BOOL.equals.unwrap())(ptr::addr_of!(t), ptr::addr_of!(t)) });
assert!(!unsafe { (BOOL.equals.unwrap())(ptr::addr_of!(t), ptr::addr_of!(f)) });
let a: IntPayload = 42;
let b: IntPayload = -7;
buf.clear();
unsafe {
(INT.format)(
ptr::addr_of!(a) as *const u8,
&mut crate::FormatSink::display(&mut buf),
)
};
assert_eq!(buf, "42");
assert!(unsafe {
(INT.equals.unwrap())(ptr::addr_of!(a) as *const u8, ptr::addr_of!(a) as *const u8)
});
assert!(!unsafe {
(INT.equals.unwrap())(ptr::addr_of!(a) as *const u8, ptr::addr_of!(b) as *const u8)
});
let by: BytePayload = 255;
buf.clear();
unsafe { (BYTE.format)(ptr::addr_of!(by), &mut crate::FormatSink::display(&mut buf)) };
assert_eq!(buf, "255");
let ch: CharPayload = 'A' as u32;
buf.clear();
unsafe {
(CHAR.format)(
ptr::addr_of!(ch) as *const u8,
&mut crate::FormatSink::display(&mut buf),
)
};
assert_eq!(buf, "A");
let f: FloatPayload = 2.5;
buf.clear();
unsafe {
(FLOAT.format)(
ptr::addr_of!(f) as *const u8,
&mut crate::FormatSink::display(&mut buf),
)
};
assert_eq!(buf, "2.5");
assert!(unsafe {
(FLOAT.equals.unwrap())(ptr::addr_of!(f) as *const u8, ptr::addr_of!(f) as *const u8)
});
let nan: FloatPayload = f64::NAN;
assert!(!unsafe {
(FLOAT.equals.unwrap())(
ptr::addr_of!(nan) as *const u8,
ptr::addr_of!(nan) as *const u8,
)
});
let pos_zero: FloatPayload = 0.0;
let neg_zero: FloatPayload = -0.0;
assert!(unsafe {
(FLOAT.equals.unwrap())(
ptr::addr_of!(pos_zero) as *const u8,
ptr::addr_of!(neg_zero) as *const u8,
)
});
let inf: FloatPayload = f64::INFINITY;
let neg_inf: FloatPayload = f64::NEG_INFINITY;
buf.clear();
unsafe {
(FLOAT.format)(
ptr::addr_of!(inf) as *const u8,
&mut crate::FormatSink::display(&mut buf),
)
};
assert_eq!(buf, "inf");
buf.clear();
unsafe {
(FLOAT.format)(
ptr::addr_of!(neg_inf) as *const u8,
&mut crate::FormatSink::display(&mut buf),
)
};
assert_eq!(buf, "-inf");
buf.clear();
unsafe {
(FLOAT.format)(
ptr::addr_of!(nan) as *const u8,
&mut crate::FormatSink::display(&mut buf),
)
};
assert_eq!(buf, "NaN");
}
#[test]
fn scalar_hash_is_stable() {
use std::ptr;
let a: IntPayload = 1234;
let mut h1 = StructHasher::new();
unsafe { (INT.hash.unwrap())(ptr::addr_of!(a) as *const u8, &mut h1) };
let mut h2 = StructHasher::new();
unsafe { (INT.hash.unwrap())(ptr::addr_of!(a) as *const u8, &mut h2) };
assert_eq!(h1.finish(), h2.finish());
let b: IntPayload = 1235;
let mut h3 = StructHasher::new();
unsafe { (INT.hash.unwrap())(ptr::addr_of!(b) as *const u8, &mut h3) };
assert_ne!(h1.finish(), h3.finish());
let fp: FloatPayload = 2.5;
let mut fh1 = StructHasher::new();
unsafe { (FLOAT.hash.unwrap())(ptr::addr_of!(fp) as *const u8, &mut fh1) };
let mut fh2 = StructHasher::new();
unsafe { (FLOAT.hash.unwrap())(ptr::addr_of!(fp) as *const u8, &mut fh2) };
assert_eq!(fh1.finish(), fh2.finish());
let pos_zero: FloatPayload = 0.0;
let neg_zero: FloatPayload = -0.0;
let mut hp = StructHasher::new();
let mut hn = StructHasher::new();
unsafe { (FLOAT.hash.unwrap())(ptr::addr_of!(pos_zero) as *const u8, &mut hp) };
unsafe { (FLOAT.hash.unwrap())(ptr::addr_of!(neg_zero) as *const u8, &mut hn) };
assert_eq!(hp.finish(), hn.finish());
}
#[test]
fn float_compare_is_numeric_with_nan_last() {
use std::ptr;
let cmp = FLOAT.compare.expect("Float is orderable");
let at = |v: &FloatPayload| ptr::addr_of!(*v) as *const u8;
let minus_two: FloatPayload = -2.0;
let minus_one: FloatPayload = -1.0;
let one: FloatPayload = 1.0;
assert_eq!(
unsafe { cmp(at(&minus_two), at(&minus_one)) },
Ordering::Less
);
assert_eq!(unsafe { cmp(at(&one), at(&minus_one)) }, Ordering::Greater);
let pos_zero: FloatPayload = 0.0;
let neg_zero: FloatPayload = -0.0;
assert_eq!(
unsafe { cmp(at(&pos_zero), at(&neg_zero)) },
Ordering::Equal
);
let nan: FloatPayload = f64::NAN;
let inf: FloatPayload = f64::INFINITY;
assert_eq!(unsafe { cmp(at(&nan), at(&inf)) }, Ordering::Greater);
assert_eq!(unsafe { cmp(at(&inf), at(&nan)) }, Ordering::Less);
assert_eq!(unsafe { cmp(at(&nan), at(&nan)) }, Ordering::Equal);
}
#[test]
fn scalar_compare_reads_its_own_payload_width() {
use std::ptr;
let int_cmp = INT.compare.expect("Int is orderable");
let a: IntPayload = -5;
let b: IntPayload = 3;
assert_eq!(
unsafe { int_cmp(ptr::addr_of!(a) as *const u8, ptr::addr_of!(b) as *const u8,) },
Ordering::Less
);
let char_cmp = CHAR.compare.expect("Char is orderable");
let lower_a: CharPayload = 'a' as u32;
let beta: CharPayload = 'β' as u32;
assert_eq!(
unsafe {
char_cmp(
ptr::addr_of!(lower_a) as *const u8,
ptr::addr_of!(beta) as *const u8,
)
},
Ordering::Less,
"'a' (U+0061) precedes 'β' (U+03B2) by scalar value"
);
let byte_cmp = BYTE.compare.expect("Byte is orderable");
let low: BytePayload = 1;
let high: BytePayload = 200;
assert_eq!(
unsafe { byte_cmp(ptr::addr_of!(low), ptr::addr_of!(high)) },
Ordering::Less,
"Byte is unsigned: 200 is not negative"
);
}
#[test]
fn bool_and_unit_have_a_container_order_and_no_source_order() {
use std::ptr;
let (f, t): (BoolPayload, BoolPayload) = (0, 1);
assert_eq!(
unsafe {
scalar_compare::<BoolPayload>(ptr::addr_of!(f).cast(), ptr::addr_of!(t).cast())
},
Ordering::Less,
"false sorts before true"
);
let unit: UnitPayload = ();
assert_eq!(
unsafe { unit_compare(ptr::addr_of!(unit).cast(), ptr::addr_of!(unit).cast()) },
Ordering::Equal,
"a singleton equals itself and nothing else exists to order it against"
);
assert!(BOOL.is_orderable());
assert!(UNIT.is_orderable());
assert!(INT.is_orderable());
assert!(CHAR.is_orderable());
assert!(FLOAT.is_orderable());
assert!(BYTE.is_orderable());
}
#[test]
fn char_validation_matches_std() {
assert!(is_valid_char('A' as u32));
assert!(is_valid_char(0x10FFFF));
assert!(!is_valid_char(0x110000));
assert!(!is_valid_char(0xD800)); }
#[test]
fn every_scalar_has_a_payload_handle_and_it_round_trips() {
use crate::descriptor::{BuiltinTypeId, Payload};
fn declared(id: BuiltinTypeId) -> Option<(&'static TypeDescriptor, usize, usize)> {
fn of<T: Copy>(p: Payload<T>) -> (&'static TypeDescriptor, usize, usize) {
(
p.descriptor(),
std::mem::size_of::<T>(),
std::mem::align_of::<T>(),
)
}
Some(match id {
BuiltinTypeId::Unit => of(UNIT_PAYLOAD),
BuiltinTypeId::Bool => of(BOOL_PAYLOAD),
BuiltinTypeId::Int => of(INT_PAYLOAD),
BuiltinTypeId::Byte => of(BYTE_PAYLOAD),
BuiltinTypeId::Char => of(CHAR_PAYLOAD),
BuiltinTypeId::Float => of(FLOAT_PAYLOAD),
_ => return None,
})
}
for (id, expected) in [
(BuiltinTypeId::Unit, &UNIT),
(BuiltinTypeId::Bool, &BOOL),
(BuiltinTypeId::Int, &INT),
(BuiltinTypeId::Byte, &BYTE),
(BuiltinTypeId::Char, &CHAR),
(BuiltinTypeId::Float, &FLOAT),
] {
let (descriptor, size, align) =
declared(id).unwrap_or_else(|| panic!("{id:?} has no payload handle"));
assert!(
std::ptr::eq(descriptor, expected),
"{id:?}'s handle names another descriptor"
);
assert_eq!(size, descriptor.size(), "{id:?} payload width");
assert_eq!(align, descriptor.align(), "{id:?} payload alignment");
}
let rt = crate::Runtime::new();
assert_eq!(rt.alloc_int(-42).as_int(), -42);
assert_eq!(rt.alloc_float(2.5).as_float(), 2.5);
unsafe {
assert_eq!(*rt.alloc_byte(255).payload::<BytePayload>(), 255);
assert_eq!(
*rt.alloc_char('A' as u32).payload::<CharPayload>(),
'A' as u32
);
}
}
#[test]
fn a_whole_numbered_float_renders_as_a_float() {
let rendered = |v: FloatPayload| {
let mut buf = String::new();
unsafe {
(FLOAT.format)(
std::ptr::addr_of!(v) as *const u8,
&mut crate::FormatSink::display(&mut buf),
)
};
buf
};
assert_eq!(rendered(1.0), "1.0");
assert_eq!(rendered(0.0), "0.0");
assert_eq!(rendered(-7.0), "-7.0");
assert_eq!(rendered(1e10), "10000000000.0");
assert_eq!(rendered(2.5), "2.5");
assert_eq!(rendered(0.1 + 0.2), "0.30000000000000004");
assert_eq!(rendered(f64::INFINITY), "inf");
assert_eq!(rendered(f64::NEG_INFINITY), "-inf");
assert_eq!(rendered(f64::NAN), "NaN");
}
#[test]
fn a_rendered_float_reads_back_as_the_same_float() {
let rendered = |v: FloatPayload| {
let mut buf = String::new();
unsafe {
(FLOAT.format)(
std::ptr::addr_of!(v) as *const u8,
&mut crate::FormatSink::display(&mut buf),
)
};
buf
};
for v in [
0.0_f64,
-0.0,
1.0,
-7.0,
2.5,
1e10,
0.1 + 0.2,
f64::MAX,
f64::MIN_POSITIVE,
] {
let text = rendered(v);
let reread: f64 = text
.parse()
.unwrap_or_else(|e| panic!("`{text}` does not read back as a Float: {e}"));
assert_eq!(
reread.to_bits(),
v.to_bits(),
"`{text}` read back as a different Float"
);
}
assert_eq!(rendered(-0.0), "-0.0");
assert_ne!(rendered(-0.0), rendered(0.0));
}
}