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:
overlookmotel committed 2026-04-15 00:54:48 +00:00
1 parent 7f60234062
commit c5b19bbc55
9 files changed
+494 -701

No files matched your search

+38 -50
View File
@@ -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()`].
///
+46 -68
View File
@@ -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) };
+210 -316
View File
@@ -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>
+48 -70
View File
@@ -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>,
+60 -79
View File
@@ -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!(
+15 -14
View File
@@ -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>() }
}
}
+54 -75
View File
@@ -36,9 +36,8 @@ mod tests;
///
/// # No `Drop`s
///
/// Objects that are allocated will never have their [`Drop`] implementation
/// called &mdash; 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())
}
+11 -13
View File
@@ -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();