use std::ptr::NonNull;
use std::rc::Rc;
use std::cell::RefCell as InteriorCell;
use crate::transport::{LKey, RKey, UdTransport};
use crate::util::{buffer::*, huge_alloc::*};
struct InBuddyBuffer {
buf: NonNull<u8>,
lkey: LKey,
rkey: RKey,
}
impl InBuddyBuffer {
#[inline(always)]
pub fn new(buf: NonNull<u8>, lkey: LKey, rkey: RKey) -> Self {
Self { buf, lkey, rkey }
}
#[inline(always)]
pub unsafe fn offset(&self, offset: usize) -> InBuddyBuffer {
InBuddyBuffer {
buf: NonNull::new_unchecked(self.buf.as_ptr().add(offset)),
lkey: self.lkey,
rkey: self.rkey,
}
}
}
struct BuddyAllocatorInner {
buddy: [Vec<InBuddyBuffer>; Self::NUM_CLASSES],
#[allow(dead_code)]
mem_registry: Vec<HugeAlloc>,
next_alloc: usize,
}
impl BuddyAllocatorInner {
const MIN_ALLOC_SIZE: usize = 1 << 6;
const MAX_ALLOC_SIZE: usize = 1 << 24;
const NUM_CLASSES: usize =
(Self::MAX_ALLOC_SIZE / Self::MIN_ALLOC_SIZE).trailing_zeros() as usize + 1;
#[cold]
fn reserve_memory(&mut self, tp: &mut UdTransport) {
let len = self.next_alloc;
self.next_alloc *= 2;
debug_assert!(len % Self::MAX_ALLOC_SIZE == 0);
let mem = alloc_raw(len);
let (lkey, rkey) = unsafe { tp.reg_mem(mem.ptr, len) };
for i in 0..(len / Self::MAX_ALLOC_SIZE) {
self.buddy[Self::NUM_CLASSES - 1].push(InBuddyBuffer::new(
unsafe { NonNull::new_unchecked(mem.ptr.add(i * Self::MAX_ALLOC_SIZE)) },
lkey,
rkey,
));
}
self.mem_registry.push(mem);
}
#[inline]
const fn size_of_class(class: usize) -> usize {
Self::MIN_ALLOC_SIZE << class
}
#[inline]
const fn class_of(len: usize) -> usize {
let len = len.next_power_of_two();
if len < Self::MIN_ALLOC_SIZE {
0
} else {
(len / Self::MIN_ALLOC_SIZE).trailing_zeros() as usize
}
}
#[inline]
fn split(&mut self, class: usize) {
debug_assert!((1..Self::NUM_CLASSES).contains(&class));
debug_assert!(!self.buddy[class].is_empty());
let size_after_split = Self::size_of_class(class - 1);
let buf1 = self.buddy[class].pop().unwrap();
let buf2 = unsafe { buf1.offset(size_after_split) };
self.buddy[class - 1].push(buf1);
self.buddy[class - 1].push(buf2);
}
}
impl BuddyAllocatorInner {
pub fn new() -> Self {
Self {
buddy: Default::default(),
mem_registry: Vec::new(),
next_alloc: Self::MAX_ALLOC_SIZE,
}
}
pub fn alloc(
&mut self,
len: usize,
tp: &mut UdTransport,
owner: &Rc<BuddyAllocator>,
) -> Buffer {
assert!(
len <= Self::MAX_ALLOC_SIZE,
"requested buffer too large (maximum: {}MB)",
Self::MAX_ALLOC_SIZE >> 20
);
let class = Self::class_of(len);
if self.buddy[class].is_empty() {
let higher_class =
((class + 1)..Self::NUM_CLASSES).find(|&c| !self.buddy[c].is_empty());
let higher_class = higher_class.unwrap_or_else(|| {
self.reserve_memory(tp);
Self::NUM_CLASSES - 1
});
debug_assert!(!self.buddy[higher_class].is_empty());
for i in ((class + 1)..=higher_class).rev() {
self.split(i);
}
debug_assert!(!self.buddy[class].is_empty());
}
let buf = self.buddy[class].pop().unwrap();
Buffer::real(
buf.buf,
Self::size_of_class(class),
buf.lkey,
buf.rkey,
Some(owner.clone()),
)
}
pub fn free(&mut self, buf: &Buffer) {
let class = Self::class_of(buf.len());
self.buddy[class].push(InBuddyBuffer::new(
unsafe { NonNull::new_unchecked(buf.as_ptr()) },
buf.lkey(),
buf.rkey(),
));
}
}
#[repr(transparent)]
pub(crate) struct BuddyAllocator {
inner: InteriorCell<BuddyAllocatorInner>,
}
impl BuddyAllocator {
pub const MAX_ALLOC_SIZE: usize = 1 << 24;
pub fn new() -> Self {
Self {
inner: InteriorCell::new(BuddyAllocatorInner::new()),
}
}
pub fn alloc(self: &Rc<Self>, len: usize, tp: &mut UdTransport) -> Buffer {
self.inner.borrow_mut().alloc(len, tp, self)
}
pub fn free(&self, buf: &Buffer) {
self.inner.borrow_mut().free(buf)
}
}