use crate::Owns;
use core::{
alloc::{AllocErr, AllocInit, AllocRef, Layout, MemoryBlock, ReallocPlacement},
fmt,
ptr,
ptr::NonNull,
};
pub struct Region<'a> {
data: &'a mut [u8],
offset: usize,
}
impl<'a> Region<'a> {
#[inline]
pub fn new(data: &'a mut [u8]) -> Self {
let current = data.as_ptr() as usize;
Self {
data,
offset: current,
}
}
pub const fn capacity(&self) -> usize {
self.data.len()
}
pub fn capacity_left(&self) -> usize {
self.data.as_ptr() as usize + self.data.len() - self.offset
}
pub fn reset(&mut self) {
self.offset = self.data.as_ptr() as usize;
debug_assert_eq!(self.capacity(), self.capacity_left());
}
#[inline]
pub fn is_last_block(&self, memory: MemoryBlock) -> bool {
memory.ptr.as_ptr() as usize + memory.size == self.offset as usize
}
}
impl fmt::Debug for Region<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Region")
.field("capacity", &self.capacity())
.field("capacity_left", &self.capacity_left())
.finish()
}
}
unsafe impl AllocRef for Region<'_> {
fn alloc(&mut self, layout: Layout, init: AllocInit) -> Result<MemoryBlock, AllocErr> {
let offset = (self.offset as *mut u8).align_offset(layout.align());
let current = self.offset.checked_add(offset).ok_or(AllocErr)?;
let new = current.checked_add(layout.size()).ok_or(AllocErr)?;
if new > self.data.as_ptr() as usize + self.data.len() {
return Err(AllocErr);
}
self.offset = new;
let memory = MemoryBlock {
ptr: unsafe { NonNull::new_unchecked(current as *mut u8) },
size: layout.size(),
};
unsafe { init.init(memory) };
Ok(memory)
}
unsafe fn dealloc(&mut self, ptr: NonNull<u8>, layout: Layout) {
debug_assert!(
self.owns(MemoryBlock {
ptr,
size: layout.size()
}),
"`ptr` must denote a block of memory currently allocated via this allocator"
);
if self.is_last_block(MemoryBlock {
ptr,
size: layout.size(),
}) {
self.offset = ptr.as_ptr() as usize;
}
}
unsafe fn grow(
&mut self,
ptr: NonNull<u8>,
layout: Layout,
new_size: usize,
placement: ReallocPlacement,
init: AllocInit,
) -> Result<MemoryBlock, AllocErr> {
let size = layout.size();
debug_assert!(
new_size >= size,
"`new_size` must be greater than or equal to `layout.size()`"
);
if layout.size() == new_size {
Ok(MemoryBlock {
ptr,
size: new_size,
})
} else if self.is_last_block(MemoryBlock {
ptr,
size: layout.size(),
}) {
let start = ptr.as_ptr() as usize;
let new = start.checked_add(new_size).ok_or(AllocErr)?;
if new > self.data.as_ptr() as usize + self.data.len() {
return Err(AllocErr);
}
self.offset = new;
let new_memory = MemoryBlock {
ptr,
size: new_size,
};
init.init_offset(new_memory, layout.size());
Ok(new_memory)
} else if placement == ReallocPlacement::MayMove {
let new_layout = Layout::from_size_align_unchecked(new_size, layout.align());
let new_memory = self.alloc(new_layout, init)?;
ptr::copy_nonoverlapping(ptr.as_ptr(), new_memory.ptr.as_ptr(), size);
Ok(new_memory)
} else {
Err(AllocErr)
}
}
unsafe fn shrink(
&mut self,
ptr: NonNull<u8>,
layout: Layout,
new_size: usize,
placement: ReallocPlacement,
) -> Result<MemoryBlock, AllocErr> {
let size = layout.size();
debug_assert!(
new_size <= size,
"`new_size` must be smaller than or equal to `layout.size()`"
);
if layout.size() == new_size {
Ok(MemoryBlock {
ptr,
size: new_size,
})
} else if self.is_last_block(MemoryBlock {
ptr,
size: layout.size(),
}) {
self.offset = ptr.as_ptr() as usize + new_size;
Ok(MemoryBlock {
ptr,
size: new_size,
})
} else if placement == ReallocPlacement::MayMove {
let new_layout = Layout::from_size_align_unchecked(new_size, layout.align());
let new_memory = self.alloc(new_layout, AllocInit::Uninitialized)?;
ptr::copy_nonoverlapping(ptr.as_ptr(), new_memory.ptr.as_ptr(), new_size);
Ok(new_memory)
} else {
Err(AllocErr)
}
}
}
impl Owns for Region<'_> {
fn owns(&self, memory: MemoryBlock) -> bool {
self.data.as_ptr() <= memory.ptr.as_ptr()
&& memory.ptr.as_ptr() as usize + memory.size <= self.offset as usize
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::wildcard_imports)]
use super::*;
use crate::helper::AsSlice;
use std::alloc::{Global, Layout};
#[test]
fn alloc_zero() {
let mut data = [1; 32];
let mut region = Region::new(&mut data);
assert_eq!(region.capacity(), 32);
assert_eq!(region.capacity(), region.capacity_left());
region
.alloc(Layout::new::<[u8; 0]>(), AllocInit::Zeroed)
.expect("Could not allocated 0 bytes");
assert_eq!(region.capacity_left(), region.capacity());
assert_eq!(data, [1; 32]);
}
#[test]
fn alloc_small() {
let mut data = [1; 32];
let mut region = Region::new(&mut data);
assert_eq!(region.capacity(), 32);
assert_eq!(region.capacity(), region.capacity_left());
region
.alloc(Layout::new::<[u8; 16]>(), AllocInit::Zeroed)
.expect("Could not allocated 16 bytes");
assert_eq!(region.capacity_left(), 16);
assert_eq!(&data[0..16], &[0; 16][..]);
assert_eq!(&data[16..], &[1; 16][..]);
}
#[test]
fn alloc_full() {
let mut data = [1; 32];
let mut region = Region::new(&mut data);
region
.alloc(Layout::new::<[u8; 32]>(), AllocInit::Zeroed)
.expect("Could not allocated 32 bytes");
assert_eq!(region.capacity_left(), 0);
assert_eq!(data, [0; 32]);
}
#[test]
fn alloc_uninitialzed() {
let mut data = [1; 32];
let mut region = Region::new(&mut data);
region
.alloc(Layout::new::<[u8; 32]>(), AllocInit::Uninitialized)
.expect("Could not allocated 32 bytes");
assert_eq!(region.capacity_left(), 0);
assert_eq!(data, [1; 32]);
}
#[test]
fn alloc_fail() {
let mut data = [1; 32];
let mut region = Region::new(&mut data);
assert!(
region
.alloc(Layout::new::<[u8; 33]>(), AllocInit::Uninitialized)
.is_err()
);
}
#[test]
fn alloc_aligned() {
let memory = Global
.alloc(
Layout::from_size_align(1024, 64).expect("Invalid layout"),
AllocInit::Uninitialized,
)
.expect("Could not allocate 1024 Bytes");
assert_eq!(memory.ptr.as_ptr() as usize % 64, 0);
let data = unsafe { memory.as_slice_mut() };
let mut region = Region::new(data);
region
.alloc(
Layout::from_size_align(5, 1).expect("Invalid layout"),
AllocInit::Uninitialized,
)
.expect("Could not allocate 5 Bytes");
let memory = region
.alloc(
Layout::from_size_align(16, 16).expect("Invalid layout"),
AllocInit::Uninitialized,
)
.expect("Could not allocate 16 Bytes");
assert_eq!(memory.ptr.as_ptr() as usize % 16, 0);
}
#[test]
fn dealloc() {
let mut data = [1; 32];
let mut region = Region::new(&mut data);
let layout = Layout::from_size_align(8, 1).expect("Invalid layout");
let memory = region
.alloc(layout, AllocInit::Uninitialized)
.expect("Could not allocate 8 bytes");
assert!(region.owns(memory));
assert_eq!(region.capacity_left(), 24);
unsafe {
region.dealloc(memory.ptr, layout);
}
assert_eq!(region.capacity_left(), 32);
assert!(!region.owns(memory));
let memory = region
.alloc(layout, AllocInit::Uninitialized)
.expect("Could not allocate 8 bytes");
assert!(region.owns(memory));
region
.alloc(layout, AllocInit::Uninitialized)
.expect("Could not allocate 8 bytes");
assert!(region.owns(memory));
assert_eq!(memory.size, 8);
assert_eq!(region.capacity_left(), 16);
unsafe {
region.dealloc(memory.ptr, layout);
}
assert!(region.owns(memory));
assert_eq!(region.capacity_left(), 16);
}
#[test]
fn realloc() {
let mut data = [1; 32];
let mut region = Region::new(&mut data);
let layout = Layout::from_size_align(8, 1).expect("Invalid layout");
let memory = region
.alloc(layout, AllocInit::Uninitialized)
.expect("Could not allocate 8 bytes");
assert_eq!(memory.size, 8);
assert_eq!(region.capacity_left(), 24);
let memory = unsafe {
region
.grow(
memory.ptr,
layout,
16,
ReallocPlacement::InPlace,
AllocInit::Uninitialized,
)
.expect("Could not grow to 16 bytes")
};
assert_eq!(memory.size, 16);
assert_eq!(region.capacity_left(), 16);
let memory = unsafe {
region
.shrink(
memory.ptr,
Layout::from_size_align(16, 1).expect("Invalid layout"),
8,
ReallocPlacement::InPlace,
)
.expect("Could not shrink to 8 bytes")
};
assert_eq!(memory.size, 8);
assert_eq!(region.capacity_left(), 24);
region
.alloc(layout, AllocInit::Uninitialized)
.expect("Could not allocate 8 bytes");
assert_eq!(region.capacity_left(), 16);
let memory = unsafe {
assert!(
region
.grow(
memory.ptr,
layout,
16,
ReallocPlacement::InPlace,
AllocInit::Uninitialized,
)
.is_err()
);
region
.grow(
memory.ptr,
layout,
8,
ReallocPlacement::InPlace,
AllocInit::Uninitialized,
)
.expect("Could not grow by 0 bytes");
region
.shrink(memory.ptr, layout, 8, ReallocPlacement::InPlace)
.expect("Could not grow by 0 bytes");
region
.grow(
memory.ptr,
layout,
16,
ReallocPlacement::MayMove,
AllocInit::Uninitialized,
)
.expect("Could not grow to 16 bytes")
};
assert_eq!(memory.size, 16);
assert_eq!(region.capacity_left(), 0);
region.reset();
assert_eq!(region.capacity_left(), region.capacity());
}
}