From 20b27460caeac7368d600400ab1f0a49308c1216 Mon Sep 17 00:00:00 2001 From: rzvxa Date: Mon, 15 Apr 2024 14:48:48 +0330 Subject: [PATCH] feat: add GCell and Orphan types. [no ci] --- crates/oxc_ast/src/traverse/cell.rs | 156 ++++++++++++++++++++++++++ crates/oxc_ast/src/traverse/mod.rs | 7 +- crates/oxc_ast/src/traverse/orphan.rs | 38 +++++++ crates/oxc_macros/src/ast_node.rs | 5 +- 4 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 crates/oxc_ast/src/traverse/cell.rs create mode 100644 crates/oxc_ast/src/traverse/orphan.rs diff --git a/crates/oxc_ast/src/traverse/cell.rs b/crates/oxc_ast/src/traverse/cell.rs new file mode 100644 index 0000000000000..9fdc3c9e7519a --- /dev/null +++ b/crates/oxc_ast/src/traverse/cell.rs @@ -0,0 +1,156 @@ +//! Cell type and token for traversing AST. +//! +//! Based on `GhostCell`. +//! All method implementations copied verbatim from original version by paper's authors +//! https://gitlab.mpi-sws.org/FP/ghostcell/-/blob/master/ghostcell/src/lib.rs +//! and `ghost_cell` crate https://docs.rs/ghost-cell . +//! +//! Only difference is that instead of using a lifetime to constrain the life of access tokens, +//! here we provide only an unsafe method `Token::new_unchecked` and the user must maintain +//! the invariant that only one token may be "in play" at same time +//! (see below for exactly what "in play" means). +//! +//! This alteration removes a lifetime, and avoids the unergonomic pattern of all the code that +//! works with a structure containing `GCell`s needing to be within a single closure. + +use std::cell::UnsafeCell; + +/// Access token for traversing AST. +#[repr(transparent)] +pub struct Token(()); + +impl Token { + /// Create new access token for traversing AST. + /// + /// It is imperative that any code operating on a single AST does not have access to more + /// than 1 token. `GCell` uses this guarantee to make it impossible to obtain a `&mut` + /// reference to any AST node while another reference exists. If more than 1 token is "in play", + /// this guarantee can be broken, and may lead to undefined behavior. + /// + /// This function is used internally by `transform`, but probably should not be used elsewhere. + /// + /// It is permissable to create multiple tokens which are never used together on the same AST. + /// In practice, this means it is possible to transform multiple ASTs on different threads + /// simultaneously. + /// + /// If operating on multiple ASTs together (e.g. concatenating 2 files), then a single token + /// must be used to access all the ASTs involved in the operation NOT 1 token per AST. + /// + /// # SAFETY + /// Caller must ensure only a single token is used with any AST at one time. + #[inline] + pub unsafe fn new_unchecked() -> Self { + Self(()) + } +} + +/// A cell type providing interior mutability, with aliasing rules enforced at compile time. +#[repr(transparent)] +pub struct GCell { + value: UnsafeCell, +} + +#[allow(dead_code)] +impl GCell { + pub const fn new(value: T) -> Self { + GCell { + value: UnsafeCell::new(value), + } + } + + pub fn into_inner(self) -> T { + self.value.into_inner() + } +} + +#[allow(dead_code, unused_variables)] +impl GCell { + #[inline] + pub fn borrow<'a>(&'a self, tk: &'a Token) -> &'a T { + unsafe { &*self.value.get() } + } + + #[inline] + pub fn borrow_mut<'a>(&'a self, tk: &'a mut Token) -> &'a mut T { + unsafe { &mut *self.value.get() } + } + + #[inline] + pub const fn as_ptr(&self) -> *mut T { + self.value.get() + } + + #[inline] + pub fn get_mut(&mut self) -> &mut T { + unsafe { &mut *self.value.get() } + } + + #[inline] + pub fn from_mut(t: &mut T) -> &mut Self { + unsafe { &mut *(t as *mut T as *mut Self) } + } +} + +#[allow(dead_code)] +impl GCell<[T]> { + #[inline] + pub fn as_slice_of_cells(&self) -> &[GCell] { + unsafe { &*(self as *const GCell<[T]> as *const [GCell]) } + } +} + +#[allow(dead_code)] +impl GCell { + #[inline] + pub fn replace(&self, value: T, tk: &mut Token) -> T { + std::mem::replace(self.borrow_mut(tk), value) + } + + #[inline] + pub fn take(&self, tk: &mut Token) -> T + where + T: Default, + { + self.replace(T::default(), tk) + } +} + +#[allow(dead_code)] +impl GCell { + #[inline] + pub fn clone(&self, tk: &Token) -> Self { + GCell::new(self.borrow(tk).clone()) + } +} + +impl Default for GCell { + #[inline] + fn default() -> Self { + Self::new(T::default()) + } +} + +impl AsMut for GCell { + #[inline] + fn as_mut(&mut self) -> &mut T { + self.get_mut() + } +} + +impl From for GCell { + fn from(t: T) -> Self { + GCell::new(t) + } +} + +// SAFETY: `GhostCell` is `Send` + `Sync`, so `GCell` can be too +unsafe impl Send for GCell {} +unsafe impl Sync for GCell {} + +/// Type alias for a shared ref to a `GCell`. +/// This is the interior-mutable equivalent to `oxc_allocator::Box`. +pub type SharedBox<'a, T> = &'a GCell; + +/// Type alias for a shared Vec +pub type SharedVec<'a, T> = oxc_allocator::Vec<'a, GCell>; + diff --git a/crates/oxc_ast/src/traverse/mod.rs b/crates/oxc_ast/src/traverse/mod.rs index 9f4a6bb50dd74..90035220fb2e8 100644 --- a/crates/oxc_ast/src/traverse/mod.rs +++ b/crates/oxc_ast/src/traverse/mod.rs @@ -1,5 +1,10 @@ +mod cell; +mod orphan; + +pub use orphan::Orphan; + /// This trait is only for checking that we didn't forgot to add `ast_node` attribute to any -/// essential types, Would get removed after cleaning things up. +/// of essential types, Would get removed after cleaning things up. pub trait TraversableTest { fn does_support_traversable() { // Yes, Yes it does my friend! diff --git a/crates/oxc_ast/src/traverse/orphan.rs b/crates/oxc_ast/src/traverse/orphan.rs new file mode 100644 index 0000000000000..d4d266dde8af7 --- /dev/null +++ b/crates/oxc_ast/src/traverse/orphan.rs @@ -0,0 +1,38 @@ +use std::ops::Deref; + +/// Wrapper for AST nodes which have been disconnected from the AST. +/// +/// This type is central to preventing a node from being attached to the AST in multiple places. +/// +/// `Orphan` cannot be `Copy` or `Clone`, or it would allow creating a duplicate ref to the +/// contained node. The original `Orphan` could be attached to the AST, and then the copy +/// could also be attached to the AST elsewhere. +#[repr(transparent)] +pub struct Orphan(T); + +impl Orphan { + /// Wrap node to indicate it's disconnected from AST. + /// SAFETY: Caller must ensure that `node` is not attached to the AST. + #[inline] + pub unsafe fn new(node: T) -> Self { + Self(node) + } + + /// Unwrap node from `Orphan`. + /// This should only be done before inserting it into the AST. + /// Not unsafe as there is nothing bad you can do with an un-orphaned AST node. + /// No APIs are provided to attach nodes to the AST, unless they're wrapped in `Orphan`. + #[inline] + pub fn inner(self) -> T { + self.0 + } +} + +impl Deref for Orphan { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + &self.0 + } +} diff --git a/crates/oxc_macros/src/ast_node.rs b/crates/oxc_macros/src/ast_node.rs index b6600e7906fac..fd2f883e88405 100644 --- a/crates/oxc_macros/src/ast_node.rs +++ b/crates/oxc_macros/src/ast_node.rs @@ -35,8 +35,9 @@ fn modify_enum(item: &mut ItemEnum) -> NodeData { } fn validate_struct_attributes<'a>(mut attrs: slice::Iter<'a, Attribute>) { - // make sure that no structure derives Clone/Copy trait. - // TODO: It might fail if there is a manual Clone/Copy trait implemented for the struct. + // make sure that no structure derives Clone/Copy traits. + // TODO: It will fail if there is a manual Clone/Copy traits implemented for the struct. + // Negative traits (!Copy and !Clone) are nightly so I'm not sure how we can fully enforce it. assert!(!attrs.any(|attr| { let args = attr.parse_args_with(Punctuated::::parse_terminated); attr.path().is_ident("derive")