use std::collections::{HashMap, HashSet};
use std::fmt::{self, Write as _};
use crate::DynamicHasher;
use crate::GcRef;
use crate::collections::nullable;
use crate::descriptor::{BuiltinTypeId, FormatSink, Tracer, TypeDescriptor};
use crate::dynamic_key::DynamicKey;
pub(crate) unsafe fn render_into(
out: &mut FormatSink<'_>,
descriptor: &TypeDescriptor,
value: GcRef,
) {
let payload = value.payload::<u8>() as *const u8;
unsafe { (descriptor.format)(payload, out) };
}
pub(crate) fn write_ordered<I: Iterator<Item = String>>(
out: &mut dyn fmt::Write,
open: &str,
entries: I,
close: &str,
) {
let _ = out.write_str(open);
for (i, entry) in entries.enumerate() {
if i > 0 {
let _ = out.write_str(", ");
}
let _ = out.write_str(&entry);
}
let _ = out.write_str(close);
}
pub(crate) unsafe fn ordered_entries(entries: &HashMap<DynamicKey, GcRef>) -> Vec<(GcRef, GcRef)> {
let mut rows: Vec<(GcRef, GcRef)> = entries.iter().map(|(k, v)| (k.value(), *v)).collect();
rows.sort_by(|a, b| unsafe { crate::ordering::container_cmp(a.0, b.0) });
rows
}
pub(crate) unsafe fn ordered_members(entries: &HashSet<DynamicKey>) -> Vec<GcRef> {
let mut rows: Vec<GcRef> = entries.iter().map(DynamicKey::value).collect();
rows.sort_by(|a, b| unsafe { crate::ordering::container_cmp(*a, *b) });
rows
}
fn write_braced<T>(
out: &mut FormatSink<'_>,
items: Vec<T>,
render: impl Fn(&mut FormatSink<'_>, T),
) {
let style = out.style();
let entries = items.into_iter().map(|item| {
let mut buf = String::new();
{
let mut s = FormatSink::styled(&mut buf, style);
render(&mut s, item);
}
buf
});
write_ordered(out, "{", entries, "}");
}
const KEY_HASH_MIX: u64 = 0x9e3779b97f4a7c15;
#[repr(C)]
pub struct MapPayload {
pub key_descriptor: *const TypeDescriptor,
pub value_descriptor: *const TypeDescriptor,
pub entries: HashMap<DynamicKey, GcRef>,
}
impl MapPayload {
#[must_use]
pub fn key(&self) -> Option<&'static TypeDescriptor> {
nullable(self.key_descriptor)
}
#[must_use]
pub fn value(&self) -> Option<&'static TypeDescriptor> {
nullable(self.value_descriptor)
}
}
unsafe fn map_trace(payload: *mut u8, tracer: &mut dyn Tracer) {
let p = unsafe { &*(payload as *const MapPayload) };
for (k, v) in p.entries.iter() {
tracer.trace(k.value());
tracer.trace(*v);
}
}
unsafe fn map_drop(payload: *mut u8) {
unsafe { std::ptr::drop_in_place(payload as *mut MapPayload) };
}
unsafe fn map_format(payload: *const u8, out: &mut FormatSink<'_>) {
let p = unsafe { &*(payload as *const MapPayload) };
let rows = unsafe { ordered_entries(&p.entries) };
write_braced(out, rows, |s, (k, v)| {
unsafe {
render_into(s, k.descriptor(), k);
let _ = s.write_str(": ");
render_into(s, v.descriptor(), v);
}
});
}
unsafe fn map_equals(a: *const u8, b: *const u8) -> bool {
let pa = unsafe { &*(a as *const MapPayload) };
let pb = unsafe { &*(b as *const MapPayload) };
if pa.entries.len() != pb.entries.len() {
return false;
}
for (k, va) in pa.entries.iter() {
let Some(vb) = pb.entries.get(k) else {
return false;
};
if !std::ptr::eq(va.descriptor(), vb.descriptor()) {
return false;
}
let Some(eq) = va.descriptor().equals else {
return false;
};
let va_p = va.payload::<u8>() as *const u8;
let vb_p = vb.payload::<u8>() as *const u8;
if !unsafe { eq(va_p, vb_p) } {
return false;
}
}
true
}
unsafe fn map_hash(payload: *const u8, hasher: &mut dyn DynamicHasher) {
let p = unsafe { &*(payload as *const MapPayload) };
hasher.write_bytes(&(p.entries.len() as u64).to_le_bytes());
let mut acc: u64 = 0;
for (k, v) in p.entries.iter() {
let (Some(hash_key), Some(hash_val)) = (k.descriptor().hash, v.descriptor().hash) else {
return;
};
let mut kh = crate::descriptor::StructHasher::new();
let k_payload = k.value().payload::<u8>() as *const u8;
unsafe { hash_key(k_payload, &mut kh) };
let mut vh = crate::descriptor::StructHasher::new();
let v_payload = v.payload::<u8>() as *const u8;
unsafe { hash_val(v_payload, &mut vh) };
let pair = kh
.finish()
.wrapping_mul(KEY_HASH_MIX)
.wrapping_add(vh.finish());
acc ^= pair;
}
hasher.write_bytes(&acc.to_le_bytes());
}
pub static MAP: TypeDescriptor = TypeDescriptor::builtin::<MapPayload>(
BuiltinTypeId::Map,
"Map",
map_trace,
map_drop,
map_format,
Some(map_equals),
Some(map_hash),
None,
)
.with_owned_bytes(map_owned_bytes);
impl MapPayload {
#[must_use]
pub(crate) fn owned_bytes(&self) -> usize {
self.entries.capacity() * (std::mem::size_of::<DynamicKey>() + std::mem::size_of::<GcRef>())
}
}
unsafe fn map_owned_bytes(payload: *const u8) -> usize {
let p = unsafe { &*(payload as *const MapPayload) };
p.owned_bytes()
}
#[repr(C)]
pub struct SetPayload {
pub element_descriptor: *const TypeDescriptor,
pub entries: HashSet<DynamicKey>,
}
impl SetPayload {
#[must_use]
pub fn element(&self) -> Option<&'static TypeDescriptor> {
nullable(self.element_descriptor)
}
}
unsafe fn set_trace(payload: *mut u8, tracer: &mut dyn Tracer) {
let p = unsafe { &*(payload as *const SetPayload) };
for k in p.entries.iter() {
tracer.trace(k.value());
}
}
unsafe fn set_drop(payload: *mut u8) {
unsafe { std::ptr::drop_in_place(payload as *mut SetPayload) };
}
unsafe fn set_format(payload: *const u8, out: &mut FormatSink<'_>) {
let p = unsafe { &*(payload as *const SetPayload) };
let members = unsafe { ordered_members(&p.entries) };
write_braced(out, members, |s, m| {
unsafe { render_into(s, m.descriptor(), m) };
});
}
unsafe fn set_equals(a: *const u8, b: *const u8) -> bool {
let pa = unsafe { &*(a as *const SetPayload) };
let pb = unsafe { &*(b as *const SetPayload) };
pa.entries.len() == pb.entries.len() && pa.entries.is_subset(&pb.entries)
}
unsafe fn set_hash(payload: *const u8, hasher: &mut dyn DynamicHasher) {
let p = unsafe { &*(payload as *const SetPayload) };
hasher.write_bytes(&(p.entries.len() as u64).to_le_bytes());
let mut acc: u64 = 0;
for k in p.entries.iter() {
let Some(hash_el) = k.descriptor().hash else {
return;
};
let mut h = crate::descriptor::StructHasher::new();
let k_payload = k.value().payload::<u8>() as *const u8;
unsafe { hash_el(k_payload, &mut h) };
acc ^= h.finish();
}
hasher.write_bytes(&acc.to_le_bytes());
}
pub static SET: TypeDescriptor = TypeDescriptor::builtin::<SetPayload>(
BuiltinTypeId::Set,
"Set",
set_trace,
set_drop,
set_format,
Some(set_equals),
Some(set_hash),
None,
)
.with_owned_bytes(set_owned_bytes);
impl SetPayload {
#[must_use]
pub(crate) fn owned_bytes(&self) -> usize {
self.entries.capacity() * std::mem::size_of::<DynamicKey>()
}
}
unsafe fn set_owned_bytes(payload: *const u8) -> usize {
let p = unsafe { &*(payload as *const SetPayload) };
p.owned_bytes()
}
#[repr(C)]
pub struct CounterPayload {
pub key_descriptor: *const TypeDescriptor,
pub entries: HashMap<DynamicKey, GcRef>,
}
impl CounterPayload {
#[must_use]
pub fn key(&self) -> Option<&'static TypeDescriptor> {
nullable(self.key_descriptor)
}
}
unsafe fn counter_trace(payload: *mut u8, tracer: &mut dyn Tracer) {
let p = unsafe { &*(payload as *const CounterPayload) };
for (k, v) in p.entries.iter() {
tracer.trace(k.value());
tracer.trace(*v);
}
}
unsafe fn counter_drop(payload: *mut u8) {
unsafe { std::ptr::drop_in_place(payload as *mut CounterPayload) };
}
unsafe fn counter_format(payload: *const u8, out: &mut FormatSink<'_>) {
let p = unsafe { &*(payload as *const CounterPayload) };
let rows = unsafe { ordered_entries(&p.entries) };
write_braced(out, rows, |s, (k, v)| {
unsafe {
render_into(s, k.descriptor(), k);
let _ = s.write_str(": ");
render_into(s, &crate::scalars::INT, v);
}
});
}
unsafe fn counter_equals(a: *const u8, b: *const u8) -> bool {
let pa = unsafe { &*(a as *const CounterPayload) };
let pb = unsafe { &*(b as *const CounterPayload) };
if pa.entries.len() != pb.entries.len() {
return false;
}
for (k, va) in pa.entries.iter() {
let Some(vb) = pb.entries.get(k) else {
return false;
};
let va_i = unsafe { *(va.payload::<i64>()) };
let vb_i = unsafe { *(vb.payload::<i64>()) };
if va_i != vb_i {
return false;
}
}
true
}
unsafe fn counter_hash(payload: *const u8, hasher: &mut dyn DynamicHasher) {
let p = unsafe { &*(payload as *const CounterPayload) };
hasher.write_bytes(&(p.entries.len() as u64).to_le_bytes());
let mut acc: u64 = 0;
for (k, v) in p.entries.iter() {
let Some(hash_key) = k.descriptor().hash else {
return;
};
let mut kh = crate::descriptor::StructHasher::new();
let k_payload = k.value().payload::<u8>() as *const u8;
unsafe { hash_key(k_payload, &mut kh) };
let v_i = unsafe { *(v.payload::<i64>()) };
let pair = kh
.finish()
.wrapping_mul(KEY_HASH_MIX)
.wrapping_add(v_i as u64);
acc ^= pair;
}
hasher.write_bytes(&acc.to_le_bytes());
}
pub static COUNTER: TypeDescriptor = TypeDescriptor::builtin::<CounterPayload>(
BuiltinTypeId::Counter,
"Counter",
counter_trace,
counter_drop,
counter_format,
Some(counter_equals),
Some(counter_hash),
None,
)
.with_owned_bytes(counter_owned_bytes);
impl CounterPayload {
#[must_use]
pub(crate) fn owned_bytes(&self) -> usize {
self.entries.capacity() * (std::mem::size_of::<DynamicKey>() + std::mem::size_of::<GcRef>())
}
}
unsafe fn counter_owned_bytes(payload: *const u8) -> usize {
let p = unsafe { &*(payload as *const CounterPayload) };
p.owned_bytes()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn map_set_counter_descriptors_report_capabilities() {
assert!(MAP.is_equatable() && MAP.is_hashable());
assert!(SET.is_equatable() && SET.is_hashable());
assert!(COUNTER.is_equatable() && COUNTER.is_hashable());
assert_eq!(MAP.name, "Map");
assert_eq!(SET.name, "Set");
assert_eq!(COUNTER.name, "Counter");
}
fn rendered<P>(format: crate::FormatFn, payload: &P) -> String {
rendered_styled(format, payload, crate::FormatStyle::Display)
}
fn rendered_styled<P>(
format: crate::FormatFn,
payload: &P,
style: crate::FormatStyle,
) -> String {
let mut s = String::new();
let mut sink = FormatSink::styled(&mut s, style);
unsafe { format((payload as *const P).cast::<u8>(), &mut sink) };
s
}
fn int_key(rt: &crate::Runtime, n: i64) -> DynamicKey {
DynamicKey::new(rt.alloc_int(n))
}
#[test]
fn map_formatting_does_not_follow_hash_table_order() {
let rt = crate::Runtime::new();
let build = |order: [i64; 6]| MapPayload {
key_descriptor: &crate::scalars::INT,
value_descriptor: &crate::scalars::INT,
entries: order
.iter()
.map(|&n| (int_key(&rt, n), rt.alloc_int(n * 10)))
.collect(),
};
let forward = rendered(map_format, &build([1, 2, 3, 4, 5, 6]));
let backward = rendered(map_format, &build([6, 5, 4, 3, 2, 1]));
assert_eq!(forward, backward, "insertion order must not show through");
assert_eq!(forward, "{1: 10, 2: 20, 3: 30, 4: 40, 5: 50, 6: 60}");
}
#[test]
fn a_containers_style_reaches_the_values_inside_it() {
let rt = crate::Runtime::new();
let payload = MapPayload {
key_descriptor: &crate::text::TEXT,
value_descriptor: &crate::text::TEXT,
entries: [(
DynamicKey::new(rt.alloc_text("k")),
rt.alloc_text(""),
)]
.into_iter()
.collect(),
};
assert_eq!(
rendered_styled(map_format, &payload, crate::FormatStyle::Display),
"{k: }",
"the program's rendering is unchanged, empty value and all"
);
assert_eq!(
rendered_styled(map_format, &payload, crate::FormatStyle::Debug),
r#"{"k": ""}"#,
"the debugger's reaches both the key and the value"
);
}
#[test]
fn set_formatting_does_not_follow_hash_table_order() {
let rt = crate::Runtime::new();
let build = |order: [i64; 5]| SetPayload {
element_descriptor: &crate::scalars::INT,
entries: order.iter().map(|&n| int_key(&rt, n)).collect(),
};
let forward = rendered(set_format, &build([3, 1, 4, 5, 9]));
let backward = rendered(set_format, &build([9, 5, 4, 1, 3]));
assert_eq!(forward, backward);
assert_eq!(forward, "{1, 3, 4, 5, 9}");
}
#[test]
fn a_sets_members_come_out_in_the_order_it_prints_them() {
let rt = crate::Runtime::new();
let build = |order: [i64; 5]| SetPayload {
element_descriptor: &crate::scalars::INT,
entries: order.iter().map(|&n| int_key(&rt, n)).collect(),
};
let read_back = |p: &SetPayload| -> Vec<i64> {
unsafe { ordered_members(&p.entries) }
.into_iter()
.map(|m| unsafe { *m.payload::<i64>() })
.collect()
};
let forward = read_back(&build([3, 1, 4, 5, 9]));
let backward = read_back(&build([9, 5, 4, 1, 3]));
assert_eq!(forward, backward, "insertion order must not show through");
assert_eq!(forward, vec![1, 3, 4, 5, 9]);
assert_eq!(
rendered(set_format, &build([3, 1, 4, 5, 9])),
"{1, 3, 4, 5, 9}"
);
}
#[test]
fn a_keyed_collections_entries_come_out_paired() {
let rt = crate::Runtime::new();
let p = MapPayload {
key_descriptor: &crate::scalars::INT,
value_descriptor: &crate::scalars::INT,
entries: [3, 1, 2]
.iter()
.map(|&n| (int_key(&rt, n), rt.alloc_int(n * 10)))
.collect(),
};
let rows = unsafe { ordered_entries(&p.entries) };
let pairs: Vec<(i64, i64)> = rows
.into_iter()
.map(|(k, v)| unsafe { (*k.payload::<i64>(), *v.payload::<i64>()) })
.collect();
assert_eq!(pairs, vec![(1, 10), (2, 20), (3, 30)]);
}
#[test]
fn counter_formatting_does_not_follow_hash_table_order() {
let rt = crate::Runtime::new();
let build = |order: [i64; 4]| CounterPayload {
key_descriptor: &crate::scalars::INT,
entries: order
.iter()
.map(|&n| (int_key(&rt, n), rt.alloc_int(n)))
.collect(),
};
let forward = rendered(counter_format, &build([2, 7, 1, 8]));
let backward = rendered(counter_format, &build([8, 1, 7, 2]));
assert_eq!(forward, backward);
assert_eq!(forward, "{1: 1, 2: 2, 7: 7, 8: 8}");
}
#[test]
fn a_set_of_ints_orders_numerically_and_not_lexicographically() {
let rt = crate::Runtime::new();
let build = |order: [i64; 4]| SetPayload {
element_descriptor: &crate::scalars::INT,
entries: order.iter().map(|&n| int_key(&rt, n)).collect(),
};
let read_back = |p: &SetPayload| -> Vec<i64> {
unsafe { ordered_members(&p.entries) }
.into_iter()
.map(|m| unsafe { *m.payload::<i64>() })
.collect()
};
assert_eq!(read_back(&build([9, 10, 100, 2])), vec![2, 9, 10, 100]);
assert_eq!(read_back(&build([2, 100, 10, 9])), vec![2, 9, 10, 100]);
assert_eq!(
rendered(set_format, &build([9, 10, 100, 2])),
"{2, 9, 10, 100}"
);
}
#[test]
fn a_keyed_collection_prints_in_the_order_it_iterates() {
let rt = crate::Runtime::new();
let p = MapPayload {
key_descriptor: &crate::text::TEXT,
value_descriptor: &crate::scalars::INT,
entries: [("a1", 2), ("a", 1)]
.iter()
.map(|&(k, v)| (DynamicKey::new(rt.alloc_text(k)), rt.alloc_int(v)))
.collect(),
};
let iterated: Vec<String> = unsafe { ordered_entries(&p.entries) }
.into_iter()
.map(|(k, _)| {
let mut s = String::new();
unsafe { render_into(&mut crate::FormatSink::display(&mut s), k.descriptor(), k) };
s
})
.collect();
assert_eq!(iterated, vec!["a".to_string(), "a1".to_string()]);
assert_eq!(rendered(map_format, &p), "{a: 1, a1: 2}");
}
#[test]
fn a_tuple_keyed_map_orders_element_wise() {
let mut rt = crate::Runtime::new();
let schema: &'static crate::tuples::TupleSchema =
Box::leak(Box::new(crate::tuples::TupleSchema {
descriptors: Box::leak(
vec![
&crate::text::TEXT as *const TypeDescriptor,
&crate::scalars::INT as *const TypeDescriptor,
]
.into_boxed_slice(),
),
}));
let pairs = [("a", 10), ("a", 9), ("b", 1)];
let values: Vec<(GcRef, GcRef)> = pairs
.iter()
.map(|&(t, n)| (rt.alloc_text(t), rt.alloc_int(n)))
.collect();
let mut ctx = rt.context();
let keys: Vec<GcRef> = values
.into_iter()
.map(|(t, n)| {
unsafe {
let tup = crate::abi::praxis_alloc_tuple(&mut ctx, schema);
crate::abi::praxis_tuple_set(&mut ctx, tup, 0, t);
crate::abi::praxis_tuple_set(&mut ctx, tup, 1, n);
tup
}
})
.collect();
let p = MapPayload {
key_descriptor: &crate::tuples::TUPLE,
value_descriptor: &crate::scalars::INT,
entries: keys
.into_iter()
.map(|k| (DynamicKey::new(k), rt.alloc_int(0)))
.collect(),
};
assert_eq!(
rendered(map_format, &p),
"{(a, 9): 0, (a, 10): 0, (b, 1): 0}"
);
}
#[test]
fn a_float_keyed_set_orders_numerically_and_puts_nan_last() {
let rt = crate::Runtime::new();
let p = SetPayload {
element_descriptor: &crate::scalars::FLOAT,
entries: [2.0, f64::NAN, 10.25, 1.5]
.iter()
.map(|&f| DynamicKey::new(rt.alloc_float(f)))
.collect(),
};
assert_eq!(rendered(set_format, &p), "{1.5, 2.0, 10.25, NaN}");
}
}