mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-14 22:29:17 +00:00
feat(allocator): make Box Send + Sync when the type it contains is (#25242)
`ArenaBox` was previously `!Send` and `!Sync`, because it contains a `NonNull` pointer. `ArenaBox<T>` has the same semantics as a `&mut T`, so can be `Send` / `Sync` if `T` is. The also matches stdlib's `Box`. Note: `ArenaVec` is _not_ `Send` because it contains an `&Allocator` reference and `Allocator` is not `Sync`. But `ArenaBox` contains nothing but the pointer - it does not contain an `&Allocator` - so it doesn't share `ArenaVec`'s restriction. This is a further step towards making the whole AST `Sync`, so ASTs can be shared across threads. After this PR, the only remaining blocker is the `Cell`s in AST for semantic IDs (e.g. `Cell<NodeId>`).
This commit is contained in:
1 parent
ea1e88a696
commit
3c8312e6dc
1 file changed
+45
-1
@@ -34,6 +34,30 @@ use crate::GetAllocator;
|
||||
#[repr(transparent)]
|
||||
pub struct Box<'alloc, T: ?Sized>(NonNull<T>, PhantomData<(&'alloc (), T)>);
|
||||
|
||||
/// SAFETY: A [`Box`] has exclusive access to the `T` it points to, and grants access to nothing else,
|
||||
/// so it gets the same auto traits as the `&'alloc mut T` it stands in for.
|
||||
///
|
||||
/// Unlike [`Vec`], a `Box` holds no `&Arena`. There is no way from a `Box` to the [`Allocator`]
|
||||
/// it points into, so 2 `Box`es on different threads cannot both allocate from the same arena -
|
||||
/// which is the reason `Vec` cannot be `Send`. [`new_in`] is the only method which touches an
|
||||
/// [`Allocator`], and it receives one as a param, so it runs on a thread which has one already.
|
||||
///
|
||||
/// A `Box` is never [`Drop`], so sending one to another thread cannot free arena memory there.
|
||||
/// `'alloc` borrows the arena, so it cannot be reset or dropped while a `Box` into it is alive
|
||||
/// on any thread.
|
||||
///
|
||||
/// A `T` which itself holds arena data keeps its own bound - a `Box<Vec<T>>` is not `Send`,
|
||||
/// because [`Vec`] is not.
|
||||
///
|
||||
/// [`Vec`]: crate::Vec
|
||||
/// [`Allocator`]: crate::Allocator
|
||||
/// [`new_in`]: Box::new_in
|
||||
unsafe impl<T: Send + ?Sized> Send for Box<'_, T> {}
|
||||
|
||||
/// SAFETY: Sharing a `&Box<T>` shares only a `&T`, so `T` being [`Sync`] is what it takes.
|
||||
/// The [`Send`] impl above covers why a `Box` grants access to nothing else.
|
||||
unsafe impl<T: Sync + ?Sized> Sync for Box<'_, T> {}
|
||||
|
||||
impl<T: ?Sized> Box<'_, T> {
|
||||
/// Const assertion that `T` is not `Drop`.
|
||||
/// Must be referenced in all methods which create a `Box`.
|
||||
@@ -291,12 +315,32 @@ impl<T: Hash> Hash for Box<'_, T> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
use std::{
|
||||
cell::Cell,
|
||||
hash::{DefaultHasher, Hash, Hasher},
|
||||
};
|
||||
|
||||
use oxc_data_structures::types::implements;
|
||||
|
||||
use crate::{Allocator, Vec};
|
||||
|
||||
use super::Box;
|
||||
|
||||
// A `Box` grants what a `&mut T` does, so it gets the same auto traits.
|
||||
// See `unsafe impl Send for Box`.
|
||||
#[test]
|
||||
fn box_send_sync() {
|
||||
assert!(implements!(Box<u32>: Send));
|
||||
assert!(implements!(Box<u32>: Sync));
|
||||
|
||||
// `Cell` is `Send` but not `Sync`
|
||||
assert!(implements!(Box<Cell<u32>>: Send));
|
||||
assert!(implements!(Box<Cell<u32>>: !Sync));
|
||||
// `Vec` is `Sync` but not `Send`
|
||||
assert!(implements!(Box<Vec<u32>>: !Send));
|
||||
assert!(implements!(Box<Vec<u32>>: Sync));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn box_deref_mut() {
|
||||
let allocator = Allocator::default();
|
||||
|
||||
Reference in new issue
Block a user