use core::mem::size_of;
use super::node::Node;
use super::os::PAGE;
use super::size_classes::SMALL_CLASS_COUNT;
pub(crate) const SEGMENT_MAGIC: u32 = 0x5E_F5_E0_01;
#[cfg_attr(not(feature = "alloc-global"), allow(dead_code))]
pub(crate) const OWNER_STATE_LIVE: u64 = 0;
#[cfg_attr(not(feature = "alloc-global"), allow(dead_code))]
pub(crate) const OWNER_STATE_ABANDONED: u64 = 1;
const OWNER_STATE_MASK: u64 = 0x1;
const OWNER_ID_SHIFT: u32 = 1;
const OWNER_ID_MASK: u64 = ((1u64 << 31) - 1) << OWNER_ID_SHIFT;
const OWNER_GEN_SHIFT: u32 = 32;
#[cfg_attr(not(feature = "alloc-global"), allow(dead_code))]
const OWNER_GEN_MASK: u64 = (u32::MAX as u64) << OWNER_GEN_SHIFT;
pub(crate) const OWNER_ID_NONE: u32 = 0x7FFF_FFFF;
#[cfg_attr(not(feature = "alloc-global"), allow(dead_code))]
#[inline(always)]
pub(crate) const fn pack_owner(state: u64, owner_id: u32, generation: u32) -> u64 {
(state & OWNER_STATE_MASK)
| ((owner_id as u64) << OWNER_ID_SHIFT)
| ((generation as u64) << OWNER_GEN_SHIFT)
}
#[cfg_attr(not(feature = "alloc-global"), allow(dead_code))]
#[inline(always)]
pub(crate) const fn unpack_owner_state(word: u64) -> u64 {
word & OWNER_STATE_MASK
}
#[cfg_attr(not(feature = "alloc-global"), allow(dead_code))]
#[inline(always)]
pub(crate) const fn unpack_owner_id(word: u64) -> u32 {
((word & OWNER_ID_MASK) >> OWNER_ID_SHIFT) as u32
}
#[cfg_attr(not(feature = "alloc-global"), allow(dead_code))]
pub(crate) const fn unpack_owner_gen(word: u64) -> u32 {
((word & OWNER_GEN_MASK) >> OWNER_GEN_SHIFT) as u32
}
pub(crate) const PAGES_PER_SEGMENT: usize = super::os::SEGMENT / PAGE;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub(crate) enum SegmentKind {
Primordial = 0,
Small = 1,
Large = 2,
}
pub(crate) enum PageClass {
Free = 0xFF,
Meta = 0xFE,
}
impl PageClass {
pub(crate) const fn encode_class(c: usize) -> u8 {
debug_assert!(c < SMALL_CLASS_COUNT, "class_idx out of range");
c as u8
}
pub(crate) fn decode(b: u8) -> Option<usize> {
match b {
0xFF | 0xFE => None,
c => {
debug_assert!((c as usize) < SMALL_CLASS_COUNT, "corrupt page map entry");
Some(c as usize)
}
}
}
}
#[repr(C)]
#[derive(Clone, Copy)]
pub(crate) struct SegmentHeader {
pub magic: u32,
pub kind: SegmentKind,
pub segment_id: u32,
pub bump: usize,
pub large_size: usize,
pub large_align: usize,
pub reservation: *mut u8,
pub reservation_len: usize,
pub owner_thread_free: *const core::sync::atomic::AtomicPtr<u8>,
pub owner_state: u64,
pub next_abandoned: u64,
pub live_count: u32,
pub decommitted: u32,
#[cfg_attr(not(feature = "numa-aware"), allow(dead_code))]
pub node_id: u32,
}
#[cfg_attr(not(feature = "alloc-global"), allow(dead_code))]
pub(crate) const ABANDONED_TAIL: u64 = u64::MAX;
pub(crate) const NO_NODE_RAW: u32 = u32::MAX;
impl SegmentHeader {
pub(crate) const fn small(
segment_id: u32,
bump: usize,
reservation: *mut u8,
reservation_len: usize,
) -> Self {
Self {
magic: SEGMENT_MAGIC,
kind: SegmentKind::Small,
segment_id,
bump,
large_size: 0,
large_align: 0,
reservation,
reservation_len,
owner_thread_free: core::ptr::null(),
owner_state: pack_owner(OWNER_STATE_LIVE, OWNER_ID_NONE, 0),
next_abandoned: ABANDONED_TAIL,
live_count: 0,
decommitted: 0,
node_id: NO_NODE_RAW,
}
}
pub(crate) const fn large(
segment_id: u32,
size: usize,
align: usize,
bump: usize,
reservation: *mut u8,
reservation_len: usize,
) -> Self {
Self {
magic: SEGMENT_MAGIC,
kind: SegmentKind::Large,
segment_id,
bump,
large_size: size,
large_align: align,
reservation,
reservation_len,
owner_thread_free: core::ptr::null(),
owner_state: pack_owner(OWNER_STATE_LIVE, OWNER_ID_NONE, 0),
next_abandoned: ABANDONED_TAIL,
live_count: 0,
decommitted: 0,
node_id: NO_NODE_RAW,
}
}
pub(crate) fn read_at(base: *mut u8) -> Self {
Node::read_struct::<SegmentHeader>(base as *const SegmentHeader)
}
#[allow(dead_code)] #[inline(always)]
pub(crate) fn kind_at(base: *mut u8) -> SegmentKind {
let off = core::mem::offset_of!(SegmentHeader, kind);
let b = Node::read_u8(Node::offset(base, off) as *const u8);
match b {
0 => SegmentKind::Primordial,
2 => SegmentKind::Large,
_ => SegmentKind::Small,
}
}
#[cfg(feature = "alloc-xthread")]
#[cfg_attr(not(feature = "alloc-global"), allow(dead_code))]
#[inline(always)]
pub(crate) fn magic_at(base: *mut u8) -> u32 {
let off = core::mem::offset_of!(SegmentHeader, magic);
Node::read_u32(Node::offset(base, off) as *const u32)
}
#[cfg(feature = "alloc-xthread")]
#[cfg_attr(not(feature = "alloc-global"), allow(dead_code))]
#[inline(always)]
pub(crate) fn owner_thread_free_at(base: *mut u8) -> *const core::sync::atomic::AtomicPtr<u8> {
let off = core::mem::offset_of!(SegmentHeader, owner_thread_free);
Node::read_ptr(Node::offset(base, off) as *const *const core::sync::atomic::AtomicPtr<u8>)
}
}
pub(crate) fn align_up(n: usize, a: usize) -> usize {
debug_assert!(a > 0, "align must be non-zero");
let q = n.div_ceil(a);
q * a
}
pub(crate) struct PageMap {
entries: *mut u8,
}
impl PageMap {
pub(crate) const FOOTPRINT: usize = PAGES_PER_SEGMENT * size_of::<u8>();
pub(crate) fn new(entries: *mut u8) -> Self {
Self { entries }
}
pub(crate) fn init_in_place(entries: *mut u8, meta_pages: usize) {
for p in 0..PAGES_PER_SEGMENT {
let byte = if p < meta_pages {
PageClass::Meta as u8
} else {
PageClass::Free as u8
};
Node::write_u8(Node::offset(entries, p), byte);
}
}
pub(crate) fn class_of(&self, p: usize) -> Option<usize> {
debug_assert!(p < PAGES_PER_SEGMENT, "page index out of range");
let byte = Node::read_u8(self.entries_at_const(p));
PageClass::decode(byte)
}
pub(crate) fn set_class(&mut self, p: usize, class_idx: usize) {
debug_assert!(p < PAGES_PER_SEGMENT, "page index out of range");
Node::write_u8(self.entries_at_const(p), PageClass::encode_class(class_idx));
}
#[cfg(feature = "alloc-decommit")]
pub(crate) fn set_free(&mut self, p: usize) {
debug_assert!(p < PAGES_PER_SEGMENT, "page index out of range");
Node::write_u8(self.entries_at_const(p), PageClass::Free as u8);
}
fn entries_at_const(&self, p: usize) -> *mut u8 {
Node::offset(self.entries, p)
}
}
pub(crate) struct BinTable {
heads: *mut u32,
}
pub(crate) const FREE_LIST_NULL: u32 = u32::MAX;
impl BinTable {
pub(crate) const FOOTPRINT: usize = SMALL_CLASS_COUNT * size_of::<u32>();
#[inline(always)]
pub(crate) fn new(heads: *mut u32) -> Self {
Self { heads }
}
pub(crate) fn init_in_place(heads: *mut u32) {
for c in 0..SMALL_CLASS_COUNT {
Node::write_u32_unaligned(
Node::offset(heads as *mut u8, c * size_of::<u32>()) as *mut u32,
FREE_LIST_NULL,
);
}
}
#[inline(always)]
pub(crate) fn head(&self, c: usize) -> u32 {
debug_assert!(c < SMALL_CLASS_COUNT, "class index out of range");
Node::read_u32_unaligned(self.heads_at_const(c))
}
#[inline(always)]
pub(crate) fn set_head(&mut self, c: usize, off: u32) {
debug_assert!(c < SMALL_CLASS_COUNT, "class index out of range");
Node::write_u32_unaligned(self.heads_at_const(c), off);
}
#[inline(always)]
fn heads_at_const(&self, c: usize) -> *mut u32 {
Node::offset(self.heads as *mut u8, c * size_of::<u32>()) as *mut u32
}
}
#[allow(dead_code)] pub(crate) const SMALL_META_FOOTPRINT: usize = Layout::small_meta_end();
pub(crate) struct Layout;
impl Layout {
pub(crate) const fn page_map_off() -> usize {
align_up_const(size_of::<SegmentHeader>(), PAGE)
}
pub(crate) const fn bin_table_off() -> usize {
Self::page_map_off() + PageMap::FOOTPRINT
}
pub(crate) const fn alloc_bitmap_off() -> usize {
align_up_const(Self::bin_table_off() + BinTable::FOOTPRINT * 2, 8)
}
#[cfg_attr(not(feature = "alloc-xthread"), allow(dead_code))]
pub(crate) const fn remote_ring_off() -> usize {
align_up_const(
Self::alloc_bitmap_off() + super::alloc_bitmap::AllocBitmap::FOOTPRINT,
4,
)
}
pub(crate) const fn small_meta_end() -> usize {
align_up_const(
Self::remote_ring_off() + super::remote_free_ring::FOOTPRINT,
PAGE,
)
}
pub(crate) const fn primordial_registry_off() -> usize {
align_up_const(
Self::remote_ring_off() + super::remote_free_ring::FOOTPRINT,
PAGE,
)
}
pub(crate) const fn primordial_hash_off() -> usize {
align_up_const(
Self::primordial_registry_off() + super::segment_table::REGISTRY_FOOTPRINT,
8,
)
}
pub(crate) const fn primordial_meta_end() -> usize {
align_up_const(
Self::primordial_hash_off() + super::segment_table::HASH_FOOTPRINT,
PAGE,
)
}
pub(crate) const fn small_meta_pages() -> usize {
Self::small_meta_end() / PAGE
}
pub(crate) const fn primordial_meta_pages() -> usize {
Self::primordial_meta_end() / PAGE
}
}
pub(crate) struct SegmentMeta {
pub base: *mut u8,
}
impl SegmentMeta {
#[inline(always)]
pub(crate) fn new(base: *mut u8) -> Self {
Self { base }
}
pub(crate) fn header(&self) -> SegmentHeader {
SegmentHeader::read_at(self.base)
}
pub(crate) fn write_header(&mut self, hdr: SegmentHeader) {
Node::write_struct(self.base as *mut SegmentHeader, hdr);
}
#[inline(always)]
pub(crate) fn bump_of(&self) -> usize {
let off = core::mem::offset_of!(SegmentHeader, bump);
Node::read_usize(Node::offset(self.base, off) as *const usize)
}
#[inline(always)]
pub(crate) fn set_bump(&mut self, value: usize) {
let off = core::mem::offset_of!(SegmentHeader, bump);
Node::write_usize(Node::offset(self.base, off) as *mut usize, value);
}
#[cfg(feature = "alloc-decommit")]
#[inline(always)]
pub(crate) fn live_count_of(&self) -> u32 {
let off = core::mem::offset_of!(SegmentHeader, live_count);
Node::read_u32(Node::offset(self.base, off) as *const u32)
}
#[cfg(feature = "alloc-decommit")]
#[inline(always)]
fn set_live_count(&mut self, value: u32) {
let off = core::mem::offset_of!(SegmentHeader, live_count);
Node::write_u32(Node::offset(self.base, off) as *mut u32, value);
}
#[cfg(feature = "alloc-decommit")]
#[inline(always)]
pub(crate) fn inc_live(&mut self) {
let v = self.live_count_of();
self.set_live_count(v.saturating_add(1));
}
#[cfg(feature = "alloc-decommit")]
#[inline(always)]
pub(crate) fn dec_live(&mut self) -> u32 {
let v = self.live_count_of();
let new = v.saturating_sub(1);
self.set_live_count(new);
new
}
#[cfg(feature = "alloc-decommit")]
#[inline(always)]
pub(crate) fn is_decommitted(&self) -> bool {
let off = core::mem::offset_of!(SegmentHeader, decommitted);
Node::read_u32(Node::offset(self.base, off) as *const u32) != 0
}
#[cfg(feature = "alloc-decommit")]
#[inline(always)]
pub(crate) fn set_decommitted(&mut self, value: bool) {
let off = core::mem::offset_of!(SegmentHeader, decommitted);
Node::write_u32(Node::offset(self.base, off) as *mut u32, u32::from(value));
}
#[cfg(feature = "numa-aware")]
pub(crate) fn node_id_of(&self) -> u32 {
let off = core::mem::offset_of!(SegmentHeader, node_id);
Node::read_u32(Node::offset(self.base, off) as *const u32)
}
#[cfg(feature = "numa-aware")]
pub(crate) fn set_node_id(&mut self, node: u32) {
let off = core::mem::offset_of!(SegmentHeader, node_id);
Node::write_u32(Node::offset(self.base, off) as *mut u32, node);
}
#[cfg(feature = "alloc-xthread")]
#[cfg_attr(not(feature = "alloc-global"), allow(dead_code))]
pub(crate) fn stamp_owner_thread_free(
&mut self,
head: *const core::sync::atomic::AtomicPtr<u8>,
) {
let off = core::mem::offset_of!(SegmentHeader, owner_thread_free);
Node::write_ptr(
Node::offset(self.base, off) as *mut *const core::sync::atomic::AtomicPtr<u8>,
head,
);
}
pub(crate) fn page_map(&self) -> PageMap {
PageMap::new(Node::offset(self.base, Layout::page_map_off()))
}
#[inline(always)]
pub(crate) fn bin_table(&self) -> BinTable {
BinTable::new(Node::offset(self.base, Layout::bin_table_off()) as *mut u32)
}
#[inline(always)]
pub(crate) fn alloc_bitmap(&self) -> super::alloc_bitmap::AllocBitmap {
super::alloc_bitmap::AllocBitmap::new(Node::offset(self.base, Layout::alloc_bitmap_off()))
}
#[cfg(feature = "alloc-xthread")]
pub(crate) fn remote_ring(&self) -> super::remote_free_ring::RemoteFreeRing {
super::remote_free_ring::RemoteFreeRing::at(self.base, Layout::remote_ring_off())
}
#[cfg_attr(not(feature = "alloc-global"), allow(dead_code))]
#[inline(always)]
pub(crate) fn owner_state_atomic(&self) -> &'static core::sync::atomic::AtomicU64 {
let off = core::mem::offset_of!(SegmentHeader, owner_state);
Node::atomic_u64_at(self.base, off)
}
#[cfg_attr(not(feature = "alloc-global"), allow(dead_code))]
pub(crate) fn next_abandoned_atomic(&self) -> &'static core::sync::atomic::AtomicU64 {
let off = core::mem::offset_of!(SegmentHeader, next_abandoned);
Node::atomic_u64_at(self.base, off)
}
}
const fn align_up_const(n: usize, a: usize) -> usize {
let mask = a - 1;
(n + mask) & !mask
}
const _: () = assert!(Layout::primordial_meta_end() + PAGE <= super::os::SEGMENT);
const _: () = assert!(Layout::small_meta_end() + PAGE <= super::os::SEGMENT);
const _: () = assert!(super::size_classes::MIN_BLOCK >= super::node::NODE_SIZE);
const _: () = assert!(size_of::<SegmentHeader>() <= PAGE);
const _: () = assert!(Layout::page_map_off() == PAGE);
const _: () = assert!(NO_NODE_RAW == u32::MAX);