use core::ptr;
use core::sync::atomic::AtomicUsize;
use crate::os;
use crate::page::Page;
use crate::prim::PrimError;
use crate::segment_map;
use crate::types::{SEGMENT_SIZE, SEGMENT_SLICE_SIZE, SLICES_PER_SEGMENT};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum SegmentKind {
Normal = 0xA110C,
Huge = 0x40BE,
}
pub struct Segment {
pub kind: SegmentKind,
pub total_size: usize,
pub next_free_slice: u32,
pub used_pages: u32,
pub thread_id: AtomicUsize,
pub mem_is_zero: bool,
pub purged_any: bool,
pub guarded: bool,
pub next: *mut Segment,
pub free_spans: *mut Page,
pub pages: [Page; SLICES_PER_SEGMENT],
}
const _: () = assert!(
core::mem::size_of::<Segment>() <= SEGMENT_SLICE_SIZE,
"segment header must fit in slice 0"
);
pub const HEADER_SLICES: usize = 1;
pub const USABLE_SLICES: usize = SLICES_PER_SEGMENT - HEADER_SLICES;
#[inline]
pub fn segment_of(p: *mut u8) -> *mut Segment {
p.with_addr(p.addr() & !(SEGMENT_SIZE - 1)).cast()
}
#[inline]
pub unsafe fn page_of(seg: *mut Segment, p: *mut u8) -> *mut Page {
let idx = (p.addr() - seg.addr()) / SEGMENT_SLICE_SIZE;
debug_assert!(
idx < SLICES_PER_SEGMENT,
"page_of: slice index out of range"
);
unsafe {
let base: *mut Page = (&raw mut (*seg).pages).cast();
let slot = base.add(idx);
let off = (*slot).slice_offset as usize;
debug_assert!(
off <= idx * core::mem::size_of::<Page>(),
"page_of: slice_offset points before the segment"
);
slot.cast::<u8>().sub(off).cast::<Page>()
}
}
#[inline]
pub const fn slot_stride() -> usize {
core::mem::size_of::<Page>()
}
const _: () = assert!(
(SLICES_PER_SEGMENT - 1) * core::mem::size_of::<Page>() <= u16::MAX as usize,
"slice_offset (bytes) must fit in u16"
);
#[inline]
pub unsafe fn page_area(seg: *mut Segment, idx: usize) -> *mut u8 {
unsafe { seg.cast::<u8>().add(idx * SEGMENT_SLICE_SIZE) }
}
#[inline]
pub unsafe fn page_index(seg: *mut Segment, page: *mut Page) -> usize {
let base: *mut Page = unsafe { (&raw mut (*seg).pages).cast() };
(page.addr() - base.addr()) / core::mem::size_of::<Page>()
}
unsafe fn wait_no_remote_in_flight(seg: *mut Segment) {
use crate::page::{XFLAG_FREEING, XMASK};
unsafe {
let base: *mut Page = (&raw mut (*seg).pages).cast();
for i in 0..SLICES_PER_SEGMENT {
let pg = base.add(i);
while (*pg)
.xthread_free
.load(core::sync::atomic::Ordering::Acquire)
& XMASK
== XFLAG_FREEING
{
core::hint::spin_loop();
}
}
}
}
pub fn segment_alloc(arena_id: i32) -> Result<*mut Segment, PrimError> {
let (ptr_, size, mem_zero) = match crate::arena::chunk_alloc(arena_id) {
Some((p, zero)) => (p, SEGMENT_SIZE, zero),
None => {
if arena_id >= 0 {
return Err(0); }
let b = os::alloc_aligned(SEGMENT_SIZE, SEGMENT_SIZE, true, false)?;
(b.ptr, b.size, b.is_zero)
}
};
let seg: *mut Segment = ptr_.cast();
unsafe {
if !mem_zero {
core::ptr::write_bytes(seg.cast::<u8>(), 0, core::mem::size_of::<Segment>());
}
(*seg).kind = SegmentKind::Normal;
(*seg).total_size = size;
(*seg).next_free_slice = HEADER_SLICES as u32;
(*seg).used_pages = 0;
(*seg).thread_id = AtomicUsize::new(crate::init::thread_id());
(*seg).mem_is_zero = mem_zero;
(*seg).purged_any = false;
(*seg).guarded = false;
(*seg).next = ptr::null_mut();
(*seg).free_spans = ptr::null_mut();
}
segment_map::register(seg);
Ok(seg)
}
pub unsafe fn segment_free(seg: *mut Segment) -> Result<(), PrimError> {
unsafe { wait_no_remote_in_flight(seg) };
segment_map::unregister(seg);
unsafe {
if (*seg).purged_any || (*seg).guarded {
let base = seg.cast::<u8>().add(HEADER_SLICES * SEGMENT_SLICE_SIZE);
let bytes = (*seg).total_size - HEADER_SLICES * SEGMENT_SLICE_SIZE;
let _ = os::protect(base, bytes, false);
let _ = os::commit(base, bytes);
(*seg).purged_any = false;
(*seg).guarded = false;
}
}
if crate::arena::chunk_free(seg.cast()) {
return Ok(());
}
unsafe {
let block = os::OsBlock {
ptr: seg.cast(),
size: (*seg).total_size,
is_large: false,
is_zero: false,
};
os::free(block)
}
}
unsafe fn span_mark(seg: *mut Segment, idx: usize, len: usize) {
unsafe {
(*seg).pages[idx].slice_offset = 0;
(*seg).pages[idx].slice_count = len as u16;
let mut j = 1;
while j < len {
(*seg).pages[idx + j].slice_offset = (j * slot_stride()) as u16;
j += 1;
}
}
}
unsafe fn span_mark_free(seg: *mut Segment, idx: usize, len: usize) {
unsafe {
span_mark(seg, idx, len);
let slot: *mut Page = &raw mut (*seg).pages[idx];
(*slot).block_size = 0; (*slot).prev = ptr::null_mut();
(*slot).next = (*seg).free_spans;
if !(*seg).free_spans.is_null() {
(*(*seg).free_spans).prev = slot;
}
(*seg).free_spans = slot;
}
}
unsafe fn span_list_remove(seg: *mut Segment, span: *mut Page) {
unsafe {
if !(*span).prev.is_null() {
(*(*span).prev).next = (*span).next;
} else {
(*seg).free_spans = (*span).next;
}
if !(*span).next.is_null() {
(*(*span).next).prev = (*span).prev;
}
(*span).next = ptr::null_mut();
(*span).prev = ptr::null_mut();
}
}
pub unsafe fn debug_validate_segment(seg: *mut Segment, where_: &str) {
#[cfg(feature = "debug_checks")]
{
unsafe {
let end = (*seg).next_free_slice as usize;
let mut idx = HEADER_SLICES;
let mut spans = 0usize;
while idx < end {
let slot: *mut Page = &raw mut (*seg).pages[idx];
assert_eq!(
(*slot).slice_offset,
0,
"{where_}: slice {idx} is not a span start (layout not tiled)"
);
let len = (*slot).slice_count as usize;
assert!(
len > 0 && idx + len <= end,
"{where_}: slice {idx} has bad slice_count {len} (end {end})"
);
for j in 1..len {
assert_eq!(
(*seg).pages[idx + j].slice_offset as usize,
j * slot_stride(),
"{where_}: slice {} lost its back-pointer",
idx + j
);
}
spans += 1;
assert!(
spans <= SLICES_PER_SEGMENT,
"{where_}: span walk did not terminate"
);
idx += len;
}
assert_eq!(idx, end, "{where_}: spans do not tile the carved region");
let mut s = (*seg).free_spans;
let mut n = 0usize;
while !s.is_null() {
let i = page_index(seg, s);
assert!(
i >= HEADER_SLICES && i < end,
"{where_}: free span {i} out of region"
);
assert_eq!((*s).slice_offset, 0, "{where_}: free span {i} not a start");
assert_eq!((*s).block_size, 0, "{where_}: free span {i} marked live");
n += 1;
assert!(
n <= SLICES_PER_SEGMENT,
"{where_}: free-span list is cyclic"
);
s = (*s).next;
}
}
}
#[cfg(not(feature = "debug_checks"))]
{
let _ = (seg, where_);
}
}
pub unsafe fn span_alloc(seg: *mut Segment, slices: usize) -> (*mut Page, bool) {
unsafe { debug_validate_segment(seg, "span_alloc:enter") };
unsafe {
let mut s = (*seg).free_spans;
while !s.is_null() {
let len = (*s).slice_count as usize;
if len >= slices {
span_list_remove(seg, s);
let idx = page_index(seg, s);
span_recommit(seg, idx, len);
if len > slices {
span_mark_free(seg, idx + slices, len - slices);
}
span_mark(seg, idx, slices);
(*seg).used_pages += 1;
return (s, false);
}
s = (*s).next;
}
let idx = (*seg).next_free_slice as usize;
if idx + slices > SLICES_PER_SEGMENT {
return (ptr::null_mut(), false);
}
(*seg).next_free_slice = (idx + slices) as u32;
(*seg).used_pages += 1;
let start: *mut Page = &raw mut (*seg).pages[idx];
span_mark(seg, idx, slices);
(start, (*seg).mem_is_zero)
}
}
pub unsafe fn span_free(seg: *mut Segment, page: *mut Page) -> bool {
unsafe { debug_validate_segment(seg, "span_free:enter") };
unsafe {
let mut idx = page_index(seg, page);
let mut len = (*page).slice_count as usize;
(*seg).used_pages -= 1;
(*page).block_size = 0;
(*page).free = ptr::null_mut();
(*page).local_free = ptr::null_mut();
(*page).used = 0;
(*page).capacity = 0;
(*page).reserved = 0;
(*page).flags = 0;
(*page).free_is_zero = false;
let right = idx + len;
if right < (*seg).next_free_slice as usize {
let rslot: *mut Page = &raw mut (*seg).pages[right];
debug_assert_eq!((*rslot).slice_offset, 0, "right neighbor not a span start");
if (*rslot).block_size == 0 {
span_list_remove(seg, rslot);
len += (*rslot).slice_count as usize;
}
}
if idx > HEADER_SLICES {
let lslot_idx = idx - 1;
let lstart_idx =
lslot_idx - (*seg).pages[lslot_idx].slice_offset as usize / slot_stride();
let lstart: *mut Page = &raw mut (*seg).pages[lstart_idx];
if (*lstart).block_size == 0 {
span_list_remove(seg, lstart);
len += idx - lstart_idx;
idx = lstart_idx;
}
}
span_mark_free(seg, idx, len);
let purge_delay = crate::options::get(15);
if purge_delay >= 0 && len >= crate::types::MEDIUM_PAGE_SLICES {
let area = page_area(seg, idx);
let bytes = len * SEGMENT_SLICE_SIZE;
let decommits = crate::options::is_enabled(5); if os::purge(area, bytes, decommits).is_ok() {
(*seg).pages[idx].purged = true;
(*seg).purged_any = true;
return true;
}
}
false
}
}
unsafe fn span_recommit(seg: *mut Segment, idx: usize, len: usize) {
unsafe {
if !(*seg).pages[idx].purged {
return;
}
(*seg).pages[idx].purged = false;
let area = page_area(seg, idx);
let _ = os::commit(area, len * SEGMENT_SLICE_SIZE);
}
}
pub fn huge_alloc(
size: usize,
align: usize,
offset: usize,
) -> Result<(*mut Segment, *mut u8), PrimError> {
debug_assert!(align.is_power_of_two() && align <= SEGMENT_SIZE / 2);
let header = SEGMENT_SLICE_SIZE;
let extra = if align > SEGMENT_SLICE_SIZE || !offset.is_multiple_of(align) {
align
} else {
0
};
let want = os::page_align_up(header + size + extra);
let chunks = want.div_ceil(SEGMENT_SIZE);
let (bptr, total, mem_zero) = match crate::arena::chunk_alloc_n(-1, chunks) {
Some((p, zero)) => (p, chunks * SEGMENT_SIZE, zero),
None => {
let b = os::alloc_aligned(want, SEGMENT_SIZE, true, false)?;
(b.ptr, b.size, b.is_zero)
}
};
let seg: *mut Segment = bptr.cast();
unsafe {
if !mem_zero {
core::ptr::write_bytes(seg.cast::<u8>(), 0, core::mem::size_of::<Segment>());
}
}
let b = os::OsBlock {
ptr: bptr,
size: total,
is_large: false,
is_zero: mem_zero,
};
segment_map::register_range(seg.addr(), b.size);
unsafe {
(*seg).kind = SegmentKind::Huge;
(*seg).total_size = b.size;
(*seg).next_free_slice = SLICES_PER_SEGMENT as u32;
(*seg).used_pages = 1;
(*seg).thread_id = AtomicUsize::new(crate::init::thread_id());
(*seg).mem_is_zero = b.is_zero;
(*seg).purged_any = false;
(*seg).guarded = false;
(*seg).next = ptr::null_mut();
(*seg).free_spans = ptr::null_mut();
let area = b.ptr.add(header);
let block = area.with_addr(((area.addr() + offset + align - 1) & !(align - 1)) - offset);
debug_assert!(block.addr() >= area.addr() && (block.addr() + offset).is_multiple_of(align));
let page: *mut Page = &raw mut (*seg).pages[1];
(*page).block_size = b.size - (block.addr() - seg.addr());
(*page).used = 1;
(*page).capacity = 1;
(*page).reserved = 1;
(*page).slice_count = (SLICES_PER_SEGMENT - 1) as u16;
(*page).slice_offset = 0;
(*page).flags = crate::page::pflags::HUGE_SEGMENT | crate::page::pflags::SINGLE_BLOCK;
(*page).free_is_zero = b.is_zero;
let mut j = 2;
while j < SLICES_PER_SEGMENT {
(*seg).pages[j].slice_offset = ((j - 1) * slot_stride()) as u16;
j += 1;
}
Ok((seg, block))
}
}
pub unsafe fn huge_free(seg: *mut Segment) -> Result<(), PrimError> {
unsafe {
wait_no_remote_in_flight(seg);
segment_map::unregister_range(seg.addr(), (*seg).total_size);
if (*seg).total_size.is_multiple_of(SEGMENT_SIZE) {
if (*seg).guarded || (*seg).purged_any {
let base = seg.cast::<u8>().add(HEADER_SLICES * SEGMENT_SLICE_SIZE);
let bytes = (*seg).total_size - HEADER_SLICES * SEGMENT_SLICE_SIZE;
let _ = os::protect(base, bytes, false);
let _ = os::commit(base, bytes);
(*seg).guarded = false;
(*seg).purged_any = false;
}
if crate::arena::chunk_free_n(seg.cast(), (*seg).total_size / SEGMENT_SIZE) {
return Ok(());
}
}
let block = os::OsBlock {
ptr: seg.cast(),
size: (*seg).total_size,
is_large: false,
is_zero: false,
};
os::free(block)
}
}