#![allow(unsafe_code)]
use core::sync::atomic::Ordering;
use super::bootstrap::{
abandoned_head_is_empty, ensure, pack_abandoned_head, unpack_abandoned_head, Registry,
ABANDONED_HEAD_EMPTY, MAX_HEAPS,
};
use super::heap_core::HeapCore;
use super::heap_slot::{HeapSlot, NEXT_FREE_TAIL, STATE_FREE, STATE_LIVE};
use super::tagged_ptr::TaggedPtr;
use crate::alloc_core::segment_header::{
pack_owner, unpack_owner_gen, unpack_owner_id, unpack_owner_state, SegmentMeta, ABANDONED_TAIL,
OWNER_ID_NONE, OWNER_STATE_ABANDONED,
};
#[doc(hidden)]
pub struct HeapRegistry;
impl HeapRegistry {
#[must_use]
pub fn claim() -> *mut HeapCore {
let idx = match Self::pick_slot() {
Some(i) => i,
None => return core::ptr::null_mut(),
};
let reg = ensure();
let slot = unsafe { reg.slots.get_unchecked(idx) };
if slot.cas_state(STATE_FREE, STATE_LIVE, Ordering::AcqRel, Ordering::Acquire)
== Err(STATE_LIVE)
{
return Self::claim(); }
let new_gen = slot.generation.fetch_add(1, Ordering::Release) + 1;
if new_gen == 1 {
let heap_ptr = slot.heap.get();
match HeapCore::new(idx as u32) {
Some(hc) => unsafe { heap_ptr.cast::<HeapCore>().write(hc) },
None => {
let _ = slot.cas_state(
STATE_LIVE,
STATE_FREE,
Ordering::Release,
Ordering::Relaxed,
);
return core::ptr::null_mut();
}
}
}
slot.heap.get().cast::<HeapCore>()
}
#[cfg(feature = "alloc-decommit")]
#[must_use]
pub fn claim_with_config(config: crate::alloc_core::LargeCacheConfig) -> *mut HeapCore {
let idx = match Self::pick_slot() {
Some(i) => i,
None => return core::ptr::null_mut(),
};
let reg = ensure();
let slot = unsafe { reg.slots.get_unchecked(idx) };
if slot.cas_state(STATE_FREE, STATE_LIVE, Ordering::AcqRel, Ordering::Acquire)
== Err(STATE_LIVE)
{
return Self::claim_with_config(config); }
let new_gen = slot.generation.fetch_add(1, Ordering::Release) + 1;
if new_gen == 1 {
let heap_ptr = slot.heap.get();
match HeapCore::new_with_config(idx as u32, config) {
Some(hc) => unsafe { heap_ptr.cast::<HeapCore>().write(hc) },
None => {
let _ = slot.cas_state(
STATE_LIVE,
STATE_FREE,
Ordering::Release,
Ordering::Relaxed,
);
return core::ptr::null_mut();
}
}
}
slot.heap.get().cast::<HeapCore>()
}
fn pick_slot() -> Option<usize> {
let reg = ensure();
pop_free_slot(reg).or_else(|| bump_count(reg))
}
pub unsafe fn recycle(heap: *mut HeapCore) {
if heap.is_null() {
return;
}
let reg = ensure();
let idx = unsafe { (*heap).id() } as usize;
if idx >= MAX_HEAPS {
return;
}
let slot = unsafe { reg.slots.get_unchecked(idx) };
if slot.cas_state(STATE_LIVE, STATE_FREE, Ordering::Release, Ordering::Relaxed)
== Err(STATE_FREE)
{
return;
}
push_free_slot(reg, idx as u32);
}
pub unsafe fn abandon_segments(heap: *mut HeapCore) {
if heap.is_null() {
return;
}
let reg = ensure();
let heap_ref: &mut HeapCore = unsafe { &mut *heap };
let owner_id = heap_ref.id();
for base in heap_ref.segment_bases() {
abandon_one_segment(reg, base, owner_id);
}
}
pub fn push_abandoned_segment(base: *mut u8) {
let reg = ensure();
push_abandoned_segment_into(reg, base);
}
#[must_use]
pub fn pop_abandoned_segment() -> Option<*mut u8> {
let reg = ensure();
let mut head = reg.abandoned_segs.load(Ordering::Acquire);
loop {
if abandoned_head_is_empty(head) {
return None;
}
let (base, tag) = unpack_abandoned_head(head);
let meta = SegmentMeta::new(base);
let next_link = meta.next_abandoned_atomic().load(Ordering::Acquire);
let new_head = if next_link == ABANDONED_TAIL {
ABANDONED_HEAD_EMPTY
} else {
let next_base = next_link as *mut u8;
pack_abandoned_head(next_base, tag)
};
match reg.abandoned_segs.compare_exchange(
head,
new_head,
Ordering::Acquire,
Ordering::Relaxed,
) {
Ok(_) => return Some(base),
Err(actual) => head = actual, }
}
}
pub unsafe fn try_adopt(adopter: *mut HeapCore) -> bool {
if adopter.is_null() {
return false;
}
loop {
let Some(base) = Self::pop_abandoned_segment() else {
return false; };
let meta = SegmentMeta::new(base);
let owner_atomic = meta.owner_state_atomic();
let cur = owner_atomic.load(Ordering::Acquire);
if unpack_owner_state(cur) != OWNER_STATE_ABANDONED {
continue;
}
let adopter_ref: &mut HeapCore = unsafe { &mut *adopter };
let new_gen = unpack_owner_gen(cur).wrapping_add(1);
let new_word = pack_owner(
crate::alloc_core::segment_header::OWNER_STATE_LIVE,
adopter_ref.id(),
new_gen,
);
match owner_atomic.compare_exchange(cur, new_word, Ordering::AcqRel, Ordering::Relaxed)
{
Ok(_) => {
let _ = adopter_ref.register_segment_internal(base);
adopter_ref.set_small_current_internal(base);
return true;
}
Err(_) => {
continue;
}
}
}
}
}
fn pop_free_slot(reg: &Registry) -> Option<usize> {
let mut head = reg.free_slots.load(Ordering::Acquire);
loop {
if TaggedPtr::is_empty(head) {
return None;
}
let (idx_v, _tag) = TaggedPtr::unpack(head);
let idx = idx_v as u32;
if idx as usize >= MAX_HEAPS {
return None;
}
let slot: &HeapSlot = unsafe { reg.slots.get_unchecked(idx as usize) };
let next = slot.next_free.load(Ordering::Acquire);
let new_head = if next == NEXT_FREE_TAIL {
TaggedPtr::empty()
} else {
TaggedPtr::pack(next as u64, _tag)
};
match reg
.free_slots
.compare_exchange(head, new_head, Ordering::Acquire, Ordering::Relaxed)
{
Ok(_) => return Some(idx as usize),
Err(actual) => head = actual, }
}
}
fn push_free_slot(reg: &Registry, idx: u32) {
let slot: &HeapSlot = unsafe { reg.slots.get_unchecked(idx as usize) };
let mut head = reg.free_slots.load(Ordering::Acquire);
loop {
let next_link = if TaggedPtr::is_empty(head) {
NEXT_FREE_TAIL
} else {
let (cur_idx, _tag) = TaggedPtr::unpack(head);
cur_idx as u32
};
slot.next_free.store(next_link, Ordering::Release);
let (_cur_idx, tag) = TaggedPtr::unpack(head);
let new_tag = tag.wrapping_add(1);
let new_head = TaggedPtr::pack(idx as u64, new_tag);
match reg
.free_slots
.compare_exchange(head, new_head, Ordering::Release, Ordering::Relaxed)
{
Ok(_) => return,
Err(actual) => head = actual,
}
}
}
fn bump_count(reg: &Registry) -> Option<usize> {
let idx = reg.count.fetch_add(1, Ordering::AcqRel);
if idx as usize >= MAX_HEAPS {
reg.count.fetch_sub(1, Ordering::AcqRel);
return None;
}
Some(idx as usize)
}
fn push_abandoned_segment_into(reg: &Registry, base: *mut u8) {
let meta = SegmentMeta::new(base);
let next_atomic = meta.next_abandoned_atomic();
let mut head = reg.abandoned_segs.load(Ordering::Acquire);
loop {
let next_link = if abandoned_head_is_empty(head) {
ABANDONED_TAIL
} else {
let (head_base, _tag) = unpack_abandoned_head(head);
head_base as u64
};
next_atomic.store(next_link, Ordering::Release);
let (_cur_base, tag) = unpack_abandoned_head(head);
let new_tag = tag.wrapping_add(1) & (super::bootstrap::ABANDON_TAG_MASK);
let new_head = pack_abandoned_head(base, new_tag);
match reg.abandoned_segs.compare_exchange(
head,
new_head,
Ordering::Release,
Ordering::Relaxed,
) {
Ok(_) => return,
Err(actual) => head = actual,
}
}
}
fn abandon_one_segment(reg: &Registry, base: *mut u8, owner_id: u32) {
let meta = SegmentMeta::new(base);
let owner_atomic = meta.owner_state_atomic();
loop {
let cur = owner_atomic.load(Ordering::Acquire);
let cur_state = unpack_owner_state(cur);
if cur_state == OWNER_STATE_ABANDONED {
return;
}
let cur_owner = unpack_owner_id(cur);
let cur_gen = unpack_owner_gen(cur);
if cur_owner != owner_id && cur_owner != OWNER_ID_NONE {
return;
}
let new_word = pack_owner(OWNER_STATE_ABANDONED, owner_id, cur_gen);
match owner_atomic.compare_exchange(cur, new_word, Ordering::AcqRel, Ordering::Relaxed) {
Ok(_) => break,
Err(_) => continue, }
}
push_abandoned_segment_into(reg, base);
}