use core::ptr;
use crate::heap::Heap;
use crate::init;
use crate::page::{Block, Page, pflags, remote_free};
use crate::segment::{self, SegmentKind, page_of, segment_of};
use crate::types::{BIN_HUGE, SMALL_SIZE_MAX};
unsafe fn unalign(pg: *mut Page, p: *mut u8) -> *mut u8 {
unsafe {
if (*pg).flags & (pflags::HAS_ALIGNED | pflags::SINGLE_BLOCK) == 0 {
return p;
}
let seg = segment_of(p);
let idx = segment::page_index(seg, pg);
let area = segment::page_area(seg, idx);
let off = p.addr() - area.addr();
let bsize = (*pg).block_size;
area.add((off / bsize) * bsize)
}
}
#[inline]
unsafe fn owner_heap(pg: *mut Page) -> *mut Heap {
unsafe {
let xh = (*pg).xheap.load(core::sync::atomic::Ordering::Acquire);
if xh != 0 {
(*init::box_of_xheap(xh)).heap.get()
} else {
my_heap() }
}
}
#[inline]
fn my_heap() -> *mut Heap {
unsafe { (*init::heap_box()).heap.get() }
}
#[inline]
pub fn malloc(size: usize) -> *mut u8 {
unsafe { (*my_heap()).malloc(size).0 }
}
pub fn zalloc(size: usize) -> *mut u8 {
unsafe {
let (p, is_zero) = (*my_heap()).malloc(size);
if !p.is_null() {
zero_block(p, is_zero);
}
p
}
}
unsafe fn zero_block(p: *mut u8, is_zero: bool) {
if is_zero {
unsafe { p.cast::<usize>().write(0) };
} else {
unsafe { core::ptr::write_bytes(p, 0, usable_size(p)) };
}
}
pub fn calloc(count: usize, size: usize) -> *mut u8 {
match count.checked_mul(size) {
Some(total) => zalloc(total),
None => ptr::null_mut(),
}
}
pub fn mallocn(count: usize, size: usize) -> *mut u8 {
match count.checked_mul(size) {
Some(total) => malloc(total),
None => ptr::null_mut(),
}
}
pub fn malloc_small(size: usize) -> *mut u8 {
debug_assert!(size <= SMALL_SIZE_MAX);
malloc(size)
}
pub fn zalloc_small(size: usize) -> *mut u8 {
debug_assert!(size <= SMALL_SIZE_MAX);
zalloc(size)
}
pub fn malloc_aligned(size: usize, align: usize) -> *mut u8 {
malloc_aligned_at(size, align, 0)
}
pub fn malloc_aligned_at(size: usize, align: usize, offset: usize) -> *mut u8 {
unsafe { (*my_heap()).malloc_aligned_at(size, align, offset).0 }
}
pub fn zalloc_aligned(size: usize, align: usize) -> *mut u8 {
zalloc_aligned_at(size, align, 0)
}
pub fn zalloc_aligned_at(size: usize, align: usize, offset: usize) -> *mut u8 {
unsafe {
let (p, is_zero) = (*my_heap()).malloc_aligned_at(size, align, offset);
if !p.is_null() {
zero_block(p, is_zero);
}
p
}
}
pub fn calloc_aligned(count: usize, size: usize, align: usize) -> *mut u8 {
calloc_aligned_at(count, size, align, 0)
}
pub fn calloc_aligned_at(count: usize, size: usize, align: usize, offset: usize) -> *mut u8 {
match count.checked_mul(size) {
Some(total) => zalloc_aligned_at(total, align, offset),
None => ptr::null_mut(),
}
}
pub unsafe fn realloc_aligned(p: *mut u8, newsize: usize, align: usize) -> *mut u8 {
unsafe { realloc_aligned_at(p, newsize, align, 0) }
}
pub unsafe fn realloc_aligned_at(
p: *mut u8,
newsize: usize,
align: usize,
offset: usize,
) -> *mut u8 {
if p.is_null() {
return malloc_aligned_at(newsize, align, offset);
}
let usable = unsafe { usable_size(p) };
if newsize <= usable
&& newsize >= usable / 2
&& (p.addr() + offset).is_multiple_of(align.max(1))
{
unsafe { (*my_heap()).stats.realloc_in_place += 1 };
return p;
}
let np = malloc_aligned_at(newsize, align, offset);
if np.is_null() {
return ptr::null_mut();
}
unsafe {
core::ptr::copy_nonoverlapping(p, np, usable.min(newsize));
free(p);
(*my_heap()).stats.realloc_moved += 1;
}
np
}
pub unsafe fn rezalloc(p: *mut u8, newsize: usize) -> *mut u8 {
unsafe { rezalloc_aligned_at(p, newsize, 1, 0) }
}
pub unsafe fn recalloc(p: *mut u8, newcount: usize, size: usize) -> *mut u8 {
match newcount.checked_mul(size) {
Some(total) => unsafe { rezalloc(p, total) },
None => ptr::null_mut(),
}
}
pub unsafe fn rezalloc_aligned(p: *mut u8, newsize: usize, align: usize) -> *mut u8 {
unsafe { rezalloc_aligned_at(p, newsize, align, 0) }
}
pub unsafe fn rezalloc_aligned_at(
p: *mut u8,
newsize: usize,
align: usize,
offset: usize,
) -> *mut u8 {
if p.is_null() {
return if align <= 1 {
zalloc(newsize)
} else {
zalloc_aligned_at(newsize, align, offset)
};
}
let usable = unsafe { usable_size(p) };
if newsize <= usable
&& newsize >= usable / 2
&& (p.addr() + offset).is_multiple_of(align.max(1))
{
unsafe { (*my_heap()).stats.realloc_in_place += 1 };
return p;
}
let np = if align <= 1 {
malloc(newsize)
} else {
malloc_aligned_at(newsize, align, offset)
};
if np.is_null() {
return ptr::null_mut();
}
unsafe {
let keep = usable.min(newsize);
core::ptr::copy_nonoverlapping(p, np, keep);
let new_usable = usable_size(np);
core::ptr::write_bytes(np.add(keep), 0, new_usable - keep);
free(p);
(*my_heap()).stats.realloc_moved += 1;
}
np
}
pub unsafe fn recalloc_aligned(p: *mut u8, newcount: usize, size: usize, align: usize) -> *mut u8 {
unsafe { recalloc_aligned_at(p, newcount, size, align, 0) }
}
pub unsafe fn recalloc_aligned_at(
p: *mut u8,
newcount: usize,
size: usize,
align: usize,
offset: usize,
) -> *mut u8 {
match newcount.checked_mul(size) {
Some(total) => unsafe { rezalloc_aligned_at(p, total, align, offset) },
None => ptr::null_mut(),
}
}
pub unsafe fn free(p: *mut u8) {
if p.is_null() {
return;
}
let seg = segment_of(p);
unsafe {
let owner_tid = (*seg).thread_id.load(core::sync::atomic::Ordering::Acquire);
let pg = page_of(seg, p);
let flags = (*pg).flags;
let local = owner_tid == init::thread_id();
if flags & pflags::SLOW_FREE == 0 {
if local {
debug_assert_eq!(
(*seg).kind,
SegmentKind::Normal,
"free fast path: HUGE_SEGMENT clear but segment is Huge"
);
debug_assert_ne!(
(*pg).bin as usize,
BIN_HUGE,
"free fast path: SINGLE_BLOCK clear but bin is BIN_HUGE"
);
crate::page::page_push_local(pg, p.cast::<Block>());
#[cfg(debug_assertions)]
{
(*owner_heap(pg)).stats.frees += 1;
}
if (*pg).used == 0 {
retire_page_cold(pg);
}
} else {
remote_free(pg, p.cast::<Block>());
}
return;
}
free_general(p);
}
}
#[cold]
#[inline(never)]
unsafe fn retire_page_cold(pg: *mut Page) {
unsafe {
let seg = segment_of(pg.cast());
(*owner_heap(pg)).retire_emptied(seg, pg);
}
}
#[cold]
#[inline(never)]
unsafe fn free_general(p: *mut u8) {
unsafe {
let seg = segment_of(p);
let owner_tid = (*seg).thread_id.load(core::sync::atomic::Ordering::Acquire);
let pg = page_of(seg, p);
let flags = (*pg).flags;
let block = if flags & pflags::HAS_ALIGNED != 0 && flags & pflags::HUGE_SEGMENT == 0 {
unalign(pg, p)
} else {
p
};
if owner_tid == init::thread_id() {
(*owner_heap(pg)).free_local_at(seg, pg, block);
} else {
remote_free(pg, block.cast::<Block>());
}
}
}
pub unsafe fn usable_size(p: *const u8) -> usize {
if p.is_null() {
return 0;
}
let seg = segment_of(p.cast_mut());
unsafe {
let pg = page_of(seg, p.cast_mut());
if (*seg).kind == SegmentKind::Huge {
return (*pg).block_size;
}
let start = unalign(pg, p.cast_mut());
(*pg).block_size - (p.addr() - start.addr())
}
}
pub unsafe fn realloc(p: *mut u8, newsize: usize) -> *mut u8 {
if p.is_null() {
return malloc(newsize);
}
let usable = unsafe { usable_size(p) };
if newsize <= usable && newsize >= usable / 2 {
unsafe { (*my_heap()).stats.realloc_in_place += 1 };
return p;
}
let np = malloc(newsize);
if np.is_null() {
return ptr::null_mut();
}
unsafe {
core::ptr::copy_nonoverlapping(p, np, usable.min(newsize));
free(p);
(*my_heap()).stats.realloc_moved += 1;
}
np
}
pub unsafe fn reallocn(p: *mut u8, count: usize, size: usize) -> *mut u8 {
match count.checked_mul(size) {
Some(total) => unsafe { realloc(p, total) },
None => ptr::null_mut(),
}
}
pub unsafe fn reallocf(p: *mut u8, newsize: usize) -> *mut u8 {
let np = unsafe { realloc(p, newsize) };
if np.is_null() && !p.is_null() {
unsafe { free(p) };
}
np
}
pub unsafe fn expand(p: *mut u8, newsize: usize) -> *mut u8 {
if p.is_null() {
return ptr::null_mut();
}
let usable = unsafe { usable_size(p) };
if newsize <= usable {
p
} else {
ptr::null_mut()
}
}
pub fn is_in_heap_region(p: *const u8) -> bool {
crate::segment_map::contains(p)
}
pub fn collect(force: bool) {
unsafe { (*my_heap()).collect(force) };
}
pub fn stats() -> crate::heap::Stats {
unsafe { (*my_heap()).stats }
}
#[inline]
unsafe fn heap_of(hb: *mut init::HeapBox) -> *mut Heap {
unsafe {
debug_assert_eq!(
(*hb).owner_tid,
init::thread_id(),
"heap used off its owning thread"
);
(*hb).heap.get()
}
}
pub unsafe fn heap_malloc(hb: *mut init::HeapBox, size: usize) -> *mut u8 {
unsafe { (*heap_of(hb)).malloc(size).0 }
}
pub unsafe fn heap_zalloc(hb: *mut init::HeapBox, size: usize) -> *mut u8 {
unsafe {
let (p, is_zero) = (*heap_of(hb)).malloc(size);
if !p.is_null() {
zero_block(p, is_zero);
}
p
}
}
pub unsafe fn heap_malloc_aligned_at(
hb: *mut init::HeapBox,
size: usize,
align: usize,
offset: usize,
) -> *mut u8 {
unsafe { (*heap_of(hb)).malloc_aligned_at(size, align, offset).0 }
}
pub unsafe fn heap_zalloc_aligned_at(
hb: *mut init::HeapBox,
size: usize,
align: usize,
offset: usize,
) -> *mut u8 {
unsafe {
let (p, is_zero) = (*heap_of(hb)).malloc_aligned_at(size, align, offset);
if !p.is_null() {
zero_block(p, is_zero);
}
p
}
}
pub unsafe fn heap_realloc(hb: *mut init::HeapBox, p: *mut u8, newsize: usize) -> *mut u8 {
if p.is_null() {
return unsafe { heap_malloc(hb, newsize) };
}
let usable = unsafe { usable_size(p) };
if newsize <= usable && newsize >= usable / 2 {
unsafe { (*heap_of(hb)).stats.realloc_in_place += 1 };
return p;
}
unsafe {
let np = heap_malloc(hb, newsize);
if np.is_null() {
return ptr::null_mut();
}
core::ptr::copy_nonoverlapping(p, np, usable.min(newsize));
free(p);
(*heap_of(hb)).stats.realloc_moved += 1;
np
}
}
pub unsafe fn heap_collect(hb: *mut init::HeapBox, force: bool) {
unsafe { (*heap_of(hb)).collect(force) };
}
pub unsafe fn heap_contains_block(hb: *mut init::HeapBox, p: *const u8) -> bool {
if p.is_null() {
return false;
}
let target = segment_of(p.cast_mut());
unsafe {
let h = heap_of(hb);
let mut seg = (*h).segments;
while !seg.is_null() {
if seg == target {
return true;
}
seg = (*seg).next;
}
let mut seg = (*h).huge_segments;
while !seg.is_null() {
if seg == target {
return true;
}
seg = (*seg).next;
}
}
false
}
pub unsafe fn heap_check_owned(hb: *mut init::HeapBox, p: *const u8) -> bool {
if !unsafe { heap_contains_block(hb, p) } {
return false;
}
let seg = segment_of(p.cast_mut());
unsafe {
if (*seg).kind == SegmentKind::Huge {
return true;
}
let pg = page_of(seg, p.cast_mut());
(*pg).block_size > 0
}
}
pub fn check_owned(p: *const u8) -> bool {
is_in_heap_region(p)
}