use core::ptr;
use core::sync::atomic::{AtomicUsize, Ordering};
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 page_off: [u32; SLICES_PER_SEGMENT],
pub pages: [Page; SLICES_PER_SEGMENT],
}
pub const PAGES_OFFSET: usize = core::mem::offset_of!(Segment, pages);
#[inline(always)]
pub const fn page_off_for(start: usize) -> u32 {
(PAGES_OFFSET + start * core::mem::size_of::<Page>()) as u32
}
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;
const _: () = assert!(crate::types::LARGE_OBJ_SIZE_MAX == USABLE_SLICES * SEGMENT_SLICE_SIZE);
#[cfg(not(all(target_arch = "wasm32", not(miri))))]
#[inline]
pub fn segment_of(p: *mut u8) -> *mut Segment {
if crate::REGION_STRIDES {
let base = crate::prim::fixed::stride_base();
let off = p.addr().wrapping_sub(base) & (SEGMENT_SIZE - 1);
p.with_addr(p.addr().wrapping_sub(off)).cast()
} else {
p.with_addr(p.addr() & !(SEGMENT_SIZE - 1)).cast()
}
}
#[cfg(all(target_arch = "wasm32", not(miri)))]
#[inline]
pub fn segment_of(p: *mut u8) -> *mut Segment {
let base = crate::segment_map::base_of(p.addr());
debug_assert!(
base != 0,
"segment_of: unmapped wasm address {:#x}",
p.addr()
);
p.with_addr(base).cast()
}
fn reserve_backing(want: usize) -> Result<(*mut u8, usize, bool), PrimError> {
#[cfg(all(target_arch = "wasm32", not(miri)))]
{
let size = os::page_align_up(want); debug_assert!(size.is_multiple_of(SEGMENT_SLICE_SIZE));
if let Some(addr) = crate::slice_pool::alloc_run(size / SEGMENT_SLICE_SIZE) {
return Ok((core::ptr::with_exposed_provenance_mut(addr), size, false));
}
let b = os::alloc_aligned(size, SEGMENT_SLICE_SIZE, true, false)?;
Ok((b.ptr, b.size, b.is_zero))
}
#[cfg(not(all(target_arch = "wasm32", not(miri))))]
{
let b = os::alloc_aligned(want, SEGMENT_SIZE, true, false)?;
Ok((b.ptr, b.size, b.is_zero))
}
}
#[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 tab: *const u32 = (&raw const (*seg).page_off).cast();
let off = *tab.add(idx) as usize;
debug_assert!(
{
let base: *mut Page = (&raw mut (*seg).pages).cast();
let slot = base.add(idx);
let back = (*slot).slice_offset as usize * slot_stride();
back <= idx * core::mem::size_of::<Page>()
&& slot.cast::<u8>().sub(back).cast::<Page>()
== seg.cast::<u8>().add(off).cast::<Page>()
},
"page_of: page_off table disagrees with slice_offset"
);
seg.cast::<u8>().add(off).cast::<Page>()
}
}
#[inline]
pub const fn slot_stride() -> usize {
core::mem::size_of::<Page>()
}
const _: () = assert!(
SLICES_PER_SEGMENT - 1 <= u16::MAX as usize,
"slice_offset (slices) 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 {
unsafe {
let idx = ((*page).area.addr() - seg.addr()) / SEGMENT_SLICE_SIZE;
debug_assert!(
{
let base: *mut Page = (&raw mut (*seg).pages).cast();
idx == (page.addr() - base.addr()) / core::mem::size_of::<Page>()
},
"page_index: Page::area disagrees with the slot-pointer form"
);
idx
}
}
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();
let end = (*seg).next_free_slice as usize;
for i in HEADER_SLICES..end {
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 (p, sz, zero) = reserve_backing(SEGMENT_SIZE)?;
(p, sz, 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(());
}
#[cfg(all(target_arch = "wasm32", not(miri)))]
unsafe {
if crate::slice_pool::free_range(seg.cast::<u8>().expose_provenance(), (*seg).total_size) {
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;
(*seg).pages[idx].area = seg.cast::<u8>().add(idx * SEGMENT_SLICE_SIZE);
let owner = page_off_for(idx);
let tab: *mut u32 = (&raw mut (*seg).page_off).cast();
*tab.wrapping_add(idx) = owner;
let base: *mut Page = (&raw mut (*seg).pages).cast();
let mut slot = base.wrapping_add(idx + 1);
let mut ent = tab.wrapping_add(idx + 1);
let mut j = 1;
while j < len {
(*slot).slice_offset = j as u16;
*ent = owner;
slot = slot.wrapping_add(1);
ent = ent.wrapping_add(1);
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() {
(*seg).free_spans = (*span).next;
} else {
(*(*span).prev).next = (*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,
"{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.store(0, Ordering::Relaxed);
(*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;
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
}
}
pub unsafe fn purge_free_spans(seg: *mut Segment) {
unsafe {
let decommits = crate::options::is_enabled(5); let mut s = (*seg).free_spans;
while !s.is_null() {
let next = (*s).next;
if !(*s).purged {
let idx = page_index(seg, s);
let len = (*s).slice_count as usize;
if len > 0 {
let area = page_area(seg, idx);
let bytes = len * SEGMENT_SLICE_SIZE;
if os::purge(area, bytes, decommits).is_ok() {
(*s).purged = true;
(*seg).purged_any = true;
}
}
}
s = next;
}
}
}
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,
arena_id: i32,
) -> Result<(*mut Segment, *mut u8), PrimError> {
debug_assert!(align.is_power_of_two() && align <= SEGMENT_SIZE / 2);
let header = SEGMENT_SLICE_SIZE;
let natural = if crate::REGION_STRIDES {
crate::prim::fixed::REGION_ALIGN
} else {
SEGMENT_SLICE_SIZE
};
let extra = if align > natural || !crate::bins::is_aligned_to(offset, 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(arena_id, chunks) {
Some((p, zero)) => (p, chunks * SEGMENT_SIZE, zero),
None => {
if arena_id >= 0 {
return Err(0); }
let (p, sz, zero) = reserve_backing(want)?;
(p, sz, 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).area = area;
debug_assert_eq!(
(*page).area,
page_area(seg, 1),
"huge: area != page_area(1)"
);
(*page).flags.store(
crate::page::pflags::HUGE_SEGMENT | crate::page::pflags::SINGLE_BLOCK,
Ordering::Relaxed,
);
(*page).free_is_zero = b.is_zero;
let owner = page_off_for(1);
let tab: *mut u32 = (&raw mut (*seg).page_off).cast();
*tab.wrapping_add(1) = owner;
let mut j = 2;
while j < SLICES_PER_SEGMENT {
(*seg).pages[j].slice_offset = (j - 1) as u16;
*tab.wrapping_add(j) = owner;
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(());
}
}
#[cfg(all(target_arch = "wasm32", not(miri)))]
if crate::slice_pool::free_range(seg.cast::<u8>().expose_provenance(), (*seg).total_size) {
return Ok(());
}
let block = os::OsBlock {
ptr: seg.cast(),
size: (*seg).total_size,
is_large: false,
is_zero: false,
};
os::free(block)
}
}