#![warn(clippy::style, clippy::pedantic, clippy::perf)]
#![allow(
clippy::missing_errors_doc,
reason = "errors are documented at their definition, not by the functions that use them"
)]
#![warn(
clippy::unnecessary_safety_comment,
clippy::unnecessary_safety_doc,
reason = "minimize confusion over what is and isn't safe"
)]
#![deny(
clippy::missing_safety_doc,
clippy::undocumented_unsafe_blocks,
clippy::multiple_unsafe_ops_per_block,
reason = "DataBuf is delicate and assumptions must be clearly declared and scoped"
)]
use std::{
alloc::Layout,
marker::PhantomData,
mem::MaybeUninit,
num::{NonZeroU32, NonZeroUsize},
ops::{Deref, DerefMut},
ptr::NonNull,
};
use crate::{error::AllocationError, ffi};
#[inline]
fn allocation_layout_size(layout: Layout) -> Result<NonZeroU32, AllocationError> {
let bytes = layout
.size()
.try_into()
.ok()
.ok_or(AllocationError::IntoUIntFailed)?;
NonZeroU32::new(bytes).ok_or(AllocationError::ZeroBytes)
}
#[inline]
fn allocation_array_size<T>(count: usize) -> Result<NonZeroU32, AllocationError> {
allocation_layout_size(Layout::array::<T>(count).map_err(|_| AllocationError::IntoUIntFailed)?)
}
#[inline]
fn allocation_val_size<T: ?Sized>(val: &T) -> Result<NonZeroU32, AllocationError> {
allocation_layout_size(Layout::for_value(val))
}
mod rl_managed {
use super::ffi;
use std::{mem::MaybeUninit, num::NonZeroU32, ptr::NonNull};
#[repr(transparent)]
#[derive(Debug)]
#[must_use]
pub struct RlManaged<T: ?Sized>( NonNull<T>);
#[inline]
pub fn mem_alloc<T>(size: NonZeroU32) -> Option<RlManaged<MaybeUninit<T>>> {
let ptr = unsafe { ffi::MemAlloc(size.get()) }.cast();
NonNull::new(ptr).map(RlManaged)
}
impl<T: ?Sized> RlManaged<T> {
#[inline]
pub(crate) const unsafe fn new(data: NonNull<T>) -> Self {
Self(data)
}
#[inline]
#[must_use]
pub const unsafe fn as_ref(&self) -> &T {
unsafe { self.0.as_ref() }
}
#[inline]
#[must_use]
pub const unsafe fn as_mut(&mut self) -> &mut T {
unsafe { self.0.as_mut() }
}
#[inline]
#[must_use]
pub const fn into_inner(self) -> NonNull<T> {
self.0
}
#[inline]
pub fn mem_realloc<U>(self, size: NonZeroU32) -> Result<RlManaged<MaybeUninit<U>>, Self> {
let new_ptr = unsafe { ffi::MemRealloc(self.0.as_ptr().cast(), size.get()) }.cast();
NonNull::new(new_ptr).map(RlManaged).ok_or(self)
}
#[inline]
pub fn mem_free(self) {
unsafe {
ffi::MemFree(self.0.as_ptr().cast());
}
}
}
impl<T> RlManaged<[T]> {
#[inline]
#[allow(
clippy::needless_pass_by_value,
reason = "passing by reference would allow `data` to be duplicated because `data.0` is Copy"
)]
pub(crate) const fn slice_from_raw_parts(data: RlManaged<T>, len: usize) -> Self {
Self(NonNull::slice_from_raw_parts(data.0, len))
}
}
}
pub use rl_managed::*;
#[derive(Debug)]
#[repr(transparent)]
#[must_use]
pub struct DataBuf<T: ?Sized> {
buf: RlManaged<T>,
_marker: PhantomData<T>,
}
impl<T: ?Sized> Drop for DataBuf<T> {
#[inline]
fn drop(&mut self) {
let mut ptr = MaybeUninit::uninit();
unsafe {
std::ptr::copy_nonoverlapping(std::ptr::from_ref(&self.buf), ptr.as_mut_ptr(), 1);
}
let data = unsafe { ptr.assume_init() }.into_inner();
unsafe {
data.drop_in_place();
}
unsafe { RlManaged::new(data) }.mem_free();
}
}
impl<T: ?Sized> Deref for DataBuf<T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl<T: ?Sized> DerefMut for DataBuf<T> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_mut()
}
}
impl<T: ?Sized> AsRef<T> for DataBuf<T> {
#[inline]
fn as_ref(&self) -> &T {
self
}
}
impl<T: ?Sized> AsMut<T> for DataBuf<T> {
#[inline]
fn as_mut(&mut self) -> &mut T {
self
}
}
impl<T> DataBuf<MaybeUninit<T>> {
#[inline]
pub const fn write(mut self, val: T) -> DataBuf<T> {
let buf = unsafe { self.buf.as_mut() };
MaybeUninit::write(buf, val);
unsafe { self.assume_init() }
}
#[inline]
pub const unsafe fn assume_init(self) -> DataBuf<T> {
unsafe { std::mem::transmute::<DataBuf<MaybeUninit<T>>, DataBuf<T>>(self) }
}
}
impl<T> DataBuf<[MaybeUninit<T>]> {
#[inline]
pub const unsafe fn assume_init(self) -> DataBuf<[T]> {
unsafe { std::mem::transmute::<DataBuf<[MaybeUninit<T>]>, DataBuf<[T]>>(self) }
}
}
impl<T: ?Sized> DataBuf<T> {
#[inline]
#[must_use]
pub const fn as_ref(&self) -> &T {
unsafe { self.buf.as_ref() }
}
#[inline]
#[must_use]
pub const fn as_mut(&mut self) -> &mut T {
unsafe { self.buf.as_mut() }
}
#[inline]
pub(crate) const fn from_rlmanaged(buf: RlManaged<T>) -> Self {
Self {
buf,
_marker: PhantomData,
}
}
#[inline]
pub(crate) const unsafe fn from_nonnull(data: NonNull<T>) -> Self {
let buf = unsafe { RlManaged::new(data) };
Self::from_rlmanaged(buf)
}
#[inline]
pub(crate) const unsafe fn from_raw(ptr: *mut T) -> Option<Self> {
if let Some(buf) = NonNull::new(ptr) {
Some(unsafe { Self::from_nonnull(buf) })
} else {
None
}
}
#[inline]
pub const fn into_inner(self) -> RlManaged<T> {
let mut buf = MaybeUninit::uninit();
unsafe {
std::ptr::copy_nonoverlapping(std::ptr::from_ref(&self.buf), buf.as_mut_ptr(), 1);
}
let buf = unsafe { buf.assume_init() };
std::mem::forget(self); buf
}
}
impl<T> DataBuf<T> {
#[inline]
pub fn alloc() -> Result<DataBuf<MaybeUninit<T>>, AllocationError> {
let bytes = allocation_array_size::<T>(1)?;
let buf = mem_alloc::<T>(bytes).ok_or(AllocationError::NullAlloc)?;
Ok(DataBuf::from_rlmanaged(buf))
}
#[inline]
pub fn alloc_from(val: T) -> Result<Self, (AllocationError, T)> {
match Self::alloc() {
Ok(buf) => Ok(buf.write(val)),
Err(e) => Err((e, val)),
}
}
#[inline]
pub fn alloc_from_clone(src: &T) -> Result<Self, AllocationError>
where
T: Clone,
{
Ok(Self::alloc()?.write(src.clone()))
}
#[inline]
pub fn alloc_from_copy(src: &T) -> Result<Self, AllocationError>
where
T: Copy,
{
Ok(Self::alloc()?.write(*src))
}
}
impl<T> DataBuf<[T]> {
#[inline]
pub(crate) const unsafe fn slice_from_nonnull(buf: NonNull<T>, len: NonZeroUsize) -> Self {
let slice = unsafe { std::slice::from_raw_parts_mut(buf.as_ptr(), len.get()) };
let buf = unsafe { NonNull::new_unchecked(slice) };
unsafe { Self::from_nonnull(buf) }
}
#[inline]
pub(crate) const unsafe fn slice_from_raw(
ptr: *mut T,
count: MaybeUninit<i32>,
) -> Option<Self> {
if let Some(buf) = NonNull::new(ptr) {
let count = unsafe { count.assume_init() };
assert!(count >= 1, "`count` should be positive");
#[cfg(target_pointer_width = "16")]
{
assert!(
count <= usize::MAX as i32,
"`count` should fit within usize"
);
}
#[allow(clippy::cast_sign_loss, reason = "intentional")]
let len = unsafe { NonZeroUsize::new_unchecked(count as usize) };
Some(unsafe { Self::slice_from_nonnull(buf, len) })
} else {
None
}
}
#[inline]
pub fn alloc(count: usize) -> Result<DataBuf<[MaybeUninit<T>]>, AllocationError> {
let bytes = allocation_array_size::<T>(count)?;
let buf = mem_alloc::<T>(bytes).ok_or(AllocationError::NullAlloc)?;
Ok(DataBuf {
buf: RlManaged::slice_from_raw_parts(buf, count),
_marker: PhantomData,
})
}
pub fn alloc_from_clone(src: &[T]) -> Result<Self, AllocationError>
where
T: Copy,
{
let mut buf = Self::alloc(src.len())?;
let uninit_src = unsafe { &*(std::ptr::from_ref::<[T]>(src) as *const [MaybeUninit<T>]) };
buf.copy_from_slice(uninit_src);
Ok(unsafe { buf.assume_init() })
}
pub fn alloc_from_copy(src: &[T]) -> Result<Self, AllocationError>
where
T: Copy,
{
let mut buf = Self::alloc(src.len())?;
let uninit_src = unsafe { &*(std::ptr::from_ref::<[T]>(src) as *const [MaybeUninit<T>]) };
buf.copy_from_slice(uninit_src);
Ok(unsafe { buf.assume_init() })
}
pub fn realloc(
self,
new_count: usize,
) -> Result<DataBuf<[MaybeUninit<T>]>, (AllocationError, Self)> {
match allocation_array_size::<T>(new_count) {
Err(e) => Err((e, self)),
Ok(bytes) => {
let new_buf = self.into_inner().mem_realloc(bytes).map_err(|old_buf| {
(AllocationError::NullAlloc, Self::from_rlmanaged(old_buf))
})?;
let new_buf = RlManaged::slice_from_raw_parts(new_buf, new_count);
Ok(DataBuf::from_rlmanaged(new_buf))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_drop_value() {
struct DropTest<F: FnMut()>(F);
impl<F: FnMut()> Drop for DropTest<F> {
fn drop(&mut self) {
(self.0)();
}
}
impl<F: FnMut()> std::fmt::Debug for DropTest<F> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("DropTest").finish()
}
}
let mut times_dropped = 0;
let buf = DataBuf::alloc_from(DropTest(|| times_dropped += 1)).unwrap();
drop(buf);
assert_eq!(
times_dropped, 1,
"DataBuf should drop contents exactly once"
);
}
#[test]
fn test_from_raw() {
type ExpectTy = [i32; 5];
const EXPECT: [i32; 5] = [64, 264, -57, 653, -153];
let bytes @ 1.. = (std::mem::size_of::<i32>() * EXPECT.len())
.try_into()
.unwrap()
else {
unreachable!()
};
let ptr = unsafe { ffi::MemAlloc(bytes) }.cast::<ExpectTy>();
assert!(!ptr.is_null(), "should be able to allocate");
unsafe {
ptr.write(EXPECT);
};
let buf = unsafe { DataBuf::from_raw(ptr) }.expect("ptr should be convertible to DataBuf");
assert_eq!(&*buf, &EXPECT);
}
#[test]
fn test_slice_from_raw() {
type ExpectTy = [i32; 5];
const EXPECT: ExpectTy = [6, -453, 364, 45632, -1233];
let bytes @ 1.. = (std::mem::size_of::<i32>() * EXPECT.len())
.try_into()
.unwrap()
else {
unreachable!()
};
let ptr = unsafe { ffi::MemAlloc(bytes) }.cast::<i32>();
assert!(!ptr.is_null(), "should be able to allocate");
unsafe {
ptr.cast::<ExpectTy>().write(EXPECT);
};
let buf = unsafe {
DataBuf::slice_from_raw(ptr, MaybeUninit::new(EXPECT.len().try_into().unwrap()))
}
.expect("ptr should be convertible to DataBuf");
assert_eq!(&*buf, &EXPECT);
}
}