#![allow(unsafe_code)]
use std::alloc::{self, Layout};
use std::ops::{Deref, DerefMut};
use std::ptr::NonNull;
pub struct AlignedBuf {
ptr: NonNull<u8>,
layout: Layout,
len: usize,
}
unsafe impl Send for AlignedBuf {}
impl AlignedBuf {
#[must_use]
pub fn new(size: usize) -> Self {
assert!(size > 0, "buffer size must be non-zero");
let page_size = page_size();
let size = size.next_multiple_of(page_size);
#[cfg(unix)]
let (ptr, layout) = alloc_unix(size, page_size);
#[cfg(windows)]
let (ptr, layout) = alloc_windows(size, page_size);
#[cfg(not(any(unix, windows)))]
let (ptr, layout) = alloc_fallback(size, page_size);
Self { ptr, layout, len: size }
}
#[must_use]
pub fn len(&self) -> usize {
self.len
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
}
impl Deref for AlignedBuf {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
}
}
impl DerefMut for AlignedBuf {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
}
}
impl Drop for AlignedBuf {
fn drop(&mut self) {
#[cfg(unix)]
{
unsafe {
alloc::dealloc(self.ptr.as_ptr(), self.layout);
}
}
#[cfg(windows)]
{
unsafe {
_aligned_free(self.ptr.as_ptr().cast::<std::ffi::c_void>());
}
}
#[cfg(not(any(unix, windows)))]
{
unsafe {
alloc::dealloc(self.ptr.as_ptr(), self.layout);
}
}
}
}
#[must_use]
fn page_size() -> usize {
#[cfg(unix)]
{
rustix::param::page_size()
}
#[cfg(windows)]
{
use windows_sys::Win32::System::SystemInformation::{GetSystemInfo, SYSTEM_INFO};
let mut sysinfo: SYSTEM_INFO = unsafe { std::mem::zeroed() };
unsafe {
GetSystemInfo(&mut sysinfo);
}
sysinfo.dwPageSize as usize
}
#[cfg(not(any(unix, windows)))]
{
4096 }
}
#[cfg(unix)]
fn alloc_unix(size: usize, page_size: usize) -> (NonNull<u8>, Layout) {
let layout =
Layout::from_size_align(size, page_size).expect("invalid layout for page-aligned buffer");
let ptr = unsafe { alloc::alloc(layout) };
let ptr = NonNull::new(ptr).expect("page-aligned allocation failed");
(ptr, layout)
}
#[cfg(windows)]
fn alloc_windows(size: usize, page_size: usize) -> (NonNull<u8>, Layout) {
let ptr = unsafe { _aligned_malloc(size, page_size) };
if ptr.is_null() {
panic!("page-aligned allocation failed");
}
let ptr = unsafe { NonNull::new_unchecked(ptr.cast::<u8>()) };
let layout = Layout::from_size_align(1, 1).unwrap();
(ptr, layout)
}
#[cfg(not(any(unix, windows)))]
fn alloc_fallback(size: usize, page_size: usize) -> (NonNull<u8>, Layout) {
let layout =
Layout::from_size_align(size, page_size).expect("invalid layout for page-aligned buffer");
let ptr = unsafe { alloc::alloc(layout) };
let ptr = NonNull::new(ptr).expect("page-aligned allocation failed");
(ptr, layout)
}
#[cfg(windows)]
unsafe extern "C" {
fn _aligned_malloc(size: usize, alignment: usize) -> *mut std::ffi::c_void;
fn _aligned_free(ptr: *mut std::ffi::c_void);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn allocates_page_aligned_buffer() {
let buf = AlignedBuf::new(1024);
assert!(buf.len() >= 1024);
assert!(!buf.is_empty());
let ptr = buf.ptr.as_ptr() as usize;
let ps = page_size();
assert_eq!(ptr % ps, 0, "buffer not page-aligned");
}
#[test]
fn buffer_is_writable_via_deref_mut() {
let mut buf = AlignedBuf::new(1024);
buf[0] = 42;
buf[999] = 24;
assert_eq!(buf[0], 42);
assert_eq!(buf[999], 24);
}
#[test]
fn deref_returns_full_slice() {
let buf = AlignedBuf::new(512);
assert_eq!(buf.len(), buf.len());
let slice: &[u8] = &buf;
assert_eq!(slice.len(), buf.len());
}
#[test]
fn respects_minimum_page_size() {
let buf = AlignedBuf::new(1);
let ps = page_size();
assert!(buf.len() >= ps);
}
#[test]
fn size_is_page_multiple() {
let buf = AlignedBuf::new(100_000);
let ps = page_size();
assert_eq!(buf.len() % ps, 0);
}
}