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

docs(allocator): document oxc_allocator crate #6037

Merged
merged 1 commit into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
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
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
Loading