use {
agave_io_uring::{Ring, RingOp},
std::{
io,
ops::{Deref, DerefMut},
ptr::{self, NonNull},
slice,
},
};
const FIXED_BUFFER_LEN: usize = 1024 * 1024 * 1024;
pub enum LargeBuffer {
Vec(Vec<u8>),
HugeTable(PageAlignedMemory),
}
impl Deref for LargeBuffer {
type Target = [u8];
fn deref(&self) -> &Self::Target {
match self {
Self::Vec(buf) => buf.as_slice(),
Self::HugeTable(mem) => mem.deref(),
}
}
}
impl DerefMut for LargeBuffer {
fn deref_mut(&mut self) -> &mut Self::Target {
match self {
Self::Vec(buf) => buf.as_mut_slice(),
Self::HugeTable(ref mut mem) => mem.deref_mut(),
}
}
}
impl AsMut<[u8]> for LargeBuffer {
fn as_mut(&mut self) -> &mut [u8] {
match self {
Self::Vec(vec) => vec.as_mut_slice(),
LargeBuffer::HugeTable(ref mut mem) => mem,
}
}
}
impl LargeBuffer {
pub fn new(size: usize) -> Self {
if size > PageAlignedMemory::page_size() {
let size = size.next_power_of_two();
if let Ok(alloc) = PageAlignedMemory::alloc_huge_table(size) {
log::info!("obtained hugetable io_uring buffer (len={size})");
return Self::HugeTable(alloc);
}
}
Self::Vec(vec![0; size])
}
}
#[derive(Debug)]
struct AllocError;
pub struct PageAlignedMemory {
ptr: NonNull<u8>,
len: usize,
}
impl PageAlignedMemory {
fn alloc_huge_table(memory_size: usize) -> Result<Self, AllocError> {
let page_size = Self::page_size();
debug_assert!(memory_size.is_power_of_two());
debug_assert!(page_size.is_power_of_two());
let aligned_size = memory_size.next_multiple_of(page_size);
let ptr = unsafe {
libc::mmap(
ptr::null_mut(),
aligned_size,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_HUGETLB,
-1,
0,
)
};
if std::ptr::eq(ptr, libc::MAP_FAILED) {
return Err(AllocError);
}
Ok(Self {
ptr: NonNull::new(ptr as *mut u8).ok_or(AllocError)?,
len: aligned_size,
})
}
fn page_size() -> usize {
unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
}
}
impl Drop for PageAlignedMemory {
fn drop(&mut self) {
unsafe {
libc::munmap(self.ptr.as_ptr() as *mut libc::c_void, self.len);
}
}
}
impl Deref for PageAlignedMemory {
type Target = [u8];
fn deref(&self) -> &Self::Target {
unsafe { slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
}
}
impl DerefMut for PageAlignedMemory {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
}
}
#[derive(Debug)]
pub(super) struct FixedIoBuffer {
ptr: *mut u8,
size: usize,
io_buf_index: Option<u16>,
}
impl FixedIoBuffer {
pub const fn empty() -> Self {
Self {
ptr: std::ptr::null_mut(),
size: 0,
io_buf_index: None,
}
}
#[allow(clippy::arithmetic_side_effects)]
pub unsafe fn split_buffer_chunks(
buffer: &mut [u8],
chunk_size: usize,
) -> impl Iterator<Item = Self> + use<'_> {
assert!(
buffer.len() / FIXED_BUFFER_LEN <= u16::MAX as usize,
"buffer too large to register in io_uring"
);
let buf_start = buffer.as_ptr() as usize;
buffer.chunks_exact_mut(chunk_size).map(move |buf| {
let io_buf_index = (buf.as_ptr() as usize - buf_start) / FIXED_BUFFER_LEN;
Self {
ptr: buf.as_mut_ptr(),
size: buf.len(),
io_buf_index: Some(io_buf_index as u16),
}
})
}
pub fn len(&self) -> usize {
self.size
}
pub unsafe fn as_mut_ptr(&self) -> *mut u8 {
self.ptr
}
pub fn io_buf_index(&self) -> Option<u16> {
self.io_buf_index
}
pub fn into_shrinked(self, size: usize) -> Self {
assert!(size <= self.size);
Self {
ptr: self.ptr,
size,
io_buf_index: self.io_buf_index,
}
}
pub unsafe fn register<S, E: RingOp<S>>(
buffer: &mut [u8],
ring: &Ring<S, E>,
) -> io::Result<()> {
let iovecs = buffer
.chunks(FIXED_BUFFER_LEN)
.map(|buf| libc::iovec {
iov_base: buf.as_ptr() as _,
iov_len: buf.len(),
})
.collect::<Vec<_>>();
unsafe { ring.register_buffers(&iovecs) }
}
}
impl AsRef<[u8]> for FixedIoBuffer {
fn as_ref(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.ptr, self.size) }
}
}