Document `Array` and `IArray`

next
Sayan Nandan 3 years ago
parent b997afe89a
commit bd679f9b79

@ -41,12 +41,27 @@ use core::ptr;
use core::slice; use core::slice;
use serde::{de::SeqAccess, de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; use serde::{de::SeqAccess, de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
/// A compile-time, fixed size array that can have unintialized memory. This array is as
/// efficient as you'd expect a normal array to be, but with the added benefit that you
/// don't have to initialize all the elements. Safe abstractions are made available
/// enabling us to not enter uninitialized space and read the _available_ elements. The
/// array size is limited to 16 bits or 2 bytes to prevent stack overflows.
///
/// ## Panics
/// To avoid stack corruption among other crazy things, several implementations like [`Extend`]
/// can panic. There are _silently corrupting_ methods too which can be used if you can uphold
/// the guarantees
pub struct Array<T, const N: usize> { pub struct Array<T, const N: usize> {
/// the maybe bad stack
stack: [MaybeUninit<T>; N], stack: [MaybeUninit<T>; N],
/// the initialized length
/// no stack should be more than 16 bytes /// no stack should be more than 16 bytes
init_len: u16, init_len: u16,
} }
/// The len scopeguard is like a scopeguard that provides panic safety incase an append-like
/// operation involving iterators causes the iterator to panic. This makes sure that we still
/// set the len on panic
pub struct LenScopeGuard<'a, T: Copy> { pub struct LenScopeGuard<'a, T: Copy> {
real_ref: &'a mut T, real_ref: &'a mut T,
temp: T, temp: T,
@ -74,7 +89,7 @@ impl<'a, T: Copy> Drop for LenScopeGuard<'a, T> {
} }
} }
// defy the compiler // defy the compiler; just some silly hackery here -- move on
struct UninitArray<T, const N: usize>(PhantomData<fn() -> T>); struct UninitArray<T, const N: usize>(PhantomData<fn() -> T>);
impl<T, const N: usize> UninitArray<T, N> { impl<T, const N: usize> UninitArray<T, N> {
@ -83,38 +98,57 @@ impl<T, const N: usize> UninitArray<T, N> {
} }
impl<T, const N: usize> Array<T, N> { impl<T, const N: usize> Array<T, N> {
/// Create a new array
pub const fn new() -> Self { pub const fn new() -> Self {
Array { Array {
stack: UninitArray::ARRAY, stack: UninitArray::ARRAY,
init_len: 0, init_len: 0,
} }
} }
/// Get the apparent length of the array
pub const fn len(&self) -> usize { pub const fn len(&self) -> usize {
self.init_len as usize self.init_len as usize
} }
/// Get the capacity of the array
pub const fn capacity(&self) -> usize { pub const fn capacity(&self) -> usize {
N N
} }
/// Check if the array is full
pub const fn is_full(&self) -> bool { pub const fn is_full(&self) -> bool {
N == self.len() N == self.len()
} }
/// Get the remaining capacity of the array
pub const fn remaining_cap(&self) -> usize { pub const fn remaining_cap(&self) -> usize {
self.capacity() - self.len() self.capacity() - self.len()
} }
/// Set the length of the array
///
/// ## Safety
/// This is one of those, use to leak memory functions. If you change the length,
/// you'll be reading random garbage from the memory and doing a double-free on drop
pub unsafe fn set_len(&mut self, len: usize) { pub unsafe fn set_len(&mut self, len: usize) {
self.init_len = len as u16; // lossy cast, we maintain all invariants self.init_len = len as u16; // lossy cast, we maintain all invariants
} }
/// Get the array as a mut ptr
unsafe fn as_mut_ptr(&mut self) -> *mut T { unsafe fn as_mut_ptr(&mut self) -> *mut T {
self.stack.as_mut_ptr() as *mut _ self.stack.as_mut_ptr() as *mut _
} }
/// Get the array as a const ptr
unsafe fn as_ptr(&self) -> *const T { unsafe fn as_ptr(&self) -> *const T {
self.stack.as_ptr() as *const _ self.stack.as_ptr() as *const _
} }
/// Push an element into the array **without any bounds checking**.
///
/// ## Safety
/// This function is **so unsafe** that you possibly don't want to call it, or
/// even think about calling it. You can end up corrupting your own stack or
/// other's valuable data
pub unsafe fn push_unchecked(&mut self, element: T) { pub unsafe fn push_unchecked(&mut self, element: T) {
let len = self.len(); let len = self.len();
ptr::write(self.as_mut_ptr().add(len), element); ptr::write(self.as_mut_ptr().add(len), element);
self.set_len(len + 1); self.set_len(len + 1);
} }
/// This is a nice version of a push that does bound checks
pub fn push_panic(&mut self, element: T) -> Result<(), ()> { pub fn push_panic(&mut self, element: T) -> Result<(), ()> {
if self.len() < N { if self.len() < N {
// so we can push it in // so we can push it in
@ -124,9 +158,12 @@ impl<T, const N: usize> Array<T, N> {
Err(()) Err(())
} }
} }
/// This is a _panicky_ but safer alternative to `push_unchecked` that panics on
/// incorrect lengths
pub fn push(&mut self, element: T) { pub fn push(&mut self, element: T) {
self.push_panic(element).unwrap(); self.push_panic(element).unwrap();
} }
/// Pop an item off the array
pub fn pop(&mut self) -> Option<T> { pub fn pop(&mut self) -> Option<T> {
if self.len() == 0 { if self.len() == 0 {
// nothing here // nothing here
@ -140,6 +177,8 @@ impl<T, const N: usize> Array<T, N> {
} }
} }
} }
/// Truncate the array to a given size. This is super safe and doesn't even panic
/// if you provide a silly `new_len`.
pub fn truncate(&mut self, new_len: usize) { pub fn truncate(&mut self, new_len: usize) {
let len = self.len(); let len = self.len();
if new_len < len { if new_len < len {
@ -153,9 +192,11 @@ impl<T, const N: usize> Array<T, N> {
} }
} }
} }
/// Empty the internal array
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.truncate(0) self.truncate(0)
} }
/// Extend self from a slice
pub fn extend_from_slice(&mut self, slice: &[T]) -> Result<(), ()> pub fn extend_from_slice(&mut self, slice: &[T]) -> Result<(), ()>
where where
T: Copy, T: Copy,
@ -164,14 +205,25 @@ impl<T, const N: usize> Array<T, N> {
// no more space here // no more space here
return Err(()); return Err(());
} }
let self_len = self.len();
let other_len = slice.len();
unsafe { unsafe {
ptr::copy_nonoverlapping(slice.as_ptr(), self.as_mut_ptr().add(self_len), other_len); self.extend_from_slice_unchecked(slice);
self.set_len(self_len + other_len);
} }
Ok(()) Ok(())
} }
/// Extend self from a slice without doing a single check
///
/// ## Safety
/// This function is just very very and. You can write giant things into your own
/// stack corrupting it, corrupting other people's things and creating undefined
/// behavior like no one else.
pub unsafe fn extend_from_slice_unchecked(&mut self, slice: &[T]) {
let self_len = self.len();
let other_len = slice.len();
ptr::copy_nonoverlapping(slice.as_ptr(), self.as_mut_ptr().add(self_len), other_len);
self.set_len(self_len + other_len);
}
/// Returns self as a `[T; N]` array if it is fully initialized. Else it will again return
/// itself
pub fn into_array(self) -> Result<[T; N], Self> { pub fn into_array(self) -> Result<[T; N], Self> {
if self.len() < self.capacity() { if self.len() < self.capacity() {
// not fully initialized // not fully initialized
@ -188,9 +240,12 @@ impl<T, const N: usize> Array<T, N> {
} }
// these operations are incredibly safe because we only pass the initialized part // these operations are incredibly safe because we only pass the initialized part
// of the array // of the array
/// Get self as a slice. Super safe because we guarantee that all the other invarians
/// are upheld
fn as_slice(&self) -> &[T] { fn as_slice(&self) -> &[T] {
unsafe { slice::from_raw_parts(self.as_ptr(), self.len()) } unsafe { slice::from_raw_parts(self.as_ptr(), self.len()) }
} }
/// Get self as a mutable slice. Super safe (see comment above)
fn as_slice_mut(&mut self) -> &mut [T] { fn as_slice_mut(&mut self) -> &mut [T] {
unsafe { slice::from_raw_parts_mut(self.as_mut_ptr(), self.len()) } unsafe { slice::from_raw_parts_mut(self.as_mut_ptr(), self.len()) }
} }
@ -262,6 +317,12 @@ impl<T, const N: usize> IntoIterator for Array<T, N> {
} }
impl<T, const N: usize> Array<T, N> { impl<T, const N: usize> Array<T, N> {
/// Extend self using an iterator.
///
/// ## Safety
/// This function can cause undefined damage to your application's stack and/or other's
/// data. Only use if you know what you're doing. If you don't use `extend_from_iter`
/// instead
pub unsafe fn extend_from_iter_unchecked<I>(&mut self, iterable: I) pub unsafe fn extend_from_iter_unchecked<I>(&mut self, iterable: I)
where where
I: IntoIterator<Item = T>, I: IntoIterator<Item = T>,
@ -283,11 +344,42 @@ impl<T, const N: usize> Array<T, N> {
} }
} }
} }
pub fn extend_from_iter<I>(&mut self, iterable: I)
where
I: IntoIterator<Item = T>,
{
unsafe {
// the ptr to start writing from
let mut ptr = Self::as_mut_ptr(self).add(self.len());
let end_ptr = Self::as_ptr(self).add(self.remaining_cap());
let mut guard = LenScopeGuard::new(&mut self.init_len);
let mut iter = iterable.into_iter();
loop {
if let Some(element) = iter.next() {
// write the element
ptr.write(element);
// move to the next location
ptr = ptr.add(1);
// tell the guard to increment
guard.incr(1);
if end_ptr == ptr {
// our current ptr points to the end of the allocation
// oh no, time for corruption, if the user says so
panic!("Overflowed stack area.")
}
} else {
return;
}
}
}
}
} }
impl<T, const N: usize> Extend<T> for Array<T, N> { impl<T, const N: usize> Extend<T> for Array<T, N> {
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) { fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
unsafe { self.extend_from_iter_unchecked(iter) } {
self.extend_from_iter::<_>(iter)
}
} }
} }

@ -49,8 +49,11 @@ use serde::{
}; };
use std::alloc as std_alloc; use std::alloc as std_alloc;
/// An arbitrary trait used for identifying something as a contiguous block of memory
pub trait MemoryBlock { pub trait MemoryBlock {
/// The type that will be used for the memory layout
type LayoutItem; type LayoutItem;
/// The number of _units_ this memory block has
fn size() -> usize; fn size() -> usize;
} }
@ -61,48 +64,68 @@ impl<T, const N: usize> MemoryBlock for [T; N] {
} }
} }
/// An union that either holds a stack (ptr) or a heap
///
/// ## Safety
/// If you're trying to access a field without knowing the most recently created one,
/// behavior is undefined.
pub union InlineArray<A: MemoryBlock> { pub union InlineArray<A: MemoryBlock> {
/// the stack
stack: ManuallyDrop<MaybeUninit<A>>, stack: ManuallyDrop<MaybeUninit<A>>,
/// a pointer to the heap allocation and the allocation size
heap_ptr_len: (*mut A::LayoutItem, usize), heap_ptr_len: (*mut A::LayoutItem, usize),
} }
impl<A: MemoryBlock> InlineArray<A> { impl<A: MemoryBlock> InlineArray<A> {
/// Get's the stack pointer. This is unsafe because it is not guranteed that the
/// stack pointer field is valid and the caller has to uphold this gurantee
unsafe fn stack_ptr(&self) -> *const A::LayoutItem { unsafe fn stack_ptr(&self) -> *const A::LayoutItem {
self.stack.as_ptr() as *const _ self.stack.as_ptr() as *const _
} }
/// Safe as `stack_ptr`, but returns a mutable pointer
unsafe fn stack_ptr_mut(&mut self) -> *mut A::LayoutItem { unsafe fn stack_ptr_mut(&mut self) -> *mut A::LayoutItem {
self.stack.as_mut_ptr() as *mut _ self.stack.as_mut_ptr() as *mut _
} }
/// Create a new union from a stack
fn from_stack(stack: MaybeUninit<A>) -> Self { fn from_stack(stack: MaybeUninit<A>) -> Self {
Self { Self {
stack: ManuallyDrop::new(stack), stack: ManuallyDrop::new(stack),
} }
} }
/// Create a new union from a heap (allocated).
fn from_heap_ptr(start_ptr: *mut A::LayoutItem, len: usize) -> Self { fn from_heap_ptr(start_ptr: *mut A::LayoutItem, len: usize) -> Self {
Self { Self {
heap_ptr_len: (start_ptr, len), heap_ptr_len: (start_ptr, len),
} }
} }
/// Returns the allocation size of the heap
unsafe fn heap_size(&self) -> usize { unsafe fn heap_size(&self) -> usize {
self.heap_ptr_len.1 self.heap_ptr_len.1
} }
/// Returns a raw ptr to the heap
unsafe fn heap_ptr(&self) -> *const A::LayoutItem { unsafe fn heap_ptr(&self) -> *const A::LayoutItem {
self.heap_ptr_len.0 self.heap_ptr_len.0
} }
/// Returns a mut ptr to the heap
unsafe fn heap_ptr_mut(&mut self) -> *mut A::LayoutItem { unsafe fn heap_ptr_mut(&mut self) -> *mut A::LayoutItem {
self.heap_ptr_len.0 as *mut _ self.heap_ptr_len.0 as *mut _
} }
/// Returns a mut ref to the heap allocation size
unsafe fn heap_size_mut(&mut self) -> &mut usize { unsafe fn heap_size_mut(&mut self) -> &mut usize {
&mut self.heap_ptr_len.1 &mut self.heap_ptr_len.1
} }
/// Returns the entire heap field
unsafe fn heap(&self) -> (*mut A::LayoutItem, usize) { unsafe fn heap(&self) -> (*mut A::LayoutItem, usize) {
self.heap_ptr_len self.heap_ptr_len
} }
/// Returns a mutable reference to the entire heap field
unsafe fn heap_mut(&mut self) -> (*mut A::LayoutItem, &mut usize) { unsafe fn heap_mut(&mut self) -> (*mut A::LayoutItem, &mut usize) {
(self.heap_ptr_mut(), &mut self.heap_ptr_len.1) (self.heap_ptr_mut(), &mut self.heap_ptr_len.1)
} }
} }
/// An utility tool for calculating the memory layout for a given `T`. Handles
/// any possible overflows
pub fn calculate_memory_layout<T>(count: usize) -> Result<Layout, ()> { pub fn calculate_memory_layout<T>(count: usize) -> Result<Layout, ()> {
let size = mem::size_of::<T>().checked_mul(count).ok_or(())?; let size = mem::size_of::<T>().checked_mul(count).ok_or(())?;
// err is cap overflow // err is cap overflow
@ -192,6 +215,7 @@ impl<A: MemoryBlock> IArray<A> {
} }
} }
} }
/// Returns the total capacity of the inline stack
fn stack_capacity() -> usize { fn stack_capacity() -> usize {
if mem::size_of::<A::LayoutItem>() > 0 { if mem::size_of::<A::LayoutItem>() > 0 {
// not a ZST, so cap of array // not a ZST, so cap of array
@ -201,6 +225,7 @@ impl<A: MemoryBlock> IArray<A> {
usize::MAX usize::MAX
} }
} }
/// Helper function that returns a ptr to the data, the len and the capacity
fn meta_triple(&self) -> DataptrLenptrCapacity<A::LayoutItem> { fn meta_triple(&self) -> DataptrLenptrCapacity<A::LayoutItem> {
unsafe { unsafe {
if unlikely(self.went_off_stack()) { if unlikely(self.went_off_stack()) {
@ -212,6 +237,7 @@ impl<A: MemoryBlock> IArray<A> {
} }
} }
} }
/// Mutable version of `meta_triple`
fn meta_triple_mut(&mut self) -> DataptrLenptrCapacityMut<A::LayoutItem> { fn meta_triple_mut(&mut self) -> DataptrLenptrCapacityMut<A::LayoutItem> {
unsafe { unsafe {
if unlikely(self.went_off_stack()) { if unlikely(self.went_off_stack()) {
@ -228,6 +254,7 @@ impl<A: MemoryBlock> IArray<A> {
} }
} }
} }
/// Returns a raw ptr to the data
fn get_data_ptr_mut(&mut self) -> *mut A::LayoutItem { fn get_data_ptr_mut(&mut self) -> *mut A::LayoutItem {
if unlikely(self.went_off_stack()) { if unlikely(self.went_off_stack()) {
// get the heap ptr // get the heap ptr
@ -237,9 +264,11 @@ impl<A: MemoryBlock> IArray<A> {
unsafe { self.store.stack_ptr_mut() } unsafe { self.store.stack_ptr_mut() }
} }
} }
/// Returns true if the allocation is now on the heap
fn went_off_stack(&self) -> bool { fn went_off_stack(&self) -> bool {
self.cap > Self::stack_capacity() self.cap > Self::stack_capacity()
} }
/// Returns the length
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
if unlikely(self.went_off_stack()) { if unlikely(self.went_off_stack()) {
// so we're off the stack // so we're off the stack
@ -249,9 +278,11 @@ impl<A: MemoryBlock> IArray<A> {
self.cap self.cap
} }
} }
/// Returns true if the IArray is empty
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.len() == 0 self.len() == 0
} }
/// Returns the capacity
fn get_capacity(&self) -> usize { fn get_capacity(&self) -> usize {
if unlikely(self.went_off_stack()) { if unlikely(self.went_off_stack()) {
self.cap self.cap
@ -259,6 +290,8 @@ impl<A: MemoryBlock> IArray<A> {
Self::stack_capacity() Self::stack_capacity()
} }
} }
/// Grow the allocation, if required, to make space for a total of `new_cap`
/// elements
fn grow_block(&mut self, new_cap: usize) { fn grow_block(&mut self, new_cap: usize) {
// infallible // infallible
unsafe { unsafe {
@ -299,6 +332,7 @@ impl<A: MemoryBlock> IArray<A> {
} }
} }
} }
/// Reserve space for `additional` elements
fn reserve(&mut self, additional: usize) { fn reserve(&mut self, additional: usize) {
let (_, &mut len, cap) = self.meta_triple_mut(); let (_, &mut len, cap) = self.meta_triple_mut();
if cap - len >= additional { if cap - len >= additional {
@ -311,6 +345,7 @@ impl<A: MemoryBlock> IArray<A> {
.expect("Capacity overflow"); .expect("Capacity overflow");
self.grow_block(new_cap) self.grow_block(new_cap)
} }
/// Push an element into this IArray
pub fn push(&mut self, val: A::LayoutItem) { pub fn push(&mut self, val: A::LayoutItem) {
unsafe { unsafe {
let (mut data_ptr, mut len, cap) = self.meta_triple_mut(); let (mut data_ptr, mut len, cap) = self.meta_triple_mut();
@ -324,6 +359,7 @@ impl<A: MemoryBlock> IArray<A> {
*len += 1; *len += 1;
} }
} }
/// Pop an element off this IArray
pub fn pop(&mut self) -> Option<A::LayoutItem> { pub fn pop(&mut self) -> Option<A::LayoutItem> {
unsafe { unsafe {
let (data_ptr, len_mut, _cap) = self.meta_triple_mut(); let (data_ptr, len_mut, _cap) = self.meta_triple_mut();
@ -340,6 +376,8 @@ impl<A: MemoryBlock> IArray<A> {
} }
} }
} }
/// Shrink this IArray so that it only occupies the required space and not anything
/// more
pub fn shrink(&mut self) { pub fn shrink(&mut self) {
if unlikely(self.went_off_stack()) { if unlikely(self.went_off_stack()) {
// it's off the stack, so no chance of moving back to the stack // it's off the stack, so no chance of moving back to the stack
@ -363,6 +401,7 @@ impl<A: MemoryBlock> IArray<A> {
self.grow_block(current_len); self.grow_block(current_len);
} }
} }
/// Truncate the IArray to a given length. This **will** call the destructors
pub fn truncate(&mut self, target_len: usize) { pub fn truncate(&mut self, target_len: usize) {
unsafe { unsafe {
let (data_ptr, len_mut, _cap) = self.meta_triple_mut(); let (data_ptr, len_mut, _cap) = self.meta_triple_mut();
@ -376,10 +415,14 @@ impl<A: MemoryBlock> IArray<A> {
} }
} }
} }
/// Clear the internal store
pub fn clear(&mut self) { pub fn clear(&mut self) {
// chop off the whole place // chop off the whole place
self.truncate(0); self.truncate(0);
} }
/// Set the len, **without calling the destructor**. This is the ultimate function
/// to make valgrind unhappy, that is, **you can create memory leaks** if you don't
/// destroy the elements yourself
unsafe fn set_len(&mut self, new_len: usize) { unsafe fn set_len(&mut self, new_len: usize) {
let (_dataptr, len_mut, _cap) = self.meta_triple_mut(); let (_dataptr, len_mut, _cap) = self.meta_triple_mut();
*len_mut = new_len; *len_mut = new_len;
@ -390,6 +433,8 @@ impl<A: MemoryBlock> IArray<A>
where where
A::LayoutItem: Copy, A::LayoutItem: Copy,
{ {
/// Create an IArray from a slice by copying the elements of the slice into
/// the IArray
pub fn from_slice(slice: &[A::LayoutItem]) -> Self { pub fn from_slice(slice: &[A::LayoutItem]) -> Self {
// FIXME(@ohsayan): Could we have had this as a From::from() method? // FIXME(@ohsayan): Could we have had this as a From::from() method?
let slice_len = slice.len(); let slice_len = slice.len();
@ -419,6 +464,7 @@ where
} }
} }
} }
/// Insert a slice at the given index
pub fn insert_slice_at_index(&mut self, slice: &[A::LayoutItem], index: usize) { pub fn insert_slice_at_index(&mut self, slice: &[A::LayoutItem], index: usize) {
self.reserve(slice.len()); self.reserve(slice.len());
let len = self.len(); let len = self.len();
@ -434,10 +480,12 @@ where
self.set_len(len + slice.len()); self.set_len(len + slice.len());
} }
} }
/// Extend the IArray by using a slice
pub fn extend_from_slice(&mut self, slice: &[A::LayoutItem]) { pub fn extend_from_slice(&mut self, slice: &[A::LayoutItem]) {
// at our len because we're appending it to the end // at our len because we're appending it to the end
self.insert_slice_at_index(slice, self.len()) self.insert_slice_at_index(slice, self.len())
} }
/// Create a new IArray from a pre-defined stack
pub fn from_stack(stack: A) -> Self { pub fn from_stack(stack: A) -> Self {
Self { Self {
cap: A::size(), cap: A::size(),

Loading…
Cancel
Save