feat(allocator): Implement SwcAlloc (#9232)

**Related issue:**

 - This is a part of https://github.com/swc-project/swc/pull/9230
This commit is contained in:
Donny/강동윤 2024-07-14 13:27:41 +09:00 committed by GitHub
parent c9ac23beb0
commit e343eb6de2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 1259 additions and 113 deletions

19
Cargo.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -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 }

View File

@ -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<T>(
&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<u8>) -> 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<NonNull<[u8]>, 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<NonNull<[u8]>, 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<u8>, 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<u8>,
old_layout: Layout,
new_layout: Layout,
) -> Result<NonNull<[u8]>, 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<u8>,
old_layout: Layout,
new_layout: Layout,
) -> Result<NonNull<[u8]>, 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<u8>,
old_layout: Layout,
new_layout: Layout,
) -> Result<NonNull<[u8]>, 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
}
}

View File

@ -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<T: ?Sized>(pub(crate) allocator_api2::boxed::Box<T, SwcAlloc>);
impl<T> From<T> for Box<T> {
#[inline(always)]
fn from(v: T) -> Self {
Box::new(v)
}
}
impl<T: ?Sized> From<allocator_api2::boxed::Box<T, SwcAlloc>> for Box<T> {
#[inline(always)]
fn from(v: allocator_api2::boxed::Box<T, SwcAlloc>) -> Self {
Box(v)
}
}
impl<T> Default for Box<T>
where
T: Default,
{
fn default() -> Self {
Box::new(Default::default())
}
}
impl<T> Box<T> {
/// 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<T: ?Sized> Box<T> {
/// 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::<i32>()) 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::<String>());
/// }
/// ```
/// 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<T>` into a `Pin<Box<T>>`. 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
/// <code>Box::into_pin([Box::new]\(x))</code> can also be written more
/// concisely using <code>[Box::pin]\(x)</code>. This `into_pin` method
/// is useful if you already have a `Box<T>`, 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<Box<T>> for
/// Pin<T>`, 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<Box<()>> for Pin<Foo> {
/// fn from(_: Box<()>) -> Pin<Foo> {
/// Pin::new(Foo)
/// }
/// }
///
/// let foo = Box::new(());
/// let bar = Pin::from(foo);
/// ```
pub fn into_pin(boxed: Self) -> Pin<Self> {
// It's not possible to move or replace the insides of a `Pin<Box<T>>`
// when `T: !Unpin`, so it's safe to pin it directly without any
// additional requirements.
unsafe { Pin::new_unchecked(boxed) }
}
}
impl<T: ?Sized> AsRef<T> for Box<T> {
fn as_ref(&self) -> &T {
&self.0
}
}
impl<T: ?Sized> AsMut<T> for Box<T> {
fn as_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T: ?Sized> Borrow<T> for Box<T> {
fn borrow(&self) -> &T {
&self.0
}
}
impl<T: ?Sized> BorrowMut<T> for Box<T> {
fn borrow_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T: ?Sized> Deref for Box<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T: ?Sized> DerefMut for Box<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T: ?Sized + BufRead> BufRead for Box<T> {
fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
self.0.fill_buf()
}
fn consume(&mut self, amt: usize) {
self.0.consume(amt)
}
}
impl<T: ?Sized> Debug for Box<T>
where
T: Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Debug::fmt(&self.0, f)
}
}
impl<T: ?Sized> Display for Box<T>
where
T: Display,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.0, f)
}
}
impl<T: ?Sized> Iterator for Box<T>
where
T: Iterator,
{
type Item = T::Item;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
fn count(self) -> usize {
self.0.count()
}
fn last(self) -> Option<Self::Item> {
self.0.last()
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
self.0.nth(n)
}
fn all<F>(&mut self, f: F) -> bool
where
F: FnMut(Self::Item) -> bool,
{
self.0.all(f)
}
fn any<F>(&mut self, f: F) -> bool
where
F: FnMut(Self::Item) -> bool,
{
self.0.any(f)
}
fn find<P>(&mut self, predicate: P) -> Option<Self::Item>
where
P: FnMut(&Self::Item) -> bool,
{
self.0.find(predicate)
}
fn position<P>(&mut self, predicate: P) -> Option<usize>
where
P: FnMut(Self::Item) -> bool,
{
self.0.position(predicate)
}
fn max(self) -> Option<Self::Item>
where
Self::Item: Ord,
{
self.0.max()
}
fn min(self) -> Option<Self::Item>
where
Self::Item: Ord,
{
self.0.min()
}
fn by_ref(&mut self) -> &mut Self {
self.0.by_ref();
self
}
fn collect<B>(self) -> B
where
B: std::iter::FromIterator<Self::Item>,
{
self.0.collect()
}
fn fold<B, F>(self, init: B, f: F) -> B
where
F: FnMut(B, Self::Item) -> B,
{
self.0.fold(init, f)
}
fn for_each<F>(self, f: F)
where
Self: Sized,
F: FnMut(Self::Item),
{
self.0.for_each(f)
}
fn partition<B, F>(self, f: F) -> (B, B)
where
Self: Sized,
B: Default + Extend<Self::Item>,
F: FnMut(&Self::Item) -> bool,
{
self.0.partition(f)
}
fn reduce<F>(mut self, f: F) -> Option<Self::Item>
where
Self: Sized,
F: FnMut(Self::Item, Self::Item) -> Self::Item,
{
let first = self.next()?;
Some(self.fold(first, f))
}
fn find_map<B, F>(&mut self, f: F) -> Option<B>
where
Self: Sized,
F: FnMut(Self::Item) -> Option<B>,
{
self.0.find_map(f)
}
fn max_by_key<B: Ord, F>(self, f: F) -> Option<Self::Item>
where
Self: Sized,
F: FnMut(&Self::Item) -> B,
{
self.0.max_by_key(f)
}
fn max_by<F>(self, compare: F) -> Option<Self::Item>
where
Self: Sized,
F: FnMut(&Self::Item, &Self::Item) -> std::cmp::Ordering,
{
self.0.max_by(compare)
}
fn min_by_key<B: Ord, F>(self, f: F) -> Option<Self::Item>
where
Self: Sized,
F: FnMut(&Self::Item) -> B,
{
self.0.min_by_key(f)
}
fn min_by<F>(self, compare: F) -> Option<Self::Item>
where
Self: Sized,
F: FnMut(&Self::Item, &Self::Item) -> std::cmp::Ordering,
{
self.0.min_by(compare)
}
fn sum<S>(self) -> S
where
Self: Sized,
S: std::iter::Sum<Self::Item>,
{
self.0.sum()
}
fn product<P>(self) -> P
where
Self: Sized,
P: std::iter::Product<Self::Item>,
{
self.0.product()
}
fn cmp<I>(self, other: I) -> std::cmp::Ordering
where
I: IntoIterator<Item = Self::Item>,
Self::Item: Ord,
Self: Sized,
{
self.0.cmp(other)
}
fn partial_cmp<I>(self, other: I) -> Option<std::cmp::Ordering>
where
I: IntoIterator,
Self::Item: PartialOrd<I::Item>,
Self: Sized,
{
self.0.partial_cmp(other)
}
fn eq<I>(self, other: I) -> bool
where
I: IntoIterator,
Self::Item: PartialEq<I::Item>,
Self: Sized,
{
self.0.eq(other)
}
fn ne<I>(self, other: I) -> bool
where
I: IntoIterator,
Self::Item: PartialEq<I::Item>,
Self: Sized,
{
self.0.ne(other)
}
fn lt<I>(self, other: I) -> bool
where
I: IntoIterator,
Self::Item: PartialOrd<I::Item>,
Self: Sized,
{
self.0.lt(other)
}
fn le<I>(self, other: I) -> bool
where
I: IntoIterator,
Self::Item: PartialOrd<I::Item>,
Self: Sized,
{
self.0.le(other)
}
fn gt<I>(self, other: I) -> bool
where
I: IntoIterator,
Self::Item: PartialOrd<I::Item>,
Self: Sized,
{
self.0.gt(other)
}
fn ge<I>(self, other: I) -> bool
where
I: IntoIterator,
Self::Item: PartialOrd<I::Item>,
Self: Sized,
{
self.0.ge(other)
}
}
impl<T: ?Sized> DoubleEndedIterator for Box<T>
where
T: DoubleEndedIterator,
{
fn next_back(&mut self) -> Option<Self::Item> {
self.0.next_back()
}
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
self.0.nth_back(n)
}
fn rfold<B, F>(self, init: B, f: F) -> B
where
Self: Sized,
F: FnMut(B, Self::Item) -> B,
{
self.0.rfold(init, f)
}
fn rfind<P>(&mut self, predicate: P) -> Option<Self::Item>
where
Self: Sized,
P: FnMut(&Self::Item) -> bool,
{
self.0.rfind(predicate)
}
}
impl<T: ?Sized> fmt::Pointer for Box<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Pointer::fmt(&self.0, f)
}
}
impl<T: ?Sized> Read for Box<T>
where
T: Read,
{
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.0.read(buf)
}
fn read_to_end(&mut self, buf: &mut std::vec::Vec<u8>) -> std::io::Result<usize> {
self.0.read_to_end(buf)
}
fn read_to_string(&mut self, buf: &mut String) -> std::io::Result<usize> {
self.0.read_to_string(buf)
}
fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> {
self.0.read_exact(buf)
}
}
impl<T: ?Sized> Seek for Box<T>
where
T: Seek,
{
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
self.0.seek(pos)
}
}
impl<T: ?Sized> fmt::Write for Box<T>
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<T: ?Sized> io::Write for Box<T>
where
T: io::Write,
{
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
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<T> FusedIterator for Box<T> where T: ?Sized + FusedIterator {}
impl<T> ExactSizeIterator for Box<T>
where
T: ?Sized + ExactSizeIterator,
{
fn len(&self) -> usize {
self.0.len()
}
}

View File

@ -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<T: ArchiveUnsized + ?Sized> Archive for Box<T> {
type Archived = ArchivedBox<T::Archived>;
type Resolver = BoxResolver<T::MetadataResolver>;
#[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<T: SerializeUnsized<S> + ?Sized, S: Fallible + ?Sized> Serialize<S> for Box<T> {
#[inline]
fn serialize(&self, serializer: &mut S) -> Result<Self::Resolver, S::Error> {
ArchivedBox::serialize_from_ref(self.as_ref(), serializer)
}
}
impl<T, D> Deserialize<Box<T>, D> for ArchivedBox<T::Archived>
where
T: ArchiveUnsized + ?Sized,
T::Archived: DeserializeUnsized<T, D>,
D: Fallible + ?Sized,
{
#[inline]
fn deserialize(&self, deserializer: &mut D) -> Result<Box<T>, 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<T: ArchivePointee + PartialEq<U> + ?Sized, U: ?Sized> PartialEq<Box<U>> for ArchivedBox<T> {
#[inline]
fn eq(&self, other: &Box<U>) -> bool {
self.get().eq(other.as_ref())
}
}
impl<T: ArchivePointee + PartialOrd<U> + ?Sized, U: ?Sized> PartialOrd<Box<U>> for ArchivedBox<T> {
#[inline]
fn partial_cmp(&self, other: &Box<U>) -> Option<cmp::Ordering> {
self.get().partial_cmp(other.as_ref())
}
}

View File

@ -0,0 +1,27 @@
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::Box;
impl<T> Serialize for Box<T>
where
T: Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.0.serialize(serializer)
}
}
impl<'de, T> Deserialize<'de> for Box<T>
where
T: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Box<T>, D::Error>
where
D: Deserializer<'de>,
{
Ok(Box(allocator_api2::boxed::Box::deserialize(deserializer)?))
}
}

View File

@ -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<F, R>(&self, f: F) -> R
where
F: FnOnce() -> R,
{
ALLOC.set(self, f)
}
}
impl From<Bump> 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,
}
}
}

View File

@ -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<T>(allocator_api2::vec::Vec<T, SwcAlloc>);
impl<T> Vec<T> {
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<T>` 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<T>`. 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<u8>` 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<u16>` 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<u8>` 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<T>` 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<T> Deref for Vec<T> {
type Target = allocator_api2::vec::Vec<T, SwcAlloc>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for Vec<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T> Default for Vec<T> {
fn default() -> Self {
Self(allocator_api2::vec::Vec::new_in(SwcAlloc))
}
}
impl<T> IntoIterator for Vec<T> {
type IntoIter = allocator_api2::vec::IntoIter<T, SwcAlloc>;
type Item = T;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<'a, T> IntoIterator for &'a Vec<T> {
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<T> {
type IntoIter = std::slice::IterMut<'a, T>;
type Item = &'a mut T;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
impl<T> FromIterator<T> for Vec<T> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let mut vec = Vec::default();
vec.extend(iter);
vec
}
}
impl<T> From<Box<[T]>> for Vec<T> {
fn from(v: Box<[T]>) -> Self {
Self(allocator_api2::vec::Vec::from(v.0))
}
}
impl<T> From<Vec<T>> for Box<[T]> {
fn from(v: Vec<T>) -> Self {
Box(v.0.into())
}
}
impl<T> Extend<T> for Vec<T> {
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
self.0.extend(iter)
}
}

View File

@ -0,0 +1,42 @@
use rkyv::{vec::ArchivedVec, DeserializeUnsized};
use super::Vec;
use crate::boxed::Box;
impl<T> rkyv::Archive for Vec<T>
where
T: rkyv::Archive,
{
type Archived = rkyv::vec::ArchivedVec<T::Archived>;
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<T: rkyv::Serialize<S>, S: rkyv::ser::ScratchSpace + rkyv::ser::Serializer + ?Sized>
rkyv::Serialize<S> for Vec<T>
{
#[inline]
fn serialize(&self, serializer: &mut S) -> Result<Self::Resolver, S::Error> {
ArchivedVec::<T::Archived>::serialize_from_slice(self, serializer)
}
}
impl<T: rkyv::Archive, D: rkyv::Fallible + ?Sized> rkyv::Deserialize<Vec<T>, D>
for ArchivedVec<T::Archived>
where
[T::Archived]: rkyv::DeserializeUnsized<[T], D>,
{
#[inline]
fn deserialize(&self, deserializer: &mut D) -> Result<Vec<T>, 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())
}
}
}

View File

@ -0,0 +1 @@

View File

@ -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" }

View File

@ -63,6 +63,19 @@ where
}
}
impl<T> EqIgnoreSpan for swc_allocator::vec::Vec<T>
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<N> EqIgnoreSpan for swc_allocator::boxed::Box<N>
where
N: EqIgnoreSpan,
{
#[inline]
fn eq_ignore_span(&self, other: &Self) -> bool {
(**self).eq_ignore_span(&**other)
}
}
impl<N> TypeEq for swc_allocator::boxed::Box<N>
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,

View File

@ -190,3 +190,12 @@ where
}
}
}
impl<T> Spanned for swc_allocator::boxed::Box<T>
where
T: Spanned,
{
fn span(&self) -> Span {
self.as_ref().span()
}
}

View File

@ -54,3 +54,18 @@ impl Take for Span {
DUMMY_SP
}
}
impl<T> Take for swc_allocator::boxed::Box<T>
where
T: Take,
{
fn dummy() -> Self {
swc_allocator::boxed::Box::new(T::dummy())
}
}
impl<T> Take for swc_allocator::vec::Vec<T> {
fn dummy() -> Self {
Default::default()
}
}