generated from EmbarkStudios/opensource-template
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
be41b38
commit 07373b7
Showing
5 changed files
with
1,635 additions
and
68 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,371 @@ | ||
#![allow(unused_unsafe)] | ||
|
||
mod page_allocator; | ||
mod page_vec; | ||
mod raw_vec; | ||
|
||
use std::{ | ||
alloc::{Layout, LayoutError}, | ||
fmt, | ||
ptr::{self, NonNull}, | ||
}; | ||
|
||
/// Copy of [`AllocError`](std::alloc::AllocError) | ||
#[derive(Copy, Clone, PartialEq, Eq, Debug)] | ||
pub struct AllocError; | ||
|
||
impl fmt::Display for AllocError { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
f.write_str("memory allocation failed") | ||
} | ||
} | ||
|
||
/// The error type for `try_reserve` methods. | ||
#[derive(Clone, PartialEq, Eq, Debug)] | ||
pub enum TryReserveError { | ||
/// Error due to the computed capacity exceeding the collection's maximum | ||
/// (usually `isize::MAX` bytes). | ||
CapacityOverflow, | ||
|
||
/// The memory allocator returned an error | ||
AllocError { | ||
/// The layout of allocation request that failed | ||
layout: Layout, | ||
}, | ||
} | ||
|
||
impl From<LayoutError> for TryReserveError { | ||
#[inline] | ||
fn from(_: LayoutError) -> Self { | ||
TryReserveError::CapacityOverflow | ||
} | ||
} | ||
|
||
impl fmt::Display for TryReserveError { | ||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { | ||
fmt.write_str("memory allocation failed")?; | ||
let reason = match &self { | ||
TryReserveError::CapacityOverflow => { | ||
" because the computed capacity exceeded the collection's maximum" | ||
} | ||
TryReserveError::AllocError { .. } => " because the memory allocator returned a error", | ||
}; | ||
fmt.write_str(reason) | ||
} | ||
} | ||
|
||
/// Copy of [`AllocRef`](std::alloc::AllocRef) | ||
pub unsafe trait AllocRef { | ||
/// Attempts to allocate a block of memory. | ||
/// | ||
/// On success, returns a [`NonNull<[u8]>`](std::mem::NonNull) meeting the size and alignment guarantees of `layout`. | ||
/// | ||
/// The returned block may have a larger size than specified by `layout.size()`, and may or may | ||
/// not have its contents initialized. | ||
/// | ||
/// # Errors | ||
/// | ||
/// Returning `Err` indicates that either memory is exhausted or `layout` does not meet | ||
/// allocator's size or alignment constraints. | ||
/// | ||
/// Implementations are encouraged to return `Err` on memory exhaustion rather than panicking or | ||
/// aborting, but this is not a strict requirement. (Specifically: it is *legal* to implement | ||
/// this trait atop an underlying native allocation library that aborts on memory exhaustion.) | ||
/// | ||
/// Clients wishing to abort computation in response to an allocation error are encouraged to | ||
/// call the [`handle_alloc_error`](alloc::alloc::handle_alloc_error) function, rather than | ||
/// directly invoking `panic!` or similar. | ||
fn alloc(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError>; | ||
|
||
/// Behaves like `alloc`, but also ensures that the returned memory is zero-initialized. | ||
/// | ||
/// # Errors | ||
/// | ||
/// Returning `Err` indicates that either memory is exhausted or `layout` does not meet | ||
/// allocator's size or alignment constraints. | ||
/// | ||
/// Implementations are encouraged to return `Err` on memory exhaustion rather than panicking or | ||
/// aborting, but this is not a strict requirement. (Specifically: it is *legal* to implement | ||
/// this trait atop an underlying native allocation library that aborts on memory exhaustion.) | ||
/// | ||
/// Clients wishing to abort computation in response to an allocation error are encouraged to | ||
/// call the [`handle_alloc_error`](alloc::alloc::handle_alloc_error) function, rather than | ||
/// directly invoking `panic!` or similar. | ||
fn alloc_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> { | ||
let ptr = self.alloc(layout)?; | ||
// SAFETY: `alloc` returns a valid memory block | ||
unsafe { | ||
(*ptr.as_ptr()) | ||
.as_mut_ptr() | ||
.write_bytes(0, (*ptr.as_ptr()).len()) | ||
} | ||
Ok(ptr) | ||
} | ||
|
||
/// Deallocates the memory referenced by `ptr`. | ||
/// | ||
/// # Safety | ||
/// | ||
/// * `ptr` must denote a block of memory [*currently allocated*] via this allocator, and | ||
/// * `layout` must [*fit*] that block of memory. | ||
/// | ||
/// [*currently allocated*]: #currently-allocated-memory | ||
/// [*fit*]: #memory-fitting | ||
unsafe fn dealloc(&self, ptr: NonNull<u8>, layout: Layout); | ||
|
||
/// Attempts to extend the memory block. | ||
/// | ||
/// Returns a new [`NonNull<[u8]>`](std::mem::NonNull) containing a pointer and the actual size of the allocated | ||
/// memory. The pointer is suitable for holding data described by `new_layout`. To accomplish | ||
/// this, the allocator may extend the allocation referenced by `ptr` to fit the new layout. | ||
/// | ||
/// If this returns `Ok`, then ownership of the memory block referenced by `ptr` has been | ||
/// transferred to this allocator. The memory may or may not have been freed, and should be | ||
/// considered unusable unless it was transferred back to the caller again via the return value | ||
/// of this method. | ||
/// | ||
/// If this method returns `Err`, then ownership of the memory block has not been transferred to | ||
/// this allocator, and the contents of the memory block are unaltered. | ||
/// | ||
/// # Safety | ||
/// | ||
/// * `ptr` must denote a block of memory [*currently allocated*] via this allocator. | ||
/// * `old_layout` must [*fit*] that block of memory (The `new_layout` argument need not fit it.). | ||
/// * `new_layout.size()` must be greater than or equal to `old_layout.size()`. | ||
/// | ||
/// [*currently allocated*]: #currently-allocated-memory | ||
/// [*fit*]: #memory-fitting | ||
/// | ||
/// # Errors | ||
/// | ||
/// Returns `Err` if the new layout does not meet the allocator's size and alignment | ||
/// constraints of the allocator, or if growing otherwise fails. | ||
/// | ||
/// Implementations are encouraged to return `Err` on memory exhaustion rather than panicking or | ||
/// aborting, but this is not a strict requirement. (Specifically: it is *legal* to implement | ||
/// this trait atop an underlying native allocation library that aborts on memory exhaustion.) | ||
/// | ||
/// Clients wishing to abort computation in response to an allocation error are encouraged to | ||
/// call the [`handle_alloc_error`](alloc::alloc::handle_alloc_error) function, rather than | ||
/// directly invoking `panic!` or similar. | ||
unsafe fn grow( | ||
&self, | ||
ptr: NonNull<u8>, | ||
old_layout: Layout, | ||
new_layout: Layout, | ||
) -> Result<NonNull<[u8]>, AllocError> { | ||
debug_assert!( | ||
new_layout.size() >= old_layout.size(), | ||
"`new_layout.size()` must be greater than or equal to `old_layout.size()`" | ||
); | ||
|
||
let new_ptr = self.alloc(new_layout)?; | ||
|
||
// SAFETY: because `new_layout.size()` must be greater than or equal to | ||
// `old_layout.size()`, both the old and new memory allocation are valid for reads and | ||
// writes for `old_layout.size()` bytes. Also, because the old allocation wasn't yet | ||
// deallocated, it cannot overlap `new_ptr`. Thus, the call to `copy_nonoverlapping` is | ||
// safe. The safety contract for `dealloc` must be upheld by the caller. | ||
unsafe { | ||
ptr::copy_nonoverlapping( | ||
ptr.as_ptr(), | ||
(*new_ptr.as_ptr()).as_mut_ptr(), | ||
old_layout.size(), | ||
); | ||
self.dealloc(ptr, old_layout); | ||
} | ||
|
||
Ok(new_ptr) | ||
} | ||
|
||
/// Behaves like `grow`, but also ensures that the new contents are set to zero before being | ||
/// returned. | ||
/// | ||
/// The memory block will contain the following contents after a successful call to | ||
/// `grow_zeroed`: | ||
/// * Bytes `0..old_layout.size()` are preserved from the original allocation. | ||
/// * Bytes `old_layout.size()..old_size` will either be preserved or zeroed, depending on | ||
/// the allocator implementation. `old_size` refers to the size of the memory block prior | ||
/// to the `grow_zeroed` call, which may be larger than the size that was originally | ||
/// requested when it was allocated. | ||
/// * Bytes `old_size..new_size` are zeroed. `new_size` refers to the size of the memory | ||
/// block returned by the `grow_zeroed` call. | ||
/// | ||
/// # Safety | ||
/// | ||
/// * `ptr` must denote a block of memory [*currently allocated*] via this allocator. | ||
/// * `old_layout` must [*fit*] that block of memory (The `new_layout` argument need not fit it.). | ||
/// * `new_layout.size()` must be greater than or equal to `old_layout.size()`. | ||
/// | ||
/// [*currently allocated*]: #currently-allocated-memory | ||
/// [*fit*]: #memory-fitting | ||
/// | ||
/// # Errors | ||
/// | ||
/// Returns `Err` if the new layout does not meet the allocator's size and alignment | ||
/// constraints of the allocator, or if growing otherwise fails. | ||
/// | ||
/// Implementations are encouraged to return `Err` on memory exhaustion rather than panicking or | ||
/// aborting, but this is not a strict requirement. (Specifically: it is *legal* to implement | ||
/// this trait atop an underlying native allocation library that aborts on memory exhaustion.) | ||
/// | ||
/// Clients wishing to abort computation in response to an allocation error are encouraged to | ||
/// call the [`handle_alloc_error`](alloc::alloc::handle_alloc_error) function, rather than | ||
/// directly invoking `panic!` or similar. | ||
unsafe fn grow_zeroed( | ||
&self, | ||
ptr: NonNull<u8>, | ||
old_layout: Layout, | ||
new_layout: Layout, | ||
) -> Result<NonNull<[u8]>, AllocError> { | ||
debug_assert!( | ||
new_layout.size() >= old_layout.size(), | ||
"`new_layout.size()` must be greater than or equal to `old_layout.size()`" | ||
); | ||
|
||
let new_ptr = self.alloc_zeroed(new_layout)?; | ||
|
||
// SAFETY: because `new_layout.size()` must be greater than or equal to | ||
// `old_layout.size()`, both the old and new memory allocation are valid for reads and | ||
// writes for `old_layout.size()` bytes. Also, because the old allocation wasn't yet | ||
// deallocated, it cannot overlap `new_ptr`. Thus, the call to `copy_nonoverlapping` is | ||
// safe. The safety contract for `dealloc` must be upheld by the caller. | ||
unsafe { | ||
ptr::copy_nonoverlapping( | ||
ptr.as_ptr(), | ||
(*new_ptr.as_ptr()).as_mut_ptr(), | ||
old_layout.size(), | ||
); | ||
self.dealloc(ptr, old_layout); | ||
} | ||
|
||
Ok(new_ptr) | ||
} | ||
|
||
/// Attempts to shrink the memory block. | ||
/// | ||
/// Returns a new [`NonNull<[u8]>`](std::mem::NonNull) containing a pointer and the actual size of the allocated | ||
/// memory. The pointer is suitable for holding data described by `new_layout`. To accomplish | ||
/// this, the allocator may shrink the allocation referenced by `ptr` to fit the new layout. | ||
/// | ||
/// If this returns `Ok`, then ownership of the memory block referenced by `ptr` has been | ||
/// transferred to this allocator. The memory may or may not have been freed, and should be | ||
/// considered unusable unless it was transferred back to the caller again via the return value | ||
/// of this method. | ||
/// | ||
/// If this method returns `Err`, then ownership of the memory block has not been transferred to | ||
/// this allocator, and the contents of the memory block are unaltered. | ||
/// | ||
/// # Safety | ||
/// | ||
/// * `ptr` must denote a block of memory [*currently allocated*] via this allocator. | ||
/// * `old_layout` must [*fit*] that block of memory (The `new_layout` argument need not fit it.). | ||
/// * `new_layout.size()` must be smaller than or equal to `old_layout.size()`. | ||
/// | ||
/// [*currently allocated*]: #currently-allocated-memory | ||
/// [*fit*]: #memory-fitting | ||
/// | ||
/// # Errors | ||
/// | ||
/// Returns `Err` if the new layout does not meet the allocator's size and alignment | ||
/// constraints of the allocator, or if shrinking otherwise fails. | ||
/// | ||
/// Implementations are encouraged to return `Err` on memory exhaustion rather than panicking or | ||
/// aborting, but this is not a strict requirement. (Specifically: it is *legal* to implement | ||
/// this trait atop an underlying native allocation library that aborts on memory exhaustion.) | ||
/// | ||
/// Clients wishing to abort computation in response to an allocation error are encouraged to | ||
/// call the [`handle_alloc_error`](alloc::alloc::handle_alloc_error) function, rather than | ||
/// directly invoking `panic!` or similar. | ||
unsafe fn shrink( | ||
&self, | ||
ptr: NonNull<u8>, | ||
old_layout: Layout, | ||
new_layout: Layout, | ||
) -> Result<NonNull<[u8]>, AllocError> { | ||
debug_assert!( | ||
new_layout.size() <= old_layout.size(), | ||
"`new_layout.size()` must be smaller than or equal to `old_layout.size()`" | ||
); | ||
|
||
let new_ptr = self.alloc(new_layout)?; | ||
|
||
// SAFETY: because `new_layout.size()` must be lower than or equal to | ||
// `old_layout.size()`, both the old and new memory allocation are valid for reads and | ||
// writes for `new_layout.size()` bytes. Also, because the old allocation wasn't yet | ||
// deallocated, it cannot overlap `new_ptr`. Thus, the call to `copy_nonoverlapping` is | ||
// safe. The safety contract for `dealloc` must be upheld by the caller. | ||
unsafe { | ||
ptr::copy_nonoverlapping( | ||
ptr.as_ptr(), | ||
(*new_ptr.as_ptr()).as_mut_ptr(), | ||
new_layout.size(), | ||
); | ||
self.dealloc(ptr, old_layout); | ||
} | ||
|
||
Ok(new_ptr) | ||
} | ||
|
||
/// Creates a "by reference" adaptor for this instance of `AllocRef`. | ||
/// | ||
/// The returned adaptor also implements `AllocRef` and will simply borrow this. | ||
#[inline(always)] | ||
fn by_ref(&self) -> &Self { | ||
self | ||
} | ||
} | ||
|
||
unsafe impl<A> AllocRef for &A | ||
where | ||
A: AllocRef + ?Sized, | ||
{ | ||
#[inline] | ||
fn alloc(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> { | ||
(**self).alloc(layout) | ||
} | ||
|
||
#[inline] | ||
fn alloc_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> { | ||
(**self).alloc_zeroed(layout) | ||
} | ||
|
||
#[inline] | ||
unsafe fn dealloc(&self, ptr: NonNull<u8>, layout: Layout) { | ||
// SAFETY: the safety contract must be upheld by the caller | ||
unsafe { (**self).dealloc(ptr, layout) } | ||
} | ||
|
||
#[inline] | ||
unsafe fn grow( | ||
&self, | ||
ptr: NonNull<u8>, | ||
old_layout: Layout, | ||
new_layout: Layout, | ||
) -> Result<NonNull<[u8]>, AllocError> { | ||
// SAFETY: the safety contract must be upheld by the caller | ||
unsafe { (**self).grow(ptr, old_layout, new_layout) } | ||
} | ||
|
||
#[inline] | ||
unsafe fn grow_zeroed( | ||
&self, | ||
ptr: NonNull<u8>, | ||
old_layout: Layout, | ||
new_layout: Layout, | ||
) -> Result<NonNull<[u8]>, AllocError> { | ||
// SAFETY: the safety contract must be upheld by the caller | ||
unsafe { (**self).grow_zeroed(ptr, old_layout, new_layout) } | ||
} | ||
|
||
#[inline] | ||
unsafe fn shrink( | ||
&self, | ||
ptr: NonNull<u8>, | ||
old_layout: Layout, | ||
new_layout: Layout, | ||
) -> Result<NonNull<[u8]>, AllocError> { | ||
// SAFETY: the safety contract must be upheld by the caller | ||
unsafe { (**self).shrink(ptr, old_layout, new_layout) } | ||
} | ||
} |
Oops, something went wrong.