use std::sync::atomic::{AtomicU32, Ordering};
use crate::heap_value::HeapKind;
pub const FLAG_MARKED: u8 = 0b0000_0001;
pub const FLAG_PINNED: u8 = 0b0000_0010;
pub const FLAG_READONLY: u8 = 0b0000_0100;
pub const DATA_OFFSET: usize = 8;
#[repr(C)]
pub struct HeapHeader {
pub refcount: AtomicU32,
pub kind: u16,
pub flags: u8,
pub _pad: u8,
}
const _: () = {
assert!(std::mem::size_of::<HeapHeader>() == 8);
assert!(std::mem::align_of::<HeapHeader>() == 4);
assert!(DATA_OFFSET == 8);
};
impl HeapHeader {
pub const OFFSET_REFCOUNT: usize = 0;
pub const OFFSET_KIND: usize = 4;
pub const OFFSET_FLAGS: usize = 6;
pub const DATA_OFFSET: usize = DATA_OFFSET;
#[inline]
pub fn new(kind: u16) -> Self {
Self {
refcount: AtomicU32::new(1),
kind,
flags: 0,
_pad: 0,
}
}
#[inline]
pub fn retain(&self) {
self.refcount.fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub fn release(&self) -> bool {
let prev = self.refcount.fetch_sub(1, Ordering::Release);
if prev == 1 {
std::sync::atomic::fence(Ordering::Acquire);
true
} else {
false
}
}
#[inline]
pub fn refcount(&self) -> u32 {
self.refcount.load(Ordering::Relaxed)
}
#[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;
}
}
impl std::fmt::Debug for HeapHeader {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HeapHeader")
.field("refcount", &self.refcount.load(Ordering::Relaxed))
.field("kind", &self.kind)
.field("flags", &self.flags)
.finish()
}
}
impl HeapKind {
pub const MAX_VARIANT: Self = HeapKind::HashMap;
#[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>(), 8);
assert_eq!(std::mem::align_of::<HeapHeader>(), 4);
}
#[test]
fn test_header_field_offsets_via_pointer_arithmetic() {
let h = HeapHeader::new(HeapKind::String as u16);
let base = &h as *const _ as usize;
let refcount_offset = &h.refcount as *const _ as usize - base;
let kind_offset = &h.kind as *const _ as usize - base;
let flags_offset = &h.flags as *const _ as usize - base;
let pad_offset = &h._pad as *const _ as usize - base;
assert_eq!(refcount_offset, 0, "refcount must be at offset 0");
assert_eq!(kind_offset, 4, "kind must be at offset 4");
assert_eq!(flags_offset, 6, "flags must be at offset 6");
assert_eq!(pad_offset, 7, "_pad must be at offset 7");
}
#[test]
fn test_header_offset_constants() {
assert_eq!(HeapHeader::OFFSET_REFCOUNT, 0);
assert_eq!(HeapHeader::OFFSET_KIND, 4);
assert_eq!(HeapHeader::OFFSET_FLAGS, 6);
assert_eq!(HeapHeader::DATA_OFFSET, 8);
assert_eq!(DATA_OFFSET, 8);
}
#[test]
fn test_new_header() {
let h = HeapHeader::new(HeapKind::TypedObject as u16);
assert_eq!(h.refcount(), 1);
assert_eq!(h.kind, HeapKind::TypedObject as u16);
assert_eq!(h.flags, 0);
assert_eq!(h._pad, 0);
}
#[test]
fn test_retain_increments_refcount() {
let h = HeapHeader::new(HeapKind::String as u16);
assert_eq!(h.refcount(), 1);
h.retain();
assert_eq!(h.refcount(), 2);
h.retain();
assert_eq!(h.refcount(), 3);
}
#[test]
fn test_release_decrements_refcount() {
let h = HeapHeader::new(HeapKind::String as u16);
h.retain(); h.retain();
assert!(!h.release()); assert_eq!(h.refcount(), 2);
assert!(!h.release()); assert_eq!(h.refcount(), 1);
assert!(h.release()); assert_eq!(h.refcount(), 0);
}
#[test]
fn test_release_returns_true_on_last_drop() {
let h = HeapHeader::new(HeapKind::TypedObject as u16);
assert!(h.release());
}
#[test]
fn test_data_offset_after_header() {
assert_eq!(
DATA_OFFSET,
std::mem::size_of::<HeapHeader>(),
"DATA_OFFSET must equal sizeof(HeapHeader)"
);
}
#[test]
fn test_heap_kind_roundtrip() {
assert_eq!(HeapKind::from_u16(0), Some(HeapKind::String));
assert_eq!(HeapKind::from_u16(1), Some(HeapKind::TypedObject));
assert_eq!(HeapKind::from_u16(2), Some(HeapKind::Closure));
assert_eq!(
HeapKind::from_u16(HeapKind::DataTable as u16),
Some(HeapKind::DataTable)
);
assert_eq!(
HeapKind::from_u16(HeapKind::TypedArray as u16),
Some(HeapKind::TypedArray)
);
assert_eq!(
HeapKind::from_u16(HeapKind::Char as u16),
Some(HeapKind::Char)
);
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::TypedArray as u8),
Some(HeapKind::TypedArray)
);
assert_eq!(
HeapKind::from_u8(HeapKind::Char as u8),
Some(HeapKind::Char)
);
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::TypedObject as u16);
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_heap_kind_accessor() {
let h = HeapHeader::new(HeapKind::Closure as u16);
assert_eq!(h.heap_kind(), Some(HeapKind::Closure));
let h2 = HeapHeader::new(0xFFFF);
assert_eq!(h2.heap_kind(), None);
}
#[test]
fn test_debug_impl() {
let h = HeapHeader::new(HeapKind::String as u16);
let dbg = format!("{:?}", h);
assert!(dbg.contains("HeapHeader"));
assert!(dbg.contains("refcount"));
assert!(dbg.contains("kind"));
}
}