mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
docs(allocator): reformat comments in Arena (#21448)
Comments-only change. Reformat comments in `Arena` code, and correct a few small mistakes.
This commit is contained in:
1 parent
7f60234062
commit
c5b19bbc55
9 files changed
+494
-701
No files matched your search
@@ -11,8 +11,7 @@ use std::{
|
||||
use super::{Arena, bumpalo_alloc::AllocErr, utils::oom};
|
||||
|
||||
impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
/// Allocate an object in this `Arena` and return an exclusive reference to
|
||||
/// it.
|
||||
/// Allocate an object in this `Arena`. Returns an exclusive reference to it.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
@@ -32,8 +31,7 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
self.alloc_with(|| val)
|
||||
}
|
||||
|
||||
/// Try to allocate an object in this `Arena` and return an exclusive
|
||||
/// reference to it.
|
||||
/// Try to allocate an object in this `Arena`. Returns an exclusive reference to it, if the allocation succeeds.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
@@ -53,13 +51,12 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
self.try_alloc_with(|| val)
|
||||
}
|
||||
|
||||
/// Pre-allocate space for an object in this `Arena`, initializes it using
|
||||
/// the closure, then returns an exclusive reference to it.
|
||||
/// Pre-allocate space for an object in this `Arena`, and initialize it using the closure.
|
||||
/// Returns an exclusive reference to it.
|
||||
///
|
||||
/// See [The `_with` Method Suffix](#initializer-functions-the-_with-method-suffix) for a
|
||||
/// discussion on the differences between the `_with` suffixed methods and
|
||||
/// those methods without it, their performance characteristics, and when
|
||||
/// you might or might not choose a `_with` suffixed method.
|
||||
/// See [The `_with` Method Suffix](#initializer-functions-the-_with-method-suffix) for a discussion
|
||||
/// of the differences between the `_with` suffixed methods and those methods without it,
|
||||
/// their performance characteristics, and when you might or might not choose a `_with` suffixed method.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
@@ -86,15 +83,14 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
F: FnOnce() -> T,
|
||||
{
|
||||
// This function is translated as:
|
||||
// - allocate space for a T on the stack
|
||||
// - call f() with the return value being put onto this stack space
|
||||
// - memcpy from the stack to the heap
|
||||
// - Allocate space for a T on the stack.
|
||||
// - Call `f()` with the return value being put onto this stack space.
|
||||
// - memcpy from the stack to the heap.
|
||||
//
|
||||
// Ideally we want LLVM to always realize that doing a stack
|
||||
// allocation is unnecessary and optimize the code so it writes
|
||||
// directly into the heap instead. It seems we get it to realize
|
||||
// this most consistently if we put this critical line into it's
|
||||
// own function instead of inlining it into the surrounding code.
|
||||
// Ideally we want LLVM to always realize that doing a stack allocation is unnecessary and optimize
|
||||
// the code so it writes directly into the heap instead. It seems we get it to realize this most
|
||||
// consistently if we put this critical line into it's own function instead of inlining it into the
|
||||
// surrounding code.
|
||||
unsafe { ptr::write(ptr, f()) };
|
||||
}
|
||||
|
||||
@@ -108,13 +104,12 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to pre-allocate space for an object in this `Arena`, initializes
|
||||
/// it using the closure, then returns an exclusive reference to it.
|
||||
/// Try to pre-allocate space for an object in this `Arena`, and initialize it using the closure.
|
||||
/// Returns an exclusive reference to it, if the allocation succeeds.
|
||||
///
|
||||
/// See [The `_with` Method Suffix](#initializer-functions-the-_with-method-suffix) for a
|
||||
/// discussion on the differences between the `_with` suffixed methods and
|
||||
/// those methods without it, their performance characteristics, and when
|
||||
/// you might or might not choose a `_with` suffixed method.
|
||||
/// See [The `_with` Method Suffix](#initializer-functions-the-_with-method-suffix) for a discussion
|
||||
/// of the differences between the `_with` suffixed methods and those methods without it,
|
||||
/// their performance characteristics, and when you might or might not choose a `_with` suffixed method.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
@@ -141,15 +136,14 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
F: FnOnce() -> T,
|
||||
{
|
||||
// This function is translated as:
|
||||
// - allocate space for a T on the stack
|
||||
// - call f() with the return value being put onto this stack space
|
||||
// - memcpy from the stack to the heap
|
||||
// - Allocate space for a T on the stack.
|
||||
// - Call `f()` with the return value being put onto this stack space.
|
||||
// - memcpy from the stack to the heap.
|
||||
//
|
||||
// Ideally we want LLVM to always realize that doing a stack
|
||||
// allocation is unnecessary and optimize the code so it writes
|
||||
// directly into the heap instead. It seems we get it to realize
|
||||
// this most consistently if we put this critical line into it's
|
||||
// own function instead of inlining it into the surrounding code.
|
||||
// Ideally we want LLVM to always realize that doing a stack allocation is unnecessary and optimize
|
||||
// the code so it writes directly into the heap instead. It seems we get it to realize this most
|
||||
// consistently if we put this critical line into it's own function instead of inlining it into the
|
||||
// surrounding code.
|
||||
unsafe { ptr::write(ptr, f()) };
|
||||
}
|
||||
|
||||
@@ -164,8 +158,7 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
}
|
||||
}
|
||||
|
||||
/// `Copy` a slice into this `Arena` and return an exclusive reference to
|
||||
/// the copy.
|
||||
/// `Copy` a slice into this `Arena` and return an exclusive reference to the copy.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
@@ -195,8 +188,8 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
}
|
||||
}
|
||||
|
||||
/// `Clone` a slice into this `Arena` and return an exclusive reference to
|
||||
/// the clone. Prefer [`alloc_slice_copy`](#method.alloc_slice_copy) if `T` is `Copy`.
|
||||
/// `Clone` a slice into this `Arena`. Returns an exclusive reference to the clone.
|
||||
/// Prefer [`alloc_slice_copy`](#method.alloc_slice_copy) if `T` is `Copy`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
@@ -240,7 +233,7 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
}
|
||||
}
|
||||
|
||||
/// `Copy` a string slice into this `Arena` and return an exclusive reference to it.
|
||||
/// `Copy` a string slice into this `Arena`. Returns an exclusive reference to it.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
@@ -260,13 +253,12 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
pub fn alloc_str(&self, src: &str) -> &mut str {
|
||||
let buffer = self.alloc_slice_copy(src.as_bytes());
|
||||
unsafe {
|
||||
// This is OK, because it already came in as str, so it is guaranteed to be utf8
|
||||
// This is OK, because it already came in as str, so it is guaranteed to be UTF-8
|
||||
str::from_utf8_unchecked_mut(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocates a new slice of size `len` into this `Arena` and returns an
|
||||
/// exclusive reference to the copy.
|
||||
/// Allocate a new slice of size `len` into this `Arena`. Returns an exclusive reference to the slice.
|
||||
///
|
||||
/// The elements of the slice are initialized using the supplied closure.
|
||||
/// The closure argument is the position in the slice.
|
||||
@@ -304,8 +296,7 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocates a new slice of size `len` into this `Arena` and returns an
|
||||
/// exclusive reference to the copy.
|
||||
/// Allocate a new slice of size `len` into this `Arena`. Returns an exclusive reference to the copy.
|
||||
///
|
||||
/// All elements of the slice are initialized to `value`.
|
||||
///
|
||||
@@ -327,8 +318,7 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
self.alloc_slice_fill_with(len, |_| value)
|
||||
}
|
||||
|
||||
/// Allocates a new slice of size `len` slice into this `Arena` and return an
|
||||
/// exclusive reference to the copy.
|
||||
/// Allocate a new slice of size `len` into this `Arena`. Return an exclusive reference to the clone.
|
||||
///
|
||||
/// All elements of the slice are initialized to `value.clone()`.
|
||||
///
|
||||
@@ -353,15 +343,14 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
self.alloc_slice_fill_with(len, |_| value.clone())
|
||||
}
|
||||
|
||||
/// Allocates a new slice of size `len` slice into this `Arena` and return an
|
||||
/// exclusive reference to the copy.
|
||||
/// Allocate a new slice of size `len` into this `Arena`. Returns an exclusive reference to the slice.
|
||||
///
|
||||
/// The elements are initialized using the supplied iterator.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if reserving space for the slice fails, or if the supplied
|
||||
/// iterator returns fewer elements than it promised.
|
||||
/// Panics if reserving space for the slice fails, or if the supplied iterator returns fewer elements than
|
||||
/// it promised.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@@ -384,8 +373,7 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Allocates a new slice of size `len` slice into this `Arena` and return an
|
||||
/// exclusive reference to the copy.
|
||||
/// Allocate a new slice of size `len` into this `Arena`. Returns an exclusive reference to the slice.
|
||||
///
|
||||
/// All elements of the slice are initialized to [`T::default()`].
|
||||
///
|
||||
|
||||
@@ -25,9 +25,7 @@ use super::{
|
||||
impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
/// Allocate space for an object with the given `Layout`.
|
||||
///
|
||||
/// The returned pointer points at uninitialized memory, and should be
|
||||
/// initialized with
|
||||
/// [`std::ptr::write`](https://doc.rust-lang.org/std/ptr/fn.write.html).
|
||||
/// The returned pointer points at uninitialized memory, and should be initialized with [`std::ptr::write`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
@@ -37,12 +35,11 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
self.try_alloc_layout(layout).unwrap_or_else(|_| oom())
|
||||
}
|
||||
|
||||
/// Attempts to allocate space for an object with the given `Layout` or else returns
|
||||
/// an `Err`.
|
||||
/// Attempt to allocate space for an object with the given `Layout`.
|
||||
///
|
||||
/// The returned pointer points at uninitialized memory, and should be
|
||||
/// initialized with
|
||||
/// [`std::ptr::write`](https://doc.rust-lang.org/std/ptr/fn.write.html).
|
||||
/// If allocation fails, returns `Err`.
|
||||
///
|
||||
/// The returned pointer points at uninitialized memory, and should be initialized with [`std::ptr::write`].
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
@@ -65,10 +62,9 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
|
||||
#[inline(always)]
|
||||
fn try_alloc_layout_fast(&self, layout: Layout) -> Option<NonNull<u8>> {
|
||||
// We don't need to check for ZSTs here since they will automatically
|
||||
// be handled properly: the pointer will be bumped by zero bytes,
|
||||
// modulo alignment. This keeps the fast path optimized for non-ZSTs,
|
||||
// which are much more common.
|
||||
// We don't need to check for ZSTs here since they will automatically be handled properly:
|
||||
// the pointer will be bumped by zero bytes, modulo alignment.
|
||||
// This keeps the fast path optimized for non-ZSTs, which are much more common.
|
||||
unsafe {
|
||||
let footer_ptr = self.current_chunk_footer.get();
|
||||
let footer = footer_ptr.as_ref();
|
||||
@@ -87,14 +83,12 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
is_pointer_aligned_to(ptr, MIN_ALIGN),
|
||||
"bump pointer {ptr:#p} should be aligned to the minimum alignment of {MIN_ALIGN:#x}"
|
||||
);
|
||||
// This `match` should be boiled away by LLVM: `MIN_ALIGN` is a
|
||||
// constant and the layout's alignment is also constant in practice
|
||||
// after inlining.
|
||||
// This `match` should be boiled away by LLVM: `MIN_ALIGN` is a constant and the layout's alignment
|
||||
// is also constant in practice after inlining
|
||||
let aligned_ptr = match layout.align().cmp(&MIN_ALIGN) {
|
||||
Ordering::Less => {
|
||||
// We need to round the size up to a multiple of `MIN_ALIGN`
|
||||
// to preserve the minimum alignment. This might overflow
|
||||
// since we cannot rely on `Layout`'s guarantees.
|
||||
// We need to round the size up to a multiple of `MIN_ALIGN` to preserve the minimum alignment.
|
||||
// This might overflow since we cannot rely on `Layout`'s guarantees.
|
||||
let aligned_size = round_up_to(layout.size(), MIN_ALIGN)?;
|
||||
|
||||
let capacity = (ptr as usize) - (start as usize);
|
||||
@@ -105,10 +99,9 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
ptr.wrapping_sub(aligned_size)
|
||||
}
|
||||
Ordering::Equal => {
|
||||
// `Layout` guarantees that rounding the size up to its
|
||||
// align cannot overflow (but does not guarantee that the
|
||||
// size is initially a multiple of the alignment, which is
|
||||
// why we need to do this rounding).
|
||||
// `Layout` guarantees that rounding the size up to its align cannot overflow
|
||||
// (but does not guarantee that the size is initially a multiple of the alignment,
|
||||
// which is why we need to do this rounding)
|
||||
let aligned_size = round_up_to_unchecked(layout.size(), layout.align());
|
||||
|
||||
let capacity = (ptr as usize) - (start as usize);
|
||||
@@ -119,10 +112,9 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
ptr.wrapping_sub(aligned_size)
|
||||
}
|
||||
Ordering::Greater => {
|
||||
// `Layout` guarantees that rounding the size up to its
|
||||
// align cannot overflow (but does not guarantee that the
|
||||
// size is initially a multiple of the alignment, which is
|
||||
// why we need to do this rounding).
|
||||
// `Layout` guarantees that rounding the size up to its align cannot overflow
|
||||
// (but does not guarantee that the size is initially a multiple of the alignment,
|
||||
// which is why we need to do this rounding)
|
||||
let aligned_size = round_up_to_unchecked(layout.size(), layout.align());
|
||||
|
||||
let aligned_ptr = round_mut_ptr_down_to(ptr, layout.align());
|
||||
@@ -157,8 +149,8 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Slow path allocation for when we need to allocate a new chunk,
|
||||
/// because there isn't enough room in our current chunk.
|
||||
/// Slow path allocation for when we need to allocate a new chunk, because there isn't enough room
|
||||
/// in our current chunk.
|
||||
#[inline(never)]
|
||||
#[cold]
|
||||
fn alloc_layout_slow(&self, layout: Layout) -> Option<NonNull<u8>> {
|
||||
@@ -167,14 +159,13 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Get a new chunk from the global allocator.
|
||||
// Get a new chunk from the global allocator
|
||||
let current_footer = self.current_chunk_footer.get();
|
||||
let current_layout = current_footer.as_ref().layout;
|
||||
|
||||
// By default, we want our new chunk to be about twice as big
|
||||
// as the previous chunk. If the global allocator refuses it,
|
||||
// we try to divide it by half until it works or the requested
|
||||
// size is smaller than the default footer size.
|
||||
// By default, we want our new chunk to be about twice as big as the previous chunk.
|
||||
// If the global allocator refuses it, we try to divide it by half until it works
|
||||
// or the requested size is smaller than the default footer size.
|
||||
let min_new_chunk_size = layout.size().max(DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER);
|
||||
let mut base_size =
|
||||
(current_layout.size() - CHUNK_FOOTER_SIZE).checked_mul(2)?.max(min_new_chunk_size);
|
||||
@@ -194,11 +185,10 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
|
||||
debug_assert_eq!(new_footer.as_ref().data.as_ptr() as usize % layout.align(), 0);
|
||||
|
||||
// Set the new chunk as our new current chunk.
|
||||
// Set the new chunk as our new current chunk
|
||||
self.current_chunk_footer.set(new_footer);
|
||||
|
||||
// And then we can rely on `try_alloc_layout_fast` to allocate
|
||||
// space within this chunk.
|
||||
// And then we can rely on `try_alloc_layout_fast` to allocate space within this chunk
|
||||
let ptr = self.try_alloc_layout_fast(layout);
|
||||
debug_assert!(ptr.is_some());
|
||||
ptr
|
||||
@@ -208,7 +198,7 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
#[inline]
|
||||
unsafe fn dealloc(&self, ptr: NonNull<u8>, layout: Layout) {
|
||||
// If the pointer is the last allocation we made, we can reuse the bytes,
|
||||
// otherwise they are simply leaked -- at least until somebody calls reset().
|
||||
// otherwise they are simply leaked - at least until somebody calls `reset()`
|
||||
unsafe {
|
||||
if self.is_last_allocation(ptr) {
|
||||
let ptr = self.current_chunk_footer.get().as_ref().ptr.get();
|
||||
@@ -232,17 +222,15 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
old_layout: Layout,
|
||||
new_layout: Layout,
|
||||
) -> Result<NonNull<u8>, AllocErr> {
|
||||
// If the new layout demands greater alignment than the old layout has,
|
||||
// then either
|
||||
// If the new layout demands greater alignment than the old layout has, then either:
|
||||
//
|
||||
// 1. the pointer happens to satisfy the new layout's alignment, so we
|
||||
// got lucky and can return the pointer as-is, or
|
||||
// 1. the pointer happens to satisfy the new layout's alignment, so we got lucky
|
||||
// and can return the pointer as-is, or
|
||||
//
|
||||
// 2. the pointer is not aligned to the new layout's demanded alignment,
|
||||
// and we are unlucky.
|
||||
// 2. the pointer is not aligned to the new layout's demanded alignment, and we are unlucky.
|
||||
//
|
||||
// In the case of (2), to successfully "shrink" the allocation, we have
|
||||
// to allocate a whole new region for the new layout.
|
||||
// In the case of (2), to successfully "shrink" the allocation, we have to allocate a whole new region
|
||||
// for the new layout.
|
||||
if old_layout.align() < new_layout.align() {
|
||||
return if is_pointer_aligned_to(ptr.as_ptr(), new_layout.align()) {
|
||||
Ok(ptr)
|
||||
@@ -255,8 +243,7 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
))]
|
||||
self.stats.record_reallocation_after_allocation();
|
||||
|
||||
// We know that these regions are nonoverlapping because
|
||||
// `new_ptr` is a fresh allocation.
|
||||
// We know that these regions are nonoverlapping because `new_ptr` is a fresh allocation
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_ptr(), new_layout.size());
|
||||
}
|
||||
@@ -270,17 +257,14 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
let old_size = old_layout.size();
|
||||
let new_size = new_layout.size();
|
||||
|
||||
// This is how much space we would *actually* reclaim while satisfying
|
||||
// the requested alignment.
|
||||
// This is how much space we would *actually* reclaim while satisfying the requested alignment
|
||||
let delta = round_down_to(old_size - new_size, new_layout.align().max(MIN_ALIGN));
|
||||
|
||||
if unsafe { self.is_last_allocation(ptr) }
|
||||
// Only reclaim the excess space (which requires a copy) if it
|
||||
// is worth it: we are actually going to recover "enough" space
|
||||
// and we can do a non-overlapping copy.
|
||||
// Only reclaim the excess space (which requires a copy) if it is worth it:
|
||||
// we are actually going to recover "enough" space and we can do a non-overlapping copy.
|
||||
//
|
||||
// We do `old_size.div_ceil(2)` so division rounds up rather than
|
||||
// down. Consider when:
|
||||
// We do `old_size.div_ceil(2)` so division rounds up rather than down. Consider when:
|
||||
//
|
||||
// old_size = 5
|
||||
// new_size = 3
|
||||
@@ -290,8 +274,7 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
// delta = 2
|
||||
// (old_size / 2) = (5 / 2) = 2
|
||||
//
|
||||
// And the the check will succeed even though we are have
|
||||
// overlapping ranges:
|
||||
// And the the check will succeed even though we are have overlapping ranges:
|
||||
//
|
||||
// |--------old-allocation-------|
|
||||
// |------from-------|
|
||||
@@ -300,17 +283,15 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
// | a | b | c | . | . |
|
||||
// +-----+-----+-----+-----+-----+
|
||||
//
|
||||
// But we MUST NOT have overlapping ranges because we use
|
||||
// `copy_nonoverlapping` below! Therefore, we round the division
|
||||
// up to avoid this issue.
|
||||
// But we MUST NOT have overlapping ranges because we use `copy_nonoverlapping` below.
|
||||
// Therefore, we round the division up to avoid this issue.
|
||||
&& delta >= old_size.div_ceil(2)
|
||||
{
|
||||
unsafe {
|
||||
let footer = self.current_chunk_footer.get();
|
||||
let footer = footer.as_ref();
|
||||
|
||||
// NB: new_ptr is aligned, because ptr *has to* be aligned, and we
|
||||
// made sure delta is aligned.
|
||||
// Note: `new_ptr` is aligned, because ptr *has to* be aligned, and we made sure delta is aligned
|
||||
let new_ptr = NonNull::new_unchecked(footer.ptr.get().as_ptr().add(delta));
|
||||
debug_assert!(
|
||||
is_pointer_aligned_to(new_ptr.as_ptr(), MIN_ALIGN),
|
||||
@@ -318,8 +299,7 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
);
|
||||
footer.ptr.set(new_ptr);
|
||||
|
||||
// NB: we know it is non-overlapping because of the size check
|
||||
// in the `if` condition.
|
||||
// Note: We know it is non-overlapping because of the size check in the `if` condition
|
||||
ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_ptr(), new_size);
|
||||
|
||||
#[cfg(all(
|
||||
@@ -332,8 +312,7 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
}
|
||||
}
|
||||
|
||||
// If this wasn't the last allocation, or shrinking wasn't worth it,
|
||||
// simply return the old pointer as-is.
|
||||
// If this wasn't the last allocation, or shrinking wasn't worth it, simply return the old pointer as is
|
||||
Ok(ptr)
|
||||
}
|
||||
|
||||
@@ -352,8 +331,7 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
let align_is_compatible = old_layout.align() >= new_layout.align();
|
||||
|
||||
if align_is_compatible && unsafe { self.is_last_allocation(ptr) } {
|
||||
// Try to allocate the delta size within this same block so we can
|
||||
// reuse the currently allocated space.
|
||||
// Try to allocate the delta size within this same block so we can reuse the currently allocated space
|
||||
let delta = new_size - old_size;
|
||||
if let Some(p) =
|
||||
self.try_alloc_layout_fast(layout_from_size_align(delta, old_layout.align())?)
|
||||
@@ -370,7 +348,7 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: do a fresh allocation and copy the existing data into it.
|
||||
// Fallback: Do a fresh allocation and copy the existing data into it
|
||||
let new_ptr = self.try_alloc_layout(new_layout)?;
|
||||
unsafe { ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_ptr(), old_size) };
|
||||
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
//! `Alloc` trait.
|
||||
|
||||
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
// Copyright 2015 The Rust Project Developers.
|
||||
// See the COPYRIGHT file at the top-level directory of this distribution and at http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
#![expect(clippy::undocumented_unsafe_blocks)]
|
||||
#![allow(unstable_name_collisions)]
|
||||
@@ -47,18 +44,14 @@ impl UnstableLayoutMethods for Layout {
|
||||
//
|
||||
// We use modular arithmetic throughout:
|
||||
//
|
||||
// 1. align is guaranteed to be > 0, so align - 1 is always
|
||||
// valid.
|
||||
// 1. align is guaranteed to be > 0, so align - 1 is always valid
|
||||
//
|
||||
// 2. `len + align - 1` can overflow by at most `align - 1`,
|
||||
// so the &-mask with `!(align - 1)` will ensure that in the
|
||||
// case of overflow, `len_rounded_up` will itself be 0.
|
||||
// Thus the returned padding, when added to `len`, yields 0,
|
||||
// which trivially satisfies the alignment `align`.
|
||||
// 2. `len + align - 1` can overflow by at most `align - 1`, so the &-mask with `!(align - 1)` will
|
||||
// ensure that in the case of overflow, `len_rounded_up` will itself be 0. Thus the returned
|
||||
// padding, when added to `len`, yields 0, which trivially satisfies the alignment `align`.
|
||||
//
|
||||
// (Of course, attempts to allocate blocks of memory whose
|
||||
// size and padding overflow in the above manner should cause
|
||||
// the allocator to yield an error anyway.)
|
||||
// (Of course, attempts to allocate blocks of memory whose size and padding overflow in the above
|
||||
// manner should cause the allocator to yield an error anyway.)
|
||||
|
||||
let len_rounded_up = len.wrapping_add(align).wrapping_sub(1) & !align.wrapping_sub(1);
|
||||
len_rounded_up.wrapping_sub(len)
|
||||
@@ -72,8 +65,7 @@ impl UnstableLayoutMethods for Layout {
|
||||
let alloc_size = padded_size.checked_mul(n).ok_or_else(new_layout_err)?;
|
||||
|
||||
unsafe {
|
||||
// self.align is already known to be valid and alloc_size has been
|
||||
// padded already.
|
||||
// self.align is already known to be valid and alloc_size has been padded already
|
||||
Ok((Layout::from_size_align_unchecked(alloc_size, self.align()), padded_size))
|
||||
}
|
||||
}
|
||||
@@ -86,8 +78,7 @@ impl UnstableLayoutMethods for Layout {
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the combination of a starting address and
|
||||
/// a total capacity of the returned block.
|
||||
/// Represents the combination of a starting address and a total capacity of the returned block.
|
||||
// #[unstable(feature = "allocator_api", issue = "32838")]
|
||||
#[derive(Debug)]
|
||||
pub struct Excess(pub NonNull<u8>, pub usize);
|
||||
@@ -96,10 +87,8 @@ fn size_align<T>() -> (usize, usize) {
|
||||
(mem::size_of::<T>(), mem::align_of::<T>())
|
||||
}
|
||||
|
||||
/// The `AllocErr` error indicates an allocation failure
|
||||
/// that may be due to resource exhaustion or to
|
||||
/// something wrong when combining the given input arguments with this
|
||||
/// allocator.
|
||||
/// The `AllocErr` error indicates an allocation failure that may be due to resource exhaustion or to
|
||||
/// something wrong when combining the given input arguments with this allocator.
|
||||
// #[unstable(feature = "allocator_api", issue = "32838")]
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct AllocErr;
|
||||
@@ -112,9 +101,8 @@ impl fmt::Display for AllocErr {
|
||||
}
|
||||
}
|
||||
|
||||
/// The `CannotReallocInPlace` error is used when `grow_in_place` or
|
||||
/// `shrink_in_place` were unable to reuse the given memory block for
|
||||
/// a requested layout.
|
||||
/// The `CannotReallocInPlace` error is used when `grow_in_place` or `shrink_in_place` were unable to reuse
|
||||
/// the given memory block for a requested layout.
|
||||
// #[unstable(feature = "allocator_api", issue = "32838")]
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct CannotReallocInPlace;
|
||||
@@ -135,47 +123,37 @@ impl fmt::Display for CannotReallocInPlace {
|
||||
}
|
||||
}
|
||||
|
||||
/// An implementation of `Alloc` can allocate, reallocate, and
|
||||
/// deallocate arbitrary blocks of data described via `Layout`.
|
||||
/// An implementation of `Alloc` can allocate, reallocate, and deallocate arbitrary blocks of data described
|
||||
/// via `Layout`.
|
||||
///
|
||||
/// Some of the methods require that a memory block be *currently
|
||||
/// allocated* via an allocator. This means that:
|
||||
/// Some of the methods require that a memory block be *currently allocated* via an allocator. This means that:
|
||||
///
|
||||
/// * the starting address for that memory block was previously
|
||||
/// returned by a previous call to an allocation method (`alloc`,
|
||||
/// `alloc_zeroed`, `alloc_excess`, `alloc_one`, `alloc_array`) or
|
||||
/// reallocation method (`realloc`, `realloc_excess`, or
|
||||
/// `realloc_array`), and
|
||||
/// * the starting address for that memory block was previously returned by a previous call to an allocation
|
||||
/// method (`alloc`, `alloc_zeroed`, `alloc_excess`, `alloc_one`, `alloc_array`) or reallocation method
|
||||
/// (`realloc`, `realloc_excess`, or `realloc_array`), and
|
||||
///
|
||||
/// * the memory block has not been subsequently deallocated, where
|
||||
/// blocks are deallocated either by being passed to a deallocation
|
||||
/// method (`dealloc`, `dealloc_one`, `dealloc_array`) or by being
|
||||
/// passed to a reallocation method (see above) that returns `Ok`.
|
||||
/// * the memory block has not been subsequently deallocated, where blocks are deallocated either by being
|
||||
/// passed to a deallocation method (`dealloc`, `dealloc_one`, `dealloc_array`) or by being passed to a
|
||||
/// reallocation method (see above) that returns `Ok`.
|
||||
///
|
||||
/// A note regarding zero-sized types and zero-sized layouts: many
|
||||
/// methods in the `Alloc` trait state that allocation requests
|
||||
/// must be non-zero size, or else undefined behavior can result.
|
||||
/// A note regarding zero-sized types and zero-sized layouts: many methods in the `Alloc` trait state that
|
||||
/// allocation requests must be non-zero size, or else undefined behavior can result.
|
||||
///
|
||||
/// * However, some higher-level allocation methods (`alloc_one`,
|
||||
/// `alloc_array`) are well-defined on zero-sized types and can
|
||||
/// optionally support them: it is left up to the implementor
|
||||
/// whether to return `Err`, or to return `Ok` with some pointer.
|
||||
/// * However, some higher-level allocation methods (`alloc_one`, `alloc_array`) are well-defined on zero-sized
|
||||
/// types and can optionally support them: it is left up to the implementor whether to return `Err`, or to
|
||||
/// return `Ok` with some pointer.
|
||||
///
|
||||
/// * If an `Alloc` implementation chooses to return `Ok` in this
|
||||
/// case (i.e. the pointer denotes a zero-sized inaccessible block)
|
||||
/// then that returned pointer must be considered "currently
|
||||
/// allocated". On such an allocator, *all* methods that take
|
||||
/// currently-allocated pointers as inputs must accept these
|
||||
/// zero-sized pointers, *without* causing undefined behavior.
|
||||
/// * If an `Alloc` implementation chooses to return `Ok` in this case (i.e. the pointer denotes a zero-sized
|
||||
/// inaccessible block) then that returned pointer must be considered "currently allocated". On such an
|
||||
/// allocator, *all* methods that take currently-allocated pointers as inputs must accept these zero-sized
|
||||
/// pointers, *without* causing undefined behavior.
|
||||
///
|
||||
/// * In other words, if a zero-sized pointer can flow out of an
|
||||
/// allocator, then that allocator must likewise accept that pointer
|
||||
/// flowing back into its deallocation and reallocation methods.
|
||||
/// * In other words, if a zero-sized pointer can flow out of an allocator, then that allocator must likewise
|
||||
/// accept that pointer flowing back into its deallocation and reallocation methods.
|
||||
///
|
||||
/// Some of the methods require that a layout *fit* a memory block.
|
||||
/// What it means for a layout to "fit" a memory block means (or
|
||||
/// equivalently, for a memory block to "fit" a layout) is that the
|
||||
/// following two conditions must hold:
|
||||
/// Some of the methods require that a layout *fit* a memory block. What it means for a layout to "fit" a
|
||||
/// memory block means (or equivalently, for a memory block to "fit" a layout) is that the following two
|
||||
/// conditions must hold:
|
||||
///
|
||||
/// 1. The block's starting address must be aligned to `layout.align()`.
|
||||
///
|
||||
@@ -183,83 +161,65 @@ impl fmt::Display for CannotReallocInPlace {
|
||||
///
|
||||
/// * `use_min` is `self.usable_size(layout).0`, and
|
||||
///
|
||||
/// * `use_max` is the capacity that was (or would have been)
|
||||
/// returned when (if) the block was allocated via a call to
|
||||
/// `alloc_excess` or `realloc_excess`.
|
||||
/// * `use_max` is the capacity that was (or would have been) returned when (if) the block was allocated
|
||||
/// via a call to `alloc_excess` or `realloc_excess`.
|
||||
///
|
||||
/// Note that:
|
||||
///
|
||||
/// * the size of the layout most recently used to allocate the block
|
||||
/// is guaranteed to be in the range `[use_min, use_max]`, and
|
||||
/// * the size of the layout most recently used to allocate the block is guaranteed to be in the range
|
||||
/// `[use_min, use_max]`, and
|
||||
///
|
||||
/// * a lower-bound on `use_max` can be safely approximated by a call to
|
||||
/// `usable_size`.
|
||||
/// * a lower-bound on `use_max` can be safely approximated by a call to `usable_size`.
|
||||
///
|
||||
/// * if a layout `k` fits a memory block (denoted by `ptr`)
|
||||
/// currently allocated via an allocator `a`, then it is legal to
|
||||
/// use that layout to deallocate it, i.e. `a.dealloc(ptr, k);`.
|
||||
/// * if a layout `k` fits a memory block (denoted by `ptr`) currently allocated via an allocator `a`, then it
|
||||
/// is legal to use that layout to deallocate it, i.e. `a.dealloc(ptr, k);`.
|
||||
///
|
||||
/// # SAFETY
|
||||
///
|
||||
/// The `Alloc` trait is an `unsafe` trait for a number of reasons, and
|
||||
/// implementors must ensure that they adhere to these contracts:
|
||||
/// The `Alloc` trait is an `unsafe` trait for a number of reasons, and implementors must ensure that they
|
||||
/// adhere to these contracts:
|
||||
///
|
||||
/// * Pointers returned from allocation functions must point to valid memory and
|
||||
/// retain their validity until at least the instance of `Alloc` is dropped
|
||||
/// itself.
|
||||
/// * Pointers returned from allocation functions must point to valid memory and retain their validity until at
|
||||
/// least the instance of `Alloc` is dropped itself.
|
||||
///
|
||||
/// * `Layout` queries and calculations in general must be correct. Callers of
|
||||
/// this trait are allowed to rely on the contracts defined on each method,
|
||||
/// and implementors must ensure such contracts remain true.
|
||||
/// * `Layout` queries and calculations in general must be correct. Callers of this trait are allowed to rely
|
||||
/// on the contracts defined on each method, and implementors must ensure such contracts remain true.
|
||||
///
|
||||
/// Note that this list may get tweaked over time as clarifications are made in
|
||||
/// the future.
|
||||
/// Note that this list may get tweaked over time as clarifications are made in the future.
|
||||
// #[unstable(feature = "allocator_api", issue = "32838")]
|
||||
pub unsafe trait Alloc {
|
||||
// (Note: some existing allocators have unspecified but well-defined
|
||||
// behavior in response to a zero size allocation request ;
|
||||
// e.g. in C, `malloc` of 0 will either return a null pointer or a
|
||||
// unique pointer, but will not have arbitrary undefined
|
||||
// behavior.
|
||||
// However in jemalloc for example,
|
||||
// `mallocx(0)` is documented as undefined behavior.)
|
||||
// (Note: some existing allocators have unspecified but well-defined behavior in response to a zero size
|
||||
// allocation request ; e.g. in C, `malloc` of 0 will either return a null pointer or a unique pointer,
|
||||
// but will not have arbitrary undefined behavior.
|
||||
// However in jemalloc for example, `mallocx(0)` is documented as undefined behavior.)
|
||||
|
||||
/// Returns a pointer meeting the size and alignment guarantees of
|
||||
/// `layout`.
|
||||
/// Returns a pointer meeting the size and alignment guarantees of `layout`.
|
||||
///
|
||||
/// If this method returns an `Ok(addr)`, then the `addr` returned
|
||||
/// will be non-null address pointing to a block of storage
|
||||
/// suitable for holding an instance of `layout`.
|
||||
/// If this method returns an `Ok(addr)`, then the `addr` returned will be non-null address pointing to a
|
||||
/// block of storage suitable for holding an instance of `layout`.
|
||||
///
|
||||
/// The returned block of storage may or may not have its contents
|
||||
/// initialized. (Extension subtraits might restrict this
|
||||
/// behavior, e.g. to ensure initialization to particular sets of
|
||||
/// bit patterns.)
|
||||
/// The returned block of storage may or may not have its contents initialized. (Extension subtraits might
|
||||
/// restrict this behavior, e.g. to ensure initialization to particular sets of bit patterns.)
|
||||
///
|
||||
/// # SAFETY
|
||||
///
|
||||
/// This function is unsafe because undefined behavior can result
|
||||
/// if the caller does not ensure that `layout` has non-zero size.
|
||||
/// This function is unsafe because undefined behavior can result if the caller does not ensure that
|
||||
/// `layout` has non-zero size.
|
||||
///
|
||||
/// (Extension subtraits might provide more specific bounds on
|
||||
/// behavior, e.g. guarantee a sentinel address or a null pointer
|
||||
/// in response to a zero-size allocation request.)
|
||||
/// (Extension subtraits might provide more specific bounds on behavior, e.g. guarantee a sentinel address
|
||||
/// or a null pointer in response to a zero-size allocation request.)
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returning `Err` indicates that either memory is exhausted or
|
||||
/// `layout` does not meet allocator's size or alignment
|
||||
/// constraints.
|
||||
/// 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.)
|
||||
/// 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`] function,
|
||||
/// rather than directly invoking `panic!` or similar.
|
||||
/// Clients wishing to abort computation in response to an allocation error are encouraged to call the
|
||||
/// [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
|
||||
///
|
||||
/// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
|
||||
unsafe fn alloc(&mut self, layout: Layout) -> Result<NonNull<u8>, AllocErr>;
|
||||
@@ -268,48 +228,39 @@ pub unsafe trait Alloc {
|
||||
///
|
||||
/// # SAFETY
|
||||
///
|
||||
/// This function is unsafe because undefined behavior can result
|
||||
/// if the caller does not ensure all of the following:
|
||||
/// This function is unsafe because undefined behavior can result if the caller does not ensure all of the
|
||||
/// following:
|
||||
///
|
||||
/// * `ptr` must denote a block of memory currently allocated via
|
||||
/// this allocator,
|
||||
/// * `ptr` must denote a block of memory currently allocated via this allocator,
|
||||
///
|
||||
/// * `layout` must *fit* that block of memory,
|
||||
///
|
||||
/// * In addition to fitting the block of memory `layout`, the
|
||||
/// alignment of the `layout` must match the alignment used
|
||||
/// to allocate that block of memory.
|
||||
/// * In addition to fitting the block of memory `layout`, the alignment of the `layout` must match the
|
||||
/// alignment used to allocate that block of memory.
|
||||
unsafe fn dealloc(&mut self, ptr: NonNull<u8>, layout: Layout);
|
||||
|
||||
// == ALLOCATOR-SPECIFIC QUANTITIES AND LIMITS ==
|
||||
// usable_size
|
||||
|
||||
/// Returns bounds on the guaranteed usable size of a successful
|
||||
/// allocation created with the specified `layout`.
|
||||
/// Returns bounds on the guaranteed usable size of a successful allocation created with the specified
|
||||
/// `layout`.
|
||||
///
|
||||
/// In particular, if one has a memory block allocated via a given
|
||||
/// allocator `a` and layout `k` where `a.usable_size(k)` returns
|
||||
/// `(l, u)`, then one can pass that block to `a.dealloc()` with a
|
||||
/// layout in the size range [l, u].
|
||||
/// In particular, if one has a memory block allocated via a given allocator `a` and layout `k` where
|
||||
/// `a.usable_size(k)` returns `(l, u)`, then one can pass that block to `a.dealloc()` with a layout in
|
||||
/// the size range [l, u].
|
||||
///
|
||||
/// (All implementors of `usable_size` must ensure that
|
||||
/// `l <= k.size() <= u`)
|
||||
/// (All implementors of `usable_size` must ensure that `l <= k.size() <= u`)
|
||||
///
|
||||
/// Both the lower- and upper-bounds (`l` and `u` respectively)
|
||||
/// are provided, because an allocator based on size classes could
|
||||
/// misbehave if one attempts to deallocate a block without
|
||||
/// providing a correct value for its size (i.e., one within the
|
||||
/// range `[l, u]`).
|
||||
/// Both the lower- and upper-bounds (`l` and `u` respectively) are provided, because an allocator based
|
||||
/// on size classes could misbehave if one attempts to deallocate a block without providing a correct
|
||||
/// value for its size (i.e., one within the range `[l, u]`).
|
||||
///
|
||||
/// Clients who wish to make use of excess capacity are encouraged
|
||||
/// to use the `alloc_excess` and `realloc_excess` instead, as
|
||||
/// this method is constrained to report conservative values that
|
||||
/// serve as valid bounds for *all possible* allocation method
|
||||
/// calls.
|
||||
/// Clients who wish to make use of excess capacity are encouraged to use the `alloc_excess` and
|
||||
/// `realloc_excess` instead, as this method is constrained to report conservative values that serve as
|
||||
/// valid bounds for *all possible* allocation method calls.
|
||||
///
|
||||
/// However, for clients that do not wish to track the capacity
|
||||
/// returned by `alloc_excess` locally, this method is likely to
|
||||
/// produce useful results.
|
||||
/// However, for clients that do not wish to track the capacity returned by `alloc_excess` locally, this
|
||||
/// method is likely to produce useful results.
|
||||
#[inline]
|
||||
fn usable_size(&self, layout: &Layout) -> (usize, usize) {
|
||||
(layout.size(), layout.size())
|
||||
@@ -318,58 +269,45 @@ pub unsafe trait Alloc {
|
||||
// == METHODS FOR MEMORY REUSE ==
|
||||
// realloc. alloc_excess, realloc_excess
|
||||
|
||||
/// Returns a pointer suitable for holding data described by
|
||||
/// a new layout with `layout`’s alignment and a size given
|
||||
/// by `new_size`. To
|
||||
/// accomplish this, this may extend or shrink the allocation
|
||||
/// referenced by `ptr` to fit the new layout.
|
||||
/// Returns a pointer suitable for holding data described by a new layout with `layout`’s alignment and a
|
||||
/// size given by `new_size`. To accomplish this, this may extend or 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 of course it was
|
||||
/// transferred back to the caller again via the return value of
|
||||
/// this method).
|
||||
/// 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 of
|
||||
/// course 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.
|
||||
/// 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
|
||||
///
|
||||
/// This function is unsafe because undefined behavior can result
|
||||
/// if the caller does not ensure all of the following:
|
||||
/// This function is unsafe because undefined behavior can result if the caller does not ensure all of the
|
||||
/// following:
|
||||
///
|
||||
/// * `ptr` must be currently allocated via this allocator,
|
||||
///
|
||||
/// * `layout` must *fit* the `ptr` (see above). (The `new_size`
|
||||
/// argument need not fit it.)
|
||||
/// * `layout` must *fit* the `ptr` (see above). (The `new_size` argument need not fit it.)
|
||||
///
|
||||
/// * `new_size` must be greater than zero.
|
||||
/// * `new_size` must be greater than zero
|
||||
///
|
||||
/// * `new_size`, when rounded up to the nearest multiple of `layout.align()`,
|
||||
/// must not overflow (i.e. the rounded value must be less than `usize::MAX`).
|
||||
/// * `new_size`, when rounded up to the nearest multiple of `layout.align()`, must not overflow (i.e. the
|
||||
/// rounded value must be less than `usize::MAX`)
|
||||
///
|
||||
/// (Extension subtraits might provide more specific bounds on
|
||||
/// behavior, e.g. guarantee a sentinel address or a null pointer
|
||||
/// in response to a zero-size allocation request.)
|
||||
/// (Extension subtraits might provide more specific bounds on behavior, e.g. guarantee a sentinel address
|
||||
/// or a null pointer in response to a zero-size allocation request.)
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` only if the new layout
|
||||
/// does not meet the allocator's size
|
||||
/// and alignment constraints of the allocator, or if reallocation
|
||||
/// otherwise fails.
|
||||
/// Returns `Err` only if the new layout does not meet the allocator's size and alignment constraints of
|
||||
/// the allocator, or if reallocation 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.)
|
||||
/// 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 a
|
||||
/// reallocation error are encouraged to call the [`handle_alloc_error`] function,
|
||||
/// rather than directly invoking `panic!` or similar.
|
||||
/// Clients wishing to abort computation in response to a reallocation error are encouraged to call the
|
||||
/// [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
|
||||
///
|
||||
/// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
|
||||
unsafe fn realloc(
|
||||
@@ -391,7 +329,7 @@ pub unsafe trait Alloc {
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise, fall back on alloc + copy + dealloc.
|
||||
// otherwise, fall back on alloc + copy + dealloc
|
||||
let new_layout = unsafe { Layout::from_size_align_unchecked(new_size, layout.align()) };
|
||||
let result = unsafe { self.alloc(new_layout) };
|
||||
if let Ok(new_ptr) = result {
|
||||
@@ -407,8 +345,7 @@ pub unsafe trait Alloc {
|
||||
result
|
||||
}
|
||||
|
||||
/// Behaves like `alloc`, but also ensures that the contents
|
||||
/// are set to zero before being returned.
|
||||
/// Behaves like `alloc`, but also ensures that the contents are set to zero before being returned.
|
||||
///
|
||||
/// # SAFETY
|
||||
///
|
||||
@@ -416,13 +353,11 @@ pub unsafe trait Alloc {
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returning `Err` indicates that either memory is exhausted or
|
||||
/// `layout` does not meet allocator's size or alignment
|
||||
/// constraints, just as in `alloc`.
|
||||
/// Returning `Err` indicates that either memory is exhausted or `layout` does not meet allocator's size
|
||||
/// or alignment constraints, just as in `alloc`.
|
||||
///
|
||||
/// Clients wishing to abort computation in response to an
|
||||
/// allocation error are encouraged to call the [`handle_alloc_error`] function,
|
||||
/// rather than directly invoking `panic!` or similar.
|
||||
/// Clients wishing to abort computation in response to an allocation error are encouraged to call the
|
||||
/// [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
|
||||
///
|
||||
/// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
|
||||
unsafe fn alloc_zeroed(&mut self, layout: Layout) -> Result<NonNull<u8>, AllocErr> {
|
||||
@@ -434,9 +369,8 @@ pub unsafe trait Alloc {
|
||||
p
|
||||
}
|
||||
|
||||
/// Behaves like `alloc`, but also returns the whole size of
|
||||
/// the returned block. For some `layout` inputs, like arrays, this
|
||||
/// may include extra storage usable for additional data.
|
||||
/// Behaves like `alloc`, but also returns the whole size of the returned block. For some `layout` inputs,
|
||||
/// like arrays, this may include extra storage usable for additional data.
|
||||
///
|
||||
/// # SAFETY
|
||||
///
|
||||
@@ -444,13 +378,11 @@ pub unsafe trait Alloc {
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returning `Err` indicates that either memory is exhausted or
|
||||
/// `layout` does not meet allocator's size or alignment
|
||||
/// constraints, just as in `alloc`.
|
||||
/// Returning `Err` indicates that either memory is exhausted or `layout` does not meet allocator's size
|
||||
/// or alignment constraints, just as in `alloc`.
|
||||
///
|
||||
/// Clients wishing to abort computation in response to an
|
||||
/// allocation error are encouraged to call the [`handle_alloc_error`] function,
|
||||
/// rather than directly invoking `panic!` or similar.
|
||||
/// Clients wishing to abort computation in response to an allocation error are encouraged to call the
|
||||
/// [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
|
||||
///
|
||||
/// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
|
||||
unsafe fn alloc_excess(&mut self, layout: Layout) -> Result<Excess, AllocErr> {
|
||||
@@ -458,9 +390,8 @@ pub unsafe trait Alloc {
|
||||
unsafe { self.alloc(layout) }.map(|p| Excess(p, usable_size.1))
|
||||
}
|
||||
|
||||
/// Behaves like `realloc`, but also returns the whole size of
|
||||
/// the returned block. For some `layout` inputs, like arrays, this
|
||||
/// may include extra storage usable for additional data.
|
||||
/// Behaves like `realloc`, but also returns the whole size of the returned block. For some `layout`
|
||||
/// inputs, like arrays, this may include extra storage usable for additional data.
|
||||
///
|
||||
/// # SAFETY
|
||||
///
|
||||
@@ -468,13 +399,11 @@ pub unsafe trait Alloc {
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returning `Err` indicates that either memory is exhausted or
|
||||
/// `layout` does not meet allocator's size or alignment
|
||||
/// constraints, just as in `realloc`.
|
||||
/// Returning `Err` indicates that either memory is exhausted or `layout` does not meet allocator's size
|
||||
/// or alignment constraints, just as in `realloc`.
|
||||
///
|
||||
/// Clients wishing to abort computation in response to a
|
||||
/// reallocation error are encouraged to call the [`handle_alloc_error`] function,
|
||||
/// rather than directly invoking `panic!` or similar.
|
||||
/// Clients wishing to abort computation in response to a reallocation error are encouraged to call the
|
||||
/// [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
|
||||
///
|
||||
/// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
|
||||
unsafe fn realloc_excess(
|
||||
@@ -490,46 +419,40 @@ pub unsafe trait Alloc {
|
||||
|
||||
/// Attempts to extend the allocation referenced by `ptr` to fit `new_size`.
|
||||
///
|
||||
/// If this returns `Ok`, then the allocator has asserted that the
|
||||
/// memory block referenced by `ptr` now fits `new_size`, and thus can
|
||||
/// be used to carry data of a layout of that size and same alignment as
|
||||
/// `layout`. (The allocator is allowed to
|
||||
/// expend effort to accomplish this, such as extending the memory block to
|
||||
/// include successor blocks, or virtual memory tricks.)
|
||||
/// If this returns `Ok`, then the allocator has asserted that the memory block referenced by `ptr` now
|
||||
/// fits `new_size`, and thus can be used to carry data of a layout of that size and same alignment as
|
||||
/// `layout`. (The allocator is allowed to expend effort to accomplish this, such as extending the memory
|
||||
/// block to include successor blocks, or virtual memory tricks.)
|
||||
///
|
||||
/// Regardless of what this method returns, ownership of the
|
||||
/// memory block referenced by `ptr` has not been transferred, and
|
||||
/// the contents of the memory block are unaltered.
|
||||
/// Regardless of what this method returns, ownership of the memory block referenced by `ptr` has not been
|
||||
/// transferred, and the contents of the memory block are unaltered.
|
||||
///
|
||||
/// # SAFETY
|
||||
///
|
||||
/// This function is unsafe because undefined behavior can result
|
||||
/// if the caller does not ensure all of the following:
|
||||
/// This function is unsafe because undefined behavior can result if the caller does not ensure all of the
|
||||
/// following:
|
||||
///
|
||||
/// * `ptr` must be currently allocated via this allocator,
|
||||
///
|
||||
/// * `layout` must *fit* the `ptr` (see above); note the
|
||||
/// `new_size` argument need not fit it,
|
||||
/// * `layout` must *fit* the `ptr` (see above); note the `new_size` argument need not fit it,
|
||||
///
|
||||
/// * `new_size` must not be less than `layout.size()`,
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err(CannotReallocInPlace)` when the allocator is
|
||||
/// unable to assert that the memory block referenced by `ptr`
|
||||
/// could fit `layout`.
|
||||
/// Returns `Err(CannotReallocInPlace)` when the allocator is unable to assert that the memory block
|
||||
/// referenced by `ptr` could fit `layout`.
|
||||
///
|
||||
/// Note that one cannot pass `CannotReallocInPlace` to the `handle_alloc_error`
|
||||
/// function; clients are expected either to be able to recover from
|
||||
/// `grow_in_place` failures without aborting, or to fall back on
|
||||
/// another reallocation method before resorting to an abort.
|
||||
/// Note that one cannot pass `CannotReallocInPlace` to the `handle_alloc_error` function; clients are
|
||||
/// expected either to be able to recover from `grow_in_place` failures without aborting, or to fall back
|
||||
/// on another reallocation method before resorting to an abort.
|
||||
unsafe fn grow_in_place(
|
||||
&mut self,
|
||||
ptr: NonNull<u8>,
|
||||
layout: Layout,
|
||||
new_size: usize,
|
||||
) -> Result<(), CannotReallocInPlace> {
|
||||
let _ = ptr; // this default implementation doesn't care about the actual address.
|
||||
let _ = ptr; // this default implementation doesn't care about the actual address
|
||||
debug_assert!(new_size >= layout.size());
|
||||
let (_l, u) = self.usable_size(&layout);
|
||||
// _l <= layout.size() [guaranteed by usable_size()]
|
||||
@@ -539,50 +462,42 @@ pub unsafe trait Alloc {
|
||||
|
||||
/// Attempts to shrink the allocation referenced by `ptr` to fit `new_size`.
|
||||
///
|
||||
/// If this returns `Ok`, then the allocator has asserted that the
|
||||
/// memory block referenced by `ptr` now fits `new_size`, and
|
||||
/// thus can only be used to carry data of that smaller
|
||||
/// layout. (The allocator is allowed to take advantage of this,
|
||||
/// carving off portions of the block for reuse elsewhere.) The
|
||||
/// truncated contents of the block within the smaller layout are
|
||||
/// unaltered, and ownership of block has not been transferred.
|
||||
/// If this returns `Ok`, then the allocator has asserted that the memory block referenced by `ptr` now
|
||||
/// fits `new_size`, and thus can only be used to carry data of that smaller layout. (The allocator is
|
||||
/// allowed to take advantage of this, carving off portions of the block for reuse elsewhere.) The
|
||||
/// truncated contents of the block within the smaller layout are unaltered, and ownership of block has
|
||||
/// not been transferred.
|
||||
///
|
||||
/// If this returns `Err`, then the memory block is considered to
|
||||
/// still represent the original (larger) `layout`. None of the
|
||||
/// block has been carved off for reuse elsewhere, ownership of
|
||||
/// the memory block has not been transferred, and the contents of
|
||||
/// the memory block are unaltered.
|
||||
/// If this returns `Err`, then the memory block is considered to still represent the original (larger)
|
||||
/// `layout`. None of the block has been carved off for reuse elsewhere, ownership of the memory block has
|
||||
/// not been transferred, and the contents of the memory block are unaltered.
|
||||
///
|
||||
/// # SAFETY
|
||||
///
|
||||
/// This function is unsafe because undefined behavior can result
|
||||
/// if the caller does not ensure all of the following:
|
||||
/// This function is unsafe because undefined behavior can result if the caller does not ensure all of the
|
||||
/// following:
|
||||
///
|
||||
/// * `ptr` must be currently allocated via this allocator,
|
||||
///
|
||||
/// * `layout` must *fit* the `ptr` (see above); note the
|
||||
/// `new_size` argument need not fit it,
|
||||
/// * `layout` must *fit* the `ptr` (see above); note the `new_size` argument need not fit it,
|
||||
///
|
||||
/// * `new_size` must not be greater than `layout.size()`
|
||||
/// (and must be greater than zero),
|
||||
/// * `new_size` must not be greater than `layout.size()` (and must be greater than zero),
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err(CannotReallocInPlace)` when the allocator is
|
||||
/// unable to assert that the memory block referenced by `ptr`
|
||||
/// could fit `layout`.
|
||||
/// Returns `Err(CannotReallocInPlace)` when the allocator is unable to assert that the memory block
|
||||
/// referenced by `ptr` could fit `layout`.
|
||||
///
|
||||
/// Note that one cannot pass `CannotReallocInPlace` to the `handle_alloc_error`
|
||||
/// function; clients are expected either to be able to recover from
|
||||
/// `shrink_in_place` failures without aborting, or to fall back
|
||||
/// on another reallocation method before resorting to an abort.
|
||||
/// Note that one cannot pass `CannotReallocInPlace` to the `handle_alloc_error` function; clients are
|
||||
/// expected either to be able to recover from `shrink_in_place` failures without aborting, or to fall
|
||||
/// back on another reallocation method before resorting to an abort.
|
||||
unsafe fn shrink_in_place(
|
||||
&mut self,
|
||||
ptr: NonNull<u8>,
|
||||
layout: Layout,
|
||||
new_size: usize,
|
||||
) -> Result<(), CannotReallocInPlace> {
|
||||
let _ = ptr; // this default implementation doesn't care about the actual address.
|
||||
let _ = ptr; // this default implementation doesn't care about the actual address
|
||||
debug_assert!(new_size <= layout.size());
|
||||
let (l, _u) = self.usable_size(&layout);
|
||||
// layout.size() <= _u [guaranteed by usable_size()]
|
||||
@@ -597,29 +512,23 @@ pub unsafe trait Alloc {
|
||||
///
|
||||
/// Captures a common usage pattern for allocators.
|
||||
///
|
||||
/// The returned block is suitable for passing to the
|
||||
/// `alloc`/`realloc` methods of this allocator.
|
||||
/// The returned block is suitable for passing to the `alloc`/`realloc` methods of this allocator.
|
||||
///
|
||||
/// Note to implementors: If this returns `Ok(ptr)`, then `ptr`
|
||||
/// must be considered "currently allocated" and must be
|
||||
/// acceptable input to methods such as `realloc` or `dealloc`,
|
||||
/// *even if* `T` is a zero-sized type. In other words, if your
|
||||
/// `Alloc` implementation overrides this method in a manner
|
||||
/// that can return a zero-sized `ptr`, then all reallocation and
|
||||
/// deallocation methods need to be similarly overridden to accept
|
||||
/// such values as input.
|
||||
/// Note to implementors: If this returns `Ok(ptr)`, then `ptr` must be considered "currently allocated"
|
||||
/// and must be acceptable input to methods such as `realloc` or `dealloc`, *even if* `T` is a zero-sized
|
||||
/// type. In other words, if your `Alloc` implementation overrides this method in a manner that can return
|
||||
/// a zero-sized `ptr`, then all reallocation and deallocation methods need to be similarly overridden to
|
||||
/// accept such values as input.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returning `Err` indicates that either memory is exhausted or
|
||||
/// `T` does not meet allocator's size or alignment constraints.
|
||||
/// Returning `Err` indicates that either memory is exhausted or `T` does not meet allocator's size or
|
||||
/// alignment constraints.
|
||||
///
|
||||
/// For zero-sized `T`, may return either of `Ok` or `Err`, but
|
||||
/// will *not* yield undefined behavior.
|
||||
/// For zero-sized `T`, may return either of `Ok` or `Err`, but will *not* yield undefined behavior.
|
||||
///
|
||||
/// Clients wishing to abort computation in response to an
|
||||
/// allocation error are encouraged to call the [`handle_alloc_error`] function,
|
||||
/// rather than directly invoking `panic!` or similar.
|
||||
/// Clients wishing to abort computation in response to an allocation error are encouraged to call the
|
||||
/// [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
|
||||
///
|
||||
/// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
|
||||
fn alloc_one<T>(&mut self) -> Result<NonNull<T>, AllocErr>
|
||||
@@ -632,21 +541,18 @@ pub unsafe trait Alloc {
|
||||
|
||||
/// Deallocates a block suitable for holding an instance of `T`.
|
||||
///
|
||||
/// The given block must have been produced by this allocator,
|
||||
/// and must be suitable for storing a `T` (in terms of alignment
|
||||
/// as well as minimum and maximum size); otherwise yields
|
||||
/// undefined behavior.
|
||||
/// The given block must have been produced by this allocator, and must be suitable for storing a `T` (in
|
||||
/// terms of alignment as well as minimum and maximum size); otherwise yields undefined behavior.
|
||||
///
|
||||
/// Captures a common usage pattern for allocators.
|
||||
///
|
||||
/// # SAFETY
|
||||
///
|
||||
/// This function is unsafe because undefined behavior can result
|
||||
/// if the caller does not ensure both:
|
||||
/// This function is unsafe because undefined behavior can result if the caller does not ensure both:
|
||||
///
|
||||
/// * `ptr` must denote a block of memory currently allocated via this allocator
|
||||
///
|
||||
/// * the layout of `T` must *fit* that block of memory.
|
||||
/// * the layout of `T` must *fit* that block of memory
|
||||
unsafe fn dealloc_one<T>(&mut self, ptr: NonNull<T>)
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -661,32 +567,26 @@ pub unsafe trait Alloc {
|
||||
///
|
||||
/// Captures a common usage pattern for allocators.
|
||||
///
|
||||
/// The returned block is suitable for passing to the
|
||||
/// `alloc`/`realloc` methods of this allocator.
|
||||
/// The returned block is suitable for passing to the `alloc`/`realloc` methods of this allocator.
|
||||
///
|
||||
/// Note to implementors: If this returns `Ok(ptr)`, then `ptr`
|
||||
/// must be considered "currently allocated" and must be
|
||||
/// acceptable input to methods such as `realloc` or `dealloc`,
|
||||
/// *even if* `T` is a zero-sized type. In other words, if your
|
||||
/// `Alloc` implementation overrides this method in a manner
|
||||
/// that can return a zero-sized `ptr`, then all reallocation and
|
||||
/// deallocation methods need to be similarly overridden to accept
|
||||
/// such values as input.
|
||||
/// Note to implementors: If this returns `Ok(ptr)`, then `ptr` must be considered "currently allocated"
|
||||
/// and must be acceptable input to methods such as `realloc` or `dealloc`, *even if* `T` is a zero-sized
|
||||
/// type. In other words, if your `Alloc` implementation overrides this method in a manner that can return
|
||||
/// a zero-sized `ptr`, then all reallocation and deallocation methods need to be similarly overridden to
|
||||
/// accept such values as input.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returning `Err` indicates that either memory is exhausted or
|
||||
/// `[T; n]` does not meet allocator's size or alignment
|
||||
/// constraints.
|
||||
/// Returning `Err` indicates that either memory is exhausted or `[T; n]` does not meet allocator's size
|
||||
/// or alignment constraints.
|
||||
///
|
||||
/// For zero-sized `T` or `n == 0`, may return either of `Ok` or
|
||||
/// `Err`, but will *not* yield undefined behavior.
|
||||
/// For zero-sized `T` or `n == 0`, may return either of `Ok` or `Err`, but will *not* yield undefined
|
||||
/// behavior.
|
||||
///
|
||||
/// Always returns `Err` on arithmetic overflow.
|
||||
///
|
||||
/// Clients wishing to abort computation in response to an
|
||||
/// allocation error are encouraged to call the [`handle_alloc_error`] function,
|
||||
/// rather than directly invoking `panic!` or similar.
|
||||
/// Clients wishing to abort computation in response to an allocation error are encouraged to call the
|
||||
/// [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
|
||||
///
|
||||
/// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
|
||||
fn alloc_array<T>(&mut self, n: usize) -> Result<NonNull<T>, AllocErr>
|
||||
@@ -699,38 +599,34 @@ pub unsafe trait Alloc {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reallocates a block previously suitable for holding `n_old`
|
||||
/// instances of `T`, returning a block suitable for holding
|
||||
/// `n_new` instances of `T`.
|
||||
/// Reallocates a block previously suitable for holding `n_old` instances of `T`, returning a block
|
||||
/// suitable for holding `n_new` instances of `T`.
|
||||
///
|
||||
/// Captures a common usage pattern for allocators.
|
||||
///
|
||||
/// The returned block is suitable for passing to the
|
||||
/// `alloc`/`realloc` methods of this allocator.
|
||||
/// The returned block is suitable for passing to the `alloc`/`realloc` methods of this allocator.
|
||||
///
|
||||
/// # SAFETY
|
||||
///
|
||||
/// This function is unsafe because undefined behavior can result
|
||||
/// if the caller does not ensure all of the following:
|
||||
/// This function is unsafe because undefined behavior can result if the caller does not ensure all of the
|
||||
/// following:
|
||||
///
|
||||
/// * `ptr` must be currently allocated via this allocator,
|
||||
///
|
||||
/// * the layout of `[T; n_old]` must *fit* that block of memory.
|
||||
/// * the layout of `[T; n_old]` must *fit* that block of memory
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returning `Err` indicates that either memory is exhausted or
|
||||
/// `[T; n_new]` does not meet allocator's size or alignment
|
||||
/// constraints.
|
||||
/// Returning `Err` indicates that either memory is exhausted or `[T; n_new]` does not meet allocator's
|
||||
/// size or alignment constraints.
|
||||
///
|
||||
/// For zero-sized `T` or `n_new == 0`, may return either of `Ok` or
|
||||
/// `Err`, but will *not* yield undefined behavior.
|
||||
/// For zero-sized `T` or `n_new == 0`, may return either of `Ok` or `Err`, but will *not* yield undefined
|
||||
/// behavior.
|
||||
///
|
||||
/// Always returns `Err` on arithmetic overflow.
|
||||
///
|
||||
/// Clients wishing to abort computation in response to a
|
||||
/// reallocation error are encouraged to call the [`handle_alloc_error`] function,
|
||||
/// rather than directly invoking `panic!` or similar.
|
||||
/// Clients wishing to abort computation in response to a reallocation error are encouraged to call the
|
||||
/// [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
|
||||
///
|
||||
/// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
|
||||
unsafe fn realloc_array<T>(
|
||||
@@ -757,18 +653,16 @@ pub unsafe trait Alloc {
|
||||
///
|
||||
/// # SAFETY
|
||||
///
|
||||
/// This function is unsafe because undefined behavior can result
|
||||
/// if the caller does not ensure both:
|
||||
/// This function is unsafe because undefined behavior can result if the caller does not ensure both:
|
||||
///
|
||||
/// * `ptr` must denote a block of memory currently allocated via this allocator
|
||||
///
|
||||
/// * the layout of `[T; n]` must *fit* that block of memory.
|
||||
/// * the layout of `[T; n]` must *fit* that block of memory
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returning `Err` indicates that either `[T; n]` or the given
|
||||
/// memory block does not meet allocator's size or alignment
|
||||
/// constraints.
|
||||
/// Returning `Err` indicates that either `[T; n]` or the given memory block does not meet allocator's
|
||||
/// size or alignment constraints.
|
||||
///
|
||||
/// Always returns `Err` on arithmetic overflow.
|
||||
unsafe fn dealloc_array<T>(&mut self, ptr: NonNull<T>, n: usize) -> Result<(), AllocErr>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//! Methods to get info about the chunks of memory allocated by an `Arena`,
|
||||
//! and associated iterator types.
|
||||
//! Methods to get info about the chunks of memory allocated by an `Arena`, and associated iterator types.
|
||||
|
||||
use std::{iter::FusedIterator, marker::PhantomData, mem, ptr::NonNull, slice};
|
||||
|
||||
@@ -25,44 +24,36 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
current_footer.ptr.get().as_ptr() as usize - current_footer.data.as_ptr() as usize
|
||||
}
|
||||
|
||||
/// Returns an iterator over each chunk of allocated memory that
|
||||
/// this arena has allocated into.
|
||||
/// Get an iterator over each chunk of allocated memory that this arena has allocated into.
|
||||
///
|
||||
/// The chunks are returned ordered by allocation time, with the most
|
||||
/// recently allocated chunk being returned first, and the least recently
|
||||
/// allocated chunk being returned last.
|
||||
/// The chunks are returned ordered by allocation time, with the most recently allocated chunk
|
||||
/// returned first, and the least recently allocated chunk returned last.
|
||||
///
|
||||
/// The values inside each chunk are also ordered by allocation time, with
|
||||
/// the most recent allocation being earlier in the slice, and the least
|
||||
/// recent allocation being towards the end of the slice.
|
||||
/// The values inside each chunk are also ordered by allocation time, with the most recent allocation
|
||||
/// earlier in the slice, and the least recent allocation towards the end of the slice.
|
||||
///
|
||||
/// # SAFETY
|
||||
///
|
||||
/// Because this method takes `&mut self`, we know that the arena
|
||||
/// reference is unique and therefore there aren't any active references to
|
||||
/// any of the objects we've allocated in it either. This potential aliasing
|
||||
/// of exclusive references is one common footgun for unsafe code that we
|
||||
/// don't need to worry about here.
|
||||
/// Because this method takes `&mut self`, we know that the arena reference is unique and therefore there
|
||||
/// aren't any active references to any of the objects we've allocated in it either. This potential
|
||||
/// aliasing of exclusive references is one common footgun for unsafe code that we don't need to worry
|
||||
/// about here.
|
||||
///
|
||||
/// However, there could be regions of uninitialized memory used as padding
|
||||
/// between allocations, which is why this iterator has items of type
|
||||
/// `[MaybeUninit<u8>]`, instead of simply `[u8]`.
|
||||
/// However, there could be regions of uninitialized memory used as padding between allocations,
|
||||
/// which is why this iterator has items of type `[MaybeUninit<u8>]`, instead of simply `[u8]`.
|
||||
///
|
||||
/// The only way to guarantee that there is no padding between allocations
|
||||
/// or within allocated objects is if all of these properties hold:
|
||||
/// The only way to guarantee that there is no padding between allocations or within allocated objects is
|
||||
/// if all of these properties hold:
|
||||
///
|
||||
/// 1. Every object allocated in this arena has the same alignment,
|
||||
/// and that alignment is at most 16.
|
||||
/// 1. Every object allocated in this arena has the same alignment, and that alignment is at most 16.
|
||||
/// 2. Every object's size is a multiple of its alignment.
|
||||
/// 3. None of the objects allocated in this arena contain any internal
|
||||
/// padding.
|
||||
/// 3. None of the objects allocated in this arena contain any internal padding.
|
||||
///
|
||||
/// If you want to use this `iter_allocated_chunks` method, it is *your*
|
||||
/// responsibility to ensure that these properties hold before calling
|
||||
/// `MaybeUninit::assume_init` or otherwise reading the returned values.
|
||||
/// If you want to use this `iter_allocated_chunks` method, it is *your* responsibility to ensure that
|
||||
/// these properties hold before calling `MaybeUninit::assume_init` or otherwise reading the returned values.
|
||||
///
|
||||
/// Finally, you must also ensure that any values allocated into the arena
|
||||
/// have not had their `Drop` implementations called on them.
|
||||
/// Finally, you must also ensure that any values allocated into the arena have not had their `Drop`
|
||||
/// implementations called on them.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@@ -71,8 +62,8 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
///
|
||||
/// let mut arena = Arena::new();
|
||||
///
|
||||
/// // Allocate a bunch of `i32`s in this arena, potentially causing
|
||||
/// // additional memory chunks to be reserved.
|
||||
/// // Allocate a bunch of `i32`s in this arena,
|
||||
/// // potentially causing additional memory chunks to be reserved
|
||||
/// for i in 0..10000 {
|
||||
/// arena.alloc(i);
|
||||
/// }
|
||||
@@ -101,8 +92,8 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
/// let chunk = arena.iter_allocated_chunks().nth(0).unwrap();
|
||||
/// assert_eq!(chunk.len(), 3);
|
||||
///
|
||||
/// // Safe because we've only allocated `u8`s in this arena, which
|
||||
/// // fulfills the above requirements.
|
||||
/// // Safe because we've only allocated `u8`s in this arena,
|
||||
/// // which fulfills the above requirements
|
||||
/// unsafe {
|
||||
/// assert_eq!(chunk[0].assume_init(), b'c');
|
||||
/// assert_eq!(chunk[1].assume_init(), b'b');
|
||||
@@ -110,24 +101,21 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
/// }
|
||||
/// ```
|
||||
pub fn iter_allocated_chunks(&mut self) -> ChunkIter<'_, MIN_ALIGN> {
|
||||
// SAFETY: Ensured by mutable borrow of `self`.
|
||||
// SAFETY: Ensured by mutable borrow of `self`
|
||||
let raw = unsafe { self.iter_allocated_chunks_raw() };
|
||||
ChunkIter { raw, arena: PhantomData }
|
||||
}
|
||||
|
||||
/// Returns an iterator over raw pointers to chunks of allocated memory that
|
||||
/// this arena has allocated into.
|
||||
/// Get an iterator over raw pointers to chunks of allocated memory that this arena has allocated into.
|
||||
///
|
||||
/// This is an unsafe version of [`iter_allocated_chunks()`](Arena::iter_allocated_chunks),
|
||||
/// with the caller responsible for safe usage of the returned pointers as
|
||||
/// well as ensuring that the iterator is not invalidated by new
|
||||
/// allocations.
|
||||
/// This is an unsafe version of [`iter_allocated_chunks()`](Arena::iter_allocated_chunks), with the caller
|
||||
/// responsible for safe usage of the returned pointers as well as ensuring that the iterator is not
|
||||
/// invalidated by new allocations.
|
||||
///
|
||||
/// # SAFETY
|
||||
///
|
||||
/// Allocations from this arena must not be performed while the returned
|
||||
/// iterator is alive. If reading the chunk data (or casting to a reference)
|
||||
/// the caller must ensure that there exist no mutable references to
|
||||
/// Allocations from this arena must not be performed while the returned iterator is alive. If reading the
|
||||
/// chunk data (or casting to a reference) the caller must ensure that there exist no mutable references to
|
||||
/// previously allocated data.
|
||||
///
|
||||
/// In addition, all of the caveats when reading the chunk data from
|
||||
@@ -136,19 +124,15 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
ChunkRawIter { footer: self.current_chunk_footer.get(), arena: PhantomData }
|
||||
}
|
||||
|
||||
/// Calculates the number of bytes currently allocated across all chunks in
|
||||
/// this arena.
|
||||
/// Calculate the number of bytes currently allocated across all chunks in this arena.
|
||||
///
|
||||
/// If you allocate types of different alignments or types with
|
||||
/// larger-than-typical alignment in the same arena, some padding
|
||||
/// bytes might get allocated in the arena. Note that those padding
|
||||
/// bytes will add to this method's resulting sum, so you cannot rely
|
||||
/// on it only counting the sum of the sizes of the things
|
||||
/// If you allocate types of different alignments or types with larger-than-typical alignment in the same
|
||||
/// arena, some padding bytes might get allocated in the arena. Note that those padding bytes will add to
|
||||
/// this method's resulting sum, so you cannot rely on it only counting the sum of the sizes of the things
|
||||
/// you've allocated in the arena.
|
||||
///
|
||||
/// The allocated bytes do not include the size of arena metadata,
|
||||
/// so the amount of memory requested from the Rust allocator is higher
|
||||
/// than the returned value.
|
||||
/// The allocated bytes do not include the size of arena metadata, so the amount of memory requested from
|
||||
/// the Rust allocator is higher than the returned value.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@@ -175,20 +159,17 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over each chunk of allocated memory that
|
||||
/// an arena has allocated into.
|
||||
/// An iterator over each chunk of allocated memory that an arena has allocated into.
|
||||
///
|
||||
/// The chunks are returned ordered by allocation time, with the most recently
|
||||
/// allocated chunk being returned first.
|
||||
/// The chunks are returned ordered by allocation time, with the most recently allocated chunk returned first.
|
||||
///
|
||||
/// The values inside each chunk are also ordered by allocation time, with the most
|
||||
/// recent allocation being earlier in the slice.
|
||||
/// The values inside each chunk are also ordered by allocation time, with the most recent allocation
|
||||
/// earlier in the slice.
|
||||
///
|
||||
/// This struct is created by the [`iter_allocated_chunks`] method on
|
||||
/// [`Arena`]. See that function for a safety description regarding reading from the returned items.
|
||||
/// This struct is created by the [`iter_allocated_chunks`] method on [`Arena`].
|
||||
/// See that function for a safety description regarding reading from the returned items.
|
||||
///
|
||||
/// [`Arena`]: struct.Arena.html
|
||||
/// [`iter_allocated_chunks`]: struct.Arena.html#method.iter_allocated_chunks
|
||||
/// [`iter_allocated_chunks`]: Arena::iter_allocated_chunks
|
||||
#[derive(Debug)]
|
||||
pub struct ChunkIter<'a, const MIN_ALIGN: usize = 1> {
|
||||
raw: ChunkRawIter<'a, MIN_ALIGN>,
|
||||
@@ -209,17 +190,14 @@ impl<'a, const MIN_ALIGN: usize> Iterator for ChunkIter<'a, MIN_ALIGN> {
|
||||
|
||||
impl<const MIN_ALIGN: usize> FusedIterator for ChunkIter<'_, MIN_ALIGN> {}
|
||||
|
||||
/// An iterator over raw pointers to chunks of allocated memory that this
|
||||
/// arena has allocated into.
|
||||
/// An iterator over raw pointers to chunks of allocated memory that this arena has allocated into.
|
||||
///
|
||||
/// See [`ChunkIter`] for details regarding the returned chunks.
|
||||
///
|
||||
/// This struct is created by the [`iter_allocated_chunks_raw`] method on
|
||||
/// [`Arena`]. See that function for a safety description regarding reading from
|
||||
/// the returned items.
|
||||
/// This struct is created by the [`iter_allocated_chunks_raw`] method on [`Arena`].
|
||||
/// See that function for a safety description regarding reading from the returned items.
|
||||
///
|
||||
/// [`Arena`]: struct.Arena.html
|
||||
/// [`iter_allocated_chunks_raw`]: struct.Arena.html#method.iter_allocated_chunks_raw
|
||||
/// [`iter_allocated_chunks_raw`]: Arena::iter_allocated_chunks_raw
|
||||
#[derive(Debug)]
|
||||
pub struct ChunkRawIter<'a, const MIN_ALIGN: usize = 1> {
|
||||
footer: NonNull<ChunkFooter>,
|
||||
|
||||
@@ -16,12 +16,11 @@ use super::{
|
||||
utils::{layout_from_size_align, oom, round_mut_ptr_down_to, round_up_to},
|
||||
};
|
||||
|
||||
// The typical page size these days.
|
||||
//
|
||||
// Note that we don't need to exactly match page size for correctness, and it is
|
||||
// okay if this is smaller than the real page size in practice. It isn't worth
|
||||
// the portability concerns and lack of const propagation that dynamically
|
||||
// looking up the actual page size implies.
|
||||
/// The typical page size these days.
|
||||
///
|
||||
/// Note that we don't need to exactly match page size for correctness, and it is okay if this is smaller than
|
||||
/// the real page size in practice. It isn't worth the portability concerns and lack of const propagation that
|
||||
/// dynamically looking up the actual page size implies.
|
||||
const TYPICAL_PAGE_SIZE: usize = 0x1000;
|
||||
|
||||
// Check the hard-coded value in `ast_tools` raw transfer generator is accurate.
|
||||
@@ -34,33 +33,35 @@ const _: () = {
|
||||
assert!(CHUNK_FOOTER_SIZE == EXPECTED_CHUNK_FOOTER_SIZE);
|
||||
};
|
||||
|
||||
// Maximum typical overhead per allocation imposed by allocators.
|
||||
/// Maximum typical overhead per allocation imposed by allocators.
|
||||
const MALLOC_OVERHEAD: usize = 16;
|
||||
|
||||
// This is the overhead from malloc, footer and alignment. For instance, if
|
||||
// we want to request a chunk of memory that has at least X bytes usable for
|
||||
// allocations (where X is aligned to CHUNK_ALIGN), then we expect that the
|
||||
// after adding a footer, malloc overhead and alignment, the chunk of memory
|
||||
// the allocator actually sets aside for us is X+OVERHEAD rounded up to the
|
||||
// nearest suitable size boundary.
|
||||
/// The overhead from malloc, footer and alignment.
|
||||
///
|
||||
/// For instance, if we want to request a chunk of memory that has at least X bytes usable for allocations
|
||||
/// (where X is aligned to `CHUNK_ALIGN`), then we expect that after adding a footer, `malloc` overhead,
|
||||
/// and alignment, the chunk of memory the allocator actually sets aside for us is `X + OVERHEAD`,
|
||||
/// rounded up to the nearest suitable size boundary.
|
||||
const OVERHEAD: usize = match round_up_to(MALLOC_OVERHEAD + CHUNK_FOOTER_SIZE, CHUNK_ALIGN) {
|
||||
Some(x) => x,
|
||||
None => panic!(),
|
||||
};
|
||||
|
||||
// The target size of our first allocation, including our overhead.
|
||||
// The available capacity will be slightly smaller.
|
||||
// 16 KiB covers the majority of real-world JS/TS files.
|
||||
/// The target size of our first allocation, including our overhead.
|
||||
///
|
||||
/// The available capacity will be slightly smaller.
|
||||
/// 16 KiB covers the majority of real-world JS/TS files.
|
||||
const FIRST_ALLOCATION_GOAL: usize = 16 * 1024;
|
||||
|
||||
// The actual size of the first allocation is going to be a bit smaller than the
|
||||
// goal. We need to make room for the footer, and we also need take the
|
||||
// alignment into account. We're trying to avoid this kind of situation:
|
||||
// https://blog.mozilla.org/nnethercote/2011/08/05/clownshoes-available-in-sizes-2101-and-up/
|
||||
/// Default chunk size for a new `Arena`, minus the size of the footer.
|
||||
///
|
||||
/// The actual size of the first allocation is going to be a bit smaller than the goal.
|
||||
/// We need to make room for the footer, and we also need take the alignment into account.
|
||||
/// We're trying to avoid this kind of situation:
|
||||
/// <https://blog.mozilla.org/nnethercote/2011/08/05/clownshoes-available-in-sizes-2101-and-up/>
|
||||
pub const DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER: usize = FIRST_ALLOCATION_GOAL - OVERHEAD;
|
||||
|
||||
/// The memory size and alignment details for a potential new chunk
|
||||
/// allocation.
|
||||
/// The memory size and alignment details for a potential new chunk allocation.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct NewChunkMemoryDetails {
|
||||
new_size_without_footer: usize,
|
||||
@@ -68,11 +69,10 @@ pub struct NewChunkMemoryDetails {
|
||||
size: usize,
|
||||
}
|
||||
|
||||
// NB: We don't have constructors as methods on `impl<N> Arena<N>` that return
|
||||
// `Self` because then `rustc` can't infer the `N` if it isn't explicitly
|
||||
// provided, even though it has a default value. There doesn't seem to be a good
|
||||
// workaround, other than putting constructors on the `Arena<DEFAULT>`; even
|
||||
// `std` does this same thing with `HashMap`, for example.
|
||||
// Note: We don't have constructors as methods on `impl<N> Arena<N>` that return `Self` because then `rustc`
|
||||
// can't infer the `N` if it isn't explicitly provided, even though it has a default value.
|
||||
// There doesn't seem to be a good workaround, other than putting constructors on the `Arena<DEFAULT>`.
|
||||
// Even `std` does this same thing with `HashMap`, for example.
|
||||
impl Arena<1> {
|
||||
/// Construct a new arena to allocate into.
|
||||
///
|
||||
@@ -81,7 +81,6 @@ impl Arena<1> {
|
||||
/// ```
|
||||
/// # use oxc_allocator::arena::Arena;
|
||||
/// let arena = Arena::new();
|
||||
/// # let _ = arena;
|
||||
/// ```
|
||||
pub fn new() -> Self {
|
||||
Self::with_capacity(0)
|
||||
@@ -108,7 +107,6 @@ impl Arena<1> {
|
||||
/// ```
|
||||
/// # use oxc_allocator::arena::Arena;
|
||||
/// let arena = Arena::with_capacity(100);
|
||||
/// # let _ = arena;
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
@@ -128,7 +126,6 @@ impl Arena<1> {
|
||||
/// # use oxc_allocator::arena::{AllocErr, Arena};
|
||||
/// # fn _foo() -> Result<(), AllocErr> {
|
||||
/// let arena = Arena::try_with_capacity(100)?;
|
||||
/// # let _ = arena;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
@@ -142,7 +139,7 @@ impl Arena<1> {
|
||||
/// 2. Computing the new chunk's memory details overflows.
|
||||
/// 3. The underlying global allocator fails to allocate the initial chunk.
|
||||
///
|
||||
/// When `capacity` is `0` no allocation is performed and `Ok` is always returned.
|
||||
/// When `capacity` is `0`, no allocation is performed, and `Ok` is always returned.
|
||||
pub fn try_with_capacity(capacity: usize) -> Result<Self, AllocErr> {
|
||||
Self::try_with_min_align_and_capacity(capacity)
|
||||
}
|
||||
@@ -151,12 +148,11 @@ impl Arena<1> {
|
||||
impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
/// Create a new `Arena` that enforces a minimum alignment.
|
||||
///
|
||||
/// The minimum alignment must be a power of two and no larger than `16`.
|
||||
/// The minimum alignment must be a power of 2, and no larger than 16.
|
||||
///
|
||||
/// Enforcing a minimum alignment can speed up allocation of objects with
|
||||
/// alignment less than or equal to the minimum alignment. This comes at the
|
||||
/// cost of introducing otherwise-unnecessary padding between allocations of
|
||||
/// objects with alignment less than the minimum.
|
||||
/// Enforcing a minimum alignment can speed up allocation of objects with alignment less than or equal to
|
||||
/// the minimum alignment. This comes at the cost of introducing otherwise-unnecessary padding between
|
||||
/// allocations of objects with alignment less than the minimum.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@@ -175,11 +171,10 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
///
|
||||
/// Panics on invalid minimum alignments.
|
||||
//
|
||||
// Because of `rustc`'s poor type inference for default type/const
|
||||
// parameters (see the comment above the `impl Arena` block with no const
|
||||
// `MIN_ALIGN` parameter) and because we don't want to force everyone to
|
||||
// specify a minimum alignment with `Arena::new()` et al, we have a separate
|
||||
// constructor for specifying the minimum alignment.
|
||||
// Because of `rustc`'s poor type inference for default type/const parameters (see the comment above
|
||||
// the `impl Arena` block with no const `MIN_ALIGN` parameter), and because we don't want to force everyone
|
||||
// to specify a minimum alignment with `Arena::new()` et al, we have a separate constructor
|
||||
// for specifying the minimum alignment.
|
||||
pub fn with_min_align() -> Self {
|
||||
assert!(MIN_ALIGN.is_power_of_two(), "MIN_ALIGN must be a power of two; found {MIN_ALIGN}");
|
||||
assert!(
|
||||
@@ -190,15 +185,13 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
Self::new_impl(EMPTY_CHUNK.get())
|
||||
}
|
||||
|
||||
/// Create a new `Arena` that enforces a minimum alignment and starts with
|
||||
/// room for at least `capacity` bytes.
|
||||
/// Create a new `Arena` that enforces a minimum alignment, and starts with room for at least `capacity` bytes.
|
||||
///
|
||||
/// The minimum alignment must be a power of two and no larger than `16`.
|
||||
/// The minimum alignment must be a power of 2, and no larger than 16.
|
||||
///
|
||||
/// Enforcing a minimum alignment can speed up allocation of objects with
|
||||
/// alignment less than or equal to the minimum alignment. This comes at the
|
||||
/// cost of introducing otherwise-unnecessary padding between allocations of
|
||||
/// objects with alignment less than the minimum.
|
||||
/// Enforcing a minimum alignment can speed up allocation of objects with alignment less than or equal to
|
||||
/// the minimum alignment. This comes at the cost of introducing otherwise-unnecessary padding between
|
||||
/// allocations of objects with alignment less than the minimum.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@@ -226,15 +219,13 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
Self::try_with_min_align_and_capacity(capacity).unwrap_or_else(|_| oom())
|
||||
}
|
||||
|
||||
/// Create a new `Arena` that enforces a minimum alignment and starts with
|
||||
/// room for at least `capacity` bytes.
|
||||
/// Create a new `Arena` that enforces a minimum alignment, and starts with room for at least `capacity` bytes.
|
||||
///
|
||||
/// The minimum alignment must be a power of two and no larger than `16`.
|
||||
/// The minimum alignment must be a power of 2, and no larger than 16.
|
||||
///
|
||||
/// Enforcing a minimum alignment can speed up allocation of objects with
|
||||
/// alignment less than or equal to the minimum alignment. This comes at the
|
||||
/// cost of introducing otherwise-unnecessary padding between allocations of
|
||||
/// objects with alignment less than the minimum.
|
||||
/// Enforcing a minimum alignment can speed up allocation of objects with alignment less than or equal to
|
||||
/// the minimum alignment. This comes at the cost of introducing otherwise-unnecessary padding between
|
||||
/// allocations of objects with alignment less than the minimum.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@@ -257,9 +248,8 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics on invalid minimum alignments.
|
||||
///
|
||||
/// Panics if allocating the initial capacity fails.
|
||||
/// * Panics on invalid minimum alignments.
|
||||
/// * Panics if allocating the initial capacity fails.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
@@ -270,7 +260,7 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
/// 2. Computing the new chunk's memory details overflows.
|
||||
/// 3. The underlying global allocator fails to allocate the initial chunk.
|
||||
///
|
||||
/// When `capacity` is `0` no allocation is performed and `Ok` is always returned.
|
||||
/// When `capacity` is 0, no allocation is performed, and `Ok` is always returned.
|
||||
pub fn try_with_min_align_and_capacity(capacity: usize) -> Result<Self, AllocErr> {
|
||||
assert!(MIN_ALIGN.is_power_of_two(), "MIN_ALIGN must be a power of two; found {MIN_ALIGN}");
|
||||
assert!(
|
||||
@@ -312,9 +302,8 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine the memory details including final size, alignment and final
|
||||
/// size without footer for a new chunk that would be allocated to fulfill
|
||||
/// an allocation request.
|
||||
/// Determine the memory details including final size, alignment, and final size without footer
|
||||
/// for a new chunk that would be allocated to fulfill an allocation request.
|
||||
pub(super) fn new_chunk_memory_details(
|
||||
new_size_without_footer: Option<usize>,
|
||||
requested_layout: Layout,
|
||||
@@ -323,7 +312,7 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
let align = CHUNK_ALIGN
|
||||
// and we have to have at least our configured minimum alignment...
|
||||
.max(MIN_ALIGN)
|
||||
// and make sure we satisfy the requested allocation's alignment.
|
||||
// and make sure we satisfy the requested allocation's alignment
|
||||
.max(requested_layout.align());
|
||||
|
||||
let mut new_size_without_footer =
|
||||
@@ -333,12 +322,10 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
round_up_to(requested_layout.size(), align).unwrap_or_else(allocation_size_overflow);
|
||||
new_size_without_footer = new_size_without_footer.max(requested_size);
|
||||
|
||||
// We want our allocations to play nice with the memory allocator, and
|
||||
// waste as little memory as possible. For small allocations, this means
|
||||
// that the entire allocation including the chunk footer and mallocs
|
||||
// internal overhead is as close to a power of two as we can go without
|
||||
// going over. For larger allocations, we only need to get close to a
|
||||
// page boundary without going over.
|
||||
// We want our allocations to play nice with the memory allocator, and waste as little memory as possible.
|
||||
// For small allocations, this means that the entire allocation including the chunk footer and `malloc`'s
|
||||
// internal overhead is as close to a power of two as we can go without going over.
|
||||
// For larger allocations, we only need to get close to a page boundary without going over.
|
||||
if new_size_without_footer < TYPICAL_PAGE_SIZE {
|
||||
new_size_without_footer =
|
||||
(new_size_without_footer + OVERHEAD).next_power_of_two() - OVERHEAD;
|
||||
@@ -357,10 +344,6 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
}
|
||||
|
||||
/// Allocate a new chunk and return its initialized footer.
|
||||
///
|
||||
/// If given, `layouts` is a tuple of the current chunk size and the
|
||||
/// layout of the allocation request that triggered us to fall back to
|
||||
/// allocating a new chunk of memory.
|
||||
pub(super) unsafe fn new_chunk(
|
||||
new_chunk_memory_details: NewChunkMemoryDetails,
|
||||
requested_layout: Layout,
|
||||
@@ -377,7 +360,7 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
let data = alloc::alloc(layout);
|
||||
let data = NonNull::new(data)?;
|
||||
|
||||
// The `ChunkFooter` is at the end of the chunk.
|
||||
// The `ChunkFooter` is at the end of the chunk
|
||||
let footer_ptr = data.as_ptr().add(new_size_without_footer);
|
||||
debug_assert_eq!((data.as_ptr() as usize) % align, 0);
|
||||
debug_assert_eq!(footer_ptr as usize % CHUNK_ALIGN, 0);
|
||||
@@ -387,11 +370,9 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
)]
|
||||
let footer_ptr = footer_ptr.cast::<ChunkFooter>();
|
||||
|
||||
// The bump pointer is initialized to the end of the range we will bump
|
||||
// out of, rounded down to the minimum alignment. It is the
|
||||
// `NewChunkMemoryDetails` constructor's responsibility to ensure that
|
||||
// even after this rounding we have enough non-zero capacity in the
|
||||
// chunk.
|
||||
// The bump pointer is initialized to the end of the range we will bump out of, rounded down to
|
||||
// the minimum alignment. It is the `NewChunkMemoryDetails` constructor's responsibility to ensure
|
||||
// that even after this rounding we have enough non-zero capacity in the chunk.
|
||||
let ptr = round_mut_ptr_down_to(footer_ptr.cast::<u8>(), MIN_ALIGN);
|
||||
debug_assert_eq!(ptr as usize % MIN_ALIGN, 0);
|
||||
debug_assert!(
|
||||
|
||||
@@ -7,13 +7,12 @@ use super::{Arena, ChunkFooter, EMPTY_CHUNK, utils::is_pointer_aligned_to};
|
||||
impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
/// Reset this arena.
|
||||
///
|
||||
/// Performs mass deallocation on everything allocated in this arena by
|
||||
/// resetting the pointer into the underlying chunk of memory to the start
|
||||
/// of the chunk. Does not run any `Drop` implementations on deallocated
|
||||
/// objects; see [the top-level documentation](struct.Arena.html) for details.
|
||||
/// Performs mass deallocation on everything allocated in this arena by resetting the pointer into
|
||||
/// the underlying chunk of memory to the start of the chunk. Does not run any `Drop` implementations
|
||||
/// on deallocated objects. See [the top-level documentation] for details.
|
||||
///
|
||||
/// If this arena has allocated multiple chunks to allocate into, then
|
||||
/// the excess chunks are returned to the global allocator.
|
||||
/// If this arena has allocated multiple chunks to allocate into, then the excess chunks are returned to
|
||||
/// the global allocator.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@@ -22,28 +21,30 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
///
|
||||
/// let mut arena = Arena::new();
|
||||
///
|
||||
/// // Allocate a bunch of things.
|
||||
/// // Allocate a bunch of things
|
||||
/// {
|
||||
/// for i in 0..100 {
|
||||
/// arena.alloc(i);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Reset the arena.
|
||||
/// // Reset the arena
|
||||
/// arena.reset();
|
||||
///
|
||||
/// // Allocate some new things in the space previously occupied by the
|
||||
/// // original things.
|
||||
/// // Allocate some new things in the space
|
||||
/// // previously occupied by the original things
|
||||
/// for j in 200..400 {
|
||||
/// arena.alloc(j);
|
||||
/// }
|
||||
///```
|
||||
/// ```
|
||||
///
|
||||
/// [the top-level documentation]: Arena
|
||||
pub fn reset(&mut self) {
|
||||
#[cfg(all(feature = "track_allocations", not(feature = "disable_track_allocations")))]
|
||||
self.stats.reset();
|
||||
|
||||
// Takes `&mut self` so `self` must be unique and there can't be any
|
||||
// borrows active that would get invalidated by resetting.
|
||||
// Takes `&mut self` so `self` must be unique, and there can't be any borrows active
|
||||
// that would get invalidated by resetting
|
||||
unsafe {
|
||||
if self.current_chunk_footer.get().as_ref().is_empty() {
|
||||
return;
|
||||
@@ -55,7 +56,7 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
let prev_chunk = cur_chunk.as_ref().prev.replace(EMPTY_CHUNK.get());
|
||||
dealloc_chunk_list(prev_chunk);
|
||||
|
||||
// Reset the bump finger to the end of the chunk.
|
||||
// Reset the bump finger to the end of the chunk
|
||||
debug_assert!(
|
||||
is_pointer_aligned_to(cur_chunk.as_ptr(), MIN_ALIGN),
|
||||
"bump pointer {cur_chunk:#p} should be aligned to the minimum alignment of {MIN_ALIGN:#x}"
|
||||
|
||||
@@ -24,8 +24,8 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
/// * `size` must be a multiple of [`CHUNK_ALIGN`].
|
||||
/// * `size` must be at least [`CHUNK_FOOTER_SIZE`].
|
||||
/// * The memory region starting at `ptr` and encompassing `size` bytes must be within a single allocation.
|
||||
/// * The memory region starting at `ptr` and encompassing `size` bytes must have been allocated from system
|
||||
/// allocator with alignment of [`CHUNK_ALIGN`] (or caller must wrap the `Arena` in `ManuallyDrop`
|
||||
/// * The memory region starting at `ptr` and encompassing `size` bytes must have been allocated
|
||||
/// from system allocator with alignment of [`CHUNK_ALIGN`] (or caller must wrap the `Arena` in `ManuallyDrop`
|
||||
/// and ensure the backing memory is freed correctly themselves).
|
||||
/// * `ptr` must have permission for writes.
|
||||
///
|
||||
@@ -53,8 +53,7 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
// * `size` is `>= CHUNK_FOOTER_SIZE` - so `size - CHUNK_FOOTER_SIZE` cannot wrap around.
|
||||
let chunk_footer_ptr = unsafe { ptr.add(size_without_footer) }.cast::<ChunkFooter>();
|
||||
// SAFETY: Caller guarantees `size` is a multiple of `CHUNK_ALIGN`.
|
||||
// Caller guarantees region from `ptr` to `ptr + size` forms a single allocation,
|
||||
// so it must be a valid layout.
|
||||
// Caller guarantees region from `ptr` to `ptr + size` forms a single allocation, so it must be a valid layout.
|
||||
let layout = unsafe { Layout::from_size_align_unchecked(size, CHUNK_ALIGN) };
|
||||
let chunk_footer = ChunkFooter {
|
||||
data: ptr,
|
||||
@@ -63,14 +62,13 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
ptr: Cell::new(chunk_footer_ptr.cast::<u8>()),
|
||||
};
|
||||
|
||||
// SAFETY: If caller has upheld safety requirements, `chunk_footer_ptr` is `CHUNK_FOOTER_SIZE`
|
||||
// bytes from the end of the allocation, and aligned on `CHUNK_ALIGN`.
|
||||
// SAFETY: If caller has upheld safety requirements, `chunk_footer_ptr` is `CHUNK_FOOTER_SIZE` bytes
|
||||
// from the end of the allocation, and aligned on `CHUNK_ALIGN`.
|
||||
// Therefore `chunk_footer_ptr` is valid for writing a `ChunkFooter`.
|
||||
unsafe { chunk_footer_ptr.write(chunk_footer) };
|
||||
|
||||
// Create `Arena` and set `can_grow` to `false`.
|
||||
// This means that the memory chunk we've just created will remain its only chunk.
|
||||
// Therefore it can never be deallocated, until the `Arena` is dropped.
|
||||
// Create `Arena` and set `can_grow` to `false`. This means that the memory chunk we've just created
|
||||
// will remain its only chunk. Therefore it can never be deallocated, until the `Arena` is dropped.
|
||||
// `Arena::reset` would only reset the "cursor" pointer, not deallocate the memory.
|
||||
let mut arena = Self::new_impl(chunk_footer_ptr);
|
||||
arena.can_grow = false;
|
||||
@@ -85,8 +83,7 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
/// # SAFETY
|
||||
///
|
||||
/// * `Arena` must have at least 1 allocated chunk.
|
||||
/// It is UB to call this method on an `Arena` which has not allocated
|
||||
/// i.e. fresh from `Arena::new`.
|
||||
/// It is UB to call this method on an `Arena` which has not allocated i.e. fresh from `Arena::new`.
|
||||
/// * `ptr` must point to within the `Arena`'s current chunk.
|
||||
/// * `ptr` must be equal to or after data pointer for this chunk.
|
||||
/// * `ptr` must be equal to or before the chunk's `ChunkFooter`.
|
||||
@@ -100,7 +97,7 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
debug_assert!(ptr.as_ptr().cast_const() <= ptr::from_ref(chunk_footer).cast::<u8>());
|
||||
debug_assert!(ptr.addr().get().is_multiple_of(MIN_ALIGN));
|
||||
|
||||
// SAFETY: Caller guarantees `Arena` has at least 1 allocated chunk, and `ptr` is valid.
|
||||
// SAFETY: Caller guarantees `Arena` has at least 1 allocated chunk, and `ptr` is valid
|
||||
#[expect(clippy::unnecessary_safety_comment)]
|
||||
chunk_footer.ptr.set(ptr);
|
||||
}
|
||||
@@ -115,10 +112,9 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
pub fn end_ptr(&self) -> NonNull<u8> {
|
||||
let chunk_footer_ptr = self.current_chunk_footer.get();
|
||||
|
||||
// SAFETY: `chunk_footer_ptr` always points to a valid `ChunkFooter`,
|
||||
// so stepping past it cannot be out of bounds of the chunk's allocation.
|
||||
// If `Arena` has not allocated, so `chunk_footer_ptr` returns a pointer to the static empty chunk,
|
||||
// it's still valid.
|
||||
// SAFETY: `chunk_footer_ptr` always points to a valid `ChunkFooter`, so stepping past it cannot be
|
||||
// out of bounds of the chunk's allocation. If `Arena` has not allocated, `chunk_footer_ptr`
|
||||
// returns a pointer to the static empty chunk, it's still valid.
|
||||
unsafe { chunk_footer_ptr.add(1).cast::<u8>() }
|
||||
}
|
||||
}
|
||||
@@ -36,9 +36,8 @@ mod tests;
|
||||
///
|
||||
/// # No `Drop`s
|
||||
///
|
||||
/// Objects that are allocated will never have their [`Drop`] implementation
|
||||
/// called — unless you do it manually yourself. This makes it relatively
|
||||
/// easy to leak memory or other resources.
|
||||
/// Objects that are allocated will never have their [`Drop`] implementation called - unless you do it manually
|
||||
/// yourself. This makes it relatively easy to leak memory or other resources.
|
||||
///
|
||||
/// If you have a type which internally manages
|
||||
///
|
||||
@@ -46,37 +45,22 @@ mod tests;
|
||||
/// * open file descriptors (e.g. [`std::fs::File`]), or
|
||||
/// * any other resource that must be cleaned up (e.g. an `mmap`)
|
||||
///
|
||||
/// and relies on its `Drop` implementation to clean up the internal resource,
|
||||
/// then if you allocate that type with an `Arena`, you need to find a new way to
|
||||
/// clean up after it yourself.
|
||||
/// and relies on its `Drop` implementation to clean up the internal resource, then if you allocate that
|
||||
/// type with an `Arena`, you need to find a new way to clean up after it yourself.
|
||||
///
|
||||
/// Potential solutions are:
|
||||
///
|
||||
/// * Using [`oxc_allocator::Box::new_in`] instead of [`Arena::alloc`], that
|
||||
/// will drop wrapped values similarly to [`std::boxed::Box`]. Note that this
|
||||
/// requires enabling the `"boxed"` Cargo feature for this crate. **This is
|
||||
/// often the easiest solution.**
|
||||
/// * Using [`oxc_allocator::Box::new_in`] instead of [`Arena::alloc`], that will drop wrapped values similarly
|
||||
/// to [`std::boxed::Box`]. This is often the easiest solution.
|
||||
///
|
||||
/// * Calling [`drop_in_place`][drop_in_place] or using
|
||||
/// [`std::mem::ManuallyDrop`][manuallydrop] to manually drop these types.
|
||||
/// * Calling `drop_in_place` or using [`std::mem::ManuallyDrop`] to manually drop these types.
|
||||
///
|
||||
/// * Using [`oxc_allocator::Vec`] instead of [`std::vec::Vec`].
|
||||
///
|
||||
/// * Avoiding allocating these problematic types within an `Arena`.
|
||||
///
|
||||
/// Note that not calling `Drop` is memory safe! Destructors are never
|
||||
/// guaranteed to run in Rust, you can't rely on them for enforcing memory
|
||||
/// safety.
|
||||
///
|
||||
/// [`Drop`]: https://doc.rust-lang.org/std/ops/trait.Drop.html
|
||||
/// [`Vec<T>`]: https://doc.rust-lang.org/std/vec/struct.Vec.html
|
||||
/// [`std::fs::File`]: https://doc.rust-lang.org/std/fs/struct.File.html
|
||||
/// [drop_in_place]: https://doc.rust-lang.org/std/ptr/fn.drop_in_place.html
|
||||
/// [manuallydrop]: https://doc.rust-lang.org/std/mem/struct.ManuallyDrop.html
|
||||
/// [`oxc_allocator::Vec`]: crate::Vec
|
||||
/// [`std::vec::Vec`]: https://doc.rust-lang.org/std/vec/struct.Vec.html
|
||||
/// [`oxc_allocator::Box::new_in`]: crate::Box::new_in
|
||||
/// [`std::boxed::Box`]: https://doc.rust-lang.org/std/boxed/struct.Box.html
|
||||
/// Note that not calling `Drop` is memory safe! Destructors are never guaranteed to run in Rust, you can't rely
|
||||
/// on them for enforcing memory safety.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@@ -97,10 +81,9 @@ mod tests;
|
||||
///
|
||||
/// # Allocation Methods Come in Many Flavors
|
||||
///
|
||||
/// There are various allocation methods on `Arena`, the simplest being
|
||||
/// [`alloc`][Arena::alloc]. The others exist to satisfy some combination of
|
||||
/// fallible allocation and initialization. The allocation methods are
|
||||
/// summarized in the following table:
|
||||
/// There are various allocation methods on [`Arena`], the simplest being [`alloc`]. The others exist to
|
||||
/// satisfy some combination of fallible allocation and initialization. The allocation methods are summarized
|
||||
/// in the following table:
|
||||
///
|
||||
/// <table>
|
||||
/// <thead>
|
||||
@@ -126,8 +109,7 @@ mod tests;
|
||||
///
|
||||
/// ## Fallible Allocation: The `try_alloc_` Method Prefix
|
||||
///
|
||||
/// These allocation methods let you recover from out-of-memory (OOM)
|
||||
/// scenarios, rather than raising a panic on OOM.
|
||||
/// These allocation methods let you recover from out-of-memory (OOM) scenarios, rather than raising a panic on OOM.
|
||||
///
|
||||
/// ```
|
||||
/// # use oxc_allocator::arena::Arena;
|
||||
@@ -152,38 +134,37 @@ mod tests;
|
||||
///
|
||||
/// ## Initializer Functions: The `_with` Method Suffix
|
||||
///
|
||||
/// Calling one of the generic `…alloc(x)` methods is essentially equivalent to
|
||||
/// the matching [`…alloc_with(|| x)`](?search=alloc_with). However if you use
|
||||
/// `…alloc_with`, then the closure will not be invoked until after allocating
|
||||
/// space for storing `x` on the heap.
|
||||
/// Calling one of the generic `...alloc(x)` methods is essentially equivalent to the matching
|
||||
/// [`...alloc_with(|| x)`](?search=alloc_with). However if you use `...alloc_with`, then the closure will not be
|
||||
/// invoked until after allocating space for storing `x` on the heap.
|
||||
///
|
||||
/// This can be useful in certain edge-cases related to compiler optimizations.
|
||||
/// When evaluating for example `arena.alloc(x)`, semantically `x` is first put
|
||||
/// on the stack and then moved onto the heap. In some cases, the compiler is
|
||||
/// able to optimize this into constructing `x` directly on the heap, however
|
||||
/// in many cases it does not.
|
||||
/// This can be useful in certain edge-cases related to compiler optimizations. When evaluating for example
|
||||
/// `arena.alloc(x)`, semantically `x` is first put on the stack and then moved onto the heap. In some cases, the
|
||||
/// compiler is able to optimize this into constructing `x` directly on the heap, however in many cases it does not.
|
||||
///
|
||||
/// The `…alloc_with` functions try to help the compiler be smarter. In most
|
||||
/// cases doing for example `arena.try_alloc_with(|| x)` on release mode will be
|
||||
/// enough to help the compiler realize that this optimization is valid and
|
||||
/// to construct `x` directly onto the heap.
|
||||
/// The `...alloc_with` functions try to help the compiler be smarter. In most cases doing for example
|
||||
/// `arena.try_alloc_with(|| x)` on release mode will be enough to help the compiler realize that this
|
||||
/// optimization is valid and to construct `x` directly onto the heap.
|
||||
///
|
||||
/// ### Warning
|
||||
///
|
||||
/// These functions critically depend on compiler optimizations to achieve their
|
||||
/// desired effect. This means that it is not an effective tool when compiling
|
||||
/// without optimizations on.
|
||||
/// These functions critically depend on compiler optimizations to achieve their desired effect.
|
||||
/// This means that it is not an effective tool when compiling without optimizations on.
|
||||
///
|
||||
/// Even when optimizations are on, these functions do not **guarantee** that
|
||||
/// the value is constructed on the heap. To the best of our knowledge no such
|
||||
/// guarantee can be made in stable Rust as of 1.54.
|
||||
/// Even when optimizations are on, these functions do not **guarantee** that the value is constructed on the heap.
|
||||
/// To the best of our knowledge no such guarantee can be made in stable Rust as of 1.54.
|
||||
///
|
||||
/// [`Vec<T>`]: std::vec::Vec
|
||||
/// [`oxc_allocator::Vec`]: crate::Vec
|
||||
/// [`oxc_allocator::Box::new_in`]: crate::Box::new_in
|
||||
/// [`alloc`]: Arena::alloc
|
||||
#[derive(Debug)]
|
||||
pub struct Arena<const MIN_ALIGN: usize = 1> {
|
||||
// The current chunk we are bump allocating within.
|
||||
/// The current chunk we are bump allocating within.
|
||||
current_chunk_footer: Cell<NonNull<ChunkFooter>>,
|
||||
// Whether this `Arena` is allowed to allocate additional chunks when the current one is full.
|
||||
/// Whether this `Arena` is allowed to allocate additional chunks when the current one is full.
|
||||
can_grow: bool,
|
||||
/// Used to track number of allocations made in this `Arena` when `track_allocations` feature is enabled
|
||||
/// Used to track number of allocations made in this `Arena` when `track_allocations` feature is enabled.
|
||||
#[cfg(all(feature = "track_allocations", not(feature = "disable_track_allocations")))]
|
||||
pub(crate) stats: AllocationStats,
|
||||
}
|
||||
@@ -212,34 +193,33 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
}
|
||||
|
||||
// SAFETY:
|
||||
// `Arena`s are safe to send between threads because nothing aliases its owned
|
||||
// chunks until you start allocating from it. But by the time you allocate from
|
||||
// it, the returned references to allocations borrow the `Arena` and therefore
|
||||
// prevent sending the `Arena` across threads until the borrows end.
|
||||
// `Arena`s are safe to send between threads because nothing aliases its owned chunks until you start allocating
|
||||
// from it. But by the time you allocate from it, the returned references to allocations borrow the `Arena` and
|
||||
// therefore prevent sending the `Arena` across threads until the borrows end.
|
||||
unsafe impl<const MIN_ALIGN: usize> Send for Arena<MIN_ALIGN> {}
|
||||
|
||||
#[repr(C)]
|
||||
#[repr(align(16))]
|
||||
#[derive(Debug)]
|
||||
struct ChunkFooter {
|
||||
// Pointer to the start of this chunk allocation. This footer is always at
|
||||
// the end of the chunk.
|
||||
/// Pointer to the start of this chunk allocation.
|
||||
/// This footer is always at the end of the chunk.
|
||||
data: NonNull<u8>,
|
||||
|
||||
// The layout of this chunk's allocation.
|
||||
/// The layout of this chunk's allocation.
|
||||
layout: Layout,
|
||||
|
||||
// Link to the previous chunk.
|
||||
//
|
||||
// Note that the last node in the `prev` linked list is the canonical empty
|
||||
// chunk, whose `prev` link points to itself.
|
||||
/// Link to the previous chunk.
|
||||
///
|
||||
/// The last node in the `prev` linked list is the canonical empty chunk, whose `prev` link points to
|
||||
/// itself.
|
||||
prev: Cell<NonNull<ChunkFooter>>,
|
||||
|
||||
// Bump allocation finger that is always in the range `self.data..=self`.
|
||||
/// Bump allocation finger that is always in the range `self.data..=self`.
|
||||
ptr: Cell<NonNull<u8>>,
|
||||
}
|
||||
|
||||
// We only support alignments of up to 16 bytes for `iter_allocated_chunks`
|
||||
/// We only support alignments of up to 16 bytes for `iter_allocated_chunks`.
|
||||
const SUPPORTED_ITER_ALIGNMENT: usize = 16;
|
||||
pub const CHUNK_ALIGN: usize = SUPPORTED_ITER_ALIGNMENT;
|
||||
pub const CHUNK_FOOTER_SIZE: usize = size_of::<ChunkFooter>();
|
||||
@@ -248,26 +228,25 @@ const _: () = assert!(align_of::<ChunkFooter>() == CHUNK_ALIGN);
|
||||
|
||||
/// A wrapper type for the canonical, statically allocated empty chunk.
|
||||
///
|
||||
/// For the canonical empty chunk to be `static`, its type must be `Sync`, which
|
||||
/// is the purpose of this wrapper type. This is safe because the empty chunk is
|
||||
/// immutable and never actually modified.
|
||||
/// For the canonical empty chunk to be `static`, its type must be `Sync`, which is the purpose of this wrapper type.
|
||||
/// This is safe because the empty chunk is immutable and never actually modified.
|
||||
#[repr(transparent)]
|
||||
struct EmptyChunkFooter(ChunkFooter);
|
||||
|
||||
unsafe impl Sync for EmptyChunkFooter {}
|
||||
|
||||
static EMPTY_CHUNK: EmptyChunkFooter = EmptyChunkFooter(ChunkFooter {
|
||||
// This chunk is empty (except the foot itself).
|
||||
// This chunk is empty (except the foot itself)
|
||||
layout: Layout::new::<ChunkFooter>(),
|
||||
|
||||
// The start of the (empty) allocatable region for this chunk is itself.
|
||||
// The start of the (empty) allocatable region for this chunk is itself
|
||||
data: NonNull::from_ref(&EMPTY_CHUNK).cast::<u8>(),
|
||||
|
||||
// The end of the (empty) allocatable region for this chunk is also itself.
|
||||
// The end of the (empty) allocatable region for this chunk is also itself
|
||||
ptr: Cell::new(NonNull::from_ref(&EMPTY_CHUNK).cast::<u8>()),
|
||||
|
||||
// Invariant: the last chunk footer in all `ChunkFooter::prev` linked lists
|
||||
// is the empty chunk footer, whose `prev` points to itself.
|
||||
// Invariant: The last chunk footer in all `ChunkFooter::prev` linked lists is the empty chunk footer,
|
||||
// whose `prev` points to itself
|
||||
prev: Cell::new(NonNull::from_ref(&EMPTY_CHUNK.0)),
|
||||
});
|
||||
|
||||
@@ -290,7 +269,7 @@ impl ChunkFooter {
|
||||
(ptr, len)
|
||||
}
|
||||
|
||||
/// Is this chunk the last empty chunk?
|
||||
/// Returns `true` if this chunk is the empty chunk (end of the linked list).
|
||||
fn is_empty(&self) -> bool {
|
||||
ptr::eq(self, EMPTY_CHUNK.get().as_ptr())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Unit tests for `Arena`.
|
||||
|
||||
// NB: Only tests which require private types, fields, or methods should be in here.
|
||||
// Note: Only tests which require private types, fields, or methods should be in here.
|
||||
// Anything that can just be tested via public API surface should be in `tests/arena/*`.
|
||||
|
||||
use std::alloc::Layout;
|
||||
@@ -20,21 +20,21 @@ use super::{
|
||||
#[cfg(doctest)]
|
||||
fn arena_not_sync() {}
|
||||
|
||||
// Uses private type `ChunkFooter`.
|
||||
// Uses private type `ChunkFooter`
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
#[test]
|
||||
fn chunk_footer_is_six_words_on_64_bit() {
|
||||
assert_eq!(size_of::<ChunkFooter>(), size_of::<usize>() * 6);
|
||||
}
|
||||
|
||||
// Uses private type `ChunkFooter`.
|
||||
// Uses private type `ChunkFooter`
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
#[test]
|
||||
fn chunk_footer_is_eight_words_on_32_bit() {
|
||||
assert_eq!(size_of::<ChunkFooter>(), size_of::<usize>() * 8);
|
||||
}
|
||||
|
||||
// Uses private `DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER`.
|
||||
// Uses private `DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER`
|
||||
#[test]
|
||||
fn allocated_bytes() {
|
||||
let mut b = Arena::new();
|
||||
@@ -50,44 +50,42 @@ fn allocated_bytes() {
|
||||
assert_eq!(b.allocated_bytes(), DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER);
|
||||
}
|
||||
|
||||
// Uses private `bumpalo_alloc` module.
|
||||
// Uses private `bumpalo_alloc` module
|
||||
#[test]
|
||||
fn test_realloc() {
|
||||
unsafe {
|
||||
const CAPACITY: usize = DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER;
|
||||
let mut b = Arena::<1>::with_min_align_and_capacity(CAPACITY);
|
||||
|
||||
// `realloc` doesn't shrink allocations that aren't "worth it".
|
||||
// `realloc` doesn't shrink allocations that aren't "worth it"
|
||||
let layout = Layout::from_size_align(100, 1).unwrap();
|
||||
let p = b.alloc_layout(layout);
|
||||
let q = (&b).realloc(p, layout, 51).unwrap();
|
||||
assert_eq!(p, q);
|
||||
b.reset();
|
||||
|
||||
// `realloc` will shrink allocations that are "worth it".
|
||||
// `realloc` will shrink allocations that are "worth it"
|
||||
let layout = Layout::from_size_align(100, 1).unwrap();
|
||||
let p = b.alloc_layout(layout);
|
||||
let q = (&b).realloc(p, layout, 50).unwrap();
|
||||
assert!(p != q);
|
||||
b.reset();
|
||||
|
||||
// `realloc` will reuse the last allocation when growing.
|
||||
// `realloc` will reuse the last allocation when growing
|
||||
let layout = Layout::from_size_align(10, 1).unwrap();
|
||||
let p = b.alloc_layout(layout);
|
||||
let q = (&b).realloc(p, layout, 11).unwrap();
|
||||
assert_eq!(q.as_ptr() as usize, p.as_ptr() as usize - 1);
|
||||
b.reset();
|
||||
|
||||
// `realloc` will allocate a new chunk when growing the last
|
||||
// allocation, if need be.
|
||||
// `realloc` will allocate a new chunk when growing the last allocation, if need be
|
||||
let layout = Layout::from_size_align(1, 1).unwrap();
|
||||
let p = b.alloc_layout(layout);
|
||||
let q = (&b).realloc(p, layout, CAPACITY + 1).unwrap();
|
||||
assert_ne!(q.as_ptr() as usize, p.as_ptr() as usize - CAPACITY);
|
||||
b.reset();
|
||||
|
||||
// `realloc` will allocate and copy when reallocating anything that
|
||||
// wasn't the last allocation.
|
||||
// `realloc` will allocate and copy when reallocating anything that wasn't the last allocation
|
||||
let layout = Layout::from_size_align(1, 1).unwrap();
|
||||
let p = b.alloc_layout(layout);
|
||||
let _ = b.alloc_layout(layout);
|
||||
@@ -97,7 +95,7 @@ fn test_realloc() {
|
||||
}
|
||||
}
|
||||
|
||||
// Uses our private `bumpalo_alloc` module.
|
||||
// Uses our private `bumpalo_alloc` module
|
||||
#[test]
|
||||
fn invalid_read() {
|
||||
let mut b = &Arena::new();
|
||||
|
||||
Reference in new issue
Block a user