use crate::{helper::AllocInit, AllocAll, ReallocInPlace};
use core::{
alloc::{AllocErr, AllocRef, Layout},
fmt,
marker::PhantomData,
mem::{self, MaybeUninit},
ptr::{self, NonNull},
};
pub struct Affix<Alloc, Prefix = (), Suffix = ()> {
pub parent: Alloc,
_prefix: PhantomData<Prefix>,
_suffix: PhantomData<Suffix>,
}
impl<Alloc: fmt::Debug, Prefix, Suffix> fmt::Debug for Affix<Alloc, Prefix, Suffix> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Affix")
.field("parent", &self.parent)
.finish()
}
}
impl<Alloc: Default, Prefix, Suffix> Default for Affix<Alloc, Prefix, Suffix> {
fn default() -> Self {
Self::new(Alloc::default())
}
}
impl<Alloc: Clone, Prefix, Suffix> Clone for Affix<Alloc, Prefix, Suffix> {
fn clone(&self) -> Self {
Self::new(self.parent.clone())
}
}
impl<Alloc: Copy, Prefix, Suffix> Copy for Affix<Alloc, Prefix, Suffix> {}
impl<Alloc: PartialEq, Prefix, Suffix> PartialEq for Affix<Alloc, Prefix, Suffix> {
fn eq(&self, other: &Self) -> bool {
self.parent.eq(&other.parent)
}
}
impl<Alloc: Eq, Prefix, Suffix> Eq for Affix<Alloc, Prefix, Suffix> {}
unsafe impl<Alloc: Send, Prefix, Suffix> Send for Affix<Alloc, Prefix, Suffix> {}
unsafe impl<Alloc: Sync, Prefix, Suffix> Sync for Affix<Alloc, Prefix, Suffix> {}
impl<Alloc: Unpin, Prefix, Suffix> Unpin for Affix<Alloc, Prefix, Suffix> {}
impl<Alloc, Prefix, Suffix> Affix<Alloc, Prefix, Suffix> {
pub const fn new(parent: Alloc) -> Self {
Self {
parent,
_prefix: PhantomData,
_suffix: PhantomData,
}
}
fn allocation_layout(layout: Layout) -> Option<(Layout, usize, usize)> {
let (layout, prefix_offset) = Layout::new::<Prefix>().extend(layout).ok()?;
let (layout, suffix_offset) = layout.extend(Layout::new::<Suffix>()).ok()?;
Some((layout, prefix_offset, suffix_offset))
}
pub unsafe fn prefix(ptr: NonNull<u8>, layout: Layout) -> NonNull<Prefix> {
if mem::size_of::<Prefix>() == 0 {
NonNull::dangling()
} else {
let (_, prefix, _) = Self::allocation_layout(layout).unwrap();
NonNull::new_unchecked(ptr.as_ptr().sub(prefix)).cast()
}
}
pub unsafe fn suffix(ptr: NonNull<u8>, layout: Layout) -> NonNull<Suffix> {
if mem::size_of::<Suffix>() == 0 {
NonNull::dangling()
} else {
let (_, prefix, suffix) = Self::allocation_layout(layout).unwrap();
NonNull::new_unchecked(ptr.as_ptr().add(suffix - prefix)).cast()
}
}
fn create_ptr(ptr: NonNull<[u8]>, offset_prefix: usize, offset_suffix: usize) -> NonNull<[u8]> {
let len = if mem::size_of::<Suffix>() == 0 {
ptr.len() - offset_prefix
} else {
offset_suffix - offset_prefix
};
let ptr = unsafe { NonNull::new_unchecked(ptr.as_mut_ptr().add(offset_prefix)) };
NonNull::slice_from_raw_parts(ptr, len)
}
#[inline]
fn alloc_impl(
layout: Layout,
alloc: impl FnOnce(Layout) -> Result<NonNull<[u8]>, AllocErr>,
) -> Result<NonNull<[u8]>, AllocErr> {
let (layout, offset_prefix, offset_suffix) =
Self::allocation_layout(layout).ok_or(AllocErr)?;
Ok(Self::create_ptr(
alloc(layout)?,
offset_prefix,
offset_suffix,
))
}
#[inline]
unsafe fn grow_impl(
old_ptr: NonNull<u8>,
old_layout: Layout,
new_size: usize,
init: AllocInit,
grow: impl FnOnce(NonNull<u8>, Layout, usize) -> Result<NonNull<[u8]>, AllocErr>,
) -> Result<NonNull<[u8]>, AllocErr> {
let (old_alloc_layout, old_offset_prefix, old_offset_suffix) =
Self::allocation_layout(old_layout).ok_or(AllocErr)?;
let old_base_ptr = NonNull::new_unchecked(old_ptr.as_ptr().sub(old_offset_prefix));
let suffix = Self::suffix(old_ptr, old_layout)
.cast::<MaybeUninit<Suffix>>()
.as_ptr()
.read();
let new_layout =
Layout::from_size_align(new_size, old_layout.align()).map_err(|_| AllocErr)?;
let (new_alloc_layout, new_offset_prefix, new_offset_suffix) =
Self::allocation_layout(new_layout).ok_or(AllocErr)?;
let new_base_ptr = grow(old_base_ptr, old_alloc_layout, new_alloc_layout.size())?;
if init == AllocInit::Zeroed {
ptr::write_bytes(
new_base_ptr
.as_non_null_ptr()
.as_ptr()
.add(old_offset_suffix),
0,
mem::size_of::<Suffix>(),
);
}
let new_ptr = Self::create_ptr(new_base_ptr, new_offset_prefix, new_offset_suffix);
Self::suffix(new_ptr.as_non_null_ptr(), new_layout)
.cast::<MaybeUninit<Suffix>>()
.as_ptr()
.write(suffix);
Ok(new_ptr)
}
#[inline]
unsafe fn shrink_impl(
old_ptr: NonNull<u8>,
old_layout: Layout,
new_size: usize,
shrink: impl FnOnce(NonNull<u8>, Layout, usize) -> Result<NonNull<[u8]>, AllocErr>,
) -> Result<NonNull<[u8]>, AllocErr> {
let (old_alloc_layout, old_offset_prefix, _) =
Self::allocation_layout(old_layout).ok_or(AllocErr)?;
let old_base_ptr = NonNull::new_unchecked(old_ptr.as_ptr().sub(old_offset_prefix));
let suffix = Self::suffix(old_ptr, old_layout)
.cast::<MaybeUninit<Suffix>>()
.as_ptr()
.read();
let new_layout =
Layout::from_size_align(new_size, old_layout.align()).map_err(|_| AllocErr)?;
let (new_alloc_layout, new_offset_prefix, new_offset_suffix) =
Self::allocation_layout(new_layout).ok_or(AllocErr)?;
let new_base_ptr = shrink(old_base_ptr, old_alloc_layout, new_alloc_layout.size())?;
let new_ptr = Self::create_ptr(new_base_ptr, new_offset_prefix, new_offset_suffix);
Self::suffix(new_ptr.as_non_null_ptr(), new_layout)
.cast::<MaybeUninit<Suffix>>()
.as_ptr()
.write(suffix);
Ok(new_ptr)
}
}
unsafe impl<Alloc, Prefix, Suffix> AllocRef for Affix<Alloc, Prefix, Suffix>
where
Alloc: AllocRef,
{
impl_alloc_ref!(parent);
unsafe fn dealloc(&mut self, ptr: NonNull<u8>, layout: Layout) {
let (layout, prefix_offset, _) = Self::allocation_layout(layout).unwrap();
let base_ptr = ptr.as_ptr().sub(prefix_offset);
self.parent
.dealloc(NonNull::new_unchecked(base_ptr), layout)
}
}
unsafe impl<Alloc, Prefix, Suffix> AllocAll for Affix<Alloc, Prefix, Suffix>
where
Alloc: AllocAll,
{
impl_alloc_all!(parent);
}
unsafe impl<Alloc, Prefix, Suffix> ReallocInPlace for Affix<Alloc, Prefix, Suffix>
where
Alloc: ReallocInPlace,
{
impl_realloc_in_place!(parent);
}
#[cfg(test)]
mod tests {
#![allow(clippy::wildcard_imports)]
use super::*;
use crate::helper::tracker;
use core::fmt;
use std::alloc::System;
#[allow(clippy::too_many_lines)]
fn test_alloc<Prefix, Suffix>(
prefix: Prefix,
layout: Layout,
suffix: Suffix,
offset_prefix: usize,
offset_suffix: usize,
) where
Prefix: fmt::Debug + Copy + PartialEq,
Suffix: fmt::Debug + Copy + PartialEq,
{
unsafe {
let mut alloc = tracker(Affix::<_, Prefix, Suffix>::new(tracker(System)));
let memory = alloc
.alloc_zeroed(layout)
.unwrap_or_else(|_| panic!("Could not allocate {} bytes", layout.size()));
if mem::size_of::<Prefix>() == 0 {
assert_eq!(
Affix::<System, Prefix, Suffix>::prefix(memory.as_non_null_ptr(), layout),
NonNull::dangling()
);
} else {
assert_eq!(
Affix::<System, Prefix, Suffix>::prefix(memory.as_non_null_ptr(), layout)
.cast()
.as_ptr(),
memory.as_mut_ptr().sub(offset_prefix)
);
}
if mem::size_of::<Suffix>() == 0 {
assert_eq!(
Affix::<System, Prefix, Suffix>::suffix(memory.as_non_null_ptr(), layout),
NonNull::dangling()
);
} else {
assert_eq!(
Affix::<System, Prefix, Suffix>::suffix(memory.as_non_null_ptr(), layout)
.cast()
.as_ptr(),
memory.as_mut_ptr().add(offset_suffix)
);
}
Affix::<System, Prefix, Suffix>::prefix(memory.as_non_null_ptr(), layout)
.as_ptr()
.write(prefix);
Affix::<System, Prefix, Suffix>::suffix(memory.as_non_null_ptr(), layout)
.as_ptr()
.write(suffix);
assert_eq!(
Affix::<System, Prefix, Suffix>::prefix(memory.as_non_null_ptr(), layout).as_ref(),
&prefix
);
assert_eq!(
Affix::<System, Prefix, Suffix>::suffix(memory.as_non_null_ptr(), layout).as_ref(),
&suffix
);
let old_size = memory.len();
let memory = alloc
.grow_zeroed(memory.as_non_null_ptr(), layout, memory.len() * 2)
.expect("Could not grow allocation");
let layout =
Layout::from_size_align(memory.len(), layout.align()).expect("Invalid layout");
for i in old_size..memory.len() {
assert_eq!(*memory.get_unchecked_mut(i).as_ref(), 0);
}
assert_eq!(
Affix::<System, Prefix, Suffix>::prefix(memory.as_non_null_ptr(), layout).as_ref(),
&prefix
);
assert_eq!(
Affix::<System, Prefix, Suffix>::suffix(memory.as_non_null_ptr(), layout).as_ref(),
&suffix
);
let memory = alloc
.shrink(memory.as_non_null_ptr(), layout, layout.size())
.expect("Could not shrink allocation");
let layout =
Layout::from_size_align(memory.len(), layout.align()).expect("Invalid layout");
assert_eq!(
Affix::<System, Prefix, Suffix>::prefix(memory.as_non_null_ptr(), layout).as_ref(),
&prefix
);
assert_eq!(
Affix::<System, Prefix, Suffix>::suffix(memory.as_non_null_ptr(), layout).as_ref(),
&suffix
);
alloc.dealloc(memory.as_non_null_ptr(), layout);
}
}
#[test]
fn test_alloc_u16_u32_u16() {
test_alloc::<u16, u16>(0xDEDE, Layout::new::<u32>(), 0xEFEF, 4, 4)
}
#[test]
fn test_alloc_zst_u32_zst() {
test_alloc::<(), ()>((), Layout::new::<u32>(), (), 0, 0)
}
#[test]
fn test_alloc_zst_u32_u16() {
test_alloc::<(), u16>((), Layout::new::<u32>(), 0xEFEF, 0, 4)
}
#[test]
fn test_alloc_u16_u64_zst() {
test_alloc::<u16, ()>(0xDEDE, Layout::new::<u32>(), (), 4, 0)
}
#[repr(align(1024))]
#[derive(Debug, Copy, Clone, PartialEq)]
struct AlignTo1024 {
a: u16,
}
#[repr(align(64))]
#[derive(Debug, Copy, Clone, PartialEq)]
struct AlignTo64;
#[test]
fn test_alloc_a1024_u32_zst() {
test_alloc::<AlignTo1024, ()>(AlignTo1024 { a: 0xDEDE }, Layout::new::<u32>(), (), 1024, 0)
}
#[test]
fn test_alloc_u16_u32_a1024() {
test_alloc::<u16, AlignTo1024>(
0xDEDE,
Layout::new::<u32>(),
AlignTo1024 { a: 0xEFEF },
4,
1020,
)
}
#[test]
fn test_alloc_a64_u32_zst() {
test_alloc::<AlignTo64, ()>(AlignTo64, Layout::new::<u32>(), (), 0, 0)
}
#[test]
fn test_alloc_u16_u32_a64() {
test_alloc::<u16, AlignTo64>(0xDEDE, Layout::new::<u32>(), AlignTo64, 4, 0)
}
}