mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
perf(semantic, allocator): branchless clone_in for semantic IDs (#24564)
Follow-on after #24422. Make cloning semantic IDs (`NodeId`, `ScopeId` etc) use branchless arithmetic. * Cloning `NodeId` now just uses a single OR operation to take into account whether to preserve the existing ID (`clone_in_with_semantic_ids`) or substitute a dummy ID (`clone_in`). * Cloning `Cell<Option<ScopeId>>` is 2 instructions (NOT + AND) on x86_64, or just 1 on aarch64 (NOT-AND). https://godbolt.org/z/1KWdc5hnb The mechanism is a type `CloneInSemanticIds` which is used as `with_semantic_ids` instead of a `bool`: ```rs #[repr(u32)] pub enum CloneInSemanticIds { With = 0, Without = u32::MAX, } ``` This is 4 bytes, rather than `bool`'s 1 byte, but that makes no difference - either way it takes 1 register to pass between functions. The choice of discriminants is what enables the cheap arithmetic. This is even smaller in terms of binary size than the previous solution of marking `Cell::clone_in_impl` as `#[inline(never)]` (OR instruction is smaller than function call), and it's more performant as it loses the function call overhead. It also keeps `Cell`'s blanket implementation of `CloneIn` generalized. Between #24422 and this PR, merging `CloneIn` with/without semantic IDs into a single path has gained us a large binary size reduction, and lost us nothing - or may even have gained us a marginal perf improvement. No visible change on our benchmarks, as we don't use `CloneIn` much, but it may well be a measurable improvement for Rolldown, which clones whole ASTs.
This commit is contained in:
1 parent
3acf4c1789
commit
ba65790029
22 files changed
+749
-437
No files matched your search
@@ -6,7 +6,7 @@ use std::{
|
||||
slice,
|
||||
};
|
||||
|
||||
use crate::{Allocator, Box, CloneIn};
|
||||
use crate::{Allocator, Box, CloneIn, CloneInSemanticIds};
|
||||
|
||||
const USIZE_BITS: usize = usize::BITS as usize;
|
||||
|
||||
@@ -177,7 +177,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for BitSet<'_> {
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
_with_semantic_ids: bool,
|
||||
_with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> BitSet<'new_alloc> {
|
||||
let slice = self.entries.as_ref();
|
||||
|
||||
@@ -8,20 +8,36 @@ use std::{
|
||||
|
||||
use crate::{Allocator, Box, HashMap, Vec};
|
||||
|
||||
/// Option to pass to [`CloneIn::clone_in_impl`] to determine how to clone semantic IDs.
|
||||
///
|
||||
/// * [`CloneInSemanticIds::With`] to clone by copying current value.
|
||||
/// * [`CloneInSemanticIds::Without`] to clone by substituting a dummy value -
|
||||
/// `NonMaxU32(0)` for a plain ID, `None` for an optional ID.
|
||||
///
|
||||
/// This is designed so that cloning semantic IDs is branchless and cheap.
|
||||
/// See `SemanticId` trait in `oxc_syntax` crate.
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u32)]
|
||||
pub enum CloneInSemanticIds {
|
||||
With = 0,
|
||||
Without = u32::MAX,
|
||||
}
|
||||
|
||||
/// A trait to explicitly clone an object into an arena allocator.
|
||||
///
|
||||
/// As a convention `Cloned` associated type should always be the same as `Self`,
|
||||
/// It'd only differ in the lifetime, Here's an example:
|
||||
///
|
||||
/// ```
|
||||
/// # use oxc_allocator::{Allocator, CloneIn, Vec};
|
||||
/// # use oxc_allocator::{Allocator, CloneIn, CloneInSemanticIds, Vec};
|
||||
/// # struct Struct<'a> {a: Vec<'a, u8>, b: u8}
|
||||
///
|
||||
/// impl<'old_alloc, 'new_alloc> CloneIn<'new_alloc> for Struct<'old_alloc> {
|
||||
/// type Cloned = Struct<'new_alloc>;
|
||||
///
|
||||
/// fn clone_in_impl(
|
||||
/// &self,
|
||||
/// with_semantic_ids: bool,
|
||||
/// with_semantic_ids: CloneInSemanticIds,
|
||||
/// allocator: &'new_alloc Allocator,
|
||||
/// ) -> Self::Cloned {
|
||||
/// Struct {
|
||||
@@ -32,9 +48,8 @@ use crate::{Allocator, Box, HashMap, Vec};
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Implementations of this trait on non-allocated items usually short-circuit to `Clone::clone`;
|
||||
/// Implementations of this trait on non-allocated items usually delegate to `Clone::clone`.
|
||||
/// However, it **isn't** guaranteed.
|
||||
///
|
||||
pub trait CloneIn<'new_alloc>: Sized {
|
||||
/// The type of the cloned object.
|
||||
///
|
||||
@@ -47,7 +62,7 @@ pub trait CloneIn<'new_alloc>: Sized {
|
||||
#[expect(clippy::inline_always)]
|
||||
#[inline(always)]
|
||||
fn clone_in(&self, allocator: &'new_alloc Allocator) -> Self::Cloned {
|
||||
self.clone_in_impl(false, allocator)
|
||||
self.clone_in_impl(CloneInSemanticIds::Without, allocator)
|
||||
}
|
||||
|
||||
/// Almost identical as `clone_in`, but for some special type, it will also clone the semantic ids.
|
||||
@@ -56,25 +71,28 @@ pub trait CloneIn<'new_alloc>: Sized {
|
||||
#[expect(clippy::inline_always)]
|
||||
#[inline(always)]
|
||||
fn clone_in_with_semantic_ids(&self, allocator: &'new_alloc Allocator) -> Self::Cloned {
|
||||
self.clone_in_impl(true, allocator)
|
||||
self.clone_in_impl(CloneInSemanticIds::With, allocator)
|
||||
}
|
||||
|
||||
/// Clone `self` into `allocator`, threading whether semantic ids should be preserved as a
|
||||
/// runtime `with_semantic_ids` flag rather than as two separate methods. This is the method
|
||||
/// that implementors provide; `clone_in` and `clone_in_with_semantic_ids` are thin wrappers
|
||||
/// around it.
|
||||
/// runtime `with_semantic_ids` flag rather than as two separate methods.
|
||||
///
|
||||
/// It exists so the `CloneIn` derive can emit a *single* recursive traversal that serves both
|
||||
/// `clone_in` (`with_semantic_ids == false`) and `clone_in_with_semantic_ids`
|
||||
/// (`with_semantic_ids == true`), instead of monomorphizing two near-identical traversals over
|
||||
/// the whole AST. Id fields recurse when `with_semantic_ids` is `true` and reset to their
|
||||
/// default when it is `false`.
|
||||
/// This is the method that implementors provide.
|
||||
/// `clone_in` and `clone_in_with_semantic_ids` are thin wrappers around it.
|
||||
///
|
||||
/// Prefer calling `clone_in` or `clone_in_with_semantic_ids` at call sites — they name the
|
||||
/// intent and delegate here.
|
||||
/// It exists so the `CloneIn` derive can emit a *single* recursive traversal that serves both:
|
||||
/// * `clone_in`: `with_semantic_ids == CloneInSemanticIds::Without`
|
||||
/// * `clone_in_with_semantic_ids`: `with_semantic_ids == CloneInSemanticIds::With`
|
||||
///
|
||||
/// This is instead of monomorphizing two near-identical traversals over the whole AST.
|
||||
/// ID fields copy existing values when `with_semantic_ids == CloneInSemanticIds::With`,
|
||||
/// and reset to their default when `CloneInSemanticIds::Without`.
|
||||
///
|
||||
/// Prefer calling `clone_in` or `clone_in_with_semantic_ids` at call sites —
|
||||
/// they name the intent and delegate here.
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned;
|
||||
}
|
||||
@@ -86,7 +104,11 @@ where
|
||||
type Cloned = Option<C>;
|
||||
|
||||
#[inline]
|
||||
fn clone_in_impl(&self, with_semantic_ids: bool, allocator: &'alloc Allocator) -> Self::Cloned {
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
self.as_ref().map(|it| it.clone_in_impl(with_semantic_ids, allocator))
|
||||
}
|
||||
}
|
||||
@@ -100,7 +122,7 @@ where
|
||||
#[inline]
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
Box::new_in(self.as_ref().clone_in_impl(with_semantic_ids, allocator), &allocator)
|
||||
@@ -115,7 +137,7 @@ where
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
let slice = self.as_ref();
|
||||
@@ -165,7 +187,7 @@ where
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
// The implementation below is equivalent to:
|
||||
@@ -206,7 +228,7 @@ where
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
// Keys in original hash map are guaranteed to be unique.
|
||||
@@ -227,21 +249,19 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'alloc, T: Copy + Default> CloneIn<'alloc> for Cell<T> {
|
||||
type Cloned = Cell<T>;
|
||||
impl<'alloc, T, C> CloneIn<'alloc> for Cell<T>
|
||||
where
|
||||
T: Copy + CloneIn<'alloc, Cloned = C>,
|
||||
{
|
||||
type Cloned = Cell<C>;
|
||||
|
||||
// `#[inline(never)]` so the `with_semantic_ids` branch below is *not* inlined at every id-field
|
||||
// clone site. Consumers that only clone *without* semantic ids (e.g. the napi transform/parse
|
||||
// bindings, via `oxc_transformer`) would otherwise carry the dead preserve branch at every site
|
||||
// — ~16 KiB on the transform binary. Outlining it to one function per `Cell<T>` keeps those
|
||||
// binaries at their pre-`clone_in_impl` size, while consumers that clone *with* ids (rolldown,
|
||||
// oxlint's react compiler) are unaffected — their win comes from the shared traversal, not this leaf.
|
||||
#[inline(never)]
|
||||
fn clone_in_impl(&self, with_semantic_ids: bool, _: &'alloc Allocator) -> Self::Cloned {
|
||||
// `Cell` in the AST only ever holds semantic ids (`Cell<NodeId>`, `Cell<Option<ScopeId>>`,
|
||||
// etc.). Preserve them when cloning *with* semantic ids, otherwise reset to the default
|
||||
// (`NodeId::DUMMY`, `None`, ...) so ids aren't carried into a fresh clone.
|
||||
if with_semantic_ids { Cell::new(self.get()) } else { Cell::new(T::default()) }
|
||||
#[inline]
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
Cell::new(self.get().clone_in_impl(with_semantic_ids, allocator))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,7 +270,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for &str {
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
_with_semantic_ids: bool,
|
||||
_with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
allocator.alloc_str(self)
|
||||
@@ -263,7 +283,7 @@ macro_rules! impl_clone_in {
|
||||
impl<'alloc> CloneIn<'alloc> for $t {
|
||||
type Cloned = Self;
|
||||
#[inline(always)]
|
||||
fn clone_in_impl(&self, _with_semantic_ids: bool, _: &'alloc Allocator) -> Self {
|
||||
fn clone_in_impl(&self, _with_semantic_ids: CloneInSemanticIds, _: &'alloc Allocator) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ pub use allocator::Allocator;
|
||||
#[cfg(feature = "bitset")]
|
||||
pub use bitset::BitSet;
|
||||
pub use boxed::{Box, Box as ArenaBox};
|
||||
pub use clone_in::CloneIn;
|
||||
pub use clone_in::{CloneIn, CloneInSemanticIds};
|
||||
pub use convert::{FromIn, IntoIn};
|
||||
pub use hash_map::{HashMap, HashMap as ArenaHashMap};
|
||||
pub use hash_set::{HashSet, HashSet as ArenaHashSet};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use bitflags::bitflags;
|
||||
|
||||
use oxc_allocator::{Allocator, CloneIn};
|
||||
use oxc_allocator::{Allocator, CloneIn, CloneInSemanticIds};
|
||||
use oxc_ast_macros::ast;
|
||||
use oxc_estree::ESTree;
|
||||
use oxc_span::{ContentEq, Span};
|
||||
@@ -126,7 +126,11 @@ impl ContentEq for CommentNewlines {
|
||||
impl<'alloc> CloneIn<'alloc> for CommentNewlines {
|
||||
type Cloned = Self;
|
||||
|
||||
fn clone_in_impl(&self, _with_semantic_ids: bool, _: &'alloc Allocator) -> Self::Cloned {
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
_with_semantic_ids: CloneInSemanticIds,
|
||||
_: &'alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::{
|
||||
fmt::{self, Display},
|
||||
};
|
||||
|
||||
use oxc_allocator::{Allocator, CloneIn, Dummy};
|
||||
use oxc_allocator::{Allocator, CloneIn, CloneInSemanticIds, Dummy};
|
||||
use oxc_data_structures::inline_string::InlineString;
|
||||
use oxc_span::ContentEq;
|
||||
|
||||
@@ -172,7 +172,11 @@ impl ContentEq for RegExpFlags {
|
||||
impl<'alloc> CloneIn<'alloc> for RegExpFlags {
|
||||
type Cloned = Self;
|
||||
|
||||
fn clone_in_impl(&self, _with_semantic_ids: bool, _: &'alloc Allocator) -> Self::Cloned {
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
_with_semantic_ids: CloneInSemanticIds,
|
||||
_: &'alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,11 +1,15 @@
|
||||
use oxc_allocator::{Allocator, CloneIn};
|
||||
use oxc_allocator::{Allocator, CloneIn, CloneInSemanticIds};
|
||||
|
||||
use crate::ast::Modifier;
|
||||
|
||||
impl<'alloc> CloneIn<'alloc> for Modifier {
|
||||
type Cloned = Self;
|
||||
|
||||
fn clone_in_impl(&self, _with_semantic_ids: bool, _: &'alloc Allocator) -> Self::Cloned {
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
_with_semantic_ids: CloneInSemanticIds,
|
||||
_: &'alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
*self
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
use oxc_allocator::{Allocator, CloneIn};
|
||||
use oxc_allocator::{Allocator, CloneIn, CloneInSemanticIds};
|
||||
|
||||
use crate::ast::*;
|
||||
|
||||
@@ -14,7 +14,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for Pattern<'_> {
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
Pattern {
|
||||
@@ -29,7 +29,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for Disjunction<'_> {
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
Disjunction {
|
||||
@@ -44,7 +44,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for Alternative<'_> {
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
Alternative {
|
||||
@@ -59,7 +59,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for Term<'_> {
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
match self {
|
||||
@@ -108,7 +108,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for BoundaryAssertion {
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
BoundaryAssertion {
|
||||
@@ -124,7 +124,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for BoundaryAssertionKind {
|
||||
#[inline(always)]
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
_with_semantic_ids: bool,
|
||||
_with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
*self
|
||||
@@ -136,7 +136,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for LookAroundAssertion<'_> {
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
LookAroundAssertion {
|
||||
@@ -153,7 +153,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for LookAroundAssertionKind {
|
||||
#[inline(always)]
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
_with_semantic_ids: bool,
|
||||
_with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
*self
|
||||
@@ -165,7 +165,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for Quantifier<'_> {
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
Quantifier {
|
||||
@@ -183,7 +183,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for Character {
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
Character {
|
||||
@@ -200,7 +200,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for CharacterKind {
|
||||
#[inline(always)]
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
_with_semantic_ids: bool,
|
||||
_with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
*self
|
||||
@@ -212,7 +212,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for CharacterClassEscape {
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
CharacterClassEscape {
|
||||
@@ -228,7 +228,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for CharacterClassEscapeKind {
|
||||
#[inline(always)]
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
_with_semantic_ids: bool,
|
||||
_with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
*self
|
||||
@@ -240,7 +240,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for UnicodePropertyEscape<'_> {
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
UnicodePropertyEscape {
|
||||
@@ -258,7 +258,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for Dot {
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
Dot { span: CloneIn::clone_in_impl(&self.span, with_semantic_ids, allocator) }
|
||||
@@ -270,7 +270,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for CharacterClass<'_> {
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
CharacterClass {
|
||||
@@ -289,7 +289,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for CharacterClassContentsKind {
|
||||
#[inline(always)]
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
_with_semantic_ids: bool,
|
||||
_with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
*self
|
||||
@@ -301,7 +301,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for CharacterClassContents<'_> {
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
match self {
|
||||
@@ -334,7 +334,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for CharacterClassRange {
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
CharacterClassRange {
|
||||
@@ -350,7 +350,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for ClassStringDisjunction<'_> {
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
ClassStringDisjunction {
|
||||
@@ -366,7 +366,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for ClassString<'_> {
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
ClassString {
|
||||
@@ -382,7 +382,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for CapturingGroup<'_> {
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
CapturingGroup {
|
||||
@@ -398,7 +398,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for IgnoreGroup<'_> {
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
IgnoreGroup {
|
||||
@@ -414,7 +414,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for Modifiers {
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
Modifiers {
|
||||
@@ -430,7 +430,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for IndexedReference {
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
IndexedReference {
|
||||
@@ -445,7 +445,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for NamedReference<'_> {
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: bool,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
NamedReference {
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::{collections::hash_map::Entry, fmt, mem};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use self_cell::self_cell;
|
||||
|
||||
use oxc_allocator::{Allocator, ArenaVec, BitSet, CloneIn};
|
||||
use oxc_allocator::{Allocator, ArenaVec, BitSet, CloneIn, CloneInSemanticIds};
|
||||
use oxc_index::IndexVec;
|
||||
use oxc_span::Span;
|
||||
use oxc_str::{ArenaIdentHashMap, Ident};
|
||||
@@ -31,11 +31,15 @@ impl CloneIn<'_> for Redeclaration {
|
||||
type Cloned = Self;
|
||||
|
||||
#[inline]
|
||||
fn clone_in_impl(&self, with_semantic_ids: bool, _allocator: &Allocator) -> Self::Cloned {
|
||||
if with_semantic_ids {
|
||||
self.clone()
|
||||
} else {
|
||||
Self { span: self.span, declaration: NodeId::DUMMY, flags: self.flags }
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &Allocator,
|
||||
) -> Self::Cloned {
|
||||
Self {
|
||||
span: self.span,
|
||||
declaration: self.declaration.clone_in_impl(with_semantic_ids, allocator),
|
||||
flags: self.flags,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::{
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use oxc_allocator::{Allocator, CloneIn, Dummy};
|
||||
use oxc_allocator::{Allocator, CloneIn, CloneInSemanticIds, Dummy};
|
||||
use oxc_ast_macros::ast;
|
||||
use oxc_estree::ESTree;
|
||||
|
||||
@@ -103,7 +103,7 @@ impl<'a> CloneIn<'a> for SourceType {
|
||||
type Cloned = Self;
|
||||
|
||||
#[inline]
|
||||
fn clone_in_impl(&self, _with_semantic_ids: bool, _: &'a Allocator) -> Self {
|
||||
fn clone_in_impl(&self, _with_semantic_ids: CloneInSemanticIds, _: &'a Allocator) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use miette::{LabeledSpan, SourceOffset, SourceSpan};
|
||||
#[cfg(feature = "serialize")]
|
||||
use serde::{Serialize, Serializer as SerdeSerializer, ser::SerializeMap};
|
||||
|
||||
use oxc_allocator::{Allocator, CloneIn, Dummy};
|
||||
use oxc_allocator::{Allocator, CloneIn, CloneInSemanticIds, Dummy};
|
||||
use oxc_ast_macros::ast;
|
||||
use oxc_estree::ESTree;
|
||||
|
||||
@@ -635,7 +635,7 @@ impl<'a> CloneIn<'a> for Span {
|
||||
type Cloned = Self;
|
||||
|
||||
#[inline]
|
||||
fn clone_in_impl(&self, _with_semantic_ids: bool, _: &'a Allocator) -> Self {
|
||||
fn clone_in_impl(&self, _with_semantic_ids: CloneInSemanticIds, _: &'a Allocator) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ use std::{
|
||||
};
|
||||
|
||||
use oxc_allocator::{
|
||||
Allocator, ArenaStringBuilder, CloneIn, Dummy, FromIn, GetAllocator, IdentBuildHasher,
|
||||
ident_hash,
|
||||
Allocator, ArenaStringBuilder, CloneIn, CloneInSemanticIds, Dummy, FromIn, GetAllocator,
|
||||
IdentBuildHasher, ident_hash,
|
||||
};
|
||||
#[cfg(feature = "serialize")]
|
||||
use oxc_estree::{ESTree, JsonSafeString, Serializer as ESTreeSerializer};
|
||||
@@ -454,7 +454,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for Ident<'_> {
|
||||
#[inline]
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
_with_semantic_ids: bool,
|
||||
_with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
let s = allocator.alloc_str(self.as_str());
|
||||
|
||||
@@ -4,7 +4,9 @@ use std::{
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
use oxc_allocator::{Allocator, ArenaStringBuilder, CloneIn, Dummy, FromIn, GetAllocator};
|
||||
use oxc_allocator::{
|
||||
Allocator, ArenaStringBuilder, CloneIn, CloneInSemanticIds, Dummy, FromIn, GetAllocator,
|
||||
};
|
||||
#[cfg(feature = "serialize")]
|
||||
use oxc_estree::{ESTree, Serializer as ESTreeSerializer};
|
||||
#[cfg(feature = "serialize")]
|
||||
@@ -119,7 +121,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for Str<'_> {
|
||||
#[inline]
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
_with_semantic_ids: bool,
|
||||
_with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
Str::from_in(self.as_str(), allocator)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
use oxc_allocator::{Allocator, CloneIn};
|
||||
use oxc_allocator::{Allocator, CloneIn, CloneInSemanticIds};
|
||||
|
||||
use crate::number::*;
|
||||
use crate::operator::*;
|
||||
@@ -16,7 +16,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for NumberBase {
|
||||
#[inline(always)]
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
_with_semantic_ids: bool,
|
||||
_with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
*self
|
||||
@@ -29,7 +29,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for BigintBase {
|
||||
#[inline(always)]
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
_with_semantic_ids: bool,
|
||||
_with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
*self
|
||||
@@ -42,7 +42,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for AssignmentOperator {
|
||||
#[inline(always)]
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
_with_semantic_ids: bool,
|
||||
_with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
*self
|
||||
@@ -55,7 +55,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for BinaryOperator {
|
||||
#[inline(always)]
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
_with_semantic_ids: bool,
|
||||
_with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
*self
|
||||
@@ -68,7 +68,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for LogicalOperator {
|
||||
#[inline(always)]
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
_with_semantic_ids: bool,
|
||||
_with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
*self
|
||||
@@ -81,7 +81,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for UnaryOperator {
|
||||
#[inline(always)]
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
_with_semantic_ids: bool,
|
||||
_with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
*self
|
||||
@@ -94,7 +94,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for UpdateOperator {
|
||||
#[inline(always)]
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
_with_semantic_ids: bool,
|
||||
_with_semantic_ids: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
*self
|
||||
|
||||
@@ -17,6 +17,7 @@ pub mod operator;
|
||||
pub mod precedence;
|
||||
pub mod reference;
|
||||
pub mod scope;
|
||||
pub mod semantic_id;
|
||||
#[cfg(feature = "serialize")]
|
||||
mod serialize;
|
||||
pub mod symbol;
|
||||
|
||||
@@ -2,13 +2,16 @@
|
||||
|
||||
use bitflags::bitflags;
|
||||
|
||||
use oxc_allocator::{Allocator, CloneIn, Dummy};
|
||||
use oxc_allocator::{Allocator, CloneIn, CloneInSemanticIds, Dummy};
|
||||
use oxc_ast_macros::ast;
|
||||
use oxc_index::define_nonmax_u32_index_type;
|
||||
|
||||
use crate::semantic_id::SemanticId;
|
||||
|
||||
define_nonmax_u32_index_type! {
|
||||
/// AST Node ID
|
||||
#[ast]
|
||||
#[clone_in(semantic_id)]
|
||||
#[content_eq(skip)]
|
||||
#[estree(skip)]
|
||||
pub struct NodeId;
|
||||
@@ -41,18 +44,15 @@ impl<'a> Dummy<'a> for NodeId {
|
||||
impl<'alloc> CloneIn<'alloc> for NodeId {
|
||||
type Cloned = Self;
|
||||
|
||||
#[inline]
|
||||
fn clone_in_impl(&self, with_semantic_ids: bool, _: &'alloc Allocator) -> Self {
|
||||
if with_semantic_ids {
|
||||
*self
|
||||
} else {
|
||||
// `with_semantic_ids == false` should never reach this, because `CloneIn` skips
|
||||
// `node_id` field, filling it with its default instead.
|
||||
unreachable!();
|
||||
}
|
||||
#[expect(clippy::inline_always)]
|
||||
#[inline(always)] // Because this method only delegates
|
||||
fn clone_in_impl(&self, with_semantic_ids: CloneInSemanticIds, _: &'alloc Allocator) -> Self {
|
||||
self.clone_id(with_semantic_ids)
|
||||
}
|
||||
}
|
||||
|
||||
impl SemanticId for NodeId {}
|
||||
|
||||
bitflags! {
|
||||
/// Contains additional information about an AST node.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
|
||||
@@ -3,15 +3,16 @@ use oxc_index::define_nonmax_u32_index_type;
|
||||
#[cfg(feature = "serialize")]
|
||||
use serde::Serialize;
|
||||
|
||||
use oxc_allocator::{Allocator, CloneIn};
|
||||
use oxc_allocator::{Allocator, CloneIn, CloneInSemanticIds};
|
||||
|
||||
use crate::{node::NodeId, scope::ScopeId, symbol::SymbolId};
|
||||
use crate::{node::NodeId, scope::ScopeId, semantic_id::SemanticId, symbol::SymbolId};
|
||||
|
||||
use oxc_ast_macros::ast;
|
||||
|
||||
define_nonmax_u32_index_type! {
|
||||
#[ast]
|
||||
#[builder(default)]
|
||||
#[clone_in(semantic_id)]
|
||||
#[content_eq(skip)]
|
||||
#[estree(skip)]
|
||||
pub struct ReferenceId;
|
||||
@@ -21,18 +22,14 @@ impl<'alloc> CloneIn<'alloc> for ReferenceId {
|
||||
type Cloned = Self;
|
||||
|
||||
#[expect(clippy::inline_always)]
|
||||
#[inline(always)]
|
||||
fn clone_in_impl(&self, with_semantic_ids: bool, _: &'alloc Allocator) -> Self {
|
||||
if with_semantic_ids {
|
||||
*self
|
||||
} else {
|
||||
// `with_semantic_ids == false` should never reach this, because `CloneIn` skips
|
||||
// `reference_id` field, filling it with its default instead.
|
||||
unreachable!();
|
||||
}
|
||||
#[inline(always)] // Because this method only delegates
|
||||
fn clone_in_impl(&self, with_semantic_ids: CloneInSemanticIds, _: &'alloc Allocator) -> Self {
|
||||
self.clone_id(with_semantic_ids)
|
||||
}
|
||||
}
|
||||
|
||||
impl SemanticId for ReferenceId {}
|
||||
|
||||
bitflags! {
|
||||
/// Describes how a symbol is being referenced in the AST.
|
||||
///
|
||||
@@ -226,7 +223,7 @@ impl<'alloc> CloneIn<'alloc> for ReferenceFlags {
|
||||
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
_with_semantic_ids: bool,
|
||||
_with_semantic_ids: CloneInSemanticIds,
|
||||
_: &'alloc oxc_allocator::Allocator,
|
||||
) -> Self::Cloned {
|
||||
*self
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
use bitflags::bitflags;
|
||||
use oxc_allocator::{Allocator, CloneIn};
|
||||
use oxc_allocator::{Allocator, CloneIn, CloneInSemanticIds};
|
||||
use oxc_index::define_nonmax_u32_index_type;
|
||||
|
||||
use oxc_ast_macros::ast;
|
||||
|
||||
use crate::semantic_id::SemanticId;
|
||||
|
||||
define_nonmax_u32_index_type! {
|
||||
#[ast]
|
||||
#[builder(default)]
|
||||
#[clone_in(semantic_id)]
|
||||
#[content_eq(skip)]
|
||||
#[estree(skip)]
|
||||
pub struct ScopeId;
|
||||
@@ -16,18 +19,14 @@ impl<'alloc> CloneIn<'alloc> for ScopeId {
|
||||
type Cloned = Self;
|
||||
|
||||
#[expect(clippy::inline_always)]
|
||||
#[inline(always)]
|
||||
fn clone_in_impl(&self, with_semantic_ids: bool, _: &'alloc Allocator) -> Self {
|
||||
if with_semantic_ids {
|
||||
*self
|
||||
} else {
|
||||
// `with_semantic_ids == false` should never reach this, because `CloneIn` skips
|
||||
// `scope_id` field, filling it with its default instead.
|
||||
unreachable!();
|
||||
}
|
||||
#[inline(always)] // Because this method only delegates
|
||||
fn clone_in_impl(&self, with_semantic_ids: CloneInSemanticIds, _: &'alloc Allocator) -> Self {
|
||||
self.clone_id(with_semantic_ids)
|
||||
}
|
||||
}
|
||||
|
||||
impl SemanticId for ScopeId {}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ScopeFlags: u16 {
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
//! [`SemanticId`] trait for branchless cloning of semantic ID types.
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
use nonmax::NonMaxU32;
|
||||
use oxc_allocator::CloneInSemanticIds;
|
||||
use oxc_index::Idx;
|
||||
|
||||
// The branchless bit-tricks in [`SemanticId`]'s methods rely on the exact discriminant values of
|
||||
// [`CloneInSemanticIds`]. This crate is separate from the one that defines [`CloneInSemanticIds`],
|
||||
// so assert the discriminants here rather than relying on them by proximity.
|
||||
const _: () = {
|
||||
assert!(CloneInSemanticIds::With as u32 == 0);
|
||||
assert!(CloneInSemanticIds::Without as u32 == u32::MAX);
|
||||
};
|
||||
|
||||
/// A semantic ID type - a [`NonMaxU32`]-backed index (`NodeId`, `ScopeId`, `SymbolId`, `ReferenceId`),
|
||||
/// created by [`define_nonmax_u32_index_type!`].
|
||||
///
|
||||
/// Provides methods to clone a semantic ID, keeping it when cloning with semantic IDs
|
||||
/// and resetting it when cloning without - branchless in both cases:
|
||||
///
|
||||
/// * [`clone_id`] - `CloneIn::clone_in_impl` for ID types delegates to it.
|
||||
/// * [`clone_cell_option_id`] - the `CloneIn` derive calls it to clone `Cell<Option<Id>>` fields
|
||||
/// (`ScopeId`, `SymbolId`, `ReferenceId`).
|
||||
///
|
||||
/// The [`Idx`] bound provides [`index`] and [`from_usize`], the two operations these methods need.
|
||||
///
|
||||
/// [`define_nonmax_u32_index_type!`]: oxc_index::define_nonmax_u32_index_type
|
||||
/// [`clone_id`]: SemanticId::clone_id
|
||||
/// [`clone_cell_option_id`]: SemanticId::clone_cell_option_id
|
||||
/// [`index`]: Idx::index
|
||||
/// [`from_usize`]: Idx::from_usize
|
||||
pub trait SemanticId: Idx {
|
||||
/// Clone a semantic ID.
|
||||
///
|
||||
/// Behavior depends on value of `with_semantic_ids`:
|
||||
///
|
||||
/// * [`CloneInSemanticIds::With`]: Copy the current value.
|
||||
/// * [`CloneInSemanticIds::Without`]: Reset to the dummy ID `Id(0)`.
|
||||
///
|
||||
/// Branchless - a single OR instruction.
|
||||
///
|
||||
/// # How this works
|
||||
///
|
||||
/// `index & mask`, where `mask` is all-ones for [`With`] (discriminant `0`)
|
||||
/// and all-zeros for [`Without`] (discriminant `u32::MAX`), then rebuilt.
|
||||
/// `from_usize`'s bounds check is elided: `index & mask <= index <= MAX`.
|
||||
///
|
||||
/// <https://godbolt.org/z/1KWdc5hnb>
|
||||
///
|
||||
/// [`With`]: CloneInSemanticIds::With
|
||||
/// [`Without`]: CloneInSemanticIds::Without
|
||||
#[must_use]
|
||||
#[expect(clippy::inline_always)]
|
||||
#[inline(always)] // Because this method is only a single instruction
|
||||
fn clone_id(&self, with_semantic_ids: CloneInSemanticIds) -> Self {
|
||||
let mask = !(with_semantic_ids as u32);
|
||||
// `index()` of a `NonMaxU32`-backed type always fits in `u32`, so `as u32` can't truncate
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
let index = self.index() as u32;
|
||||
let masked = index & mask;
|
||||
Self::from_usize(masked as usize)
|
||||
}
|
||||
|
||||
/// Clone a `Cell<Option<Id>>` semantic ID field.
|
||||
///
|
||||
/// Behavior depends on value of `with_semantic_ids`:
|
||||
///
|
||||
/// * [`CloneInSemanticIds::With`]: Copy the current value.
|
||||
/// * [`CloneInSemanticIds::Without`]: Reset to `None`.
|
||||
///
|
||||
/// Branchless - a load plus an AND-NOT operation
|
||||
/// (a single `bic` instruction on aarch64, `not` + `and` on x86_64).
|
||||
///
|
||||
/// <https://godbolt.org/z/1KWdc5hnb>
|
||||
///
|
||||
/// # How this works
|
||||
///
|
||||
/// The in-memory representation of `Option<Id>` (equivalently `Option<NonMaxU32>`) is:
|
||||
///
|
||||
/// * `None` = `0` (niche optimization - `NonMaxU32` wraps a `NonZeroU32` whose niche is 0).
|
||||
/// * `Some(v)` = `!v` (`NonMaxU32` stores its value bitwise-inverted, and is never 0).
|
||||
///
|
||||
/// On that representation, "keep, or replace with `None`" is `bits & mask`,
|
||||
/// where `mask` is all-ones for [`With`] and all-zeros for [`Without`].
|
||||
///
|
||||
/// The code below computes exactly that, but via safe APIs.
|
||||
/// `bits` rebuilds the stored form as a plain logical value (`!id.index()` for `Some`, `0` for `None`),
|
||||
/// and `NonMaxU32::new(!x)` inverts the encoding (`new` returns `None` exactly when `x == 0`).
|
||||
/// Correctness is plain logic and doesn't depend on the representation - only the codegen does.
|
||||
/// The compiler folds it all to the same instructions as an unsafe `transmute`-based version.
|
||||
///
|
||||
/// [`With`]: CloneInSemanticIds::With
|
||||
/// [`Without`]: CloneInSemanticIds::Without
|
||||
#[expect(clippy::inline_always)]
|
||||
#[inline(always)] // Because this method is only a few instructions
|
||||
fn clone_cell_option_id(
|
||||
cell: &Cell<Option<Self>>,
|
||||
with_semantic_ids: CloneInSemanticIds,
|
||||
) -> Cell<Option<Self>> {
|
||||
let mask = !(with_semantic_ids as u32);
|
||||
// `index()` of a `NonMaxU32`-backed type always fits in `u32`, so `as u32` can't truncate
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
let bits = match cell.get() {
|
||||
Some(id) => !(id.index() as u32),
|
||||
None => 0,
|
||||
};
|
||||
let masked = bits & mask;
|
||||
let raw = NonMaxU32::new(!masked);
|
||||
Cell::new(raw.map(|raw| Self::from_usize(raw.get() as usize)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::cell::Cell;
|
||||
|
||||
use oxc_allocator::CloneInSemanticIds;
|
||||
|
||||
use crate::node::NodeId;
|
||||
|
||||
use super::SemanticId;
|
||||
|
||||
// `NonMaxU32` cannot represent `u32::MAX`, so `u32::MAX - 1` is the largest ID we can test
|
||||
const INPUTS: [u32; 11] = [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
42,
|
||||
1000,
|
||||
u32::MAX / 2 - 1,
|
||||
u32::MAX / 2,
|
||||
u32::MAX / 2 + 1,
|
||||
u32::MAX - 1000,
|
||||
u32::MAX - 2,
|
||||
u32::MAX - 1,
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn clone_id() {
|
||||
for n in INPUTS {
|
||||
let id = NodeId::from_usize(n as usize);
|
||||
// `With` copies the current value
|
||||
assert_eq!(id.clone_id(CloneInSemanticIds::With), id);
|
||||
// `Without` resets to the dummy ID `NodeId(0)`
|
||||
assert_eq!(id.clone_id(CloneInSemanticIds::Without), NodeId::from_usize(0));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clone_cell_option_id() {
|
||||
// `None` is returned unchanged by both variants
|
||||
let cell = Cell::new(None);
|
||||
assert_eq!(NodeId::clone_cell_option_id(&cell, CloneInSemanticIds::With).get(), None);
|
||||
assert_eq!(NodeId::clone_cell_option_id(&cell, CloneInSemanticIds::Without).get(), None);
|
||||
|
||||
for n in INPUTS {
|
||||
let id = NodeId::from_usize(n as usize);
|
||||
let cell = Cell::new(Some(id));
|
||||
// `With` copies the current value
|
||||
assert_eq!(
|
||||
NodeId::clone_cell_option_id(&cell, CloneInSemanticIds::With).get(),
|
||||
Some(id)
|
||||
);
|
||||
// `Without` resets to `None`
|
||||
assert_eq!(
|
||||
NodeId::clone_cell_option_id(&cell, CloneInSemanticIds::Without).get(),
|
||||
None
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,17 @@
|
||||
use bitflags::bitflags;
|
||||
use oxc_allocator::{Allocator, CloneIn};
|
||||
use oxc_allocator::{Allocator, CloneIn, CloneInSemanticIds};
|
||||
use oxc_index::define_nonmax_u32_index_type;
|
||||
#[cfg(feature = "serialize")]
|
||||
use serde::Serialize;
|
||||
|
||||
use oxc_ast_macros::ast;
|
||||
|
||||
use crate::semantic_id::SemanticId;
|
||||
|
||||
define_nonmax_u32_index_type! {
|
||||
#[ast]
|
||||
#[builder(default)]
|
||||
#[clone_in(semantic_id)]
|
||||
#[content_eq(skip)]
|
||||
#[estree(skip)]
|
||||
pub struct SymbolId;
|
||||
@@ -18,18 +21,14 @@ impl<'alloc> CloneIn<'alloc> for SymbolId {
|
||||
type Cloned = Self;
|
||||
|
||||
#[expect(clippy::inline_always)]
|
||||
#[inline(always)]
|
||||
fn clone_in_impl(&self, with_semantic_ids: bool, _: &'alloc Allocator) -> Self {
|
||||
if with_semantic_ids {
|
||||
*self
|
||||
} else {
|
||||
// `with_semantic_ids == false` should never reach this, because `CloneIn` skips
|
||||
// `symbol_id` field, filling it with its default instead.
|
||||
unreachable!();
|
||||
}
|
||||
#[inline(always)] // Because this method only delegates
|
||||
fn clone_in_impl(&self, with_semantic_ids: CloneInSemanticIds, _: &'alloc Allocator) -> Self {
|
||||
self.clone_id(with_semantic_ids)
|
||||
}
|
||||
}
|
||||
|
||||
impl SemanticId for SymbolId {}
|
||||
|
||||
define_nonmax_u32_index_type! {
|
||||
pub struct RedeclarationId;
|
||||
}
|
||||
|
||||
@@ -38,19 +38,23 @@ impl Derive for DeriveCloneIn {
|
||||
&[("clone_in", attr_positions!(StructMaybeDerived | EnumMaybeDerived | StructField))]
|
||||
}
|
||||
|
||||
/// Parse `#[clone_in(default)]` on struct, enum, or struct field.
|
||||
/// Parse `#[clone_in(default)]` on struct, enum, or struct field,
|
||||
/// or `#[clone_in(semantic_id)]` on struct.
|
||||
fn parse_attr(&self, _attr_name: &str, location: AttrLocation, part: AttrPart) -> Result<()> {
|
||||
// No need to check attr name is `clone_in`, because that's the only attribute this derive handles.
|
||||
if !matches!(part, AttrPart::Tag("default")) {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
match location {
|
||||
AttrLocation::Struct(struct_def) => struct_def.clone_in.is_default = true,
|
||||
AttrLocation::Enum(enum_def) => enum_def.clone_in.is_default = true,
|
||||
AttrLocation::StructField(struct_def, field_index) => {
|
||||
struct_def.fields[field_index].clone_in.is_default = true;
|
||||
}
|
||||
match part {
|
||||
AttrPart::Tag("default") => match location {
|
||||
AttrLocation::Struct(struct_def) => struct_def.clone_in.is_default = true,
|
||||
AttrLocation::Enum(enum_def) => enum_def.clone_in.is_default = true,
|
||||
AttrLocation::StructField(struct_def, field_index) => {
|
||||
struct_def.fields[field_index].clone_in.is_default = true;
|
||||
}
|
||||
_ => return Err(()),
|
||||
},
|
||||
AttrPart::Tag("semantic_id") => match location {
|
||||
AttrLocation::Struct(struct_def) => struct_def.clone_in.is_semantic_id = true,
|
||||
_ => return Err(()),
|
||||
},
|
||||
_ => return Err(()),
|
||||
}
|
||||
|
||||
@@ -65,7 +69,7 @@ impl Derive for DeriveCloneIn {
|
||||
use std::cell::Cell;
|
||||
|
||||
///@@line_break
|
||||
use oxc_allocator::{Allocator, CloneIn};
|
||||
use oxc_allocator::{Allocator, CloneIn, CloneInSemanticIds};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,10 +92,19 @@ fn derive_struct(struct_def: &StructDef, schema: &Schema) -> TokenStream {
|
||||
let fields = struct_def.fields.iter().map(|field| {
|
||||
let field_ident = field.ident();
|
||||
if struct_field_is_default(field, schema) {
|
||||
// Fields (or fields whose type is) marked `#[clone_in(default)]` always reset to
|
||||
// their default. Semantic id `Cell`s are *not* marked — they thread the
|
||||
// `with_semantic_ids` flag through their `CloneIn` impl instead (see `oxc_allocator`).
|
||||
// Fields (or fields whose type is) marked `#[clone_in(default)]` always reset to their default
|
||||
quote!( #field_ident: Default::default() )
|
||||
} else if struct_field_is_semantic_id_cell_option(field, schema) {
|
||||
// `Cell<Option<Id>>` semantic ID fields (`scope_id`, `symbol_id`, `reference_id`)
|
||||
// clone via `SemanticId::clone_cell_option_id`, which keeps the ID or resets it to `None`,
|
||||
// depending on `with_semantic_ids`.
|
||||
//
|
||||
// The generic `Option<T>` / `Cell<T>` `CloneIn` impls can't produce that reset -
|
||||
// `Option<T>`'s impl maps over `Some`, which would reset `Some(id)` to `Some(dummy)`, not `None`.
|
||||
//
|
||||
// `SemanticId` lives in `oxc_syntax`, so it's named by its full path here rather than
|
||||
// imported in the prelude (which is shared with crates that don't depend on `oxc_syntax`).
|
||||
quote!( #field_ident: oxc_syntax::semantic_id::SemanticId::clone_cell_option_id(&self.#field_ident, with_semantic_ids) )
|
||||
} else {
|
||||
quote!( #field_ident: CloneIn::clone_in_impl(&self.#field_ident, with_semantic_ids, allocator) )
|
||||
}
|
||||
@@ -120,6 +133,17 @@ fn struct_field_is_default(field: &FieldDef, schema: &Schema) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get if a struct field is a `Cell<Option<Id>>`, where `Id` is a semantic ID type
|
||||
/// (marked `#[clone_in(semantic_id)]`).
|
||||
///
|
||||
/// Such fields are cloned with `SemanticId::clone_cell_option_id`.
|
||||
fn struct_field_is_semantic_id_cell_option(field: &FieldDef, schema: &Schema) -> bool {
|
||||
let TypeDef::Cell(cell_def) = field.type_def(schema) else { return false };
|
||||
let TypeDef::Option(option_def) = cell_def.inner_type(schema) else { return false };
|
||||
let TypeDef::Struct(struct_def) = option_def.inner_type(schema) else { return false };
|
||||
struct_def.clone_in.is_semantic_id
|
||||
}
|
||||
|
||||
fn derive_enum(enum_def: &EnumDef, schema: &Schema) -> TokenStream {
|
||||
let type_ident = enum_def.ident();
|
||||
|
||||
@@ -209,7 +233,7 @@ fn generate_impl(
|
||||
#inline
|
||||
fn clone_in_impl(
|
||||
&self,
|
||||
#flag_param: bool,
|
||||
#flag_param: CloneInSemanticIds,
|
||||
allocator: &'new_alloc Allocator,
|
||||
) -> Self::Cloned {
|
||||
#clone_in_impl_body
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
pub struct CloneInType {
|
||||
/// `true` if should be replaced with default value when cloning
|
||||
pub is_default: bool,
|
||||
/// `true` if type is a semantic ID (`NodeId`, `ScopeId`, `SymbolId`, `ReferenceId`).
|
||||
/// Fields of these types are cloned using `SemanticId` trait.
|
||||
pub is_semantic_id: bool,
|
||||
}
|
||||
|
||||
/// Details for `CloneIn` derive on a struct field.
|
||||
|
||||
Reference in new issue
Block a user