diff --git a/Cargo.lock b/Cargo.lock index 226a451392b..de37a4f3b5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,6 +62,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +dependencies = [ + "serde", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -406,6 +415,9 @@ name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +dependencies = [ + "allocator-api2", +] [[package]] name = "bytecheck" @@ -3688,7 +3700,13 @@ dependencies = [ name = "swc_allocator" version = "0.1.1" dependencies = [ + "allocator-api2", "bumpalo", + "ptr_meta", + "rkyv", + "scoped-tls", + "serde", + "serde_derive", ] [[package]] @@ -3810,6 +3828,7 @@ dependencies = [ "serde_json", "siphasher", "sourcemap", + "swc_allocator", "swc_atoms", "swc_eq_ignore_macros", "swc_visit", diff --git a/Cargo.toml b/Cargo.toml index 66a445736df..ae8f45d1ca9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ resolver = "2" Inflector = "0.11.4" ahash = "0.8.8" + allocator-api2 = "0.2.18" ansi_term = "0.12.1" anyhow = "1.0.81" arbitrary = "1" @@ -95,6 +96,7 @@ resolver = "2" phf = "0.11.2" pretty_assertions = "1.3" proc-macro2 = "1.0.24" + ptr_meta = "0.1.4" quote = "1.0.7" rayon = "1.7.0" regex = "1.5.4" diff --git a/crates/swc_allocator/Cargo.toml b/crates/swc_allocator/Cargo.toml index ca32f62fd4c..621eb8d27c0 100644 --- a/crates/swc_allocator/Cargo.toml +++ b/crates/swc_allocator/Cargo.toml @@ -8,5 +8,19 @@ name = "swc_allocator" repository = { workspace = true } version = "0.1.1" +[features] +rkyv = ["dep:rkyv"] +serde = ["dep:serde", "dep:serde_derive"] + [dependencies] -bumpalo = { workspace = true, features = ["boxed", "collections"] } +allocator-api2 = { workspace = true, features = ["serde"] } +bumpalo = { workspace = true, features = [ + "allocator-api2", + "boxed", + "collections", +] } +ptr_meta = { workspace = true } +rkyv = { workspace = true, optional = true } +scoped-tls = { workspace = true } +serde = { workspace = true, optional = true } +serde_derive = { workspace = true, optional = true } diff --git a/crates/swc_allocator/src/alloc.rs b/crates/swc_allocator/src/alloc.rs new file mode 100644 index 00000000000..4d80a5fa935 --- /dev/null +++ b/crates/swc_allocator/src/alloc.rs @@ -0,0 +1,153 @@ +use std::{alloc::Layout, ptr::NonNull}; + +use allocator_api2::alloc::Global; +use scoped_tls::scoped_thread_local; + +use crate::Allocator; + +scoped_thread_local!(pub(crate) static ALLOC: Allocator); + +#[derive(Debug, Clone, Copy, Default)] +pub struct SwcAlloc; + +impl SwcAlloc { + /// `true` is passed to `f` if the box is allocated with a custom allocator. + fn with_allocator( + &self, + f: impl FnOnce(&dyn allocator_api2::alloc::Allocator, bool) -> T, + ) -> T { + if ALLOC.is_set() { + ALLOC.with(|a| { + // + f(&&**a as &dyn allocator_api2::alloc::Allocator, true) + }) + } else { + f(&allocator_api2::alloc::Global, false) + } + } +} + +/// Set the last bit to 1 +fn mark_ptr_as_arena_mode(ptr: NonNull<[u8]>) -> NonNull<[u8]> { + let (mut raw_ptr, metadata) = ptr_meta::PtrExt::to_raw_parts(ptr.as_ptr()); + + raw_ptr = (raw_ptr as usize | 1) as *mut (); + + unsafe { + // Safety: + NonNull::new_unchecked(ptr_meta::from_raw_parts_mut(raw_ptr, metadata)) + } +} + +fn is_ptr_in_arena_mode(ptr: NonNull) -> bool { + let ptr = ptr.as_ptr() as usize; + ptr & 1 == 1 +} + +unsafe impl allocator_api2::alloc::Allocator for SwcAlloc { + fn allocate(&self, layout: Layout) -> Result, allocator_api2::alloc::AllocError> { + self.with_allocator(|a, is_arena_mode| { + let ptr = a.allocate(layout)?; + + if is_arena_mode { + Ok(mark_ptr_as_arena_mode(ptr)) + } else { + Ok(ptr) + } + }) + } + + fn allocate_zeroed( + &self, + layout: Layout, + ) -> Result, allocator_api2::alloc::AllocError> { + self.with_allocator(|a, is_arena_mode| { + let ptr = a.allocate_zeroed(layout)?; + + if is_arena_mode { + Ok(mark_ptr_as_arena_mode(ptr)) + } else { + Ok(ptr) + } + }) + } + + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { + if is_ptr_in_arena_mode(ptr) { + debug_assert!( + ALLOC.is_set(), + "Deallocating a pointer allocated with arena mode with a non-arena mode allocator" + ); + + ALLOC.with(|alloc| { + unsafe { + // Safety: We are in unsafe fn + (&**alloc).deallocate(ptr, layout) + } + }) + } else { + Global.deallocate(ptr, layout) + } + } + + unsafe fn grow( + &self, + ptr: NonNull, + old_layout: Layout, + new_layout: Layout, + ) -> Result, allocator_api2::alloc::AllocError> { + if is_ptr_in_arena_mode(ptr) { + debug_assert!( + ALLOC.is_set(), + "Growing a pointer allocated with arena mode with a non-arena mode allocator" + ); + + ALLOC.with(|alloc| (&**alloc).grow(ptr, old_layout, new_layout)) + } else { + Global.grow(ptr, old_layout, new_layout) + } + } + + unsafe fn grow_zeroed( + &self, + ptr: NonNull, + old_layout: Layout, + new_layout: Layout, + ) -> Result, allocator_api2::alloc::AllocError> { + if is_ptr_in_arena_mode(ptr) { + debug_assert!( + ALLOC.is_set(), + "Growing a pointer allocated with arena mode with a non-arena mode allocator" + ); + + ALLOC.with(|alloc| (&**alloc).grow_zeroed(ptr, old_layout, new_layout)) + } else { + Global.grow_zeroed(ptr, old_layout, new_layout) + } + } + + unsafe fn shrink( + &self, + ptr: NonNull, + old_layout: Layout, + new_layout: Layout, + ) -> Result, allocator_api2::alloc::AllocError> { + if is_ptr_in_arena_mode(ptr) { + debug_assert!( + ALLOC.is_set(), + "Shrinking a pointer allocated with arena mode with a non-arena mode allocator" + ); + + ALLOC.with(|alloc| (&**alloc).shrink(ptr, old_layout, new_layout)) + } else { + Global.shrink(ptr, old_layout, new_layout) + } + } + + fn by_ref(&self) -> &Self + where + Self: Sized, + { + self + } +} diff --git a/crates/swc_allocator/src/boxed/mod.rs b/crates/swc_allocator/src/boxed/mod.rs new file mode 100644 index 00000000000..09e7ce5346b --- /dev/null +++ b/crates/swc_allocator/src/boxed/mod.rs @@ -0,0 +1,623 @@ +use std::{ + borrow::{Borrow, BorrowMut}, + fmt::{self, Debug, Display, Formatter}, + io::{self, BufRead, Read, Seek}, + iter::FusedIterator, + ops::{Deref, DerefMut}, + pin::Pin, +}; + +use crate::alloc::SwcAlloc; + +#[cfg(feature = "rkyv")] +mod rkyv; +#[cfg(feature = "serde")] +mod serde; + +/// A special `Box` which has size of [`std::boxed::Box`] but **may** be +/// allocated with a custom allocator. +/// +/// +/// # Representation +/// +/// The last bit is 1 if the box is allocated with a custom allocator. +#[repr(transparent)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Box(pub(crate) allocator_api2::boxed::Box); + +impl From for Box { + #[inline(always)] + fn from(v: T) -> Self { + Box::new(v) + } +} + +impl From> for Box { + #[inline(always)] + fn from(v: allocator_api2::boxed::Box) -> Self { + Box(v) + } +} + +impl Default for Box +where + T: Default, +{ + fn default() -> Self { + Box::new(Default::default()) + } +} + +impl Box { + /// Allocates a new box with `value`. + /// + /// See [`std::boxed::Box::new`]. + #[inline(always)] + pub fn new(value: T) -> Self { + Self(allocator_api2::boxed::Box::new_in(value, SwcAlloc)) + } + + /// Moves the value out of the box. + pub fn unbox(self) -> T { + allocator_api2::boxed::Box::into_inner(self.0) + } +} + +impl Box { + /// Constructs a box from a raw pointer. + /// + /// After calling this function, the raw pointer is owned by the + /// resulting `Box`. Specifically, the `Box` destructor will call + /// the destructor of `T` and free the allocated memory. For this + /// to be safe, the memory must have been allocated in accordance + /// with the [memory layout] used by `Box` . + /// + /// # Safety + /// + /// This function is unsafe because improper use may lead to + /// memory problems. For example, a double-free may occur if the + /// function is called twice on the same raw pointer. + /// + /// The safety conditions are described in the [memory layout] section. + /// + /// # Examples + /// + /// Recreate a `Box` which was previously converted to a raw pointer + /// using [`Box::into_raw`]: + /// ``` + /// let x = Box::new(5); + /// let ptr = Box::into_raw(x); + /// let x = unsafe { Box::from_raw(ptr) }; + /// ``` + /// Manually create a `Box` from scratch by using the global allocator: + /// ``` + /// use std::alloc::{alloc, Layout}; + /// + /// unsafe { + /// let ptr = alloc(Layout::new::()) as *mut i32; + /// // In general .write is required to avoid attempting to destruct + /// // the (uninitialized) previous contents of `ptr`, though for this + /// // simple example `*ptr = 5` would have worked as well. + /// ptr.write(5); + /// let x = Box::from_raw(ptr); + /// } + /// ``` + /// + /// [memory layout]: self#memory-layout + /// [`Layout`]: crate::Layout + pub unsafe fn from_raw(raw: *mut T) -> Self { + Self(allocator_api2::boxed::Box::from_raw_in(raw, SwcAlloc)) + } + + /// Consumes the `Box`, returning a wrapped raw pointer. + /// + /// The pointer will be properly aligned and non-null. + /// + /// After calling this function, the caller is responsible for the + /// memory previously managed by the `Box`. In particular, the + /// caller should properly destroy `T` and release the memory, taking + /// into account the [memory layout] used by `Box`. The easiest way to + /// do this is to convert the raw pointer back into a `Box` with the + /// [`Box::from_raw`] function, allowing the `Box` destructor to perform + /// the cleanup. + /// + /// Note: this is an associated function, which means that you have + /// to call it as `Box::into_raw(b)` instead of `b.into_raw()`. This + /// is so that there is no conflict with a method on the inner type. + /// + /// # Examples + /// Converting the raw pointer back into a `Box` with [`Box::from_raw`] + /// for automatic cleanup: + /// ``` + /// let x = Box::new(String::from("Hello")); + /// let ptr = Box::into_raw(x); + /// let x = unsafe { Box::from_raw(ptr) }; + /// ``` + /// Manual cleanup by explicitly running the destructor and deallocating + /// the memory: + /// ``` + /// use std::alloc::{dealloc, Layout}; + /// use std::ptr; + /// + /// let x = Box::new(String::from("Hello")); + /// let ptr = Box::into_raw(x); + /// unsafe { + /// ptr::drop_in_place(ptr); + /// dealloc(ptr as *mut u8, Layout::new::()); + /// } + /// ``` + /// Note: This is equivalent to the following: + /// ``` + /// let x = Box::new(String::from("Hello")); + /// let ptr = Box::into_raw(x); + /// unsafe { + /// drop(Box::from_raw(ptr)); + /// } + /// ``` + /// + /// [memory layout]: self#memory-layout + pub fn into_raw(b: Self) -> *mut T { + allocator_api2::boxed::Box::into_raw(b.0) + } + + /// Converts a `Box` into a `Pin>`. If `T` does not implement + /// [`Unpin`], then `*boxed` will be pinned in memory and unable to be + /// moved. + /// + /// This conversion does not allocate on the heap and happens in place. + /// + /// This is also available via [`From`]. + /// + /// Constructing and pinning a `Box` with + /// Box::into_pin([Box::new]\(x)) can also be written more + /// concisely using [Box::pin]\(x). This `into_pin` method + /// is useful if you already have a `Box`, or you are constructing a + /// (pinned) `Box` in a different way than with [`Box::new`]. + /// + /// # Notes + /// + /// It's not recommended that crates add an impl like `From> for + /// Pin`, as it'll introduce an ambiguity when calling `Pin::from`. + /// A demonstration of such a poor impl is shown below. + /// + /// ```compile_fail + /// # use std::pin::Pin; + /// struct Foo; // A type defined in this crate. + /// impl From> for Pin { + /// fn from(_: Box<()>) -> Pin { + /// Pin::new(Foo) + /// } + /// } + /// + /// let foo = Box::new(()); + /// let bar = Pin::from(foo); + /// ``` + pub fn into_pin(boxed: Self) -> Pin { + // It's not possible to move or replace the insides of a `Pin>` + // when `T: !Unpin`, so it's safe to pin it directly without any + // additional requirements. + unsafe { Pin::new_unchecked(boxed) } + } +} + +impl AsRef for Box { + fn as_ref(&self) -> &T { + &self.0 + } +} + +impl AsMut for Box { + fn as_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl Borrow for Box { + fn borrow(&self) -> &T { + &self.0 + } +} + +impl BorrowMut for Box { + fn borrow_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl Deref for Box { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +impl DerefMut for Box { + fn deref_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl BufRead for Box { + fn fill_buf(&mut self) -> std::io::Result<&[u8]> { + self.0.fill_buf() + } + + fn consume(&mut self, amt: usize) { + self.0.consume(amt) + } +} + +impl Debug for Box +where + T: Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Debug::fmt(&self.0, f) + } +} + +impl Display for Box +where + T: Display, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.0, f) + } +} + +impl Iterator for Box +where + T: Iterator, +{ + type Item = T::Item; + + fn next(&mut self) -> Option { + self.0.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } + + fn count(self) -> usize { + self.0.count() + } + + fn last(self) -> Option { + self.0.last() + } + + fn nth(&mut self, n: usize) -> Option { + self.0.nth(n) + } + + fn all(&mut self, f: F) -> bool + where + F: FnMut(Self::Item) -> bool, + { + self.0.all(f) + } + + fn any(&mut self, f: F) -> bool + where + F: FnMut(Self::Item) -> bool, + { + self.0.any(f) + } + + fn find

(&mut self, predicate: P) -> Option + where + P: FnMut(&Self::Item) -> bool, + { + self.0.find(predicate) + } + + fn position

(&mut self, predicate: P) -> Option + where + P: FnMut(Self::Item) -> bool, + { + self.0.position(predicate) + } + + fn max(self) -> Option + where + Self::Item: Ord, + { + self.0.max() + } + + fn min(self) -> Option + where + Self::Item: Ord, + { + self.0.min() + } + + fn by_ref(&mut self) -> &mut Self { + self.0.by_ref(); + self + } + + fn collect(self) -> B + where + B: std::iter::FromIterator, + { + self.0.collect() + } + + fn fold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.0.fold(init, f) + } + + fn for_each(self, f: F) + where + Self: Sized, + F: FnMut(Self::Item), + { + self.0.for_each(f) + } + + fn partition(self, f: F) -> (B, B) + where + Self: Sized, + B: Default + Extend, + F: FnMut(&Self::Item) -> bool, + { + self.0.partition(f) + } + + fn reduce(mut self, f: F) -> Option + where + Self: Sized, + F: FnMut(Self::Item, Self::Item) -> Self::Item, + { + let first = self.next()?; + Some(self.fold(first, f)) + } + + fn find_map(&mut self, f: F) -> Option + where + Self: Sized, + F: FnMut(Self::Item) -> Option, + { + self.0.find_map(f) + } + + fn max_by_key(self, f: F) -> Option + where + Self: Sized, + F: FnMut(&Self::Item) -> B, + { + self.0.max_by_key(f) + } + + fn max_by(self, compare: F) -> Option + where + Self: Sized, + F: FnMut(&Self::Item, &Self::Item) -> std::cmp::Ordering, + { + self.0.max_by(compare) + } + + fn min_by_key(self, f: F) -> Option + where + Self: Sized, + F: FnMut(&Self::Item) -> B, + { + self.0.min_by_key(f) + } + + fn min_by(self, compare: F) -> Option + where + Self: Sized, + F: FnMut(&Self::Item, &Self::Item) -> std::cmp::Ordering, + { + self.0.min_by(compare) + } + + fn sum(self) -> S + where + Self: Sized, + S: std::iter::Sum, + { + self.0.sum() + } + + fn product

(self) -> P + where + Self: Sized, + P: std::iter::Product, + { + self.0.product() + } + + fn cmp(self, other: I) -> std::cmp::Ordering + where + I: IntoIterator, + Self::Item: Ord, + Self: Sized, + { + self.0.cmp(other) + } + + fn partial_cmp(self, other: I) -> Option + where + I: IntoIterator, + Self::Item: PartialOrd, + Self: Sized, + { + self.0.partial_cmp(other) + } + + fn eq(self, other: I) -> bool + where + I: IntoIterator, + Self::Item: PartialEq, + Self: Sized, + { + self.0.eq(other) + } + + fn ne(self, other: I) -> bool + where + I: IntoIterator, + Self::Item: PartialEq, + Self: Sized, + { + self.0.ne(other) + } + + fn lt(self, other: I) -> bool + where + I: IntoIterator, + Self::Item: PartialOrd, + Self: Sized, + { + self.0.lt(other) + } + + fn le(self, other: I) -> bool + where + I: IntoIterator, + Self::Item: PartialOrd, + Self: Sized, + { + self.0.le(other) + } + + fn gt(self, other: I) -> bool + where + I: IntoIterator, + Self::Item: PartialOrd, + Self: Sized, + { + self.0.gt(other) + } + + fn ge(self, other: I) -> bool + where + I: IntoIterator, + Self::Item: PartialOrd, + Self: Sized, + { + self.0.ge(other) + } +} + +impl DoubleEndedIterator for Box +where + T: DoubleEndedIterator, +{ + fn next_back(&mut self) -> Option { + self.0.next_back() + } + + fn nth_back(&mut self, n: usize) -> Option { + self.0.nth_back(n) + } + + fn rfold(self, init: B, f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + self.0.rfold(init, f) + } + + fn rfind

(&mut self, predicate: P) -> Option + where + Self: Sized, + P: FnMut(&Self::Item) -> bool, + { + self.0.rfind(predicate) + } +} + +impl fmt::Pointer for Box { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Pointer::fmt(&self.0, f) + } +} + +impl Read for Box +where + T: Read, +{ + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.0.read(buf) + } + + fn read_to_end(&mut self, buf: &mut std::vec::Vec) -> std::io::Result { + self.0.read_to_end(buf) + } + + fn read_to_string(&mut self, buf: &mut String) -> std::io::Result { + self.0.read_to_string(buf) + } + + fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> { + self.0.read_exact(buf) + } +} + +impl Seek for Box +where + T: Seek, +{ + fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { + self.0.seek(pos) + } +} + +impl fmt::Write for Box +where + T: fmt::Write, +{ + fn write_str(&mut self, s: &str) -> fmt::Result { + self.0.write_str(s) + } + + fn write_char(&mut self, c: char) -> fmt::Result { + self.0.write_char(c) + } + + fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result { + self.0.write_fmt(args) + } +} + +impl io::Write for Box +where + T: io::Write, +{ + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } + + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + self.0.write_all(buf) + } + + fn write_fmt(&mut self, fmt: fmt::Arguments) -> io::Result<()> { + self.0.write_fmt(fmt) + } +} + +impl FusedIterator for Box where T: ?Sized + FusedIterator {} + +impl ExactSizeIterator for Box +where + T: ?Sized + ExactSizeIterator, +{ + fn len(&self) -> usize { + self.0.len() + } +} diff --git a/crates/swc_allocator/src/boxed/rkyv.rs b/crates/swc_allocator/src/boxed/rkyv.rs new file mode 100644 index 00000000000..2c55abf28d3 --- /dev/null +++ b/crates/swc_allocator/src/boxed/rkyv.rs @@ -0,0 +1,59 @@ +use std::{alloc, cmp}; + +use rkyv::{ + boxed::{ArchivedBox, BoxResolver}, + Archive, ArchivePointee, ArchiveUnsized, Deserialize, DeserializeUnsized, Fallible, Serialize, + SerializeUnsized, +}; + +use super::Box; + +impl Archive for Box { + type Archived = ArchivedBox; + type Resolver = BoxResolver; + + #[inline] + unsafe fn resolve(&self, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) { + ArchivedBox::resolve_from_ref(self.as_ref(), pos, resolver, out); + } +} + +impl + ?Sized, S: Fallible + ?Sized> Serialize for Box { + #[inline] + fn serialize(&self, serializer: &mut S) -> Result { + ArchivedBox::serialize_from_ref(self.as_ref(), serializer) + } +} + +impl Deserialize, D> for ArchivedBox +where + T: ArchiveUnsized + ?Sized, + T::Archived: DeserializeUnsized, + D: Fallible + ?Sized, +{ + #[inline] + fn deserialize(&self, deserializer: &mut D) -> Result, D::Error> { + unsafe { + let data_address = self + .get() + .deserialize_unsized(deserializer, |layout| alloc::alloc(layout))?; + let metadata = self.get().deserialize_metadata(deserializer)?; + let ptr = ptr_meta::from_raw_parts_mut(data_address, metadata); + Ok(Box::from_raw(ptr)) + } + } +} + +impl + ?Sized, U: ?Sized> PartialEq> for ArchivedBox { + #[inline] + fn eq(&self, other: &Box) -> bool { + self.get().eq(other.as_ref()) + } +} + +impl + ?Sized, U: ?Sized> PartialOrd> for ArchivedBox { + #[inline] + fn partial_cmp(&self, other: &Box) -> Option { + self.get().partial_cmp(other.as_ref()) + } +} diff --git a/crates/swc_allocator/src/boxed/serde.rs b/crates/swc_allocator/src/boxed/serde.rs new file mode 100644 index 00000000000..17c76e81d71 --- /dev/null +++ b/crates/swc_allocator/src/boxed/serde.rs @@ -0,0 +1,27 @@ +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use super::Box; + +impl Serialize for Box +where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.0.serialize(serializer) + } +} + +impl<'de, T> Deserialize<'de> for Box +where + T: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + Ok(Box(allocator_api2::boxed::Box::deserialize(deserializer)?)) + } +} diff --git a/crates/swc_allocator/src/lib.rs b/crates/swc_allocator/src/lib.rs index 5c6a3917885..27728c2b479 100644 --- a/crates/swc_allocator/src/lib.rs +++ b/crates/swc_allocator/src/lib.rs @@ -2,15 +2,34 @@ //! //! API designed after [`oxc_allocator`](https://github.com/oxc-project/oxc/tree/725571aad193ec6ba779c820baeb4a7774533ed7/crates/oxc_allocator/src). +#![allow(clippy::needless_doctest_main)] + use std::ops::{Deref, DerefMut}; use bumpalo::Bump; +use crate::alloc::ALLOC; + +mod alloc; +pub mod boxed; +pub mod vec; + #[derive(Default)] pub struct Allocator { alloc: Bump, } +impl Allocator { + /// Invokes `f` in a scope where the allocations are done in this allocator. + #[inline(always)] + pub fn scope(&self, f: F) -> R + where + F: FnOnce() -> R, + { + ALLOC.set(self, f) + } +} + impl From for Allocator { fn from(alloc: Bump) -> Self { Self { alloc } @@ -30,115 +49,3 @@ impl DerefMut for Allocator { &mut self.alloc } } - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(transparent)] -pub struct Box<'alloc, T>(bumpalo::boxed::Box<'alloc, T>); - -impl<'alloc, T> Box<'alloc, T> { - #[inline(always)] - pub fn new(alloc: &'alloc Allocator, value: T) -> Self { - Self(bumpalo::boxed::Box::new_in(value, alloc)) - } -} - -impl<'alloc, T> Deref for Box<'alloc, T> { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl<'alloc, T> DerefMut for Box<'alloc, T> { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(transparent)] -pub struct Vec<'alloc, T>(bumpalo::collections::Vec<'alloc, T>); - -impl<'alloc, T> Vec<'alloc, T> { - #[inline(always)] - pub fn new(alloc: &'alloc Allocator) -> Self { - Self(bumpalo::collections::Vec::new_in(alloc)) - } - - #[inline(always)] - pub fn with_capacity(alloc: &'alloc Allocator, capacity: usize) -> Self { - Self(bumpalo::collections::Vec::with_capacity_in(capacity, alloc)) - } -} - -impl<'alloc, T> Deref for Vec<'alloc, T> { - type Target = [T]; - - fn deref(&self) -> &[T] { - &self.0 - } -} - -impl<'alloc, T> DerefMut for Vec<'alloc, T> { - fn deref_mut(&mut self) -> &mut [T] { - &mut self.0 - } -} - -impl<'alloc, T> IntoIterator for Vec<'alloc, T> { - type IntoIter = bumpalo::collections::vec::IntoIter<'alloc, T>; - type Item = T; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct String<'alloc>(bumpalo::collections::String<'alloc>); - -impl<'alloc> String<'alloc> { - #[inline(always)] - pub fn new(alloc: &'alloc Allocator) -> Self { - Self(bumpalo::collections::String::new_in(alloc)) - } - - #[inline(always)] - pub fn with_capacity(alloc: &'alloc Allocator, capacity: usize) -> Self { - Self(bumpalo::collections::String::with_capacity_in( - capacity, alloc, - )) - } -} - -impl Deref for String<'_> { - type Target = str; - - fn deref(&self) -> &str { - &self.0 - } -} - -impl DerefMut for String<'_> { - fn deref_mut(&mut self) -> &mut str { - &mut self.0 - } -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum CowStr<'alloc> { - Borrowed(&'alloc str), - Owned(String<'alloc>), -} - -impl Deref for CowStr<'_> { - type Target = str; - - fn deref(&self) -> &str { - match self { - CowStr::Borrowed(s) => s, - CowStr::Owned(s) => s, - } - } -} diff --git a/crates/swc_allocator/src/vec/mod.rs b/crates/swc_allocator/src/vec/mod.rs new file mode 100644 index 00000000000..9b3adbca19d --- /dev/null +++ b/crates/swc_allocator/src/vec/mod.rs @@ -0,0 +1,241 @@ +use std::ops::{Deref, DerefMut}; + +#[cfg(feature = "rkyv")] +mod rkyv; + +use crate::{alloc::SwcAlloc, boxed::Box}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +#[cfg_attr( + feature = "serde", + derive(serde_derive::Serialize, serde_derive::Deserialize) +)] +pub struct Vec(allocator_api2::vec::Vec); + +impl Vec { + pub fn new() -> Self { + Default::default() + } + + pub fn with_capacity(capacity: usize) -> Self { + Self(allocator_api2::vec::Vec::with_capacity_in( + capacity, SwcAlloc, + )) + } + + /// Converts the vector into [`Box<[T]>`][owned slice]. + /// + /// Before doing the conversion, this method discards excess capacity like + /// [`shrink_to_fit`]. + /// + /// [owned slice]: Box + /// [`shrink_to_fit`]: Vec::shrink_to_fit + /// + /// # Examples + /// + /// ``` + /// let v = vec![1, 2, 3]; + /// + /// let slice = v.into_boxed_slice(); + /// ``` + /// + /// Any excess capacity is removed: + /// + /// ``` + /// let mut vec = Vec::with_capacity(10); + /// vec.extend([1, 2, 3]); + /// + /// assert!(vec.capacity() >= 10); + /// let slice = vec.into_boxed_slice(); + /// assert_eq!(slice.into_vec().capacity(), 3); + /// ``` + pub fn into_boxed_slice(self) -> Box<[T]> { + self.0.into_boxed_slice().into() + } + + /// Consumes and leaks the `Vec`, returning a mutable reference to the + /// contents, `&'a mut [T]`. Note that the type `T` must outlive the + /// chosen lifetime `'a`. If the type has only static references, or + /// none at all, then this may be chosen to be `'static`. + /// + /// As of Rust 1.57, this method does not reallocate or shrink the `Vec`, + /// so the leaked allocation may include unused capacity that is not part + /// of the returned slice. + /// + /// This function is mainly useful for data that lives for the remainder of + /// the program's life. Dropping the returned reference will cause a memory + /// leak. + /// + /// # Examples + /// + /// Simple usage: + /// + /// ``` + /// let x = vec![1, 2, 3]; + /// let static_ref: &'static mut [usize] = x.leak(); + /// static_ref[0] += 1; + /// assert_eq!(static_ref, &[2, 2, 3]); + /// ``` + pub fn leak(self) -> &'static mut [T] { + self.0.leak() + } + + /// Creates a `Vec` directly from a pointer, a length, and a capacity. + /// + /// # Safety + /// + /// This is highly unsafe, due to the number of invariants that aren't + /// checked: + /// + /// * `ptr` must have been allocated using the global allocator, such as via + /// the [`alloc::alloc`] function. + /// * `T` needs to have the same alignment as what `ptr` was allocated with. + /// (`T` having a less strict alignment is not sufficient, the alignment + /// really needs to be equal to satisfy the [`dealloc`] requirement that + /// memory must be allocated and deallocated with the same layout.) + /// * The size of `T` times the `capacity` (ie. the allocated size in bytes) + /// needs to be the same size as the pointer was allocated with. (Because + /// similar to alignment, [`dealloc`] must be called with the same layout + /// `size`.) + /// * `length` needs to be less than or equal to `capacity`. + /// * The first `length` values must be properly initialized values of type + /// `T`. + /// * `capacity` needs to be the capacity that the pointer was allocated + /// with. + /// * The allocated size in bytes must be no larger than `isize::MAX`. See + /// the safety documentation of [`pointer::offset`]. + /// + /// These requirements are always upheld by any `ptr` that has been + /// allocated via `Vec`. Other allocation sources are allowed if the + /// invariants are upheld. + /// + /// Violating these may cause problems like corrupting the allocator's + /// internal data structures. For example it is normally **not** safe + /// to build a `Vec` from a pointer to a C `char` array with length + /// `size_t`, doing so is only safe if the array was initially allocated by + /// a `Vec` or `String`. + /// It's also not safe to build one from a `Vec` and its length, + /// because the allocator cares about the alignment, and these two types + /// have different alignments. The buffer was allocated with alignment 2 + /// (for `u16`), but after turning it into a `Vec` it'll be + /// deallocated with alignment 1. To avoid these issues, it is often + /// preferable to do casting/transmuting using [`slice::from_raw_parts`] + /// instead. + /// + /// The ownership of `ptr` is effectively transferred to the + /// `Vec` which may then deallocate, reallocate or change the + /// contents of memory pointed to by the pointer at will. Ensure + /// that nothing else uses the pointer after calling this + /// function. + /// + /// [`String`]: crate::string::String + /// [`alloc::alloc`]: crate::alloc::alloc + /// [`dealloc`]: crate::alloc::GlobalAlloc::dealloc + /// + /// # Examples + /// + /// ``` + /// use std::ptr; + /// use std::mem; + /// + /// let v = vec![1, 2, 3]; + // FIXME Update this when vec_into_raw_parts is stabilized + /// // Prevent running `v`'s destructor so we are in complete control + /// // of the allocation. + /// let mut v = mem::ManuallyDrop::new(v); + /// + /// // Pull out the various important pieces of information about `v` + /// let p = v.as_mut_ptr(); + /// let len = v.len(); + /// let cap = v.capacity(); + /// + /// unsafe { + /// // Overwrite memory with 4, 5, 6 + /// for i in 0..len { + /// ptr::write(p.add(i), 4 + i); + /// } + /// + /// // Put everything back together into a Vec + /// let rebuilt = Vec::from_raw_parts(p, len, cap); + /// assert_eq!(rebuilt, [4, 5, 6]); + /// } + /// ``` + pub unsafe fn from_raw_parts(ptr: *mut T, length: usize, capacity: usize) -> Self { + Self(allocator_api2::vec::Vec::from_raw_parts_in( + ptr, length, capacity, SwcAlloc, + )) + } +} + +impl Deref for Vec { + type Target = allocator_api2::vec::Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Vec { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Default for Vec { + fn default() -> Self { + Self(allocator_api2::vec::Vec::new_in(SwcAlloc)) + } +} + +impl IntoIterator for Vec { + type IntoIter = allocator_api2::vec::IntoIter; + type Item = T; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} +impl<'a, T> IntoIterator for &'a Vec { + type IntoIter = std::slice::Iter<'a, T>; + type Item = &'a T; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, T> IntoIterator for &'a mut Vec { + type IntoIter = std::slice::IterMut<'a, T>; + type Item = &'a mut T; + + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +impl FromIterator for Vec { + fn from_iter>(iter: I) -> Self { + let mut vec = Vec::default(); + vec.extend(iter); + vec + } +} + +impl From> for Vec { + fn from(v: Box<[T]>) -> Self { + Self(allocator_api2::vec::Vec::from(v.0)) + } +} + +impl From> for Box<[T]> { + fn from(v: Vec) -> Self { + Box(v.0.into()) + } +} + +impl Extend for Vec { + fn extend>(&mut self, iter: I) { + self.0.extend(iter) + } +} diff --git a/crates/swc_allocator/src/vec/rkyv.rs b/crates/swc_allocator/src/vec/rkyv.rs new file mode 100644 index 00000000000..edad89668fd --- /dev/null +++ b/crates/swc_allocator/src/vec/rkyv.rs @@ -0,0 +1,42 @@ +use rkyv::{vec::ArchivedVec, DeserializeUnsized}; + +use super::Vec; +use crate::boxed::Box; + +impl rkyv::Archive for Vec +where + T: rkyv::Archive, +{ + type Archived = rkyv::vec::ArchivedVec; + type Resolver = rkyv::vec::VecResolver; + + unsafe fn resolve(&self, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) { + rkyv::vec::ArchivedVec::resolve_from_slice(self, pos, resolver, out); + } +} + +impl, S: rkyv::ser::ScratchSpace + rkyv::ser::Serializer + ?Sized> + rkyv::Serialize for Vec +{ + #[inline] + fn serialize(&self, serializer: &mut S) -> Result { + ArchivedVec::::serialize_from_slice(self, serializer) + } +} + +impl rkyv::Deserialize, D> + for ArchivedVec +where + [T::Archived]: rkyv::DeserializeUnsized<[T], D>, +{ + #[inline] + fn deserialize(&self, deserializer: &mut D) -> Result, D::Error> { + unsafe { + let data_address = + (**self).deserialize_unsized(deserializer, |layout| std::alloc::alloc(layout))?; + let metadata = self.as_slice().deserialize_metadata(deserializer)?; + let ptr = ptr_meta::from_raw_parts_mut(data_address, metadata); + Ok(Box::<[T]>::from_raw(ptr).into()) + } + } +} diff --git a/crates/swc_allocator/tests/apis.rs b/crates/swc_allocator/tests/apis.rs new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/crates/swc_allocator/tests/apis.rs @@ -0,0 +1 @@ + diff --git a/crates/swc_common/Cargo.toml b/crates/swc_common/Cargo.toml index 15480f7386d..7e842f79c9c 100644 --- a/crates/swc_common/Cargo.toml +++ b/crates/swc_common/Cargo.toml @@ -65,6 +65,7 @@ url = { workspace = true } ast_node = { version = "0.9.8", path = "../ast_node" } better_scoped_tls = { version = "0.1.1", path = "../better_scoped_tls" } from_variant = { version = "0.1.8", path = "../from_variant" } +swc_allocator = { version = "0.1.1", path = "../swc_allocator" } swc_atoms = { version = "0.6.5", path = "../swc_atoms" } swc_eq_ignore_macros = { version = "0.1.3", path = "../swc_eq_ignore_macros" } swc_visit = { version = "0.5.14", path = "../swc_visit" } diff --git a/crates/swc_common/src/eq.rs b/crates/swc_common/src/eq.rs index 57d2d8911b4..ffd615011b0 100644 --- a/crates/swc_common/src/eq.rs +++ b/crates/swc_common/src/eq.rs @@ -63,6 +63,19 @@ where } } +impl EqIgnoreSpan for swc_allocator::vec::Vec +where + T: EqIgnoreSpan, +{ + fn eq_ignore_span(&self, other: &Self) -> bool { + self.len() == other.len() + && self + .iter() + .zip(other.iter()) + .all(|(a, b)| a.eq_ignore_span(b)) + } +} + /// Derive with `#[derive(TypeEq)]`. pub trait TypeEq { /// **Note**: This method should return `true` for non-type values. @@ -172,6 +185,26 @@ macro_rules! deref { deref!(Box, Rc, Arc); +impl EqIgnoreSpan for swc_allocator::boxed::Box +where + N: EqIgnoreSpan, +{ + #[inline] + fn eq_ignore_span(&self, other: &Self) -> bool { + (**self).eq_ignore_span(&**other) + } +} + +impl TypeEq for swc_allocator::boxed::Box +where + N: TypeEq, +{ + #[inline] + fn type_eq(&self, other: &Self) -> bool { + (**self).type_eq(&**other) + } +} + impl<'a, N> EqIgnoreSpan for &'a N where N: EqIgnoreSpan, diff --git a/crates/swc_common/src/pos.rs b/crates/swc_common/src/pos.rs index 9fb8d3ced48..07196bc52e9 100644 --- a/crates/swc_common/src/pos.rs +++ b/crates/swc_common/src/pos.rs @@ -190,3 +190,12 @@ where } } } + +impl Spanned for swc_allocator::boxed::Box +where + T: Spanned, +{ + fn span(&self) -> Span { + self.as_ref().span() + } +} diff --git a/crates/swc_common/src/util/take.rs b/crates/swc_common/src/util/take.rs index 8dfd21394c2..376a987940f 100644 --- a/crates/swc_common/src/util/take.rs +++ b/crates/swc_common/src/util/take.rs @@ -54,3 +54,18 @@ impl Take for Span { DUMMY_SP } } + +impl Take for swc_allocator::boxed::Box +where + T: Take, +{ + fn dummy() -> Self { + swc_allocator::boxed::Box::new(T::dummy()) + } +} + +impl Take for swc_allocator::vec::Vec { + fn dummy() -> Self { + Default::default() + } +}