Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(allocator): add Allocator::capacity and used_bytes methods #8621

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions crates/oxc_allocator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,100 @@ impl Allocator {
self.bump.reset();
}

/// Calculate the total capacity of this [`Allocator`] including all chunks, in bytes.
///
/// Note: This is the total amount of memory the [`Allocator`] owns NOT the total size of data
/// that's been allocated in it. If you want the latter, use [`used_bytes`] instead.
///
/// # Examples
/// ```
/// use oxc_allocator::Allocator;
///
/// let capacity = 64 * 1024; // 64 KiB
/// let mut allocator = Allocator::with_capacity(capacity);
/// allocator.alloc(123u64); // 8 bytes
///
/// // Result is the capacity (64 KiB), not the size of allocated data (8 bytes).
/// // `Allocator::with_capacity` may allocate a bit more than requested.
/// assert!(allocator.capacity() >= capacity);
/// ```
///
/// [`used_bytes`]: Allocator::used_bytes
//
// `#[inline(always)]` because it just delegates to `bumpalo`
#[expect(clippy::inline_always)]
#[inline(always)]
pub fn capacity(&self) -> usize {
self.bump.allocated_bytes()
}

/// Calculate the total size of data used in this [`Allocator`], in bytes.
///
/// This is the total amount of memory that has been *used* in the [`Allocator`], NOT the amount of
/// memory the [`Allocator`] owns. If you want the latter, use [`capacity`] instead.
///
/// The result includes:
///
/// 1. Padding bytes between objects which have been allocated to preserve alignment of types
/// where they have different alignments or have larger-than-typical alignment.
/// 2. Excess capacity in [`Vec`]s, [`String`]s and [`HashMap`]s.
/// 3. Objects which were allocated but later dropped. [`Allocator`] does not re-use allocations,
/// so anything which is allocated into arena continues to take up "dead space", even after it's
/// no longer referenced anywhere.
/// 4. "Dead space" left over where a [`Vec`], [`String`] or [`HashMap`] has grown and had to make
/// a new allocation to accommodate its new larger size. Its old allocation continues to take up
/// "dead" space in the allocator, unless it was the most recent allocation.
///
/// In practice, this almost always means that the result returned from this function will be an
/// over-estimate vs the amount of "live" data in the arena.
///
/// However, if you are using the result of this method to create a new `Allocator` to clone
/// an AST into, it is theoretically possible (though very unlikely) that it may be a slight
/// under-estimate of the capacity required in new allocator to clone the AST into, depending
/// on the order that `&str`s were allocated into arena in parser vs the order they get allocated
/// during cloning. The order allocations are made in affects the amount of padding bytes required.
///
/// # Examples
/// ```
/// use oxc_allocator::{Allocator, Vec};
///
/// let capacity = 64 * 1024; // 64 KiB
/// let mut allocator = Allocator::with_capacity(capacity);
///
/// allocator.alloc(1u8); // 1 byte with alignment 1
/// allocator.alloc(2u8); // 1 byte with alignment 1
/// allocator.alloc(3u64); // 8 bytes with alignment 8
///
/// // Only 10 bytes were allocated, but 16 bytes were used, in order to align `3u64` on 8
/// assert_eq!(allocator.used_bytes(), 16);
///
/// allocator.reset();
///
/// let mut vec = Vec::<u64>::with_capacity_in(2, &allocator);
///
/// // Allocate something else, so `vec`'s allocation is not the most recent
/// allocator.alloc(123u64);
///
/// // `vec` has to grow beyond it's initial capacity
/// vec.extend([1, 2, 3, 4]);
///
/// // `vec` takes up 32 bytes, and `123u64` takes up 8 bytes = 40 total.
/// // But there's an additional 16 bytes consumed for `vec`'s original capacity of 2,
/// // which is still using up space
/// assert_eq!(allocator.used_bytes(), 56);
/// ```
///
/// [`capacity`]: Allocator::capacity
pub fn used_bytes(&self) -> usize {
let mut bytes = 0;
// SAFETY: No allocations are made while `chunks_iter` is alive. No data is read from the chunks.
let chunks_iter = unsafe { self.bump.iter_allocated_chunks_raw() };
for (_, size) in chunks_iter {
bytes += size;
}
bytes
}

/// Get inner [`bumpalo::Bump`].
///
/// This method is not public. We don't want to expose `bumpalo::Allocator` to user.
Expand Down
Loading