use std::cmp::Reverse;
use std::collections::BinaryHeap;
use std::fmt::Write as _;
use crate::GcRef;
use crate::collections::nullable;
use crate::descriptor::{BuiltinTypeId, FormatSink, Tracer, TypeDescriptor};
pub(crate) fn in_pop_order<T: Ord, F: Fn(&T) -> GcRef>(
items: &BinaryHeap<T>,
value_of: F,
) -> Vec<GcRef> {
let mut ordered: Vec<&T> = items.iter().collect();
ordered.sort_unstable_by(|a, b| b.cmp(a));
ordered.into_iter().map(value_of).collect()
}
unsafe fn write_in_pop_order<T: Ord, F: Fn(&T) -> GcRef>(
out: &mut FormatSink<'_>,
items: &BinaryHeap<T>,
value_of: F,
) {
let _ = out.write_str("[");
for (i, value) in in_pop_order(items, value_of).into_iter().enumerate() {
if i > 0 {
let _ = out.write_str(", ");
}
let ep = value.payload::<u8>() as *const u8;
unsafe { (value.descriptor().format)(ep, out) };
}
let _ = out.write_str("]");
}
#[derive(Clone, Copy)]
pub struct HeapEntry {
pub value: GcRef,
pub descriptor: &'static TypeDescriptor,
}
impl PartialEq for HeapEntry {
fn eq(&self, other: &Self) -> bool {
if self.value == other.value {
return true;
}
match self.descriptor.equals {
Some(equals) => {
let a = self.value.payload::<u8>() as *const u8;
let b = other.value.payload::<u8>() as *const u8;
unsafe { equals(a, b) }
}
None => false,
}
}
}
impl Eq for HeapEntry {}
impl PartialOrd for HeapEntry {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for HeapEntry {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
if !std::ptr::eq(self.descriptor, other.descriptor) {
return std::cmp::Ordering::Equal;
}
match self.descriptor.compare {
Some(compare) => unsafe {
compare(
self.value.payload::<u8>() as *const u8,
other.value.payload::<u8>() as *const u8,
)
},
None => std::cmp::Ordering::Equal,
}
}
}
impl std::fmt::Debug for HeapEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut rendered = String::new();
unsafe {
(self.descriptor.format)(
self.value.payload::<u8>() as *const u8,
&mut FormatSink::debug(&mut rendered),
);
};
write!(f, "HeapEntry({rendered})")
}
}
#[repr(C)]
pub struct MaxHeapPayload {
pub element_descriptor: *const TypeDescriptor,
pub items: BinaryHeap<HeapEntry>,
}
impl MaxHeapPayload {
#[must_use]
pub fn element(&self) -> Option<&'static TypeDescriptor> {
nullable(self.element_descriptor)
}
}
unsafe fn max_heap_trace(payload: *mut u8, tracer: &mut dyn Tracer) {
let p = unsafe { &*(payload as *const MaxHeapPayload) };
for entry in p.items.iter() {
tracer.trace(entry.value);
}
}
unsafe fn max_heap_drop(payload: *mut u8) {
unsafe { std::ptr::drop_in_place(payload as *mut MaxHeapPayload) };
}
unsafe fn max_heap_format(payload: *const u8, out: &mut FormatSink<'_>) {
let p = unsafe { &*(payload as *const MaxHeapPayload) };
unsafe { write_in_pop_order(out, &p.items, |e| e.value) };
}
pub static MAX_HEAP: TypeDescriptor = TypeDescriptor::builtin::<MaxHeapPayload>(
BuiltinTypeId::MaxHeap,
"MaxHeap",
max_heap_trace,
max_heap_drop,
max_heap_format,
None,
None,
None,
)
.with_owned_bytes(max_heap_owned_bytes);
impl MaxHeapPayload {
#[must_use]
pub(crate) fn owned_bytes(&self) -> usize {
self.items.capacity() * std::mem::size_of::<HeapEntry>()
}
}
unsafe fn max_heap_owned_bytes(payload: *const u8) -> usize {
let p = unsafe { &*(payload as *const MaxHeapPayload) };
p.owned_bytes()
}
#[repr(C)]
pub struct MinHeapPayload {
pub element_descriptor: *const TypeDescriptor,
pub items: BinaryHeap<Reverse<HeapEntry>>,
}
impl MinHeapPayload {
#[must_use]
pub fn element(&self) -> Option<&'static TypeDescriptor> {
nullable(self.element_descriptor)
}
}
unsafe fn min_heap_trace(payload: *mut u8, tracer: &mut dyn Tracer) {
let p = unsafe { &*(payload as *const MinHeapPayload) };
for entry in p.items.iter() {
tracer.trace(entry.0.value);
}
}
unsafe fn min_heap_drop(payload: *mut u8) {
unsafe { std::ptr::drop_in_place(payload as *mut MinHeapPayload) };
}
unsafe fn min_heap_format(payload: *const u8, out: &mut FormatSink<'_>) {
let p = unsafe { &*(payload as *const MinHeapPayload) };
unsafe { write_in_pop_order(out, &p.items, |e| e.0.value) };
}
pub static MIN_HEAP: TypeDescriptor = TypeDescriptor::builtin::<MinHeapPayload>(
BuiltinTypeId::MinHeap,
"MinHeap",
min_heap_trace,
min_heap_drop,
min_heap_format,
None,
None,
None,
)
.with_owned_bytes(min_heap_owned_bytes);
impl MinHeapPayload {
#[must_use]
pub(crate) fn owned_bytes(&self) -> usize {
self.items.capacity() * std::mem::size_of::<Reverse<HeapEntry>>()
}
}
unsafe fn min_heap_owned_bytes(payload: *const u8) -> usize {
let p = unsafe { &*(payload as *const MinHeapPayload) };
p.owned_bytes()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn heap_descriptors_are_non_equatable() {
assert!(!MAX_HEAP.is_equatable());
assert!(!MAX_HEAP.is_hashable());
assert_eq!(MAX_HEAP.name, "MaxHeap");
assert!(!MIN_HEAP.is_equatable());
assert!(!MIN_HEAP.is_hashable());
assert_eq!(MIN_HEAP.name, "MinHeap");
}
#[test]
fn float_heap_entries_use_numeric_order() {
let rt = crate::Runtime::new();
let minus_two = HeapEntry {
value: rt.alloc_float(-2.0),
descriptor: &crate::scalars::FLOAT,
};
let minus_one = HeapEntry {
value: rt.alloc_float(-1.0),
descriptor: &crate::scalars::FLOAT,
};
assert_eq!(
minus_two.cmp(&minus_one),
std::cmp::Ordering::Less,
"orderable Float values must use IEEE numeric ordering, not signed bit-pattern order"
);
}
#[test]
fn char_heap_entries_order_by_unicode_scalar_value() {
let rt = crate::Runtime::new();
let a = HeapEntry {
value: rt.alloc_char('a' as u32),
descriptor: &crate::scalars::CHAR,
};
let beta = HeapEntry {
value: rt.alloc_char('β' as u32),
descriptor: &crate::scalars::CHAR,
};
assert_eq!(a.cmp(&beta), std::cmp::Ordering::Less);
assert_eq!(beta.cmp(&a), std::cmp::Ordering::Greater);
}
#[test]
fn a_heaps_snapshot_is_the_order_draining_it_would_give() {
let rt = crate::Runtime::new();
let entry = |n: i64| HeapEntry {
value: rt.alloc_int(n),
descriptor: &crate::scalars::INT,
};
let read_back = |items: Vec<GcRef>| -> Vec<i64> {
items
.into_iter()
.map(|v| unsafe { *v.payload::<i64>() })
.collect()
};
let mut max: BinaryHeap<HeapEntry> = BinaryHeap::new();
for n in [3, 1, 2] {
max.push(entry(n));
}
assert_eq!(read_back(in_pop_order(&max, |e| e.value)), vec![3, 2, 1]);
let mut min: BinaryHeap<Reverse<HeapEntry>> = BinaryHeap::new();
for n in [3, 1, 2] {
min.push(Reverse(entry(n)));
}
assert_eq!(read_back(in_pop_order(&min, |e| e.0.value)), vec![1, 2, 3]);
let mut drained = Vec::new();
while let Some(Reverse(e)) = min.pop() {
drained.push(unsafe { *e.value.payload::<i64>() });
}
assert_eq!(drained, vec![1, 2, 3]);
let mut max_again: BinaryHeap<HeapEntry> = BinaryHeap::new();
for n in [3, 1, 2] {
max_again.push(entry(n));
}
let _ = in_pop_order(&max_again, |e| e.value);
assert_eq!(max_again.len(), 3, "iterating is not popping");
}
#[test]
fn entries_of_different_types_do_not_dispatch_a_callback() {
let rt = crate::Runtime::new();
let int = HeapEntry {
value: rt.alloc_int(1),
descriptor: &crate::scalars::INT,
};
let text = HeapEntry {
value: rt.alloc_text("zzz"),
descriptor: &crate::text::TEXT,
};
assert_eq!(int.cmp(&text), std::cmp::Ordering::Equal);
assert_eq!(text.cmp(&int), std::cmp::Ordering::Equal);
}
#[test]
fn an_element_type_with_no_container_order_compares_equal_rather_than_reading_bytes() {
let mut rt = crate::Runtime::new();
assert!(
!crate::closures::CLOSURE.is_orderable(),
"a closure has no ordering of any kind (ADR-138)"
);
let mut ctx = rt.context();
let make = |ctx: &mut crate::RuntimeContext| HeapEntry {
value: unsafe { crate::abi::praxis_alloc_closure(ctx, std::ptr::null(), 0) },
descriptor: &crate::closures::CLOSURE,
};
let a = make(&mut ctx);
let b = make(&mut ctx);
assert_eq!(a.cmp(&b), std::cmp::Ordering::Equal);
}
#[test]
fn a_text_heap_pops_in_lexicographic_order() {
let rt = crate::Runtime::new();
let mut items = BinaryHeap::new();
for s in ["pear", "apple", "quince", "banana"] {
items.push(Reverse(HeapEntry {
value: rt.alloc_text(s),
descriptor: &crate::text::TEXT,
}));
}
let payload = MinHeapPayload {
element_descriptor: &crate::text::TEXT,
items,
};
assert_eq!(
rendered(min_heap_format, &payload),
"[apple, banana, pear, quince]"
);
}
fn rendered<P>(format: crate::FormatFn, payload: &P) -> String {
let mut s = String::new();
let mut sink = FormatSink::display(&mut s);
unsafe { format((payload as *const P).cast::<u8>(), &mut sink) };
s
}
#[test]
fn heap_formatting_does_not_depend_on_insertion_order() {
let rt = crate::Runtime::new();
let build = |order: [i64; 5]| {
let mut items = BinaryHeap::new();
for n in order {
items.push(HeapEntry {
value: rt.alloc_int(n),
descriptor: &crate::scalars::INT,
});
}
MaxHeapPayload {
element_descriptor: &crate::scalars::INT,
items,
}
};
let ascending = build([1, 5, 3, 9, 2]);
let descending = build([2, 9, 3, 5, 1]);
let backing =
|p: &MaxHeapPayload| p.items.iter().map(|e| format!("{e:?}")).collect::<Vec<_>>();
assert_ne!(
backing(&ascending),
backing(&descending),
"the two backing arrays must actually differ, or this proves nothing"
);
let a = rendered(max_heap_format, &ascending);
let d = rendered(max_heap_format, &descending);
assert_eq!(a, d, "the same contents must render the same way");
assert_eq!(a, "[9, 5, 3, 2, 1]", "a max-heap renders in pop order");
}
#[test]
fn a_min_heap_renders_smallest_first() {
let rt = crate::Runtime::new();
let mut items = BinaryHeap::new();
for n in [4_i64, 1, 7, 2] {
items.push(Reverse(HeapEntry {
value: rt.alloc_int(n),
descriptor: &crate::scalars::INT,
}));
}
let payload = MinHeapPayload {
element_descriptor: &crate::scalars::INT,
items,
};
assert_eq!(rendered(min_heap_format, &payload), "[1, 2, 4, 7]");
}
}