use crate::local_alloc::ThreadAllocator;
use crate::local_alloc::page::{push_page_front, unlink_page_from_list, with_page_list_token};
use core::ptr::NonNull;
use mnemosyne_arena::{HasSegmentPool, deallocate_segment};
use mnemosyne_core::constants::NUM_SIZE_CLASSES;
use mnemosyne_core::policy::AllocPolicy;
use mnemosyne_core::types::{Page, Segment, SegmentOwner};
const MIN_RETAINED_OWNED_SEGMENTS: usize = 3;
const RECLAIM_THRESHOLD_SEGMENTS: usize = MIN_RETAINED_OWNED_SEGMENTS + 1;
impl<B: HasSegmentPool> ThreadAllocator<B> {
pub fn reclaim_owned_segments(&mut self) {
unsafe { self.set_current_segment(None) };
let mut curr = self.owned_segments_head;
while !curr.is_null() {
unsafe {
let next = (*curr).next_owned_segment;
let dynamic_encrypted = (*curr).free_list_encrypted;
let mut total_allocations = 0;
let mut mask = (*curr).page_occupied_mask;
while mask != 0 {
let i = mask.trailing_zeros() as usize;
mask &= mask - 1;
if i == 0 {
continue;
}
let page = &mut (*curr).pages[i];
let reclaimed =
page.reclaim_thread_free_if_present_for_segment(dynamic_encrypted, curr, i);
if reclaimed > 0 {
self.record_cross_thread_reclaimed(reclaimed);
}
total_allocations += page.alloc_count;
}
(*curr).owner = SegmentOwner::NONE;
(*curr).owner_allocator = core::ptr::null_mut();
(*curr).is_current = false;
(*curr).next_owned_segment = core::ptr::null_mut();
(*curr).prev_owned_segment = core::ptr::null_mut();
if total_allocations == 0 {
deallocate_segment::<B>(curr);
} else {
B::global_orphan_pool().push_unbounded(curr);
}
curr = next;
}
}
self.next_page_index = 0;
self.owned_segments_head = core::ptr::null_mut();
self.owned_segment_count = 0;
self.active_pages = [None; NUM_SIZE_CLASSES];
self.full_pages = [None; NUM_SIZE_CLASSES];
self.empty_pages = None;
}
pub unsafe fn try_reclaim_segment(&mut self, segment: *mut Segment) -> bool {
if self
.current_segment
.is_some_and(|current| current.as_ptr() == segment)
{
return false;
}
if self.owned_segment_count < RECLAIM_THRESHOLD_SEGMENTS {
return false;
}
unsafe {
let dynamic_encrypted = (*segment).free_list_encrypted;
let mut mask = (*segment).page_occupied_mask;
while mask != 0 {
let i = mask.trailing_zeros() as usize;
mask &= mask - 1;
if i == 0 {
continue;
}
let pg = &mut (*segment).pages[i];
if pg.alloc_count > 0 {
let reclaimed = pg.reclaim_thread_free_if_present_for_segment(
dynamic_encrypted,
segment,
i,
);
if reclaimed > 0 {
self.record_cross_thread_reclaimed(reclaimed);
}
if pg.alloc_count > 0 {
return false;
}
}
}
}
unsafe {
unlink_segment_pages(self, segment);
self.unlink_owned_segment(segment);
}
unsafe { detach_and_release_segment::<B>(segment) };
true
}
pub unsafe fn periodic_defragmentation_sweep<P: AllocPolicy>(&mut self) {
let mut curr = self.owned_segments_head;
while !curr.is_null() {
let segment = curr;
curr = unsafe { (*segment).next_owned_segment };
if self.is_current_segment(segment) {
continue;
}
let dynamic_encrypted = unsafe { (*segment).free_list_encrypted };
let mut total_allocations = 0;
if unsafe { (*segment).page_occupied_mask != 0 } {
unsafe {
with_page_list_token::<B, _>(|mut token| {
let mut mask = (*segment).page_occupied_mask;
while mask != 0 {
let i = mask.trailing_zeros() as usize;
mask &= mask - 1;
if i == 0 {
continue;
}
let pg = &mut (*segment).pages[i];
let reclaimed = pg.reclaim_thread_free_if_present_for_segment(
dynamic_encrypted,
segment,
i,
);
if reclaimed > 0 {
self.record_cross_thread_reclaimed(reclaimed);
}
total_allocations += pg.alloc_count;
if pg.alloc_count == 0 && (pg.list_state == 1 || pg.list_state == 2) {
let class = pg.size_class as usize;
let is_only_active =
crate::free::is_sole_active_page(self.active_pages[class], pg);
if !is_only_active {
let pg_ptr = NonNull::new_unchecked(pg as *mut Page);
let branded_page = token.page(pg_ptr);
if pg.list_state == 1 {
unlink_page_from_list(
&mut token,
self.active_pages.get_unchecked_mut(class),
branded_page,
);
} else {
unlink_page_from_list(
&mut token,
self.full_pages.get_unchecked_mut(class),
branded_page,
);
}
push_page_front(
&mut token,
&mut self.empty_pages,
branded_page,
3,
);
}
}
}
});
}
}
if total_allocations == 0 && self.owned_segment_count >= RECLAIM_THRESHOLD_SEGMENTS {
unsafe {
unlink_segment_pages(self, segment);
self.unlink_owned_segment(segment);
detach_and_release_segment::<B>(segment);
}
}
}
}
}
unsafe fn unlink_segment_pages<B: HasSegmentPool>(
alloc: &mut ThreadAllocator<B>,
segment: *mut Segment,
) {
let mut mask = unsafe { (*segment).page_linked_mask };
while mask != 0 {
let i = mask.trailing_zeros() as usize;
mask &= mask - 1;
let pg = unsafe { &mut (*segment).pages[i] };
let state = pg.list_state;
if state == 1 || state == 2 {
let class = pg.size_class as usize;
unsafe { alloc.unlink_page(pg as *mut Page, class) };
} else if state == 3 {
unsafe { alloc.unlink_empty_page(pg as *mut Page) };
}
}
}
#[inline]
unsafe fn detach_and_release_segment<B: HasSegmentPool>(segment: *mut Segment) {
unsafe {
(*segment).owner = SegmentOwner::NONE;
(*segment).owner_allocator = core::ptr::null_mut();
deallocate_segment::<B>(segment);
}
}