use std::fmt::Write as _;
use crate::DynamicHasher;
use crate::GcRef;
use crate::descriptor::{BuiltinTypeId, FormatSink, Tracer, TypeDescriptor};
use crate::repr_c_vec::ReprCVec;
#[repr(C)]
pub struct VecPayload {
pub element_descriptor: *const TypeDescriptor,
pub items: ReprCVec<GcRef>,
}
const _: () = assert!(std::mem::offset_of!(VecPayload, element_descriptor) == 0);
const _: () = assert!(std::mem::offset_of!(VecPayload, items) == 8);
const _: () = assert!(std::mem::size_of::<VecPayload>() == 32);
#[cfg(not(feature = "std-vec-payload"))]
pub const INLINE_VEC_SITE: crate::repr_c_vec::InlineSliceSite =
crate::repr_c_vec::InlineSliceSite::new(
BuiltinTypeId::Vec,
std::mem::align_of::<VecPayload>(),
std::mem::offset_of!(VecPayload, items),
std::mem::size_of::<GcRef>(),
);
impl VecPayload {
#[must_use]
pub fn element(&self) -> Option<&'static TypeDescriptor> {
ElementSeq::element(self)
}
}
impl ElementSeq for VecPayload {
fn element_descriptor(&self) -> *const TypeDescriptor {
self.element_descriptor
}
fn items(&self) -> impl ExactSizeIterator<Item = GcRef> {
self.items.iter().copied()
}
fn extra_shape(&self) -> Option<u64> {
None
}
}
pub(crate) fn same_element(a: *const TypeDescriptor, b: *const TypeDescriptor) -> bool {
a.is_null() || b.is_null() || std::ptr::eq(a, b)
}
#[inline]
pub(crate) fn nullable(d: *const TypeDescriptor) -> Option<&'static TypeDescriptor> {
(!d.is_null()).then(|| unsafe { &*d })
}
pub(crate) trait ElementSeq {
fn element_descriptor(&self) -> *const TypeDescriptor;
fn items(&self) -> impl ExactSizeIterator<Item = GcRef>;
fn extra_shape(&self) -> Option<u64>;
#[must_use]
fn element(&self) -> Option<&'static TypeDescriptor> {
nullable(self.element_descriptor())
}
}
unsafe fn seq_trace<S: ElementSeq>(payload: *mut u8, tracer: &mut dyn Tracer) {
let p = unsafe { &*(payload as *const S) };
for item in p.items() {
tracer.trace(item);
}
}
unsafe fn seq_format<S: ElementSeq>(payload: *const u8, out: &mut FormatSink<'_>) {
let p = unsafe { &*(payload as *const S) };
let _ = out.write_str("[");
let Some(elem_desc) = p.element() else {
let _ = out.write_str("]");
return;
};
for (i, item) in p.items().enumerate() {
if i > 0 {
let _ = out.write_str(", ");
}
let elem_payload = item.payload::<u8>() as *const u8;
unsafe { (elem_desc.format)(elem_payload, out) };
}
let _ = out.write_str("]");
}
unsafe fn seq_equals<S: ElementSeq>(a: *const u8, b: *const u8) -> bool {
let pa = unsafe { &*(a as *const S) };
let pb = unsafe { &*(b as *const S) };
if !same_element(pa.element_descriptor(), pb.element_descriptor()) {
return false;
}
if pa.extra_shape() != pb.extra_shape() {
return false;
}
if pa.items().len() != pb.items().len() {
return false;
}
let Some(elem) = pa.element() else {
return true;
};
let Some(eq) = elem.equals else {
return false;
};
for (x, y) in pa.items().zip(pb.items()) {
let xe = x.payload::<u8>() as *const u8;
let ye = y.payload::<u8>() as *const u8;
if !unsafe { eq(xe, ye) } {
return false;
}
}
true
}
unsafe fn seq_hash<S: ElementSeq>(payload: *const u8, hasher: &mut dyn DynamicHasher) {
let p = unsafe { &*(payload as *const S) };
let Some(hash_elem) = p.element().and_then(|d| d.hash) else {
return;
};
hasher.write_bytes(&(p.items().len() as u64).to_le_bytes());
if let Some(shape) = p.extra_shape() {
hasher.write_bytes(&shape.to_le_bytes());
}
for item in p.items() {
let elem_payload = item.payload::<u8>() as *const u8;
unsafe { hash_elem(elem_payload, hasher) };
}
}
unsafe fn vec_drop(payload: *mut u8) {
unsafe { std::ptr::drop_in_place(payload as *mut VecPayload) };
}
pub static VEC: TypeDescriptor = TypeDescriptor::builtin::<VecPayload>(
BuiltinTypeId::Vec,
"Vec",
seq_trace::<VecPayload>,
vec_drop,
seq_format::<VecPayload>,
Some(seq_equals::<VecPayload>),
Some(seq_hash::<VecPayload>),
None,
)
.with_owned_bytes(vec_owned_bytes);
impl VecPayload {
#[must_use]
pub(crate) fn owned_bytes(&self) -> usize {
self.items.capacity() * std::mem::size_of::<GcRef>()
}
}
unsafe fn vec_owned_bytes(payload: *const u8) -> usize {
let p = unsafe { &*(payload as *const VecPayload) };
p.owned_bytes()
}
use std::collections::VecDeque;
#[repr(C)]
pub struct DequePayload {
pub element_descriptor: *const TypeDescriptor,
pub items: VecDeque<GcRef>,
}
impl ElementSeq for DequePayload {
fn element_descriptor(&self) -> *const TypeDescriptor {
self.element_descriptor
}
fn items(&self) -> impl ExactSizeIterator<Item = GcRef> {
self.items.iter().copied()
}
fn extra_shape(&self) -> Option<u64> {
None
}
}
unsafe fn deque_drop(payload: *mut u8) {
unsafe { std::ptr::drop_in_place(payload as *mut DequePayload) };
}
pub static DEQUE: TypeDescriptor = TypeDescriptor::builtin::<DequePayload>(
BuiltinTypeId::Deque,
"Deque",
seq_trace::<DequePayload>,
deque_drop,
seq_format::<DequePayload>,
Some(seq_equals::<DequePayload>),
Some(seq_hash::<DequePayload>),
None,
)
.with_owned_bytes(deque_owned_bytes);
impl DequePayload {
#[must_use]
pub(crate) fn owned_bytes(&self) -> usize {
self.items.capacity() * std::mem::size_of::<GcRef>()
}
}
unsafe fn deque_owned_bytes(payload: *const u8) -> usize {
let p = unsafe { &*(payload as *const DequePayload) };
p.owned_bytes()
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct VecExtent {
len: usize,
}
impl VecExtent {
pub const MAX_ITEMS: usize = GridExtent::MAX_CELLS;
#[must_use]
pub const fn new(len: i64) -> Option<VecExtent> {
if len < 0 {
return None;
}
let len = len as usize;
if len > Self::MAX_ITEMS {
return None;
}
Some(VecExtent { len })
}
#[inline]
#[must_use]
pub const fn len(self) -> usize {
self.len
}
#[inline]
#[must_use]
pub const fn is_empty(self) -> bool {
self.len == 0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct GridExtent {
width: usize,
height: usize,
cells: usize,
}
impl GridExtent {
pub const MAX_CELLS: usize = 1 << 28;
#[must_use]
pub const fn new(width: i64, height: i64) -> Option<GridExtent> {
if width < 0 || height < 0 {
return None;
}
let (width, height) = (width as usize, height as usize);
let Some(cells) = width.checked_mul(height) else {
return None;
};
if cells > Self::MAX_CELLS {
return None;
}
Some(GridExtent {
width,
height,
cells,
})
}
#[inline]
#[must_use]
pub const fn width(self) -> usize {
self.width
}
#[inline]
#[must_use]
pub const fn height(self) -> usize {
self.height
}
#[inline]
#[must_use]
pub const fn cells(self) -> usize {
self.cells
}
}
#[repr(C)]
pub struct GridPayload {
pub element_descriptor: *const TypeDescriptor,
pub items: Vec<GcRef>,
pub width: usize,
}
impl ElementSeq for GridPayload {
fn element_descriptor(&self) -> *const TypeDescriptor {
self.element_descriptor
}
fn items(&self) -> impl ExactSizeIterator<Item = GcRef> {
self.items.iter().copied()
}
fn extra_shape(&self) -> Option<u64> {
Some(self.width as u64)
}
}
unsafe fn grid_drop(payload: *mut u8) {
unsafe { std::ptr::drop_in_place(payload as *mut GridPayload) };
}
pub static GRID: TypeDescriptor = TypeDescriptor::builtin::<GridPayload>(
BuiltinTypeId::Grid,
"Grid",
seq_trace::<GridPayload>,
grid_drop,
seq_format::<GridPayload>,
Some(seq_equals::<GridPayload>),
Some(seq_hash::<GridPayload>),
None,
)
.with_owned_bytes(grid_owned_bytes);
impl GridPayload {
#[must_use]
pub(crate) fn owned_bytes(&self) -> usize {
self.items.capacity() * std::mem::size_of::<GcRef>()
}
}
unsafe fn grid_owned_bytes(payload: *const u8) -> usize {
let p = unsafe { &*(payload as *const GridPayload) };
p.owned_bytes()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_vec_extent_refuses_a_negative_or_absurd_length() {
assert!(VecExtent::new(-1).is_none());
assert!(VecExtent::new(i64::MIN).is_none());
assert!(VecExtent::new(VecExtent::MAX_ITEMS as i64 + 1).is_none());
assert!(VecExtent::new(i64::MAX).is_none());
assert_eq!(VecExtent::new(0).expect("zero is a length").len(), 0);
assert!(VecExtent::new(0).expect("zero is a length").is_empty());
assert_eq!(VecExtent::new(7).expect("seven is a length").len(), 7);
assert_eq!(
VecExtent::new(VecExtent::MAX_ITEMS as i64)
.expect("the cap itself is allowed")
.len(),
VecExtent::MAX_ITEMS
);
}
#[test]
fn a_vecs_cap_is_a_grids_cap() {
assert_eq!(VecExtent::MAX_ITEMS, GridExtent::MAX_CELLS);
}
#[test]
fn vec_descriptor_reports_capabilities() {
assert!(VEC.is_equatable());
assert!(VEC.is_hashable());
assert_eq!(VEC.name, "Vec");
}
#[test]
fn empty_vectors_with_different_element_types_are_not_equal() {
let rt = crate::Runtime::new();
let ints = rt.alloc_vec(&crate::scalars::INT, Vec::new());
let floats = rt.alloc_vec(&crate::scalars::FLOAT, Vec::new());
assert!(
!ints.equals(&floats),
"a collection's element descriptor is part of its runtime type identity"
);
}
#[test]
fn grids_that_differ_only_in_width_are_not_equal() {
let rt = crate::Runtime::new();
let cells: Vec<GcRef> = (0..6_i64).map(|v| rt.alloc_int(v)).collect();
let two_wide = rt.alloc_grid(&crate::scalars::INT, cells.clone(), 2);
let three_wide = rt.alloc_grid(&crate::scalars::INT, cells.clone(), 3);
let also_two_wide = rt.alloc_grid(&crate::scalars::INT, cells, 2);
assert!(
!two_wide.equals(&three_wide),
"a 3×2 grid is not a 2×3 one however its cells fall"
);
assert!(
two_wide.equals(&also_two_wide),
"the shape check must not reject two grids that agree on it"
);
}
#[test]
fn a_vec_payload_is_thirty_two_bytes_with_the_items_at_offset_eight() {
assert_eq!(std::mem::size_of::<VecPayload>(), 32);
assert_eq!(std::mem::offset_of!(VecPayload, element_descriptor), 0);
assert_eq!(std::mem::offset_of!(VecPayload, items), 8);
}
#[cfg(not(feature = "std-vec-payload"))]
#[test]
fn a_backend_can_read_the_length_and_the_elements_out_of_a_live_payload() {
let rt = crate::Runtime::new();
let elements: Vec<GcRef> = (0..7_i64).map(|v| rt.alloc_int(v)).collect();
let vec_ref = rt.alloc_vec(&crate::scalars::INT, elements);
let base = vec_ref.payload::<u8>().cast_const();
let (items_ptr, len) = unsafe {
(
base.add(8).cast::<*const GcRef>().read(),
base.add(16).cast::<usize>().read(),
)
};
assert_eq!(len, 7);
for i in 0..len {
let element = unsafe { *items_ptr.add(i) };
assert!(std::ptr::eq(element.descriptor(), &crate::scalars::INT));
assert_eq!(unsafe { *element.payload::<i64>() }, i as i64);
}
}
#[cfg(not(feature = "std-vec-payload"))]
#[test]
fn the_inline_vec_site_addresses_a_live_vec_from_its_object_base() {
let rt = crate::Runtime::new();
let elements: Vec<GcRef> = (0..5_i64).map(|v| rt.alloc_int(v)).collect();
let vec_ref = rt.alloc_vec(&crate::scalars::INT, elements);
assert!(
std::ptr::eq(INLINE_VEC_SITE.type_id().descriptor(), vec_ref.descriptor()),
"the site names the descriptor the proof compares against, and it \
must be the one a live `Vec` carries"
);
let base = vec_ref.as_ptr().cast::<u8>().cast_const();
let (items, len) = unsafe {
(
base.add(INLINE_VEC_SITE.elements_offset())
.cast::<*const GcRef>()
.read(),
base.add(INLINE_VEC_SITE.len_offset())
.cast::<usize>()
.read(),
)
};
assert_eq!(len, 5);
assert_eq!(INLINE_VEC_SITE.element_shift(), 3, "a GcRef is eight bytes");
for i in 0..len {
let element = unsafe { *items.add(i) };
assert_eq!(unsafe { *element.payload::<i64>() }, i as i64);
}
}
#[test]
fn a_vec_that_reallocates_across_a_collection_keeps_every_element() {
let rt = crate::Runtime::new();
let mut scope = crate::roots::RootScope::new();
let vec_ref = rt.alloc_vec(&crate::scalars::INT, Vec::new());
scope.root(vec_ref);
const BASE: i64 = 1_000_000;
for i in 0..512_i64 {
let element = rt.alloc_int(BASE + i);
unsafe { &mut *vec_ref.payload::<VecPayload>() }
.items
.push(element);
}
for i in 0..64_i64 {
let _ = rt.alloc_int(BASE + 100_000 + i);
}
rt.collect_with(&scope);
let p = unsafe { &*vec_ref.payload::<VecPayload>() };
assert_eq!(p.items.len(), 512);
assert!(p.items.capacity() >= 512);
for (i, item) in p.items.iter().enumerate() {
assert_eq!(unsafe { *item.payload::<i64>() }, BASE + i as i64);
}
}
#[test]
fn the_owned_bytes_callback_charges_the_pacer_for_the_whole_buffer() {
let rt = crate::Runtime::new();
let elements: Vec<GcRef> = (0..10_i64).map(|v| rt.alloc_int(v)).collect();
let vec_ref = rt.alloc_vec(&crate::scalars::INT, elements);
let p = unsafe { &*vec_ref.payload::<VecPayload>() };
let expected = p.items.capacity() * std::mem::size_of::<GcRef>();
let reported = unsafe { vec_owned_bytes(vec_ref.payload::<u8>() as *const u8) };
assert_eq!(reported, expected);
assert!(reported >= 10 * std::mem::size_of::<GcRef>());
}
}