use core::{
alloc::{AllocErr, AllocInit, AllocRef, Layout, MemoryBlock, ReallocPlacement},
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,
}
}
const fn align(request: Layout) -> usize {
let prefix_align = mem::align_of::<Prefix>();
if prefix_align < request.align() {
request.align()
} else {
prefix_align
}
}
#[allow(clippy::question_mark)]
const fn allocation_layout(request: Layout) -> Option<(usize, Layout, usize)> {
let prefix_layout = Layout::new::<Prefix>();
let prefix_offset = if let Some(offset) = prefix_layout
.size()
.checked_add(prefix_layout.padding_needed_for(request.align()))
{
offset
} else {
return None;
};
let size = if let Some(size) = prefix_offset.checked_add(request.size()) {
size
} else {
return None;
};
let prefix_request_layout =
if let Ok(layout) = Layout::from_size_align(size, Self::align(request)) {
layout
} else {
return None;
};
let padding = prefix_request_layout.padding_needed_for(mem::align_of::<Suffix>());
let (_, prefix_suffix_offset) =
if let Some(prefix_suffix_offset) = prefix_request_layout.size().checked_add(padding) {
(
prefix_suffix_offset.wrapping_sub(prefix_offset),
prefix_suffix_offset,
)
} else {
return None;
};
let size = if let Some(size) = prefix_suffix_offset.checked_add(mem::size_of::<Suffix>()) {
size
} else {
return None;
};
let align = Self::align(request);
if let Ok(layout) = Layout::from_size_align(size, align) {
Some((prefix_offset, layout, prefix_suffix_offset))
} else {
None
}
}
const fn prefix_offset(request: Layout) -> usize {
let prefix = Layout::new::<Prefix>();
prefix.size() + prefix.padding_needed_for(request.align())
}
const unsafe fn suffix_offset(request: Layout) -> (usize, usize) {
let prefix_offset = Self::prefix_offset(request);
let prefix_request_layout =
Layout::from_size_align_unchecked(prefix_offset + request.size(), Self::align(request));
let padding = prefix_request_layout.padding_needed_for(mem::align_of::<Suffix>());
let prefix_suffix_offset = prefix_request_layout.size() + padding;
(prefix_suffix_offset - prefix_offset, prefix_suffix_offset)
}
const unsafe fn unchecked_allocation_layout(request: Layout) -> (usize, Layout, usize) {
let (_, prefix_suffix_offset) = Self::suffix_offset(request);
let size = prefix_suffix_offset + mem::size_of::<Suffix>();
let prefix_offset = Self::prefix_offset(request);
let layout = Layout::from_size_align_unchecked(size, Self::align(request));
(prefix_offset, layout, prefix_suffix_offset)
}
pub unsafe fn prefix(ptr: NonNull<u8>, layout: Layout) -> NonNull<Prefix> {
if mem::size_of::<Prefix>() == 0 {
NonNull::dangling()
} else {
NonNull::new_unchecked(ptr.as_ptr().sub(Self::prefix_offset(layout))).cast()
}
}
pub unsafe fn suffix(ptr: NonNull<u8>, layout: Layout) -> NonNull<Suffix> {
if mem::size_of::<Suffix>() == 0 {
NonNull::dangling()
} else {
NonNull::new_unchecked(ptr.as_ptr().add(Self::suffix_offset(layout).0)).cast()
}
}
}
unsafe impl<Alloc, Prefix, Suffix> AllocRef for Affix<Alloc, Prefix, Suffix>
where
Alloc: AllocRef,
{
fn alloc(&mut self, layout: Layout, init: AllocInit) -> Result<MemoryBlock, AllocErr> {
let (offset_prefix, layout, offset_suffix) =
Self::allocation_layout(layout).ok_or(AllocErr)?;
let memory = self.parent.alloc(layout, init)?;
Ok(MemoryBlock {
ptr: unsafe { NonNull::new_unchecked(memory.ptr.as_ptr().add(offset_prefix)) },
size: if mem::size_of::<Suffix>() == 0 {
memory.size - offset_prefix
} else {
offset_suffix - offset_prefix
},
})
}
unsafe fn dealloc(&mut self, ptr: NonNull<u8>, layout: Layout) {
debug_assert_eq!(
Self::allocation_layout(layout).unwrap(),
Self::unchecked_allocation_layout(layout)
);
let (prefix_offset, layout, _) = Self::unchecked_allocation_layout(layout);
let base_ptr = ptr.as_ptr().sub(prefix_offset);
self.parent
.dealloc(NonNull::new_unchecked(base_ptr), layout)
}
unsafe fn grow(
&mut self,
ptr: NonNull<u8>,
old_layout: Layout,
new_size: usize,
placement: ReallocPlacement,
init: AllocInit,
) -> Result<MemoryBlock, AllocErr> {
debug_assert_eq!(
Self::allocation_layout(old_layout).unwrap(),
Self::unchecked_allocation_layout(old_layout)
);
let (old_offset_prefix, old_alloc_layout, old_offset_suffix) =
Self::unchecked_allocation_layout(old_layout);
let ptr = ptr.as_ptr().sub(old_offset_prefix);
let new_layout = Layout::from_size_align_unchecked(new_size, old_layout.align());
let (new_offset_prefix, new_alloc_layout, new_offset_suffix) =
Self::allocation_layout(new_layout).ok_or(AllocErr)?;
let suffix: MaybeUninit<Suffix> = ptr::read(ptr.add(old_offset_suffix) as *mut _);
let memory = self.parent.grow(
NonNull::new_unchecked(ptr),
old_alloc_layout,
new_alloc_layout.size(),
placement,
init,
)?;
if init == AllocInit::Zeroed {
ptr::write_bytes(
memory.ptr.as_ptr().add(old_offset_suffix),
0,
mem::size_of::<Suffix>(),
);
}
ptr::write(memory.ptr.as_ptr().add(new_offset_suffix) as *mut _, suffix);
Ok(MemoryBlock {
ptr: NonNull::new_unchecked(memory.ptr.as_ptr().add(new_offset_prefix)),
size: if mem::size_of::<Suffix>() == 0 {
memory.size - new_offset_prefix
} else {
new_offset_suffix - new_offset_prefix
},
})
}
unsafe fn shrink(
&mut self,
ptr: NonNull<u8>,
old_layout: Layout,
new_size: usize,
placement: ReallocPlacement,
) -> Result<MemoryBlock, AllocErr> {
debug_assert_eq!(
Self::allocation_layout(old_layout).unwrap(),
Self::unchecked_allocation_layout(old_layout)
);
let (old_offset_prefix, old_alloc_layout, old_offset_suffix) =
Self::unchecked_allocation_layout(old_layout);
let ptr = ptr.as_ptr().sub(old_offset_prefix);
let new_layout = Layout::from_size_align_unchecked(new_size, old_layout.align());
let (new_offset_prefix, new_alloc_layout, new_offset_suffix) =
Self::unchecked_allocation_layout(new_layout);
let suffix: MaybeUninit<Suffix> = ptr::read(ptr.add(old_offset_suffix) as *mut _);
let memory = self.parent.shrink(
NonNull::new_unchecked(ptr),
old_alloc_layout,
new_alloc_layout.size(),
placement,
)?;
ptr::write(memory.ptr.as_ptr().add(new_offset_suffix) as *mut _, suffix);
Ok(MemoryBlock {
ptr: NonNull::new_unchecked(memory.ptr.as_ptr().add(new_offset_prefix)),
size: if mem::size_of::<Suffix>() == 0 {
memory.size - new_offset_prefix
} else {
new_offset_suffix - new_offset_prefix
},
})
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::wildcard_imports)]
use super::*;
use crate::{
helper::{AsSlice, Tracker},
Proxy,
};
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 = Proxy {
alloc: Affix::<System, Prefix, Suffix>::default(),
callbacks: Tracker::default(),
};
let memory = alloc
.alloc(layout, AllocInit::Zeroed)
.unwrap_or_else(|_| panic!("Could not allocate {} bytes", layout.size()));
if mem::size_of::<Prefix>() == 0 {
assert_eq!(
Affix::<System, Prefix, Suffix>::prefix(memory.ptr, layout),
NonNull::dangling()
);
} else {
assert_eq!(
Affix::<System, Prefix, Suffix>::prefix(memory.ptr, layout)
.cast()
.as_ptr(),
memory.ptr.as_ptr().sub(offset_prefix)
);
}
if mem::size_of::<Suffix>() == 0 {
assert_eq!(
Affix::<System, Prefix, Suffix>::suffix(memory.ptr, layout),
NonNull::dangling()
);
} else {
assert_eq!(
Affix::<System, Prefix, Suffix>::suffix(memory.ptr, layout)
.cast()
.as_ptr(),
memory.ptr.as_ptr().add(offset_suffix)
);
}
Affix::<System, Prefix, Suffix>::prefix(memory.ptr, layout)
.as_ptr()
.write(prefix);
Affix::<System, Prefix, Suffix>::suffix(memory.ptr, layout)
.as_ptr()
.write(suffix);
assert_eq!(
Affix::<System, Prefix, Suffix>::prefix(memory.ptr, layout).as_ref(),
&prefix
);
assert_eq!(memory.as_slice(), &vec![0_u8; memory.size][..]);
assert_eq!(
Affix::<System, Prefix, Suffix>::suffix(memory.ptr, layout).as_ref(),
&suffix
);
let growed_memory = alloc
.grow(
memory.ptr,
layout,
memory.size * 2,
ReallocPlacement::MayMove,
AllocInit::Zeroed,
)
.expect("Could not grow allocation");
let new_layout =
Layout::from_size_align(memory.size * 2, layout.align()).expect("Invalid layout");
assert_eq!(
Affix::<System, Prefix, Suffix>::prefix(growed_memory.ptr, new_layout).as_ref(),
&prefix
);
assert_eq!(
growed_memory.as_slice(),
&vec![0_u8; growed_memory.size][..]
);
assert_eq!(
Affix::<System, Prefix, Suffix>::suffix(growed_memory.ptr, new_layout).as_ref(),
&suffix
);
let memory = alloc
.shrink(
growed_memory.ptr,
new_layout,
layout.size(),
ReallocPlacement::MayMove,
)
.expect("Could not shrink allocation");
assert_eq!(
Affix::<System, Prefix, Suffix>::prefix(memory.ptr, layout).as_ref(),
&prefix
);
assert_eq!(memory.as_slice(), &vec![0_u8; memory.size][..]);
assert_eq!(
Affix::<System, Prefix, Suffix>::suffix(memory.ptr, layout).as_ref(),
&suffix
);
alloc.dealloc(memory.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: 0xDEDE },
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)
}
}