use crate::bpf_intf::scx_userspace_arena_alloc_pages_args;
use crate::bpf_intf::scx_userspace_arena_free_pages_args;
use anyhow::Result;
use buddy_system_allocator::Heap;
use libbpf_rs::ProgramInput;
use std::alloc::Layout;
use std::ptr::NonNull;
use std::sync::Mutex;
pub unsafe trait Allocator {
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, anyhow::Error>;
fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, anyhow::Error> {
let ptr = self.allocate(layout)?;
let slice: &mut [u8] = unsafe { &mut *ptr.as_ptr() };
slice.fill(0);
Ok(ptr)
}
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout);
}
type FreeList = Vec<(NonNull<[u8]>, Layout)>;
pub struct HeapAllocator<T>
where
T: Allocator,
{
backing_allocator: T,
alloc: Mutex<(Heap<31>, FreeList)>,
}
impl<T> HeapAllocator<T>
where
T: Allocator,
{
pub fn new(backing_allocator: T) -> Self {
Self {
backing_allocator,
alloc: Mutex::new((Heap::empty(), Vec::new())),
}
}
}
impl<T> Drop for HeapAllocator<T>
where
T: Allocator,
{
fn drop(&mut self) {
for a in self.alloc.get_mut().unwrap().1.iter() {
let first_byte_pointer = unsafe {
NonNull::new_unchecked(a.0.as_ptr() as *mut u8)
};
unsafe {
self.backing_allocator.deallocate(first_byte_pointer, a.1);
}
}
}
}
unsafe impl<T> Allocator for HeapAllocator<T>
where
T: Allocator,
{
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, anyhow::Error> {
let mut guard = self.alloc.lock().unwrap();
let (alloc, free_list) = &mut *guard;
if let Ok(a) = alloc.alloc(layout) {
return Ok(NonNull::slice_from_raw_parts(a, layout.size()));
}
let next_allocation_size = alloc
.stats_total_bytes()
.next_power_of_two()
.clamp(16 * 1024, 1024 * 1024);
let backing_layout = if layout.size() > next_allocation_size {
layout
} else {
Layout::from_size_align(next_allocation_size, 1)?
};
let ptr = self.backing_allocator.allocate(backing_layout)?;
free_list.push((ptr, backing_layout));
unsafe {
alloc.init(ptr.cast::<u8>().as_ptr() as usize, backing_layout.size())
};
alloc
.alloc(layout)
.map(|a| NonNull::slice_from_raw_parts(a, layout.size()))
.map_err(|_| anyhow::anyhow!("failed to allocate"))
}
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
self.alloc.lock().unwrap().0.dealloc(ptr, layout)
}
}
pub unsafe fn call_allocate_program(
prog: &libbpf_rs::ProgramMut<'_>,
layout: Layout,
) -> anyhow::Result<NonNull<[u8]>> {
let mut args = scx_userspace_arena_alloc_pages_args {
sz: u32::try_from(layout.size())?,
ret: std::ptr::null_mut(),
};
let input = ProgramInput {
context_in: Some(unsafe {
std::slice::from_raw_parts_mut(
&mut args as *mut _ as *mut u8,
std::mem::size_of_val(&args),
)
}),
..Default::default()
};
prog.test_run(input)?;
let base = NonNull::new(args.ret as *mut u8)
.ok_or_else(|| anyhow::anyhow!("arena allocation failed"))?;
Ok(NonNull::slice_from_raw_parts(base, args.sz as usize))
}
pub unsafe fn call_deallocate_program(
prog: &libbpf_rs::ProgramMut<'_>,
addr: NonNull<u8>,
layout: Layout,
) {
let mut args = scx_userspace_arena_free_pages_args {
addr: addr.as_ptr() as *mut std::ffi::c_void,
sz: u32::try_from(layout.size())
.expect("memory allocated in the arena must fit in 32-bits"),
};
let input = ProgramInput {
context_in: Some(unsafe {
std::slice::from_raw_parts_mut(
&mut args as *mut _ as *mut u8,
std::mem::size_of_val(&args),
)
}),
..Default::default()
};
prog.test_run(input).unwrap();
}