use crate::strategies::cpu_id::NoCpuId;
use crate::util::cache::CachePadded;
use crate::{AllocError, CpuId, InitError, PageSize, PhysRange, PhysicalAllocator, RegionInit};
use core::marker::PhantomData;
use core::mem::{ManuallyDrop, MaybeUninit};
use core::num::NonZeroUsize;
use core::ptr;
use core::sync::atomic::AtomicUsize;
use core::sync::atomic::Ordering::Relaxed;
pub(crate) const fn pad_cells<A, const N: usize>(items: [A; N]) -> [CachePadded<A>; N] {
let src = ManuallyDrop::new(items);
let src_ptr = &src as *const ManuallyDrop<[A; N]> as *const A;
let mut cells: [MaybeUninit<CachePadded<A>>; N] = [const { MaybeUninit::uninit() }; N];
let mut i = 0;
while i < N {
let a = unsafe { ptr::read(src_ptr.add(i)) };
cells[i] = MaybeUninit::new(CachePadded::new(a));
i += 1;
}
unsafe {
ptr::read(&cells as *const [MaybeUninit<CachePadded<A>>; N] as *const [CachePadded<A>; N])
}
}
pub struct RegionedAllocator<const REGIONS: usize, A, S = NoCpuId> {
regions: [CachePadded<A>; REGIONS],
bounds: [(AtomicUsize, AtomicUsize); REGIONS],
base_frame: PageSize,
_selector: PhantomData<fn() -> S>,
}
impl<const REGIONS: usize, A, S> RegionedAllocator<REGIONS, A, S> {
pub const fn new(base_frame: PageSize, regions: [A; REGIONS]) -> Self {
assert!(REGIONS > 0, "REGIONS must be > 0");
Self {
regions: pad_cells(regions),
bounds: [const { (AtomicUsize::new(0), AtomicUsize::new(0)) }; REGIONS],
base_frame,
_selector: PhantomData,
}
}
pub unsafe fn try_init_at(
&self,
idx: usize,
phys_base: usize,
span_len: usize,
usable: &[PhysRange],
) -> Result<(), InitError>
where
A: RegionInit,
{
assert!(
idx < REGIONS,
"region index {idx} out of range (REGIONS={REGIONS})"
);
if !phys_base.is_multiple_of(self.base_frame.bytes()) {
return Err(InitError::Misaligned {
required: self.base_frame.bytes(),
});
}
if span_len == 0 || !span_len.is_multiple_of(self.base_frame.bytes()) {
return Err(InitError::InvalidSpan);
}
let span_end = phys_base
.checked_add(span_len)
.ok_or(InitError::InvalidSpan)?;
if self.bounds[idx].0.load(Relaxed) != 0 || self.bounds[idx].1.load(Relaxed) != 0 {
return Err(InitError::AlreadyInitialized);
}
for j in 0..REGIONS {
if j == idx {
continue;
}
let lo = self.bounds[j].0.load(Relaxed);
let hi = self.bounds[j].1.load(Relaxed);
if hi != 0 && phys_base < hi && span_end > lo {
return Err(InitError::OverlapsRegion { other: j });
}
}
unsafe { self.regions[idx].try_init(phys_base, span_len, usable) }?;
self.bounds[idx].0.store(phys_base, Relaxed);
self.bounds[idx].1.store(span_end, Relaxed);
Ok(())
}
pub unsafe fn init_at(
&self,
idx: usize,
phys_base: usize,
span_len: usize,
usable: &[PhysRange],
) where
A: RegionInit,
{
match unsafe { self.try_init_at(idx, phys_base, span_len, usable) } {
Ok(()) => {}
Err(e) => panic!("RegionedAllocator::init_at failed: {e:?}"),
}
}
pub unsafe fn add_usable(&self, base: usize, len: usize)
where
A: RegionInit,
{
let Some(owner) = self.region_of(base) else {
debug_assert!(false, "add_usable: base {base:#x} not owned by any region");
return;
};
debug_assert!(
base.checked_add(len)
.is_some_and(|end| end <= self.bounds[owner].1.load(Relaxed)),
"add_usable range escapes its owning region"
);
unsafe { self.regions[owner].add_usable(base, len) };
}
pub fn alloc_in_region(
&self,
idx: usize,
ps: PageSize,
count: NonZeroUsize,
) -> Result<usize, AllocError>
where
A: PhysicalAllocator,
{
assert!(
idx < REGIONS,
"region index {idx} out of range (REGIONS={REGIONS})"
);
self.regions[idx].allocate_physical(ps, count)
}
pub fn alloc_in_chain(
&self,
chain: &[usize],
ps: PageSize,
count: NonZeroUsize,
) -> Result<usize, AllocError>
where
A: PhysicalAllocator,
{
for &idx in chain {
assert!(
idx < REGIONS,
"region index {idx} in chain out of range (REGIONS={REGIONS})"
);
match self.regions[idx].allocate_physical(ps, count) {
Ok(phys) => return Ok(phys),
Err(AllocError::OutOfMemory) => continue,
Err(e) => return Err(e),
}
}
Err(AllocError::OutOfMemory)
}
#[inline]
fn region_of(&self, phys: usize) -> Option<usize> {
for i in 0..REGIONS {
let lo = self.bounds[i].0.load(Relaxed);
let hi = self.bounds[i].1.load(Relaxed);
if phys >= lo && phys < hi {
return Some(i);
}
}
None
}
#[cfg(any(feature = "stats", test))]
pub(crate) fn regions(&self) -> impl ExactSizeIterator<Item = &A> {
self.regions.iter().map(|r| &**r)
}
#[cfg(test)]
pub(crate) fn region_bounds(&self, idx: usize) -> (usize, usize) {
(
self.bounds[idx].0.load(Relaxed),
self.bounds[idx].1.load(Relaxed),
)
}
}
unsafe impl<const REGIONS: usize, A, S> PhysicalAllocator for RegionedAllocator<REGIONS, A, S>
where
A: PhysicalAllocator,
S: CpuId,
{
fn allocate_physical(&self, ps: PageSize, count: NonZeroUsize) -> Result<usize, AllocError> {
let start = S::current_cpu() % REGIONS;
for offset in 0..REGIONS {
let i = (start + offset) % REGIONS;
match self.regions[i].allocate_physical(ps, count) {
Ok(phys) => return Ok(phys),
Err(AllocError::OutOfMemory) => continue,
Err(e) => return Err(e),
}
}
Err(AllocError::OutOfMemory)
}
unsafe fn deallocate_physical(&self, ps: PageSize, count: NonZeroUsize, phys: usize) {
match self.region_of(phys) {
Some(owner) => unsafe { self.regions[owner].deallocate_physical(ps, count, phys) },
None => debug_assert!(
false,
"deallocate_physical: phys {phys:#x} not owned by any region"
),
}
}
}
#[cfg(any(feature = "stats", test))]
impl<const REGIONS: usize, A: crate::AllocatorStats, S> crate::AllocatorStats
for RegionedAllocator<REGIONS, A, S>
{
fn total_bytes(&self) -> usize {
self.regions().map(|r| r.total_bytes()).sum()
}
fn free_bytes(&self) -> usize {
self.regions().map(|r| r.free_bytes()).sum()
}
fn largest_free_bytes(&self) -> usize {
self.regions()
.map(|r| r.largest_free_bytes())
.max()
.unwrap_or(0)
}
}