use core::ptr;
use core::sync::atomic::Ordering;
use crate::heap::Heap;
use crate::init;
use crate::page::{Block, Page, pflags, remote_free};
use crate::segment::{self, Segment, 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.load(Ordering::Relaxed) & (pflags::HAS_ALIGNED | pflags::SINGLE_BLOCK) == 0 {
return p;
}
let area = (*pg).area;
debug_assert!(
{
let seg = segment_of(p);
area == segment::page_area(seg, segment::page_index(seg, pg))
},
"unalign: Page::area disagrees with page_area(page_index(..))"
);
if (*pg).flags.load(Ordering::Relaxed) & pflags::SINGLE_BLOCK != 0 {
return area;
}
let off = p.addr() - area.addr();
let bsize = (*pg).block_size;
area.add(crate::bins::div_by_block_size(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 {
let h = my_heap();
debug_assert!(!h.is_null(), "owner_heap fallback on a heapless thread");
h
}
}
}
#[inline]
fn my_heap() -> *mut Heap {
let hb = init::heap_box();
if hb.is_null() {
return core::ptr::null_mut();
}
unsafe { (*hb).heap.get() }
}
#[inline(always)]
fn debug_foreign_pointer_guard(p: *mut u8) {
#[cfg(any(debug_assertions, feature = "debug_checks"))]
{
debug_assert!(
crate::segment_map::contains(p),
"rusty_alloc: free() called on a pointer this allocator never returned \
({p:p} is not in any registered segment window). A foreign pointer here \
reads metadata from memory we do not own — see UNSAFE.md and R-001."
);
}
#[cfg(not(any(debug_assertions, feature = "debug_checks")))]
{
let _ = p;
}
}
#[inline]
fn stat_realloc(in_place: bool) {
#[cfg(debug_assertions)]
{
let h = my_heap();
if !h.is_null() {
unsafe {
if in_place {
(*h).stats.realloc_in_place += 1;
} else {
(*h).stats.realloc_moved += 1;
}
}
}
}
#[cfg(not(debug_assertions))]
{
let _ = in_place;
}
}
#[inline]
pub fn malloc(size: usize) -> *mut u8 {
let hb = init::heap_box_fast();
unsafe {
if size <= SMALL_SIZE_MAX {
let w = crate::types::wsize_from_size(size);
let h = (*hb).heap.get();
let p = (*h).direct[w];
let b = crate::page::page_pop(p);
if !b.is_null() {
#[cfg(debug_assertions)]
{
(*h).stats.allocs += 1;
}
return b;
}
}
malloc_slow(hb, size)
}
}
#[inline]
pub fn malloc_or(size: usize, on_oom: fn(usize) -> *mut u8) -> *mut u8 {
let hb = init::heap_box_fast();
unsafe {
if size <= SMALL_SIZE_MAX {
let w = crate::types::wsize_from_size(size);
let h = (*hb).heap.get();
let p = (*h).direct[w];
let b = crate::page::page_pop(p);
if !b.is_null() {
#[cfg(debug_assertions)]
{
(*h).stats.allocs += 1;
}
return b;
}
}
malloc_or_slow(hb, size, on_oom)
}
}
#[cold]
#[inline(never)]
unsafe fn malloc_or_slow(
hb: *mut init::HeapBox,
size: usize,
on_oom: fn(usize) -> *mut u8,
) -> *mut u8 {
let p = unsafe { malloc_slow(hb, size) };
if p.is_null() { on_oom(size) } else { p }
}
#[cold]
#[inline(never)]
unsafe fn malloc_slow(hb: *mut init::HeapBox, size: usize) -> *mut u8 {
if hb != init::empty_heap_box_ptr() {
return unsafe { (*(*hb).heap.get()).malloc_generic(size).0 };
}
malloc_first(size)
}
#[cold]
#[inline(never)]
fn malloc_first(size: usize) -> *mut u8 {
let hb = init::ensure_heap(init::empty_heap_box_ptr());
if hb.is_null() {
return ptr::null_mut();
}
unsafe { (*(*hb).heap.get()).malloc_generic(size).0 }
}
pub fn zalloc(size: usize) -> *mut u8 {
let hb = init::heap_box_fast();
if hb == init::empty_heap_box_ptr() {
return zalloc_first(size);
}
unsafe { (*(*hb).heap.get()).zalloc(size) }
}
#[cold]
#[inline(never)]
fn zalloc_first(size: usize) -> *mut u8 {
let h = my_heap();
if h.is_null() {
return ptr::null_mut(); }
unsafe { (*h).zalloc(size) }
}
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 {
let hb = init::heap_box_fast();
unsafe {
let mask = align.wrapping_sub(1);
if offset == 0
&& size <= SMALL_SIZE_MAX
&& mask < crate::types::SEGMENT_SIZE / 2
&& align & mask == 0
{
let h = (*hb).heap.get();
let w = crate::types::wsize_from_size(size);
let p = (*h).direct[w];
let b = (*p).free;
if !b.is_null() && b.addr() & mask == 0 {
let blk = crate::page::page_pop(p);
if !blk.is_null() {
#[cfg(debug_assertions)]
{
(*h).stats.allocs += 1;
}
return blk;
}
}
}
malloc_aligned_slow(hb, size, align, offset)
}
}
#[cold]
#[inline(never)]
unsafe fn malloc_aligned_slow(
hb: *mut init::HeapBox,
size: usize,
align: usize,
offset: usize,
) -> *mut u8 {
if hb != init::empty_heap_box_ptr() {
unsafe {
let mask = align.wrapping_sub(1);
if offset == 0
&& size > SMALL_SIZE_MAX
&& size <= crate::types::MEDIUM_OBJ_SIZE_MAX
&& mask < crate::types::SEGMENT_SIZE / 2
&& align & mask == 0
{
let h = (*hb).heap.get();
let p = (*h).pages[crate::bins::bin(size)].first;
if !p.is_null() {
let b = (*p).free;
if !b.is_null() && b.addr() & mask == 0 {
let blk = crate::page::page_pop(p);
if !blk.is_null() {
#[cfg(debug_assertions)]
{
(*h).stats.allocs += 1;
}
return blk;
}
}
}
}
}
return unsafe {
(*(*hb).heap.get())
.malloc_aligned_at_slow(size, align, offset)
.0
};
}
malloc_aligned_first(size, align, offset)
}
#[cold]
#[inline(never)]
fn malloc_aligned_first(size: usize, align: usize, offset: usize) -> *mut u8 {
let h = my_heap();
if h.is_null() {
return ptr::null_mut(); }
unsafe { (*h).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 {
let hb = init::heap_box_fast();
if hb == init::empty_heap_box_ptr() {
return zalloc_aligned_first(size, align, offset);
}
unsafe {
let (p, is_zero) = (*(*hb).heap.get()).malloc_aligned_at(size, align, offset);
if !p.is_null() {
zero_block(p, is_zero);
}
p
}
}
#[cold]
#[inline(never)]
fn zalloc_aligned_first(size: usize, align: usize, offset: usize) -> *mut u8 {
let h = my_heap();
if h.is_null() {
return ptr::null_mut(); }
unsafe {
let (p, is_zero) = (*h).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
&& crate::bins::is_aligned_to(p.addr() + offset, align)
{
stat_realloc(true);
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);
stat_realloc(false);
}
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
&& crate::bins::is_aligned_to(p.addr() + offset, align)
{
stat_realloc(true);
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);
stat_realloc(false);
}
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) {
unsafe { free_inline(p) }
}
#[inline(always)]
pub unsafe fn free_inline(p: *mut u8) {
if p.is_null() {
return;
}
debug_foreign_pointer_guard(p);
let seg = segment_of(p);
unsafe {
let owner_tid = (*seg).thread_id.load(core::sync::atomic::Ordering::Acquire);
#[cfg(not(all(target_arch = "x86_64", target_os = "linux", not(miri))))]
let local = crate::ONE_THREAD || owner_tid == init::thread_id();
let pg = page_of(seg, p);
let flags = (*pg).flags.load(Ordering::Relaxed);
if flags & pflags::SLOW_FREE == 0 {
#[cfg(all(target_arch = "x86_64", target_os = "linux", not(miri)))]
core::arch::asm!(
"cmp {tid}, fs:0",
"jne {remote}",
tid = in(reg) owner_tid,
remote = label {
unsafe { remote_free(pg, p.cast::<Block>()) };
return;
},
options(nostack, readonly),
);
#[cfg(not(all(target_arch = "x86_64", target_os = "linux", not(miri))))]
if !local {
remote_free(pg, p.cast::<Block>());
return;
}
{
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_link_local(pg, p.cast::<Block>());
#[cfg(debug_assertions)]
{
(*owner_heap(pg)).stats.frees += 1;
}
#[cfg(all(target_arch = "x86_64", not(miri)))]
{
core::arch::asm!(
"sub dword ptr [{pg} + {off}], 1",
"jle {cold}",
pg = in(reg) pg,
off = const crate::page::USED_OFFSET,
cold = label {
unsafe {
if (*pg).used == 0
&& (*pg).next.is_null()
&& (*pg).prev.is_null()
{
return;
}
return retire_or_abort(pg);
}
},
options(nostack),
);
}
#[cfg(not(all(target_arch = "x86_64", not(miri))))]
{
let u = (*pg).used.wrapping_sub(1);
(*pg).used = u;
if (u as i32) <= 0 {
if u == 0 && (*pg).next.is_null() && (*pg).prev.is_null() {
return;
}
return retire_or_abort(pg);
}
}
}
return;
}
free_general(p, seg, pg, owner_tid);
}
}
#[cold]
#[inline(never)]
unsafe fn retire_or_abort(pg: *mut Page) {
if (unsafe { (*pg).used } as i32) < 0 {
crate::page::double_free_abort();
}
if unsafe { (*pg).next.is_null() && (*pg).prev.is_null() } {
return;
}
unsafe {
let seg = segment_of(pg.cast());
(*owner_heap(pg)).retire_emptied(seg, pg);
}
}
#[cold]
#[inline(never)]
unsafe fn free_general(p: *mut u8, seg: *mut Segment, pg: *mut Page, owner_tid: usize) {
unsafe {
debug_assert_eq!(seg, segment_of(p));
debug_assert_eq!(pg, page_of(seg, p));
let flags = (*pg).flags.load(Ordering::Relaxed);
let block = if flags & pflags::HAS_ALIGNED != 0 && flags & pflags::HUGE_SEGMENT == 0 {
unalign(pg, p)
} else {
p
};
#[cfg(all(target_arch = "x86_64", target_os = "linux", not(miri)))]
{
core::arch::asm!(
"cmp {tid}, fs:0",
"jne {remote}",
tid = in(reg) owner_tid,
remote = label {
unsafe { remote_free(pg, block.cast::<Block>()) };
return;
},
options(nostack, readonly),
);
(*owner_heap(pg)).free_local_at(seg, pg, block);
}
#[cfg(not(all(target_arch = "x86_64", target_os = "linux", not(miri))))]
if crate::ONE_THREAD || 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());
let flags = (*pg).flags.load(Ordering::Relaxed);
if flags & (pflags::HAS_ALIGNED | pflags::SINGLE_BLOCK | pflags::HUGE_SEGMENT) == 0 {
return (*pg).block_size;
}
debug_assert_eq!(
flags & pflags::HUGE_SEGMENT != 0,
(*seg).kind == SegmentKind::Huge,
"usable_size: HUGE_SEGMENT flag disagrees with the segment kind"
);
usable_size_slow(pg, p, flags)
}
}
#[cold]
#[inline(never)]
unsafe fn usable_size_slow(pg: *mut Page, p: *const u8, flags: u8) -> usize {
unsafe {
if flags & pflags::HUGE_SEGMENT != 0 {
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 {
stat_realloc(true);
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_inline(p);
stat_realloc(false);
}
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) {
let h = my_heap();
if h.is_null() {
return; }
unsafe { (*h).collect(force) };
}
pub fn stats() -> crate::heap::Stats {
let h = my_heap();
if h.is_null() {
return crate::heap::Stats::new(); }
unsafe { (*h).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 {
#[cfg(debug_assertions)]
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);
#[cfg(debug_assertions)]
{
(*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)
}