#![no_std]
#[allow(unused_imports)]
#[macro_use]
extern crate log;
extern crate alloc;
use core::{
alloc::Layout,
fmt,
ptr::NonNull,
sync::atomic::{AtomicBool, Ordering},
};
use strum::{IntoStaticStr, VariantArray};
const PAGE_SIZE: usize = 0x1000;
#[cfg(any(tlsf, buddy_slab, test))]
const MIN_RECLAIM_PAGES: usize = 16;
#[cfg(any(tlsf, buddy_slab, test))]
const MAX_RECLAIM_ATTEMPTS: usize = 4;
pub type PageReclaimFn = fn(num_pages: usize) -> usize;
static PAGE_RECLAIM_FN: ax_sync::SpinLock<Option<PageReclaimFn>> = ax_sync::SpinLock::new(None);
static PAGE_RECLAIM_ACTIVE: AtomicBool = AtomicBool::new(false);
struct PageReclaimLease;
impl PageReclaimLease {
fn try_acquire() -> Option<Self> {
PAGE_RECLAIM_ACTIVE
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
.ok()
.map(|_| Self)
}
}
impl Drop for PageReclaimLease {
fn drop(&mut self) {
PAGE_RECLAIM_ACTIVE.store(false, Ordering::Release);
}
}
pub fn register_page_reclaim_fn(f: PageReclaimFn) {
*PAGE_RECLAIM_FN.lock_irqsave() = Some(f);
}
pub fn try_page_reclaim(num_pages: usize) -> usize {
let Some(_lease) = PageReclaimLease::try_acquire() else {
return 0;
};
let reclaim_fn = { *PAGE_RECLAIM_FN.lock_irqsave() };
reclaim_fn.map_or(0, |f| f(num_pages))
}
#[cfg(any(tlsf, buddy_slab, test))]
pub(crate) fn retry_after_page_reclaim<T>(
target_pages: usize,
mut attempt: impl FnMut() -> AllocResult<T>,
mut reclaim: impl FnMut(usize) -> usize,
) -> AllocResult<T> {
match attempt() {
Ok(value) => return Ok(value),
Err(AllocError::NoMemory) => {}
Err(error) => return Err(error),
}
let target_pages = target_pages.max(MIN_RECLAIM_PAGES);
for _ in 0..MAX_RECLAIM_ATTEMPTS {
let reclaimed = reclaim(target_pages);
match attempt() {
Ok(value) => return Ok(value),
Err(AllocError::NoMemory) if reclaimed != 0 => {}
Err(error) => return Err(error),
}
}
Err(AllocError::NoMemory)
}
#[cfg(any(tlsf, buddy_slab))]
pub(crate) fn retry_after_registered_reclaim<T>(
target_pages: usize,
attempt: impl FnMut() -> AllocResult<T>,
) -> AllocResult<T> {
retry_after_page_reclaim(target_pages, attempt, try_page_reclaim)
}
#[cfg(any(tlsf, buddy_slab))]
pub(crate) const fn layout_reclaim_pages(layout: Layout) -> usize {
layout.size().div_ceil(PAGE_SIZE)
}
mod page;
pub use page::GlobalPage;
#[cfg(feature = "tracking")]
pub mod tracking;
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, VariantArray, IntoStaticStr)]
pub enum UsageKind {
RustHeap,
VirtMem,
PageCache,
PageTable,
TaskStack,
Dma,
Global,
}
#[derive(Clone, Copy)]
pub struct Usages([usize; UsageKind::VARIANTS.len()]);
impl Usages {
const fn new() -> Self {
Self([0; UsageKind::VARIANTS.len()])
}
#[allow(dead_code)]
fn alloc(&mut self, kind: UsageKind, size: usize) {
self.0[kind as usize] += size;
}
#[allow(dead_code)]
fn dealloc(&mut self, kind: UsageKind, size: usize) {
self.0[kind as usize] -= size;
}
pub fn get(&self, kind: UsageKind) -> usize {
self.0[kind as usize]
}
}
impl fmt::Debug for Usages {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d = f.debug_struct("UsageStats");
for &kind in UsageKind::VARIANTS {
d.field(kind.into(), &self.0[kind as usize]);
}
d.finish()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum AllocError {
#[error("invalid allocation parameter")]
InvalidParam,
#[error("allocator is already initialized")]
AlreadyInitialized,
#[error("memory region overlaps an existing allocation region")]
MemoryOverlap,
#[error("not enough memory")]
NoMemory,
#[error("memory was not allocated by this allocator")]
NotAllocated,
#[error("allocator is not initialized")]
NotInitialized,
#[error("allocation was not found")]
NotFound,
}
pub type AllocResult<T = ()> = Result<T, AllocError>;
pub trait AllocatorOps {
fn name(&self) -> &'static str;
fn init(&self, start_vaddr: usize, size: usize) -> AllocResult;
fn add_memory(&self, start_vaddr: usize, size: usize) -> AllocResult;
fn alloc(&self, layout: Layout) -> AllocResult<NonNull<u8>>;
fn dealloc(&self, pos: NonNull<u8>, layout: Layout);
fn alloc_pages(&self, num_pages: usize, align: usize, kind: UsageKind) -> AllocResult<usize>;
fn alloc_dma32_pages(
&self,
num_pages: usize,
align: usize,
kind: UsageKind,
) -> AllocResult<usize>;
fn alloc_pages_at(
&self,
start: usize,
num_pages: usize,
align: usize,
kind: UsageKind,
) -> AllocResult<usize>;
fn dealloc_pages(&self, pos: usize, num_pages: usize, kind: UsageKind);
fn used_bytes(&self) -> usize;
fn available_bytes(&self) -> usize;
fn used_pages(&self) -> usize;
fn available_pages(&self) -> usize;
fn usages(&self) -> Usages;
}
#[cfg(buddy_slab)]
mod buddy_slab;
#[cfg(not(any(tlsf, buddy_slab)))]
mod stub_impl;
#[cfg(tlsf)]
mod tlsf_impl;
#[cfg(buddy_slab)]
use buddy_slab as imp;
pub use imp::{
DefaultByteAllocator, GlobalAllocator, global_add_memory, global_init, init_percpu_slab,
};
#[cfg(not(any(tlsf, buddy_slab)))]
use stub_impl as imp;
#[cfg(tlsf)]
use tlsf_impl as imp;
pub fn global_allocator() -> &'static GlobalAllocator {
imp::global_allocator()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_memory_retries_after_reclaim_progress() {
let mut attempts = 0;
let mut reclaims = 0;
let result = retry_after_page_reclaim(
1,
|| {
attempts += 1;
(attempts == 2).then_some(42).ok_or(AllocError::NoMemory)
},
|target| {
reclaims += 1;
assert_eq!(target, MIN_RECLAIM_PAGES);
1
},
);
assert_eq!(result, Ok(42));
assert_eq!(attempts, 2);
assert_eq!(reclaims, 1);
}
#[test]
fn zero_reclaim_progress_gets_one_concurrent_retry() {
let mut attempts = 0;
let mut reclaims = 0;
let result = retry_after_page_reclaim::<()>(
32,
|| {
attempts += 1;
Err(AllocError::NoMemory)
},
|target| {
reclaims += 1;
assert_eq!(target, 32);
0
},
);
assert_eq!(result, Err(AllocError::NoMemory));
assert_eq!(attempts, 2);
assert_eq!(reclaims, 1);
}
#[test]
fn non_memory_error_does_not_enter_reclaim() {
let mut reclaims = 0;
let result = retry_after_page_reclaim::<()>(
1,
|| Err(AllocError::InvalidParam),
|_| {
reclaims += 1;
1
},
);
assert_eq!(result, Err(AllocError::InvalidParam));
assert_eq!(reclaims, 0);
}
#[test]
fn reclaim_progress_has_a_bounded_retry_budget() {
let mut attempts = 0;
let mut reclaims = 0;
let result = retry_after_page_reclaim::<()>(
usize::MAX,
|| {
attempts += 1;
Err(AllocError::NoMemory)
},
|_| {
reclaims += 1;
1
},
);
assert_eq!(result, Err(AllocError::NoMemory));
assert_eq!(attempts, MAX_RECLAIM_ATTEMPTS + 1);
assert_eq!(reclaims, MAX_RECLAIM_ATTEMPTS);
}
}