diff --git a/crates/oxc_semantic/src/binder.rs b/crates/oxc_semantic/src/binder.rs index 2e11b513b93c8..aefcd82195020 100644 --- a/crates/oxc_semantic/src/binder.rs +++ b/crates/oxc_semantic/src/binder.rs @@ -76,7 +76,7 @@ impl<'a> Binder<'a> for VariableDeclarator<'a> { // avoid same symbols appear in multi-scopes builder.scoping.remove_binding(scope_id, name); builder.scoping.add_binding(target_scope_id, name, symbol_id); - builder.scoping.symbol_scope_ids[symbol_id] = target_scope_id; + builder.scoping.set_symbol_scope_id(symbol_id, target_scope_id); } break; } diff --git a/crates/oxc_semantic/src/lib.rs b/crates/oxc_semantic/src/lib.rs index 418c6471250c0..e65eb6fd8add8 100644 --- a/crates/oxc_semantic/src/lib.rs +++ b/crates/oxc_semantic/src/lib.rs @@ -36,6 +36,7 @@ mod is_global_reference; #[cfg(feature = "linter")] mod jsdoc; mod label; +mod multi_index_vec; mod node; mod scoping; mod stats; diff --git a/crates/oxc_semantic/src/multi_index_vec.rs b/crates/oxc_semantic/src/multi_index_vec.rs new file mode 100644 index 0000000000000..67f676765000e --- /dev/null +++ b/crates/oxc_semantic/src/multi_index_vec.rs @@ -0,0 +1,444 @@ +/// A macro that generates a struct-of-arrays (SoA) container backed by a single allocation. +/// +/// Instead of N separate `IndexVec`s each with their own `ptr + len + capacity`, +/// this generates a struct with: +/// - A single allocation containing all field arrays contiguously +/// - A single `len` and `cap` (stored as `u32`) +/// - Per-field typed accessors with a single bounds check +/// - A single capacity check on `push` +/// +/// This is modeled on Zig's `MultiArrayList`. +/// +/// # Usage +/// +/// ```ignore +/// multi_index_vec! { +/// pub struct ScopeTable { +/// pub parent_ids => parent_ids_mut: Option, +/// pub node_ids => node_ids_mut: NodeId, +/// pub flags => flags_mut: ScopeFlags, +/// } +/// } +/// ``` +/// +/// The `=> name_mut` part specifies the mutable accessor name (needed because +/// declarative macros cannot concatenate identifiers). +macro_rules! multi_index_vec { + ( + $(#[$meta:meta])* + $vis:vis struct $name:ident<$idx:ty> { + $( + $fvis:vis $fname:ident => $fname_mut:ident : $fty:ty + ),* $(,)? + } + ) => { + $(#[$meta])* + $vis struct $name { + /// Pointer to the base of the single allocation. + /// Layout: `[Field1 × cap][Field2 × cap]...` with alignment padding between fields. + base: ::core::ptr::NonNull, + /// Cached pointers to the start of each field's array within the allocation. + $( $fname: ::core::ptr::NonNull<$fty>, )* + /// Number of elements currently stored. + len: u32, + /// Total capacity of each field array. + cap: u32, + } + + // SAFETY: All data is fully owned by this struct. The raw pointers do not + // alias with any other pointers. This is equivalent to Vec's Send/Sync impls + // which are safe because Vec owns its data. + unsafe impl Send for $name + where + $( $fty: Send, )* + {} + // SAFETY: Immutable access to the data through `&self` does not allow mutation. + // Mutable access requires `&mut self` which guarantees exclusive access. + unsafe impl Sync for $name + where + $( $fty: Sync, )* + {} + + #[expect(clippy::allow_attributes, reason = "macro-generated methods may not all be used")] + impl $name { + /// Maximum number of elements this table can hold while still being + /// representable by the index type. + #[inline(always)] + fn max_len() -> usize { + <$idx as ::oxc_index::Idx>::MAX.saturating_add(1) + } + + /// Compute the memory layout for a given capacity. + /// Returns `None` if `cap == 0`. + fn compute_layout(cap: usize) -> Option<::core::alloc::Layout> { + if cap == 0 { + return None; + } + let mut size = 0usize; + let mut max_align = 1usize; + $( + let align = ::core::mem::align_of::<$fty>(); + if align > max_align { max_align = align; } + size = (size + align - 1) & !(align - 1); + size += ::core::mem::size_of::<$fty>().checked_mul(cap).expect("capacity overflow"); + )* + Some(::core::alloc::Layout::from_size_align(size, max_align).expect("invalid layout")) + } + + /// Set cached field pointers based on the base allocation pointer and capacity. + /// + /// # Safety + /// `base` must point to a valid allocation of the layout returned by + /// `compute_layout(cap)`. The allocation must be at least as aligned as + /// the maximum field alignment. + unsafe fn set_pointers(&mut self, base: *mut u8, cap: usize) { + let mut _offset = 0usize; + $( + let align = ::core::mem::align_of::<$fty>(); + _offset = (_offset + align - 1) & !(align - 1); + #[expect(clippy::cast_ptr_alignment)] + { + // SAFETY: `_offset` is within the allocation, and properly aligned + // for `$fty` because `compute_layout` aligns each field's offset, + // and the base pointer has at least `max_align` alignment. + self.$fname = unsafe { + ::core::ptr::NonNull::new_unchecked( + base.add(_offset).cast::<$fty>() + ) + }; + } + _offset += ::core::mem::size_of::<$fty>() * cap; + )* + } + + /// Create a new empty container. + #[inline] + $vis fn new() -> Self { + Self { + base: ::core::ptr::NonNull::dangling(), + $( $fname: ::core::ptr::NonNull::dangling(), )* + len: 0, + cap: 0, + } + } + + /// Returns the number of elements. + #[inline] + $vis fn len(&self) -> usize { + self.len as usize + } + + /// Returns `true` if there are no elements. + #[inline] + $vis fn is_empty(&self) -> bool { + self.len == 0 + } + + /// Reserve capacity for at least `additional` more elements. + $vis fn reserve(&mut self, additional: usize) { + let required = (self.len as usize).checked_add(additional).expect("capacity overflow"); + assert!( + required <= Self::max_len(), + "capacity exceeds index type range" + ); + if required <= self.cap as usize { + return; + } + self.grow_to(required); + } + + /// Grow the allocation to hold at least `min_cap` elements. + fn grow_to(&mut self, min_cap: usize) { + let max_len = Self::max_len(); + assert!(min_cap <= max_len, "capacity exceeds index type range"); + let new_cap = if self.cap == 0 { + min_cap.max(4) + } else { + (self.cap as usize).checked_mul(2).expect("capacity overflow").max(min_cap) + } + .min(max_len); + + let old_cap = self.cap as usize; + let old_len = self.len as usize; + let new_layout = Self::compute_layout(new_cap) + .expect("new capacity must be > 0"); + + // SAFETY: All pointer arithmetic is within bounds of the allocations. + // Old data is copied field-by-field from the old allocation to the new one. + // The old allocation is freed after the copy. + unsafe { + let new_base = ::std::alloc::alloc(new_layout); + if new_base.is_null() { + ::std::alloc::handle_alloc_error(new_layout); + } + + // Copy old data to new allocation. + if old_cap > 0 { + let old_base = self.base.as_ptr(); + let mut _old_offset = 0usize; + let mut _new_offset = 0usize; + $( + let align = ::core::mem::align_of::<$fty>(); + let elem_size = ::core::mem::size_of::<$fty>(); + _old_offset = (_old_offset + align - 1) & !(align - 1); + _new_offset = (_new_offset + align - 1) & !(align - 1); + ::core::ptr::copy_nonoverlapping( + old_base.add(_old_offset), + new_base.add(_new_offset), + elem_size * old_len, + ); + _old_offset += elem_size * old_cap; + _new_offset += elem_size * new_cap; + )* + + let old_layout = Self::compute_layout(old_cap).unwrap(); + ::std::alloc::dealloc(old_base, old_layout); + } + + self.base = ::core::ptr::NonNull::new_unchecked(new_base); + self.set_pointers(new_base, new_cap); + self.cap = u32::try_from(new_cap).expect("capacity exceeds u32"); + } + } + + /// Push a new element to all field arrays. Returns the index of the new element. + $vis fn push(&mut self, $( $fname: $fty ),*) -> $idx { + if self.len == self.cap { + self.grow_to(self.len as usize + 1); + } + let idx = self.len as usize; + // SAFETY: `idx < cap` is guaranteed because we just grew if needed. + // The field pointers are valid and properly aligned for their types. + unsafe { + $( + self.$fname.as_ptr().add(idx).write($fname); + )* + } + self.len += 1; + // SAFETY: `idx < len <= cap <= max_len = Idx::MAX + 1`, so `idx <= Idx::MAX`. + unsafe { <$idx as ::oxc_index::Idx>::from_usize_unchecked(idx) } + } + + /// Iterate over all valid indices. + $vis fn iter_ids(&self) -> impl Iterator { + let len = self.len as usize; + // SAFETY: Valid indices are `0..len`, and by invariant `len <= Idx::MAX + 1`. + (0..len).map(|i| unsafe { <$idx as ::oxc_index::Idx>::from_usize_unchecked(i) }) + } + + #[inline(always)] + fn checked_idx(&self, id: $idx) -> usize { + let idx = ::oxc_index::Idx::index(id); + assert!(idx < self.len as usize, "index out of bounds"); + idx + } + + // Per-field accessors. + $( + #[inline] + $fvis fn $fname(&self, id: $idx) -> &$fty { + let idx = self.checked_idx(id); + // SAFETY: `idx` is validated by `checked_idx`. + // The pointer is aligned and valid for reads. + unsafe { &*self.$fname.as_ptr().add(idx) } + } + + #[inline] + #[allow(dead_code)] + $fvis fn $fname_mut(&mut self, id: $idx) -> &mut $fty { + let idx = self.checked_idx(id); + // SAFETY: `idx` is validated by `checked_idx`. + // The pointer is aligned and valid. `&mut self` guarantees no aliasing. + unsafe { &mut *self.$fname.as_ptr().add(idx) } + } + )* + } + + impl Clone for $name + where + $( $fty: Clone, )* + { + fn clone(&self) -> Self { + let len = self.len as usize; + if len == 0 { + return Self::new(); + } + // Clone with exact capacity (no excess). + let layout = Self::compute_layout(len) + .expect("len > 0 so layout must exist"); + // SAFETY: We allocate a fresh buffer and copy `len` elements per field. + // All source pointers are valid for `len` reads, and destination pointers + // are valid for `len` writes. + unsafe { + let new_base = ::std::alloc::alloc(layout); + if new_base.is_null() { + ::std::alloc::handle_alloc_error(layout); + } + let mut result = Self { + base: ::core::ptr::NonNull::new_unchecked(new_base), + $( $fname: ::core::ptr::NonNull::dangling(), )* + len: self.len, + cap: self.len, // exact capacity + }; + result.set_pointers(new_base, len); + $( + if ::core::mem::needs_drop::<$fty>() { + for i in 0..len { + result.$fname.as_ptr().add(i).write( + (*self.$fname.as_ptr().add(i)).clone(), + ); + } + } else { + ::core::ptr::copy_nonoverlapping( + self.$fname.as_ptr(), + result.$fname.as_ptr(), + len, + ); + } + )* + result + } + } + } + + impl Drop for $name { + fn drop(&mut self) { + if self.cap == 0 { + return; + } + let len = self.len as usize; + $( + if ::core::mem::needs_drop::<$fty>() { + for i in 0..len { + // SAFETY: `i < len <= cap`, so the pointer is within the allocation. + // Each element is only dropped once. + unsafe { + ::core::ptr::drop_in_place(self.$fname.as_ptr().add(i)); + } + } + } + )* + let layout = Self::compute_layout(self.cap as usize).unwrap(); + // SAFETY: `self.base` was allocated with this layout. + unsafe { + ::std::alloc::dealloc(self.base.as_ptr(), layout); + } + } + } + + impl Default for $name { + fn default() -> Self { + Self::new() + } + } + + impl ::core::fmt::Debug for $name { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_struct(stringify!($name)) + .field("len", &self.len) + .field("cap", &self.cap) + .finish() + } + } + }; +} + +pub(crate) use multi_index_vec; + +#[cfg(test)] +mod tests { + oxc_index::define_index_type! { + struct TestId = usize; + } + + multi_index_vec! { + struct TestTable { + values => values_mut: u32, + } + } + + oxc_index::define_index_type! { + struct SmallId = u8; + MAX_INDEX = 3; + } + + multi_index_vec! { + struct SmallTable { + values => values_mut: u32, + } + } + + oxc_index::define_index_type! { + struct StringId = usize; + } + + multi_index_vec! { + struct StringTable { + values => values_mut: String, + } + } + + #[test] + fn iter_ids_are_valid() { + let mut table = TestTable::new(); + assert!(table.is_empty()); + table.reserve(3); + table.push(1); + table.push(2); + table.push(3); + assert_eq!(table.len(), 3); + + let ids = table.iter_ids().collect::>(); + assert_eq!(ids.len(), 3); + assert_eq!(usize::from(ids[0]), 0); + assert_eq!(usize::from(ids[1]), 1); + assert_eq!(usize::from(ids[2]), 2); + } + + #[test] + #[should_panic(expected = "index out of bounds")] + fn accessor_panics_on_invalid_index() { + let mut table = TestTable::new(); + table.push(1); + let _ = table.values(TestId::from(1usize)); + } + + #[test] + #[should_panic(expected = "capacity exceeds index type range")] + fn reserve_panics_when_exceeding_index_range() { + let mut table = SmallTable::new(); + assert!(table.is_empty()); + assert_eq!(table.len(), 0); + let _ = table.iter_ids().count(); + table.reserve(5); + } + + #[test] + #[should_panic(expected = "capacity exceeds index type range")] + fn push_panics_when_exceeding_index_range() { + let mut table = SmallTable::new(); + let id = table.push(1); + assert_eq!(*table.values(id), 1); + table.push(2); + table.push(3); + table.push(4); + table.push(5); + } + + #[test] + fn clone_clones_non_copy_fields() { + let mut table = StringTable::new(); + assert!(table.is_empty()); + table.reserve(1); + let id = table.push(String::from("a")); + assert_eq!(table.len(), 1); + let _ = table.iter_ids().count(); + let mut clone = table.clone(); + + table.values_mut(id).push('1'); + clone.values_mut(id).push('2'); + + assert_eq!(table.values(id), "a1"); + assert_eq!(clone.values(id), "a2"); + } +} diff --git a/crates/oxc_semantic/src/scoping.rs b/crates/oxc_semantic/src/scoping.rs index 25ad19a4067e9..ef3115e3674f5 100644 --- a/crates/oxc_semantic/src/scoping.rs +++ b/crates/oxc_semantic/src/scoping.rs @@ -13,6 +13,8 @@ use oxc_syntax::{ symbol::{SymbolFlags, SymbolId}, }; +use crate::multi_index_vec::multi_index_vec; + pub type Bindings<'a> = ArenaIdentHashMap<'a, SymbolId>; pub type UnresolvedReferences<'a> = ArenaIdentHashMap<'a, ArenaVec<'a, ReferenceId>>; @@ -37,6 +39,34 @@ impl CloneIn<'_> for Redeclaration { } } +multi_index_vec! { + /// Scope tree stored as struct-of-arrays in a single allocation. + /// + /// Contains parent IDs, node IDs, and flags for all scopes. Using a single + /// allocation with one `len`/`cap` instead of 3 separate `IndexVec`s saves + /// memory (no redundant len/cap) and CPU (one bounds check, one capacity + /// check on push). + struct ScopeTable { + parent_ids => parent_ids_mut: Option, + node_ids => node_ids_mut: NodeId, + flags => flags_mut: ScopeFlags, + } +} + +multi_index_vec! { + /// Symbol table stored as struct-of-arrays in a single allocation. + /// + /// Contains spans, flags, scope IDs, and declaration node IDs for all symbols. + /// Using a single allocation with one `len`/`cap` instead of 4 separate `IndexVec`s + /// saves memory and CPU. + struct SymbolTable { + symbol_spans => symbol_spans_mut: Span, + symbol_flags => symbol_flags_mut: SymbolFlags, + symbol_scope_ids => symbol_scope_ids_mut: ScopeId, + symbol_declarations => symbol_declarations_mut: NodeId, + } +} + /// # Symbol Table and Scope Tree /// /// ## Symbol Table @@ -56,26 +86,16 @@ impl CloneIn<'_> for Redeclaration { /// - Scopes can have 0 or more child scopes. /// - Nodes that create a scope store the [`ScopeId`] of the scope they create. pub struct Scoping { - /* Symbol Table Fields */ - pub(crate) symbol_spans: IndexVec, - pub(crate) symbol_flags: IndexVec, - pub(crate) symbol_scope_ids: IndexVec, - /// Pointer to the AST Node where this symbol is declared - pub(crate) symbol_declarations: IndexVec, + /* Symbol Table - single allocation for all symbol-indexed flat fields */ + symbol_table: SymbolTable, pub(crate) references: IndexVec, /// Function or Variable Symbol IDs that are marked with `@__NO_SIDE_EFFECTS__`. pub(crate) no_side_effects: FxHashSet, - /* Scope Tree Fields */ - /// Maps a scope to the parent scope it belongs in. - scope_parent_ids: IndexVec>, - - /// Maps a scope to its node id. - scope_node_ids: IndexVec, - - scope_flags: IndexVec, + /* Scope Tree - single allocation for all scope-indexed flat fields */ + scope_table: ScopeTable, pub(crate) cell: ScopingCell, } @@ -89,15 +109,10 @@ impl fmt::Debug for Scoping { impl Default for Scoping { fn default() -> Self { Self { - symbol_spans: IndexVec::new(), - symbol_flags: IndexVec::new(), - symbol_scope_ids: IndexVec::new(), - symbol_declarations: IndexVec::new(), + symbol_table: SymbolTable::new(), references: IndexVec::new(), no_side_effects: FxHashSet::default(), - scope_parent_ids: IndexVec::new(), - scope_node_ids: IndexVec::new(), - scope_flags: IndexVec::new(), + scope_table: ScopeTable::new(), cell: ScopingCell::new(Allocator::default(), |allocator| ScopingInner { symbol_names: ArenaVec::new_in(allocator), resolved_references: ArenaVec::new_in(allocator), @@ -272,13 +287,13 @@ impl Scoping { /// Returns the number of symbols in this table. #[inline] pub fn symbols_len(&self) -> usize { - self.symbol_spans.len() + self.symbol_table.len() } /// Returns `true` if this table contains no symbols. #[inline] pub fn symbols_is_empty(&self) -> bool { - self.symbol_spans.is_empty() + self.symbol_table.is_empty() } pub fn symbol_names(&self) -> impl Iterator + '_ { @@ -296,7 +311,7 @@ impl Scoping { /// /// [`ScopeTree::iter_bindings_in`]: crate::scoping::Scoping::iter_bindings_in pub fn symbol_ids(&self) -> impl Iterator + '_ { - self.symbol_spans.iter_enumerated().map(|(symbol_id, _)| symbol_id) + self.symbol_table.iter_ids() } /// Get the [`Span`] of the [`AstNode`] declaring a symbol. @@ -304,7 +319,7 @@ impl Scoping { /// [`AstNode`]: crate::node::AstNode #[inline] pub fn symbol_span(&self, symbol_id: SymbolId) -> Span { - self.symbol_spans[symbol_id] + *self.symbol_table.symbol_spans(symbol_id) } /// Get the identifier name a symbol is bound to. @@ -334,13 +349,13 @@ impl Scoping { /// To find how a symbol is used, use [`Scoping::get_resolved_references`]. #[inline] pub fn symbol_flags(&self, symbol_id: SymbolId) -> SymbolFlags { - self.symbol_flags[symbol_id] + *self.symbol_table.symbol_flags(symbol_id) } /// Get a mutable reference to a symbol's [flags](SymbolFlags). #[inline] pub fn symbol_flags_mut(&mut self, symbol_id: SymbolId) -> &mut SymbolFlags { - &mut self.symbol_flags[symbol_id] + self.symbol_table.symbol_flags_mut(symbol_id) } #[inline] @@ -356,17 +371,17 @@ impl Scoping { #[inline] pub fn union_symbol_flag(&mut self, symbol_id: SymbolId, includes: SymbolFlags) { - self.symbol_flags[symbol_id] |= includes; + *self.symbol_table.symbol_flags_mut(symbol_id) |= includes; } #[inline] pub fn set_symbol_scope_id(&mut self, symbol_id: SymbolId, scope_id: ScopeId) { - self.symbol_scope_ids[symbol_id] = scope_id; + *self.symbol_table.symbol_scope_ids_mut(symbol_id) = scope_id; } #[inline] pub fn symbol_scope_id(&self, symbol_id: SymbolId) -> ScopeId { - self.symbol_scope_ids[symbol_id] + *self.symbol_table.symbol_scope_ids(symbol_id) } /// Get the ID of the AST node declaring a symbol. @@ -381,7 +396,7 @@ impl Scoping { /// [`BindingPattern`]: oxc_ast::ast::BindingPattern #[inline] pub fn symbol_declaration(&self, symbol_id: SymbolId) -> NodeId { - self.symbol_declarations[symbol_id] + *self.symbol_table.symbol_declarations(symbol_id) } pub fn create_symbol( @@ -396,10 +411,7 @@ impl Scoping { cell.symbol_names.push(name.clone_in(allocator)); cell.resolved_references.push(ArenaVec::new_in(allocator)); }); - self.symbol_spans.push(span); - self.symbol_flags.push(flags); - self.symbol_scope_ids.push(scope_id); - self.symbol_declarations.push(node_id) + self.symbol_table.push(span, flags, scope_id, node_id) } pub fn add_symbol_redeclaration( @@ -495,7 +507,7 @@ impl Scoping { /// If symbol is `const`, always returns `false`. /// Otherwise, returns `true` if the symbol is assigned to somewhere in AST. pub fn symbol_is_mutated(&self, symbol_id: SymbolId) -> bool { - if self.symbol_flags[symbol_id].contains(SymbolFlags::ConstVariable) { + if self.symbol_table.symbol_flags(symbol_id).contains(SymbolFlags::ConstVariable) { false } else { self.get_resolved_references(symbol_id).any(Reference::is_write) @@ -538,22 +550,17 @@ impl Scoping { additional_references: usize, additional_scopes: usize, ) { - self.symbol_spans.reserve(additional_symbols); - self.symbol_flags.reserve(additional_symbols); - self.symbol_scope_ids.reserve(additional_symbols); - self.symbol_declarations.reserve(additional_symbols); + self.symbol_table.reserve(additional_symbols); self.cell.with_dependent_mut(|_allocator, cell| { cell.symbol_names.reserve_exact(additional_symbols); cell.resolved_references.reserve_exact(additional_symbols); }); self.references.reserve(additional_references); - self.scope_parent_ids.reserve(additional_scopes); - self.scope_flags.reserve(additional_scopes); + self.scope_table.reserve(additional_scopes); self.cell.with_dependent_mut(|_allocator, cell| { cell.bindings.reserve(additional_scopes); }); - self.scope_node_ids.reserve(additional_scopes); } pub fn no_side_effects(&self) -> &FxHashSet { @@ -569,7 +576,7 @@ impl Scoping { /// program scope. #[inline] pub fn scopes_len(&self) -> usize { - self.scope_parent_ids.len() + self.scope_table.len() } /// Returns `true` if there are no scopes in the program. @@ -578,7 +585,7 @@ impl Scoping { /// since there is a root scope. #[inline] pub fn scopes_is_empty(&self) -> bool { - self.scope_parent_ids.is_empty() + self.scope_table.is_empty() } /// Iterate over the scopes that contain a scope. @@ -586,11 +593,11 @@ impl Scoping { /// The first element of this iterator will be the scope itself. This /// guarantees the iterator will have at least 1 element. pub fn scope_ancestors(&self, scope_id: ScopeId) -> impl Iterator + '_ { - std::iter::successors(Some(scope_id), |&scope_id| self.scope_parent_ids[scope_id]) + std::iter::successors(Some(scope_id), |&scope_id| *self.scope_table.parent_ids(scope_id)) } pub fn scope_descendants_from_root(&self) -> impl Iterator + '_ { - self.scope_parent_ids.iter_enumerated().map(|(scope_id, _)| scope_id) + self.scope_table.iter_ids() } /// Get the root [`Program`] scope id. @@ -607,7 +614,7 @@ impl Scoping { /// This is a shorthand for `scope.get_flags(scope.root_scope_id())`. #[inline] pub fn root_scope_flags(&self) -> ScopeFlags { - self.scope_flags[self.root_scope_id()] + *self.scope_table.flags(self.root_scope_id()) } #[inline] @@ -657,12 +664,12 @@ impl Scoping { #[inline] pub fn scope_flags(&self, scope_id: ScopeId) -> ScopeFlags { - self.scope_flags[scope_id] + *self.scope_table.flags(scope_id) } #[inline] pub fn scope_flags_mut(&mut self, scope_id: ScopeId) -> &mut ScopeFlags { - &mut self.scope_flags[scope_id] + self.scope_table.flags_mut(scope_id) } /// Get [`ScopeFlags`] for a new child scope under `parent_scope_id`. @@ -673,18 +680,18 @@ impl Scoping { #[inline] pub fn scope_parent_id(&self, scope_id: ScopeId) -> Option { - self.scope_parent_ids[scope_id] + *self.scope_table.parent_ids(scope_id) } pub fn set_scope_parent_id(&mut self, scope_id: ScopeId, parent_id: Option) { - self.scope_parent_ids[scope_id] = parent_id; + *self.scope_table.parent_ids_mut(scope_id) = parent_id; } /// Change the parent scope of a scope. /// /// This will also remove the scope from the child list of the old parent and add it to the new parent. pub fn change_scope_parent_id(&mut self, scope_id: ScopeId, new_parent_id: Option) { - self.scope_parent_ids[scope_id] = new_parent_id; + *self.scope_table.parent_ids_mut(scope_id) = new_parent_id; } /// Get a variable binding by name that was declared in the top-level scope @@ -730,7 +737,7 @@ impl Scoping { if let Some(symbol_id) = cell.bindings[scope_id].get(&name) { return Some(*symbol_id); } - let parent_scope_id = self.scope_parent_ids[scope_id]?; + let parent_scope_id = (*self.scope_table.parent_ids(scope_id))?; scope_id = parent_scope_id; } } @@ -746,7 +753,7 @@ impl Scoping { /// [`AstNode`]: crate::AstNode #[inline] pub fn get_node_id(&self, scope_id: ScopeId) -> NodeId { - self.scope_node_ids[scope_id] + *self.scope_table.node_ids(scope_id) } /// Iterate over all bindings declared in the entire program. @@ -785,13 +792,10 @@ impl Scoping { node_id: NodeId, flags: ScopeFlags, ) -> ScopeId { - let scope_id = self.scope_parent_ids.push(parent_id); - self.scope_flags.push(flags); + let scope_id = self.scope_table.push(parent_id, node_id, flags); self.cell.with_dependent_mut(|allocator, cell| { cell.bindings.push(Bindings::new_in(allocator)); }); - self.scope_node_ids.push(node_id); - scope_id } @@ -872,7 +876,7 @@ impl Scoping { self.cell.with_dependent_mut(|_allocator, cell| { for bindings in &mut cell.bindings { bindings.retain(|_name, symbol_id| { - let flags = self.symbol_flags[*symbol_id]; + let flags = *self.symbol_table.symbol_flags(*symbol_id); !flags.intersects( SymbolFlags::TypeAlias | SymbolFlags::Interface @@ -892,15 +896,10 @@ impl Scoping { let cell = self.cell.borrow_dependent(); Self { - symbol_spans: self.symbol_spans.clone(), - symbol_flags: self.symbol_flags.clone(), - symbol_scope_ids: self.symbol_scope_ids.clone(), - symbol_declarations: self.symbol_declarations.clone(), + symbol_table: self.symbol_table.clone(), references: self.references.clone(), no_side_effects: self.no_side_effects.clone(), - scope_parent_ids: self.scope_parent_ids.clone(), - scope_node_ids: self.scope_node_ids.clone(), - scope_flags: self.scope_flags.clone(), + scope_table: self.scope_table.clone(), cell: { let allocator = Allocator::with_capacity(used_bytes); ScopingCell::new(allocator, |allocator| ScopingInner { diff --git a/tasks/track_memory_allocations/allocs_minifier.snap b/tasks/track_memory_allocations/allocs_minifier.snap index 67a6373909467..6842b14c2207d 100644 --- a/tasks/track_memory_allocations/allocs_minifier.snap +++ b/tasks/track_memory_allocations/allocs_minifier.snap @@ -1,14 +1,14 @@ File | File size || Sys allocs | Sys reallocs || Arena allocs | Arena reallocs | Arena bytes ------------------------------------------------------------------------------------------------------------------------------------------- -checker.ts | 2.92 MB || 3990 | 1675 || 152600 | 28244 +checker.ts | 2.92 MB || 3981 | 1672 || 152600 | 28244 -cal.com.tsx | 1.06 MB || 21108 | 474 || 37138 | 4586 +cal.com.tsx | 1.06 MB || 21099 | 471 || 37138 | 4586 -RadixUIAdoptionSection.jsx | 2.52 kB || 52 | 0 || 30 | 6 +RadixUIAdoptionSection.jsx | 2.52 kB || 42 | 0 || 30 | 6 -pdf.mjs | 567.30 kB || 4680 | 572 || 47462 | 7734 +pdf.mjs | 567.30 kB || 4671 | 569 || 47462 | 7734 -antd.js | 6.69 MB || 10703 | 2517 || 331644 | 69358 +antd.js | 6.69 MB || 10694 | 2514 || 331644 | 69358 -binder.ts | 193.08 kB || 416 | 123 || 7075 | 824 +binder.ts | 193.08 kB || 407 | 120 || 7075 | 824 diff --git a/tasks/track_memory_allocations/allocs_semantic.snap b/tasks/track_memory_allocations/allocs_semantic.snap index 51de9686a2d73..825bccee030e8 100644 --- a/tasks/track_memory_allocations/allocs_semantic.snap +++ b/tasks/track_memory_allocations/allocs_semantic.snap @@ -1,14 +1,14 @@ File | File size || Sys allocs | Sys reallocs || Arena allocs | Arena reallocs | Arena bytes ------------------------------------------------------------------------------------------------------------------------------------------- -checker.ts | 2.92 MB || 2094 | 1017 || 0 | 0 +checker.ts | 2.92 MB || 2089 | 1017 || 0 | 0 -cal.com.tsx | 1.06 MB || 14994 | 208 || 0 | 0 +cal.com.tsx | 1.06 MB || 14989 | 208 || 0 | 0 -RadixUIAdoptionSection.jsx | 2.52 kB || 18 | 0 || 0 | 0 +RadixUIAdoptionSection.jsx | 2.52 kB || 13 | 0 || 0 | 0 -pdf.mjs | 567.30 kB || 1752 | 286 || 0 | 0 +pdf.mjs | 567.30 kB || 1747 | 286 || 0 | 0 -antd.js | 6.69 MB || 4129 | 503 || 0 | 0 +antd.js | 6.69 MB || 4124 | 503 || 0 | 0 -binder.ts | 193.08 kB || 211 | 67 || 0 | 0 +binder.ts | 193.08 kB || 206 | 67 || 0 | 0