Skip to content

Commit

Permalink
docs(allocator): document oxc_allocator crate (#6037)
Browse files Browse the repository at this point in the history
Part of #5870
  • Loading branch information
DonIsaac committed Sep 25, 2024
1 parent d60ceb4 commit 3099709
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 3 deletions.
34 changes: 31 additions & 3 deletions crates/oxc_allocator/src/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,29 @@ use serde::{Serialize, Serializer};

use crate::Allocator;

/// A Box without Drop.
/// A Box without [`Drop`].
///
/// This is used for over coming self-referential structs.
/// It is a memory leak if the boxed value has a `Drop` implementation.
/// It is a memory leak if the boxed value has a [`Drop`] implementation.
pub struct Box<'alloc, T: ?Sized>(NonNull<T>, PhantomData<(&'alloc (), T)>);

impl<'alloc, T> Box<'alloc, T> {
/// Take ownership of the value stored in this [`Box`], consuming the box in
/// the process.
///
/// ## Example
/// ```
/// use oxc_allocator::{Allocator, Box};
///
/// let arena = Allocator::default();
///
/// // Put `5` into the arena and on the heap.
/// let boxed: Box<i32> = Box::new_in(5, &arena);
/// // Move it back to the stack. `boxed` has been consumed.
/// let i = boxed.unbox();
///
/// assert_eq!(i, 5);
/// ```
pub fn unbox(self) -> T {
// SAFETY:
// This pointer read is safe because the reference `self.0` is
Expand All @@ -34,11 +51,22 @@ impl<'alloc, T> Box<'alloc, T> {
}

impl<'alloc, T> Box<'alloc, T> {
/// Put a `value` into a memory arena and get back a [`Box`] with ownership
/// to the allocation.
///
/// ## Example
/// ```
/// use oxc_allocator::{Allocator, Box};
///
/// let arena = Allocator::default();
/// let in_arena: Box<i32> = Box::new_in(5, &arena);
/// ```
pub fn new_in(value: T, allocator: &Allocator) -> Self {
Self(NonNull::from(allocator.alloc(value)), PhantomData)
}

/// Create a fake `Box` with a dangling pointer.
/// Create a fake [`Box`] with a dangling pointer.
///
/// # SAFETY
/// Safe to create, but must never be dereferenced, as does not point to a valid `T`.
/// Only purpose is for mocking types without allocating for const assertions.
Expand Down
48 changes: 48 additions & 0 deletions crates/oxc_allocator/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,44 @@
//! # ⚓ Oxc Memory Allocator
//!
//! Oxc uses a bump-based memory arena for faster AST allocations. This crate
//! contains an [`Allocator`] for creating such arenas, as well as ports of
//! memory management data types from `std` adapted to use this arena.
//!
//! ## No `Drop`s
//! Objects allocated into oxc memory arenas are never [`Dropped`](Drop), making
//! it relatively easy to leak memory if you're not careful. Memory is released
//! in bulk when the allocator is dropped.
//!
//! ## Examples
//! ```
//! use oxc_allocator::{Allocator, Box};
//!
//! struct Foo {
//! pub a: i32
//! }
//! impl std::ops::Drop for Foo {
//! fn drop(&mut self) {
//! // Arena boxes are never dropped.
//! unreachable!();
//! }
//! }
//!
//! let allocator = Allocator::default();
//! let foo = Box::new_in(Foo { a: 0 }, &allocator);
//! drop(foo);
//! ```
//!
//! Consumers of the [`oxc` umbrella crate](https://crates.io/crates/oxc) pass
//! [`Allocator`] references to other tools.
//!
//! ```
//! use oxc::{allocator::Allocator, parser::Parser, span::SourceType};
//!
//! let allocator = Allocator::default()
//! let parsed = Parser::new(&allocator, "let x = 1;", SourceType::default());
//! assert!(parsed.errors.is_empty());
//! ```

use std::{
convert::From,
ops::{Deref, DerefMut},
Expand All @@ -16,6 +57,13 @@ pub use clone_in::CloneIn;
pub use convert::{FromIn, IntoIn};
pub use vec::Vec;

/// A bump-allocated memory arena based on [bumpalo].
///
/// ## No `Drop`s
///
/// Objects that are bump-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.
#[derive(Default)]
pub struct Allocator {
bump: Bump,
Expand Down
68 changes: 68 additions & 0 deletions crates/oxc_allocator/src/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,72 @@ use crate::Allocator;
pub struct Vec<'alloc, T>(vec::Vec<T, &'alloc Bump>);

impl<'alloc, T> Vec<'alloc, T> {
/// Constructs a new, empty `Vec<T>`.
///
/// The vector will not allocate until elements are pushed onto it.
///
/// # Examples
///
/// ```
/// use oxc_allocator::{Allocator, Vec};
///
/// let arena = Allocator::default();
///
/// let mut vec: Vec<i32> = Vec::new_in(&arena);
/// assert!(vec.is_empty());
/// ```
#[inline]
pub fn new_in(allocator: &'alloc Allocator) -> Self {
Self(vec::Vec::new_in(allocator))
}

/// Constructs a new, empty `Vec<T>` with at least the specified capacity
/// with the provided allocator.
///
/// The vector will be able to hold at least `capacity` elements without
/// reallocating. This method is allowed to allocate for more elements than
/// `capacity`. If `capacity` is 0, the vector will not allocate.
///
/// It is important to note that although the returned vector has the
/// minimum *capacity* specified, the vector will have a zero *length*.
///
/// For `Vec<T>` where `T` is a zero-sized type, there will be no allocation
/// and the capacity will always be `usize::MAX`.
///
/// # Panics
///
/// Panics if the new capacity exceeds `isize::MAX` bytes.
///
/// # Examples
///
/// ```
/// use oxc_allocator::{Allocator, Vec};
///
/// let arena = Allocator::default();
///
/// let mut vec = Vec::with_capacity_in(10, &arena);
///
/// // The vector contains no items, even though it has capacity for more
/// assert_eq!(vec.len(), 0);
/// assert_eq!(vec.capacity(), 10);
///
/// // These are all done without reallocating...
/// for i in 0..10 {
/// vec.push(i);
/// }
/// assert_eq!(vec.len(), 10);
/// assert_eq!(vec.capacity(), 10);
///
/// // ...but this may make the vector reallocate
/// vec.push(11);
/// assert_eq!(vec.len(), 11);
/// assert!(vec.capacity() >= 11);
///
/// // A vector of a zero-sized type will always over-allocate, since no
/// // allocation is necessary
/// let vec_units = Vec::<()>::with_capacity_in(10, &arena);
/// assert_eq!(vec_units.capacity(), usize::MAX);
/// ```
#[inline]
pub fn with_capacity_in(capacity: usize, allocator: &'alloc Allocator) -> Self {
Self(vec::Vec::with_capacity_in(capacity, allocator))
Expand Down Expand Up @@ -126,6 +187,13 @@ mod test {
use super::Vec;
use crate::Allocator;

#[test]
fn vec_with_capacity() {
let allocator = Allocator::default();
let v: Vec<i32> = Vec::with_capacity_in(10, &allocator);
assert!(v.is_empty());
}

#[test]
fn vec_debug() {
let allocator = Allocator::default();
Expand Down

0 comments on commit 3099709

Please sign in to comment.