use std::cell::Cell;
use std::num::NonZeroU32;
use std::ptr::NonNull;
use std::sync::atomic::{AtomicU32, Ordering};
use crate::descriptor::TypeDescriptor;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct HeapId(NonZeroU32);
impl HeapId {
pub(crate) fn mint() -> HeapId {
static NEXT: AtomicU32 = AtomicU32::new(1);
let raw = NEXT.fetch_add(1, Ordering::Relaxed);
HeapId(NonZeroU32::new(raw).expect("HeapId space exhausted"))
}
#[inline]
pub const fn get(self) -> u32 {
self.0.get()
}
}
#[repr(C)]
pub struct GcHeader {
descriptor: Cell<*const TypeDescriptor>,
payload_offset: u16,
heap_id: Cell<u32>,
}
impl GcHeader {
pub const DESCRIPTOR_OFFSET: usize = core::mem::offset_of!(GcHeader, descriptor);
pub const PAYLOAD_OFFSET_FIELD_OFFSET: usize = core::mem::offset_of!(GcHeader, payload_offset);
pub const HEAP_ID_OFFSET: usize = core::mem::offset_of!(GcHeader, heap_id);
#[inline]
pub const fn payload_offset_for(payload_align: usize) -> usize {
assert!(
payload_align.is_power_of_two(),
"payload alignment must be a power of two"
);
round_up(std::mem::size_of::<GcHeader>(), payload_align)
}
#[inline]
pub(crate) fn new(
descriptor: &'static TypeDescriptor,
payload_offset: u16,
heap_id: HeapId,
) -> GcHeader {
GcHeader {
descriptor: Cell::new(descriptor as *const TypeDescriptor),
payload_offset,
heap_id: Cell::new(heap_id.get()),
}
}
#[inline]
pub fn descriptor(&self) -> &'static TypeDescriptor {
let ptr = self.descriptor.get();
assert!(
!ptr.is_null(),
"descriptor read from a poisoned (swept) GcHeader"
);
unsafe { &*ptr }
}
#[inline]
pub fn payload<T>(&self) -> *mut T {
let header_ptr = self as *const GcHeader as *mut u8;
unsafe { header_ptr.add(self.payload_offset as usize) as *mut T }
}
#[inline]
pub fn heap_id(&self) -> Option<HeapId> {
NonZeroU32::new(self.heap_id.get()).map(HeapId)
}
#[inline]
pub fn is_poisoned(&self) -> bool {
self.descriptor.get().is_null()
}
#[inline]
pub(crate) fn poison(&self) {
self.descriptor.set(std::ptr::null());
self.heap_id.set(0);
}
#[cfg(test)]
pub(crate) fn detached() -> GcHeader {
GcHeader {
descriptor: Cell::new(std::ptr::null()),
payload_offset: std::mem::size_of::<GcHeader>() as u16,
heap_id: Cell::new(0),
}
}
}
pub(crate) const fn round_up(n: usize, align: usize) -> usize {
debug_assert!(align.is_power_of_two());
(n + align - 1) & !(align - 1)
}
#[repr(transparent)]
pub struct GcRef(NonNull<GcHeader>);
impl PartialEq for GcRef {
#[inline]
fn eq(&self, other: &GcRef) -> bool {
self.as_ptr() == other.as_ptr()
}
}
impl Eq for GcRef {}
impl std::hash::Hash for GcRef {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_ptr().hash(state);
}
}
impl GcRef {
#[inline]
pub unsafe fn from_non_null(ptr: NonNull<GcHeader>) -> GcRef {
GcRef(ptr)
}
#[inline]
pub unsafe fn from_raw(ptr: *mut GcHeader) -> GcRef {
let nn = unsafe { NonNull::new_unchecked(ptr) };
GcRef(nn)
}
#[inline]
pub fn as_ptr(self) -> *mut GcHeader {
self.0.as_ptr()
}
#[inline]
pub fn as_non_null(self) -> NonNull<GcHeader> {
self.0
}
#[inline]
pub fn header(&self) -> &GcHeader {
unsafe { self.0.as_ref() }
}
#[inline]
pub fn descriptor(&self) -> &'static TypeDescriptor {
self.header().descriptor()
}
#[inline]
pub fn payload<T>(&self) -> *mut T {
self.header().payload::<T>()
}
}
impl Clone for GcRef {
#[inline]
fn clone(&self) -> GcRef {
*self
}
}
impl Copy for GcRef {}
impl std::fmt::Debug for GcRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "GcRef({:p})", self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gcref_is_pointer_sized() {
assert_eq!(
std::mem::size_of::<GcRef>(),
std::mem::size_of::<*mut u8>(),
"GcRef must be exactly one pointer"
);
assert_eq!(
std::mem::align_of::<GcRef>(),
std::mem::align_of::<*mut u8>()
);
}
#[test]
fn gcref_round_trips_a_real_header() {
let mut header = GcHeader::detached();
let nn = NonNull::from(&mut header);
let r = unsafe { GcRef::from_non_null(nn) };
assert_eq!(r.as_ptr(), nn.as_ptr());
assert_eq!(r.as_non_null(), nn);
}
#[test]
fn round_up_is_correct() {
assert_eq!(round_up(0, 8), 0);
assert_eq!(round_up(1, 8), 8);
assert_eq!(round_up(8, 8), 8);
assert_eq!(round_up(9, 8), 16);
assert_eq!(round_up(16, 1), 16);
}
#[test]
fn header_layout_is_fixed() {
assert_eq!(std::mem::size_of::<GcHeader>(), 16);
assert_eq!(std::mem::align_of::<GcHeader>(), 8);
}
#[test]
fn the_header_is_descriptor_offset_and_heap_id_and_nothing_else() {
assert_eq!(std::mem::size_of::<GcHeader>(), 16);
assert_eq!(std::mem::align_of::<GcHeader>(), 8);
assert_eq!(GcHeader::DESCRIPTOR_OFFSET, 0);
assert_eq!(GcHeader::payload_offset_for(8), 16);
}
#[test]
fn the_folded_payload_offset_moved_at_v19_and_is_pinned_here() {
assert_eq!(std::mem::size_of::<GcHeader>(), 16);
assert_eq!(
GcHeader::payload_offset_for(std::mem::align_of::<GcHeader>()),
16
);
assert_eq!(
GcHeader::payload_offset_for(std::mem::align_of::<crate::enums::EnumPayload>()),
16,
"the offset lower.rs:Inst::EnumTag folds into an immediate"
);
assert_eq!(
crate::abi::RUNTIME_ABI_VERSION,
20,
"the offset above last moved at v19 and is 16 at this version; a \
bump must re-confirm it here rather than orphan this test"
);
}
#[test]
fn the_ladder_floor_follows_the_header() {
assert_eq!(crate::page::MIN_BLOCK, std::mem::size_of::<GcHeader>());
assert_eq!(crate::page::BLOCK_GRANULE, std::mem::align_of::<GcHeader>());
}
#[test]
fn payload_offset_pads_only_for_overaligned_payloads() {
let header = std::mem::size_of::<GcHeader>();
for align in [1_usize, 2, 4, 8, 16] {
assert_eq!(GcHeader::payload_offset_for(align), header);
}
assert_eq!(GcHeader::payload_offset_for(64), 64);
}
#[test]
fn the_descriptor_is_at_the_offset_generated_code_reads() {
assert_eq!(GcHeader::DESCRIPTOR_OFFSET, 0);
let header = GcHeader::new(
&crate::scalars::INT,
GcHeader::payload_offset_for(8) as u16,
HeapId::mint(),
);
let base = &header as *const GcHeader as *const u8;
let read_back = unsafe {
base.add(GcHeader::DESCRIPTOR_OFFSET)
.cast::<*const TypeDescriptor>()
.read()
};
assert!(
std::ptr::eq(read_back, &crate::scalars::INT),
"the word at DESCRIPTOR_OFFSET is the descriptor the header was built with"
);
}
#[test]
fn payload_offset_is_recorded_in_the_header() {
let header = GcHeader::new(
&crate::scalars::INT,
GcHeader::payload_offset_for(8) as u16,
HeapId::mint(),
);
let base = &header as *const GcHeader as usize;
assert_eq!(
header.payload::<i64>() as usize - base,
GcHeader::payload_offset_for(8)
);
}
#[test]
fn a_poisoned_header_has_no_heap_and_reports_itself() {
let header = GcHeader::new(
&crate::scalars::INT,
GcHeader::payload_offset_for(8) as u16,
HeapId::mint(),
);
assert!(!header.is_poisoned());
assert!(header.heap_id().is_some());
header.poison();
assert!(header.is_poisoned());
assert_eq!(header.heap_id(), None);
}
#[test]
fn minted_heap_ids_are_distinct_and_non_zero() {
let a = HeapId::mint();
let b = HeapId::mint();
assert_ne!(a, b);
assert_ne!(a.get(), 0);
}
}