use crate::heap_value::{HeapKind, HeapValue};
pub const FLAG_MARKED: u8 = 0b0000_0001;
pub const FLAG_PINNED: u8 = 0b0000_0010;
pub const FLAG_READONLY: u8 = 0b0000_0100;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C, align(16))]
pub struct HeapHeader {
pub kind: u16,
pub elem_type: u8,
pub flags: u8,
pub len: u32,
pub cap: u32,
_pad: u32,
pub aux: u64,
_reserved: u64,
}
const _: () = {
assert!(std::mem::size_of::<HeapHeader>() == 32);
assert!(std::mem::align_of::<HeapHeader>() == 16);
};
pub mod elem_types {
pub const UNTYPED: u8 = 0;
pub const F64: u8 = 1;
pub const I64: u8 = 2;
pub const STRING: u8 = 3;
pub const BOOL: u8 = 4;
pub const TYPED_OBJECT: u8 = 5;
}
impl HeapHeader {
#[inline]
pub fn new(kind: HeapKind) -> Self {
Self {
kind: kind as u16,
elem_type: 0,
flags: 0,
len: 0,
cap: 0,
_pad: 0,
aux: 0,
_reserved: 0,
}
}
#[inline]
pub fn with_len_aux(kind: HeapKind, len: u32, aux: u64) -> Self {
Self {
kind: kind as u16,
elem_type: 0,
flags: 0,
len,
cap: 0,
_pad: 0,
aux,
_reserved: 0,
}
}
pub fn from_heap_value(value: &HeapValue) -> Self {
let kind = value.kind();
let mut header = Self::new(kind);
match value {
HeapValue::String(s) => {
header.len = s.len() as u32;
}
HeapValue::Array(arr) => {
header.len = arr.len() as u32;
header.cap = arr.len() as u32;
}
HeapValue::TypedObject {
schema_id, slots, ..
} => {
header.len = slots.len() as u32;
header.aux = *schema_id;
}
HeapValue::Closure {
function_id,
upvalues,
} => {
header.len = upvalues.len() as u32;
header.aux = *function_id as u64;
}
HeapValue::DataTable(dt) => {
header.len = dt.row_count() as u32;
}
HeapValue::TypedTable { schema_id, table } => {
header.len = table.row_count() as u32;
header.aux = *schema_id;
}
HeapValue::RowView {
schema_id, row_idx, ..
} => {
header.len = 1;
header.aux = *schema_id;
header.cap = *row_idx as u32;
}
HeapValue::ColumnRef {
schema_id, col_id, ..
} => {
header.aux = *schema_id;
header.cap = *col_id;
}
HeapValue::IndexedTable {
schema_id,
table,
index_col,
} => {
header.len = table.row_count() as u32;
header.aux = *schema_id;
header.cap = *index_col;
}
HeapValue::Enum(_) => {
}
HeapValue::Future(id) => {
header.aux = *id;
}
HeapValue::TaskGroup { kind, task_ids } => {
header.elem_type = *kind;
header.len = task_ids.len() as u32;
}
_ => {}
}
header
}
#[inline]
pub fn heap_kind(&self) -> Option<HeapKind> {
HeapKind::from_u16(self.kind)
}
#[inline]
pub fn has_flag(&self, flag: u8) -> bool {
self.flags & flag != 0
}
#[inline]
pub fn set_flag(&mut self, flag: u8) {
self.flags |= flag;
}
#[inline]
pub fn clear_flag(&mut self, flag: u8) {
self.flags &= !flag;
}
pub const OFFSET_KIND: usize = 0;
pub const OFFSET_ELEM_TYPE: usize = 2;
pub const OFFSET_FLAGS: usize = 3;
pub const OFFSET_LEN: usize = 4;
pub const OFFSET_CAP: usize = 8;
pub const OFFSET_AUX: usize = 16;
}
impl HeapKind {
pub const MAX_VARIANT: Self = HeapKind::FloatArraySlice;
#[inline]
pub fn from_u16(v: u16) -> Option<Self> {
if v <= Self::MAX_VARIANT as u16 {
Some(unsafe { std::mem::transmute(v as u8) })
} else {
None
}
}
#[inline]
pub fn from_u8(v: u8) -> Option<Self> {
Self::from_u16(v as u16)
}
}
const _: () = {
assert!(
std::mem::size_of::<HeapKind>() == 1,
"HeapKind must be repr(u8) — transmute in from_u16 depends on this"
);
};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_header_size_and_alignment() {
assert_eq!(std::mem::size_of::<HeapHeader>(), 32);
assert_eq!(std::mem::align_of::<HeapHeader>(), 16);
}
#[test]
fn test_header_field_offsets() {
assert_eq!(HeapHeader::OFFSET_KIND, 0);
assert_eq!(HeapHeader::OFFSET_ELEM_TYPE, 2);
assert_eq!(HeapHeader::OFFSET_FLAGS, 3);
assert_eq!(HeapHeader::OFFSET_LEN, 4);
assert_eq!(HeapHeader::OFFSET_CAP, 8);
assert_eq!(HeapHeader::OFFSET_AUX, 16);
let h = HeapHeader::new(HeapKind::String);
let base = &h as *const _ as usize;
assert_eq!(&h.kind as *const _ as usize - base, HeapHeader::OFFSET_KIND);
assert_eq!(
&h.elem_type as *const _ as usize - base,
HeapHeader::OFFSET_ELEM_TYPE
);
assert_eq!(
&h.flags as *const _ as usize - base,
HeapHeader::OFFSET_FLAGS
);
assert_eq!(&h.len as *const _ as usize - base, HeapHeader::OFFSET_LEN);
assert_eq!(&h.cap as *const _ as usize - base, HeapHeader::OFFSET_CAP);
assert_eq!(&h.aux as *const _ as usize - base, HeapHeader::OFFSET_AUX);
}
#[test]
fn test_new_header() {
let h = HeapHeader::new(HeapKind::Array);
assert_eq!(h.kind, HeapKind::Array as u16);
assert_eq!(h.elem_type, 0);
assert_eq!(h.flags, 0);
assert_eq!(h.len, 0);
assert_eq!(h.cap, 0);
assert_eq!(h.aux, 0);
}
#[test]
fn test_with_len_aux() {
let h = HeapHeader::with_len_aux(HeapKind::TypedObject, 5, 0xDEAD_BEEF);
assert_eq!(h.kind, HeapKind::TypedObject as u16);
assert_eq!(h.len, 5);
assert_eq!(h.aux, 0xDEAD_BEEF);
}
#[test]
fn test_heap_kind_roundtrip() {
assert_eq!(HeapKind::from_u16(0), Some(HeapKind::String));
assert_eq!(HeapKind::from_u16(1), Some(HeapKind::Array));
assert_eq!(HeapKind::from_u16(2), Some(HeapKind::TypedObject));
assert_eq!(
HeapKind::from_u16(HeapKind::F32Array as u16),
Some(HeapKind::F32Array)
);
assert_eq!(
HeapKind::from_u16(HeapKind::Set as u16),
Some(HeapKind::Set)
);
assert_eq!(
HeapKind::from_u16(HeapKind::Char as u16),
Some(HeapKind::Char)
);
assert_eq!(
HeapKind::from_u16(HeapKind::ProjectedRef as u16),
Some(HeapKind::ProjectedRef)
);
assert_eq!(
HeapKind::from_u16(HeapKind::MAX_VARIANT as u16 + 1),
None
);
assert_eq!(HeapKind::from_u16(255), None);
}
#[test]
fn test_heap_kind_from_u8() {
assert_eq!(HeapKind::from_u8(0), Some(HeapKind::String));
assert_eq!(
HeapKind::from_u8(HeapKind::F32Array as u8),
Some(HeapKind::F32Array)
);
assert_eq!(
HeapKind::from_u8(HeapKind::ProjectedRef as u8),
Some(HeapKind::ProjectedRef)
);
assert_eq!(HeapKind::from_u8(200), None);
}
#[test]
fn test_heap_kind_all_variants_roundtrip_through_transmute() {
let max = HeapKind::MAX_VARIANT as u16;
for i in 0..=max {
let kind = HeapKind::from_u16(i)
.unwrap_or_else(|| panic!("HeapKind::from_u16({i}) returned None — gap in contiguous repr(u8) enum"));
assert_eq!(
kind as u16, i,
"HeapKind variant at discriminant {i} round-tripped to {}",
kind as u16
);
}
}
#[test]
fn test_flags() {
let mut h = HeapHeader::new(HeapKind::Array);
assert!(!h.has_flag(FLAG_MARKED));
assert!(!h.has_flag(FLAG_PINNED));
h.set_flag(FLAG_MARKED);
assert!(h.has_flag(FLAG_MARKED));
assert!(!h.has_flag(FLAG_PINNED));
h.set_flag(FLAG_PINNED);
assert!(h.has_flag(FLAG_MARKED));
assert!(h.has_flag(FLAG_PINNED));
h.clear_flag(FLAG_MARKED);
assert!(!h.has_flag(FLAG_MARKED));
assert!(h.has_flag(FLAG_PINNED));
}
#[test]
fn test_from_heap_value_string() {
let hv = HeapValue::String(std::sync::Arc::new("hello".to_string()));
let h = HeapHeader::from_heap_value(&hv);
assert_eq!(h.kind, HeapKind::String as u16);
assert_eq!(h.len, 5);
}
#[test]
fn test_from_heap_value_typed_object() {
let hv = HeapValue::TypedObject {
schema_id: 42,
slots: vec![crate::slot::ValueSlot::from_number(0.0); 3].into_boxed_slice(),
heap_mask: 0,
};
let h = HeapHeader::from_heap_value(&hv);
assert_eq!(h.kind, HeapKind::TypedObject as u16);
assert_eq!(h.len, 3);
assert_eq!(h.aux, 42);
}
#[test]
fn test_from_heap_value_closure() {
let hv = HeapValue::Closure {
function_id: 7,
upvalues: vec![],
};
let h = HeapHeader::from_heap_value(&hv);
assert_eq!(h.kind, HeapKind::Closure as u16);
assert_eq!(h.len, 0);
assert_eq!(h.aux, 7);
}
}