use core::ptr;
use core::sync::atomic::{AtomicUsize, Ordering};
pub mod pflags {
pub const HAS_ALIGNED: u8 = 1 << 0;
pub const SINGLE_BLOCK: u8 = 1 << 1;
pub const IN_FULL: u8 = 1 << 2;
pub const HUGE_SEGMENT: u8 = 1 << 3;
pub const SLOW_FREE: u8 = HAS_ALIGNED | SINGLE_BLOCK | IN_FULL | HUGE_SEGMENT;
}
pub const XMASK: usize = 0b11;
pub const XFLAG_NORMAL: usize = 0;
pub const XFLAG_DELAYED: usize = 1;
pub const XFLAG_FREEING: usize = 2;
pub const XFLAG_NEVER: usize = 3;
#[repr(C)]
pub struct Block {
pub next: *mut Block,
}
#[inline]
pub unsafe fn block_next(page: *const Page, b: *const Block) -> *mut Block {
unsafe {
#[cfg(not(feature = "secure"))]
{
let _ = page;
(*b).next
}
#[cfg(feature = "secure")]
{
let enc = (*b).next as usize;
if enc == 0 {
return core::ptr::null_mut();
}
let keys = (*page).keys;
let dec = (enc ^ keys[0]).wrapping_sub(keys[1]);
assert!(
dec.is_multiple_of(crate::types::MAX_ALIGN_SIZE.min(8)),
"rusty_alloc: corrupted free list (secure mode)"
);
crate::ptr_with_addr(b as *mut Block, dec)
}
}
}
#[inline]
pub unsafe fn block_set_next(page: *const Page, b: *mut Block, next: *mut Block) {
unsafe {
#[cfg(not(feature = "secure"))]
{
let _ = page;
(*b).next = next;
}
#[cfg(feature = "secure")]
{
if next.is_null() {
(*b).next = core::ptr::null_mut();
} else {
let keys = (*page).keys;
let enc = (next.addr().wrapping_add(keys[1])) ^ keys[0];
(*b).next = crate::ptr_with_addr(b, enc);
}
}
}
}
pub struct DelayedList {
pub head: AtomicUsize,
}
impl DelayedList {
pub const fn new() -> DelayedList {
DelayedList {
head: AtomicUsize::new(0),
}
}
}
impl Default for DelayedList {
fn default() -> Self {
Self::new()
}
}
pub struct Page {
pub free: *mut Block,
pub local_free: *mut Block,
pub xthread_free: AtomicUsize,
pub xheap: AtomicUsize,
pub next: *mut Page,
pub prev: *mut Page,
pub used: u32,
pub capacity: u32,
pub reserved: u32,
pub block_size: usize,
pub slice_count: u16,
pub slice_offset: u16,
pub bin: u8,
pub flags: u8,
pub free_is_zero: bool,
pub purged: bool,
pub heap_tag: i32,
#[cfg(feature = "secure")]
pub keys: [usize; 2],
}
#[inline]
pub unsafe fn debug_validate_page(page: *const Page, where_: &str) {
#[cfg(feature = "debug_checks")]
{
unsafe {
assert!(!page.is_null(), "{where_}: null page");
assert_eq!((*page).slice_offset, 0, "{where_}: not a span start");
assert!((*page).block_size > 0, "{where_}: dead page (block_size 0)");
assert!(
(*page).block_size.is_multiple_of(8),
"{where_}: block_size {} not word-aligned",
(*page).block_size
);
assert!((*page).slice_count > 0, "{where_}: zero slice_count");
assert!(
(*page).capacity <= (*page).reserved,
"{where_}: capacity {} > reserved {}",
(*page).capacity,
(*page).reserved
);
assert!(
(*page).used <= (*page).capacity,
"{where_}: used {} > capacity {}",
(*page).used,
(*page).capacity
);
assert!(
((*page).bin as usize) <= crate::types::BIN_FULL,
"{where_}: bin {} out of range",
(*page).bin
);
}
}
#[cfg(not(feature = "debug_checks"))]
{
let _ = (page, where_);
}
}
impl Page {
pub const fn empty_sentinel() -> Page {
Page {
free: ptr::null_mut(),
local_free: ptr::null_mut(),
xthread_free: AtomicUsize::new(0),
xheap: AtomicUsize::new(0),
next: ptr::null_mut(),
prev: ptr::null_mut(),
used: 0,
capacity: 0,
reserved: 0,
block_size: 8,
slice_count: 1,
slice_offset: 0,
bin: 0,
flags: 0,
free_is_zero: false,
purged: false,
heap_tag: 0,
#[cfg(feature = "secure")]
keys: [0; 2],
}
}
}
#[repr(transparent)]
pub struct EmptyPage(Page);
unsafe impl Sync for EmptyPage {}
pub static EMPTY_PAGE: EmptyPage = EmptyPage(Page::empty_sentinel());
#[inline]
pub const fn empty_page_ptr() -> *mut Page {
&raw const EMPTY_PAGE.0 as *mut Page
}
#[inline]
pub unsafe fn page_pop(page: *mut Page) -> *mut u8 {
unsafe { debug_validate_page(page, "page_pop") };
let block = unsafe { (*page).free };
if block.is_null() {
return ptr::null_mut();
}
unsafe {
(*page).free = block_next(page, block);
(*page).used += 1;
}
block.cast()
}
#[inline]
pub unsafe fn page_push_local(page: *mut Page, block: *mut Block) {
unsafe { debug_validate_page(page, "page_push_local") };
unsafe {
block_set_next(page, block, (*page).local_free);
(*page).local_free = block;
(*page).used = (*page).used.wrapping_sub(1);
if ((*page).used as i32) < 0 {
double_free_abort();
}
}
}
#[cold]
#[inline(never)]
fn double_free_abort() -> ! {
std::process::abort()
}
pub unsafe fn remote_free(page: *mut Page, block: *mut Block) {
loop {
let x = unsafe { (*page).xthread_free.load(Ordering::Acquire) };
match x & XMASK {
XFLAG_DELAYED => {
let claimed = unsafe {
(*page)
.xthread_free
.compare_exchange_weak(
x,
(x & !XMASK) | XFLAG_FREEING,
Ordering::AcqRel,
Ordering::Relaxed,
)
.is_ok()
};
if claimed {
unsafe {
let dl = (*page).xheap.load(Ordering::Acquire) as *const DelayedList;
debug_assert!(!dl.is_null(), "DELAYED page without an owner heap");
loop {
let head = (*dl).head.load(Ordering::Acquire);
(*block).next = crate::ptr_with_addr(block, head);
if (*dl)
.head
.compare_exchange_weak(
head,
block as usize,
Ordering::AcqRel,
Ordering::Relaxed,
)
.is_ok()
{
break;
}
}
loop {
let y = (*page).xthread_free.load(Ordering::Acquire);
if (*page)
.xthread_free
.compare_exchange_weak(
y,
(y & !XMASK) | XFLAG_DELAYED,
Ordering::AcqRel,
Ordering::Relaxed,
)
.is_ok()
{
break;
}
}
}
return;
}
}
XFLAG_FREEING => core::hint::spin_loop(),
flag => {
unsafe {
block_set_next(page, block, crate::ptr_with_addr(block, x & !XMASK));
if (*page)
.xthread_free
.compare_exchange_weak(
x,
(block as usize) | flag,
Ordering::Release,
Ordering::Relaxed,
)
.is_ok()
{
return;
}
}
}
}
}
}
pub unsafe fn page_set_flag(page: *mut Page, flag: usize) {
loop {
let x = unsafe { (*page).xthread_free.load(Ordering::Acquire) };
if x & XMASK == XFLAG_FREEING {
core::hint::spin_loop();
continue;
}
let ok = unsafe {
(*page)
.xthread_free
.compare_exchange_weak(x, (x & !XMASK) | flag, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
};
if ok {
return;
}
}
}
pub unsafe fn page_collect(page: *mut Page) {
unsafe {
if (*page).free.is_null() {
(*page).free = (*page).local_free;
(*page).local_free = ptr::null_mut();
if !(*page).free.is_null() {
(*page).free_is_zero = false;
}
}
loop {
let x = (*page).xthread_free.load(Ordering::Acquire);
let head = (x & !XMASK) as *mut Block;
if head.is_null() {
break;
}
if (*page)
.xthread_free
.compare_exchange_weak(x, x & XMASK, Ordering::AcqRel, Ordering::Relaxed)
.is_err()
{
continue;
}
(*page).free_is_zero = false;
let mut tail = head;
let mut n = 1u32;
while !block_next(page, tail).is_null() {
tail = block_next(page, tail);
n += 1;
}
block_set_next(page, tail, (*page).free);
(*page).free = head;
if n > (*page).used {
double_free_abort();
}
(*page).used -= n;
break;
}
}
}
pub unsafe fn page_extend(page: *mut Page, area: *mut u8) {
unsafe { debug_validate_page(page, "page_extend") };
unsafe {
let bsize = (*page).block_size;
let capacity = (*page).capacity as usize;
let reserved = (*page).reserved as usize;
if capacity >= reserved {
return;
}
let take = ((4096 / bsize).max(1)).min(reserved - capacity);
let start = area.add(capacity * bsize);
let mut i = take;
let mut head: *mut Block = (*page).free;
while i > 0 {
i -= 1;
let b: *mut Block = start.add(i * bsize).cast();
block_set_next(page, b, head);
head = b;
}
(*page).free = head;
(*page).capacity = (capacity + take) as u32;
}
}
#[inline]
pub unsafe fn page_all_free(page: *mut Page) -> bool {
unsafe { (*page).used == 0 }
}