diff --git a/Cargo.lock b/Cargo.lock index 55e04838dec71..414062b7af64a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -179,14 +179,21 @@ dependencies = [ ] [[package]] -name = "bumpalo" -version = "3.16.0" +name = "bump-scope" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "2b9ad7c0b139a8d8d73a36be7596c592e98dbbd11dec24050e7483621451ad76" dependencies = [ "allocator-api2", + "sptr", ] +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "byteorder" version = "1.5.0" @@ -1434,7 +1441,7 @@ name = "oxc_allocator" version = "0.34.0" dependencies = [ "allocator-api2", - "bumpalo", + "bump-scope", "serde", "serde_json", ] @@ -2864,6 +2871,12 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "sptr" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" + [[package]] name = "stable_deref_trait" version = "1.2.0" diff --git a/Cargo.toml b/Cargo.toml index a8d8413ab2304..90615c74c0812 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -128,7 +128,7 @@ base64 = "0.22.1" base64-simd = "0.8" bitflags = "2.6.0" bpaf = "0.9.15" -bumpalo = "3.16.0" +bump-scope = "0.10.6" cfg-if = "1.0.0" compact_str = "0.8.0" console = "0.15.8" diff --git a/crates/oxc_allocator/Cargo.toml b/crates/oxc_allocator/Cargo.toml index e46f84dd14419..5eb6d5c190260 100644 --- a/crates/oxc_allocator/Cargo.toml +++ b/crates/oxc_allocator/Cargo.toml @@ -20,7 +20,7 @@ doctest = false [dependencies] allocator-api2 = { workspace = true } -bumpalo = { workspace = true, features = ["allocator-api2", "collections"] } +bump-scope = { workspace = true } serde = { workspace = true, optional = true } diff --git a/crates/oxc_allocator/src/boxed.rs b/crates/oxc_allocator/src/boxed.rs index 209e6011d7612..979204b6444e7 100644 --- a/crates/oxc_allocator/src/boxed.rs +++ b/crates/oxc_allocator/src/boxed.rs @@ -68,7 +68,7 @@ impl<'alloc, T> Box<'alloc, T> { /// let in_arena: Box = Box::new_in(5, &arena); /// ``` pub fn new_in(value: T, allocator: &Allocator) -> Self { - Self(NonNull::from(allocator.alloc(value)), PhantomData) + Self(allocator.alloc(value).into_raw(), PhantomData) } /// Create a fake [`Box`] with a dangling pointer. @@ -176,7 +176,7 @@ mod test { let allocator = Allocator::default(); let mut b = Box::new_in("x", &allocator); let b = &mut *b; - *b = allocator.alloc("v"); + *b = allocator.bump.alloc("v").into_mut(); assert_eq!(*b, "v"); } diff --git a/crates/oxc_allocator/src/lib.rs b/crates/oxc_allocator/src/lib.rs index fbfc15a2e8f2b..1b8c5e1226a83 100644 --- a/crates/oxc_allocator/src/lib.rs +++ b/crates/oxc_allocator/src/lib.rs @@ -44,21 +44,26 @@ use std::{ ops::{Deref, DerefMut}, }; -pub use bumpalo::collections::String; -use bumpalo::Bump; - mod address; mod boxed; mod clone_in; mod convert; +mod string; mod vec; pub use address::{Address, GetAddress}; +use allocator_api2::alloc::Global; pub use boxed::Box; pub use clone_in::CloneIn; pub use convert::{FromIn, IntoIn}; +pub use string::String; pub use vec::Vec; +const BUMP_UPWARDS: bool = false; +const MINIMUM_ALIGNMENT: usize = 1; + +type Bump = bump_scope::Bump; + /// A bump-allocated memory arena based on [bumpalo]. /// /// ## No `Drop`s @@ -71,6 +76,13 @@ pub struct Allocator { bump: Bump, } +impl Allocator { + /// Allocate a `str`. + pub fn alloc_str(&self, src: &str) -> &mut str { + self.bump.alloc_str(src).into_mut() + } +} + impl From for Allocator { fn from(bump: Bump) -> Self { Self { bump } @@ -95,7 +107,7 @@ impl DerefMut for Allocator { mod test { use std::ops::Deref; - use bumpalo::Bump; + use bump_scope::Bump; use crate::Allocator; diff --git a/crates/oxc_allocator/src/string.rs b/crates/oxc_allocator/src/string.rs new file mode 100644 index 0000000000000..0835df103ca35 --- /dev/null +++ b/crates/oxc_allocator/src/string.rs @@ -0,0 +1,156 @@ +use std::{ + mem, + ops::{Deref, DerefMut}, + ptr, str, +}; + +use crate::{Allocator, Vec}; + +/// A bump-allocated string. +pub struct String<'a> { + vec: Vec<'a, u8>, +} + +impl<'a> String<'a> { + /// Constructs a new empty `String`. + #[inline] + pub fn new_in(allocator: &'a Allocator) -> Self { + Self { vec: Vec::new_in(allocator) } + } + + /// Constructs a `String` from a `&str`. + #[inline] + pub fn from_str_in(s: &str, allocator: &'a Allocator) -> Self { + // code taken from `bumpalo::collections::String::from_str_in` + let len = s.len(); + let mut t = String::with_capacity_in(len, allocator); + // SAFETY: + // * `src` is valid for reads of `s.len()` bytes by virtue of being an allocated `&str`. + // * `dst` is valid for writes of `s.len()` bytes as `String::with_capacity_in(s.len(), bump)` + // above guarantees that. + // * Alignment is not relevant as `u8` has no alignment requirements. + // * Source and destination ranges cannot overlap as we just reserved the destination + // range from the bump. + unsafe { ptr::copy_nonoverlapping(s.as_ptr(), t.vec.as_mut_ptr(), len) }; + // SAFETY: We reserved sufficent capacity for the string above. + // The elements at `0..len` were initialized by `copy_nonoverlapping` above. + unsafe { t.vec.set_len(len) }; + t + } + + /// Constructs a new empty `String` with the specified capacity. + #[inline] + pub fn with_capacity_in(capacity: usize, allocator: &'a Allocator) -> Self { + Self { vec: Vec::with_capacity_in(capacity, allocator) } + } + + /// Converts a `String` into a `&str`. + #[inline] + pub fn into_bump_str(self) -> &'a str { + #![allow( + clippy::undocumented_unsafe_blocks, + clippy::missing_transmute_annotations, + clippy::forget_non_drop + )] + // code taken from `bumpalo::collections::String::into_bump_str` + let s = unsafe { + let s = self.as_str(); + mem::transmute(s) + }; + mem::forget(self); + s + } + + /// Appends a given string slice to the end of this string. + #[inline] + pub fn push_str(&mut self, string: &str) { + self.vec.extend_from_slice_copy(string.as_bytes()); + } + + /// Appends a given `char` to the end of this string. + #[inline] + pub fn push(&mut self, ch: char) { + match ch.len_utf8() { + 1 => self.vec.push(ch as u8), + _ => self.vec.extend_from_slice(ch.encode_utf8(&mut [0; 4]).as_bytes()), + } + } + + /// Extracts a string slice containing the entire `String`. + #[inline] + pub fn as_str(&self) -> &str { + self + } +} + +impl Deref for String<'_> { + type Target = str; + + #[inline] + fn deref(&self) -> &Self::Target { + #[allow(clippy::undocumented_unsafe_blocks)] + unsafe { + str::from_utf8_unchecked(&self.vec) + } + } +} + +impl DerefMut for String<'_> { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + #[allow(clippy::undocumented_unsafe_blocks)] + unsafe { + str::from_utf8_unchecked_mut(&mut self.vec) + } + } +} + +// code taken from `bumpalo::collections::Vec` +impl<'alloc> Vec<'alloc, u8> { + /// Copies all elements in the slice `other` and appends them to the `Vec`. + /// + /// Note that this function is same as [`extend_from_slice`] except that it is optimized for + /// slices of types that implement the `Copy` trait. If and when Rust gets specialization + /// this function will likely be deprecated (but still available). + pub fn extend_from_slice_copy(&mut self, other: &[u8]) { + // Reserve space in the Vec for the values to be added + self.reserve(other.len()); + + // Copy values into the space that was just reserved + // SAFETY: + // * `self` has enough capacity to store `other.len()` more items as `self.reserve(other.len())` + // above guarantees that. + // * Source and destination data ranges cannot overlap as we just reserved the destination + // range from the bump. + unsafe { + self.extend_from_slice_copy_unchecked(other); + } + } + + /// Helper method to copy all of the items in `other` and append them to the end of `self`. + /// + /// SAFETY: + /// * The caller is responsible for: + /// * calling [`reserve`](Self::reserve) beforehand to guarantee that there is enough + /// capacity to store `other.len()` more items. + /// * guaranteeing that `self` and `other` do not overlap. + unsafe fn extend_from_slice_copy_unchecked(&mut self, other: &[u8]) { + let old_len = self.len(); + debug_assert!(old_len + other.len() <= self.capacity()); + + // SAFETY: + // * `src` is valid for reads of `other.len()` values by virtue of being a `&[T]`. + // * `dst` is valid for writes of `other.len()` bytes because the caller of this + // method is required to `reserve` capacity to store at least `other.len()` items + // beforehand. + // * Because `src` is a `&[T]` and dst is a `&[T]` within the `Vec`, + // `copy_nonoverlapping`'s alignment requirements are met. + // * Caller is required to guarantee that the source and destination ranges cannot overlap + unsafe { + let src = other.as_ptr(); + let dst = self.as_mut_ptr().add(old_len); + ptr::copy_nonoverlapping(src, dst, other.len()); + self.set_len(old_len + other.len()); + } + } +} diff --git a/crates/oxc_allocator/src/vec.rs b/crates/oxc_allocator/src/vec.rs index 273120f69e66c..7d4cb76b81ac8 100644 --- a/crates/oxc_allocator/src/vec.rs +++ b/crates/oxc_allocator/src/vec.rs @@ -12,11 +12,10 @@ use std::{ }; use allocator_api2::vec; -use bumpalo::Bump; #[cfg(any(feature = "serialize", test))] use serde::{ser::SerializeSeq, Serialize, Serializer}; -use crate::{Allocator, Box}; +use crate::{Allocator, Box, Bump}; /// A `Vec` without [`Drop`], which stores its data in the arena allocator. /// @@ -29,7 +28,7 @@ use crate::{Allocator, Box}; /// Note: This is not a soundness issue, as Rust does not support relying on `drop` /// being called to guarantee soundness. #[derive(PartialEq, Eq)] -pub struct Vec<'alloc, T>(ManuallyDrop>); +pub struct Vec<'alloc, T>(pub(crate) ManuallyDrop>); impl<'alloc, T> Vec<'alloc, T> { /// Constructs a new, empty `Vec`.