Skip to content

Commit 7b7e9ec

Browse files
committed
feat(allocator): add Allocator::capacity and used_bytes methods
1 parent 7b82475 commit 7b7e9ec

File tree

1 file changed

+94
-0
lines changed
  • crates/oxc_allocator/src

1 file changed

+94
-0
lines changed

crates/oxc_allocator/src/lib.rs

+94
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,100 @@ impl Allocator {
204204
self.bump.reset();
205205
}
206206

207+
/// Calculate the total capacity of this [`Allocator`] including all chunks, in bytes.
208+
///
209+
/// Note: This is the total amount of memory the [`Allocator`] owns NOT the total size of data
210+
/// that's been allocated in it. If you want the latter, use [`used_bytes`] instead.
211+
///
212+
/// # Examples
213+
/// ```
214+
/// use oxc_allocator::Allocator;
215+
///
216+
/// let capacity = 64 * 1024; // 64 KiB
217+
/// let mut allocator = Allocator::with_capacity(capacity);
218+
/// allocator.alloc(123u64); // 8 bytes
219+
///
220+
/// // Result is the capacity (64 KiB), not the size of allocated data (8 bytes).
221+
/// // `Allocator::with_capacity` may allocate a bit more than requested.
222+
/// assert!(allocator.capacity() >= capacity);
223+
/// ```
224+
///
225+
/// [`used_bytes`]: Allocator::used_bytes
226+
//
227+
// `#[inline(always)]` because it just delegates to `bumpalo`
228+
#[expect(clippy::inline_always)]
229+
#[inline(always)]
230+
pub fn capacity(&self) -> usize {
231+
self.bump.allocated_bytes()
232+
}
233+
234+
/// Calculate the total size of data used in this [`Allocator`], in bytes.
235+
///
236+
/// This is the total amount of memory that has been *used* in the [`Allocator`], NOT the amount of
237+
/// memory the [`Allocator`] owns. If you want the latter, use [`capacity`] instead.
238+
///
239+
/// The result includes:
240+
///
241+
/// 1. Padding bytes between objects which have been allocated to preserve alignment of types
242+
/// where they have different alignments or have larger-than-typical alignment.
243+
/// 2. Excess capacity in [`Vec`]s, [`String`]s and [`HashMap`]s.
244+
/// 3. Objects which were allocated but later dropped. [`Allocator`] does not re-use allocations,
245+
/// so anything which is allocated into arena continues to take up "dead space", even after it's
246+
/// no longer referenced anywhere.
247+
/// 4. "Dead space" left over where a [`Vec`], [`String`] or [`HashMap`] has grown and had to make
248+
/// a new allocation to accomodate its new larger size. Its old allocation continues to take up
249+
/// "dead" space in the allocator, unless it was the most recent allocation.
250+
///
251+
/// In practice, this almost always means that the result returned from this function will be an
252+
/// over-estimate vs the amount of "live" data in the arena.
253+
///
254+
/// However, if you are using the result of this method to create a new `Allocator` to clone
255+
/// an AST into, it is theoretically possible (though very unlikely) that it may be a slight
256+
/// under-estimate of the capacity required in new allocator to clone the AST into, depending
257+
/// on the order that `&str`s were allocated into arena in parser vs the order they get allocated
258+
/// during cloning. The order allocations are made in affects the amount of padding bytes required.
259+
///
260+
/// # Examples
261+
/// ```
262+
/// use oxc_allocator::{Allocator, Vec};
263+
///
264+
/// let capacity = 64 * 1024; // 64 KiB
265+
/// let mut allocator = Allocator::with_capacity(capacity);
266+
///
267+
/// allocator.alloc(1u8); // 1 byte with alignment 1
268+
/// allocator.alloc(2u8); // 1 byte with alignment 1
269+
/// allocator.alloc(3u64); // 8 bytes with alignment 8
270+
///
271+
/// // Only 10 bytes were allocated, but 16 bytes were used, in order to align `3u64` on 8
272+
/// assert_eq!(allocator.used_bytes(), 16);
273+
///
274+
/// allocator.reset();
275+
///
276+
/// let mut vec = Vec::<u64>::with_capacity_in(2, &allocator);
277+
///
278+
/// // Allocate something else, so `vec`'s allocation is not the most recent
279+
/// allocator.alloc(123u64);
280+
///
281+
/// // `vec` has to grow beyond it's initial capacity
282+
/// vec.extend([1, 2, 3, 4]);
283+
///
284+
/// // `vec` takes up 32 bytes, and `123u64` takes up 8 bytes = 40 total.
285+
/// // But there's an additional 16 bytes consumed for `vec`'s original capacity of 2,
286+
/// // which is still using up space
287+
/// assert_eq!(allocator.used_bytes(), 56);
288+
/// ```
289+
///
290+
/// [`capacity`]: Allocator::capacity
291+
pub fn used_bytes(&self) -> usize {
292+
let mut bytes = 0;
293+
// SAFETY: No allocations are made while `chunks_iter` is alive. No data is read from the chunks.
294+
let chunks_iter = unsafe { self.bump.iter_allocated_chunks_raw() };
295+
for (_, size) in chunks_iter {
296+
bytes += size;
297+
}
298+
bytes
299+
}
300+
207301
/// Get inner [`bumpalo::Bump`].
208302
///
209303
/// This method is not public. We don't want to expose `bumpalo::Allocator` to user.

0 commit comments

Comments
 (0)