use std::collections::hash_map::DefaultHasher;
use std::fmt;
use std::hash::{Hash, Hasher};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[repr(u32)]
pub enum BuiltinTypeId {
Unit = 0,
Bool,
Int,
Byte,
Char,
Float,
Text,
Vec,
Deque,
Grid,
Map,
Set,
Counter,
MinHeap,
MaxHeap,
BitSet,
Tuple,
Record,
Enum,
Closure,
VarCell,
Range,
}
impl BuiltinTypeId {
pub const COUNT: usize = 22;
pub const fn from_u32(v: u32) -> Option<BuiltinTypeId> {
use BuiltinTypeId::*;
Some(match v {
0 => Unit,
1 => Bool,
2 => Int,
3 => Byte,
4 => Char,
5 => Float,
6 => Text,
7 => Vec,
8 => Deque,
9 => Grid,
10 => Map,
11 => Set,
12 => Counter,
13 => MinHeap,
14 => MaxHeap,
15 => BitSet,
16 => Tuple,
17 => Record,
18 => Enum,
19 => Closure,
20 => VarCell,
21 => Range,
_ => return None,
})
}
pub fn descriptor(self) -> &'static TypeDescriptor {
BUILTINS[self as usize]
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct TypeId(u32);
impl TypeId {
#[inline]
pub const fn to_u32(self) -> u32 {
self.0
}
#[inline]
pub const fn as_builtin(self) -> Option<BuiltinTypeId> {
BuiltinTypeId::from_u32(self.0)
}
}
pub trait Tracer {
fn trace(&mut self, reference: crate::GcRef);
}
pub trait DynamicHasher {
fn write_bytes(&mut self, bytes: &[u8]);
fn finish(&self) -> u64;
}
pub struct StructHasher(DefaultHasher);
impl StructHasher {
pub fn new() -> Self {
StructHasher(DefaultHasher::new())
}
}
impl Default for StructHasher {
fn default() -> Self {
Self::new()
}
}
impl DynamicHasher for StructHasher {
fn write_bytes(&mut self, bytes: &[u8]) {
self.0.write(bytes);
}
fn finish(&self) -> u64 {
self.0.finish()
}
}
pub(crate) fn hash_value<H: DynamicHasher + ?Sized, T: Hash + ?Sized>(hasher: &mut H, value: &T) {
struct HasherShim<'a, H: ?Sized>(&'a mut H);
impl<H: DynamicHasher + ?Sized> Hasher for HasherShim<'_, H> {
#[inline]
fn write(&mut self, bytes: &[u8]) {
self.0.write_bytes(bytes);
}
#[inline]
fn finish(&self) -> u64 {
self.0.finish()
}
}
value.hash(&mut HasherShim(hasher));
}
pub type TraceFn = unsafe fn(payload: *mut u8, tracer: &mut dyn Tracer);
pub type DropFn = unsafe fn(payload: *mut u8);
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum FormatStyle {
Display,
Debug,
}
pub struct FormatSink<'a> {
out: &'a mut dyn fmt::Write,
style: FormatStyle,
}
impl<'a> FormatSink<'a> {
pub fn display(out: &'a mut dyn fmt::Write) -> FormatSink<'a> {
FormatSink {
out,
style: FormatStyle::Display,
}
}
pub fn debug(out: &'a mut dyn fmt::Write) -> FormatSink<'a> {
FormatSink {
out,
style: FormatStyle::Debug,
}
}
#[must_use]
pub fn style(&self) -> FormatStyle {
self.style
}
pub fn styled(out: &'a mut dyn fmt::Write, style: FormatStyle) -> FormatSink<'a> {
FormatSink { out, style }
}
}
impl fmt::Write for FormatSink<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.out.write_str(s)
}
}
pub type FormatFn = unsafe fn(payload: *const u8, out: &mut FormatSink<'_>);
pub type EqualsFn = unsafe fn(a: *const u8, b: *const u8) -> bool;
pub type HashFn = unsafe fn(payload: *const u8, hasher: &mut dyn DynamicHasher);
pub type OwnedBytesFn = unsafe fn(payload: *const u8) -> usize;
pub type CompareFn = unsafe fn(a: *const u8, b: *const u8) -> std::cmp::Ordering;
#[derive(Clone, Copy)]
pub struct TypeDescriptor {
id: TypeId,
pub name: &'static str,
size: usize,
align: usize,
pub trace: TraceFn,
pub drop_value: DropFn,
pub format: FormatFn,
pub equals: Option<EqualsFn>,
pub hash: Option<HashFn>,
pub compare: Option<CompareFn>,
pub owned_bytes: Option<OwnedBytesFn>,
}
impl TypeDescriptor {
#[allow(clippy::too_many_arguments)]
pub const fn builtin<P>(
builtin: BuiltinTypeId,
name: &'static str,
trace: TraceFn,
drop_value: DropFn,
format: FormatFn,
equals: Option<EqualsFn>,
hash: Option<HashFn>,
compare: Option<CompareFn>,
) -> TypeDescriptor {
TypeDescriptor {
id: TypeId(builtin as u32),
name,
size: std::mem::size_of::<P>(),
align: std::mem::align_of::<P>(),
trace,
drop_value,
format,
equals,
hash,
compare,
owned_bytes: None,
}
}
#[cfg(test)]
#[allow(clippy::too_many_arguments)]
pub const fn for_test<P>(
n: u32,
name: &'static str,
trace: TraceFn,
drop_value: DropFn,
format: FormatFn,
equals: Option<EqualsFn>,
hash: Option<HashFn>,
compare: Option<CompareFn>,
) -> TypeDescriptor {
TypeDescriptor {
id: TypeId(u32::MAX - n),
name,
size: std::mem::size_of::<P>(),
align: std::mem::align_of::<P>(),
trace,
drop_value,
format,
equals,
hash,
compare,
owned_bytes: None,
}
}
#[must_use]
pub const fn with_owned_bytes(self, owned_bytes: OwnedBytesFn) -> TypeDescriptor {
TypeDescriptor {
id: self.id,
name: self.name,
size: self.size,
align: self.align,
trace: self.trace,
drop_value: self.drop_value,
format: self.format,
equals: self.equals,
hash: self.hash,
compare: self.compare,
owned_bytes: Some(owned_bytes),
}
}
#[inline]
pub unsafe fn owned_bytes_of(&self, payload: *const u8) -> usize {
match self.owned_bytes {
Some(f) => unsafe { f(payload) },
None => 0,
}
}
#[inline]
pub const fn id(&self) -> TypeId {
self.id
}
#[inline]
pub const fn as_builtin(&self) -> Option<BuiltinTypeId> {
self.id.as_builtin()
}
#[inline]
pub const fn size(&self) -> usize {
self.size
}
#[inline]
pub const fn align(&self) -> usize {
self.align
}
#[inline]
pub fn is_equatable(&self) -> bool {
self.equals.is_some()
}
#[inline]
pub fn is_hashable(&self) -> bool {
self.hash.is_some()
}
#[inline]
pub fn is_orderable(&self) -> bool {
self.compare.is_some()
}
}
pub struct Payload<T: Copy> {
descriptor: &'static TypeDescriptor,
_payload: std::marker::PhantomData<fn() -> T>,
}
impl<T: Copy> Clone for Payload<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: Copy> Copy for Payload<T> {}
impl<T: Copy> Payload<T> {
#[must_use]
pub const fn new(descriptor: &'static TypeDescriptor) -> Payload<T> {
assert!(
std::mem::size_of::<T>() == descriptor.size(),
"payload type is not this descriptor's width"
);
assert!(
std::mem::align_of::<T>() == descriptor.align(),
"payload type is not this descriptor's alignment"
);
Payload {
descriptor,
_payload: std::marker::PhantomData,
}
}
#[must_use]
pub const fn descriptor(self) -> &'static TypeDescriptor {
self.descriptor
}
#[must_use]
#[inline]
pub unsafe fn read(self, payload: *const u8) -> T {
unsafe { payload.cast::<T>().read() }
}
}
impl<T: Copy> fmt::Debug for Payload<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Payload")
.field("descriptor", &self.descriptor.name)
.field("size", &std::mem::size_of::<T>())
.finish()
}
}
impl fmt::Debug for TypeDescriptor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TypeDescriptor")
.field("id", &self.id)
.field("name", &self.name)
.field("size", &self.size)
.field("align", &self.align)
.field("equatable", &self.is_equatable())
.field("hashable", &self.is_hashable())
.finish()
}
}
pub static BUILTINS: [&TypeDescriptor; BuiltinTypeId::COUNT] = [
&crate::scalars::UNIT,
&crate::scalars::BOOL,
&crate::scalars::INT,
&crate::scalars::BYTE,
&crate::scalars::CHAR,
&crate::scalars::FLOAT,
&crate::text::TEXT,
&crate::collections::VEC,
&crate::collections::DEQUE,
&crate::collections::GRID,
&crate::maps::MAP,
&crate::maps::SET,
&crate::maps::COUNTER,
&crate::heaps::MIN_HEAP,
&crate::heaps::MAX_HEAP,
&crate::bitset::BITSET,
&crate::tuples::TUPLE,
&crate::records::RECORD,
&crate::enums::ENUM,
&crate::closures::CLOSURE,
&crate::var_cell::VAR_CELL,
&crate::range::RANGE,
];
#[must_use]
pub fn builtin_descriptor_addresses() -> [*const TypeDescriptor; BuiltinTypeId::COUNT] {
BUILTINS.map(|d| d as *const TypeDescriptor)
}
#[cfg(test)]
mod tests {
use super::*;
unsafe fn dummy_trace(_: *mut u8, _: &mut dyn Tracer) {}
unsafe fn dummy_drop(_: *mut u8) {}
unsafe fn dummy_format(_: *const u8, _: &mut FormatSink<'_>) {}
unsafe fn dummy_eq(a: *const u8, b: *const u8) -> bool {
a == b
}
unsafe fn dummy_hash(_: *const u8, _: &mut dyn DynamicHasher) {}
#[test]
fn descriptor_constructs_and_reports_capabilities() {
static EQUATABLE_ONLY: TypeDescriptor = TypeDescriptor::for_test::<i64>(
0,
"EquatableOnly",
dummy_trace,
dummy_drop,
dummy_format,
Some(dummy_eq),
None,
None,
);
assert!(EQUATABLE_ONLY.is_equatable());
assert!(!EQUATABLE_ONLY.is_hashable());
assert!(!EQUATABLE_ONLY.is_orderable());
static HASHABLE: TypeDescriptor = TypeDescriptor::for_test::<[u64; 2]>(
1,
"Key",
dummy_trace,
dummy_drop,
dummy_format,
Some(dummy_eq),
Some(dummy_hash),
None,
);
assert!(HASHABLE.is_equatable());
assert!(HASHABLE.is_hashable());
assert_eq!(HASHABLE.size(), 16);
assert_eq!(HASHABLE.align(), 8);
}
#[test]
fn test_descriptor_ids_are_not_builtins() {
static PROBE: TypeDescriptor = TypeDescriptor::for_test::<u8>(
0,
"Probe",
dummy_trace,
dummy_drop,
dummy_format,
None,
None,
None,
);
assert_eq!(PROBE.as_builtin(), None);
}
#[test]
fn builtin_type_ids_are_globally_unique() {
let mut by_id = std::collections::BTreeMap::new();
for descriptor in BUILTINS {
if let Some(previous) = by_id.insert(descriptor.id(), descriptor.name) {
panic!(
"built-in descriptors {previous} and {} share {:?}; descriptor IDs are runtime type identity",
descriptor.name,
descriptor.id()
);
}
}
assert_eq!(by_id.len(), BuiltinTypeId::COUNT);
}
#[test]
fn builtins_are_indexed_by_their_id() {
for (index, descriptor) in BUILTINS.iter().enumerate() {
assert_eq!(
descriptor.id().to_u32(),
index as u32,
"BUILTINS[{index}] is {} whose id is {:?}",
descriptor.name,
descriptor.id()
);
let builtin = BuiltinTypeId::from_u32(index as u32).expect("index is in range");
assert!(std::ptr::eq(builtin.descriptor(), *descriptor));
}
assert!(BuiltinTypeId::from_u32(BuiltinTypeId::COUNT as u32).is_none());
}
#[test]
fn builtin_descriptors_have_a_stable_address() {
assert!(std::ptr::eq(&crate::scalars::INT, &crate::scalars::INT));
assert!(std::ptr::eq(
BuiltinTypeId::Int.descriptor(),
&crate::scalars::INT
));
assert!(!std::ptr::eq(
&crate::scalars::FLOAT,
&crate::text::TEXT as &TypeDescriptor
));
}
}