From 6a9f4db60988cde8e3b0f5f357c4b6a8a5bf3a19 Mon Sep 17 00:00:00 2001 From: overlookmotel <557937+overlookmotel@users.noreply.github.com> Date: Fri, 26 Jul 2024 00:14:35 +0000 Subject: [PATCH] perf(semantic): reduce storage size for symbol redeclarations (#4463) Most symbols don't have redeclarations. So instead of storing `Vec` directly in `redeclare_variables` (24 bytes per symbol), store `Option` (4 bytes). `RedeclarationId` indexes into `redeclarations` where the actual `Vec` is stored. But for symbols with no redeclarations (the vast majority), it takes 4 bytes per symbol only. --- .../src/rules/eslint/no_redeclare.rs | 4 +-- .../eslint/no_shadow_restricted_names.rs | 2 +- crates/oxc_semantic/src/builder.rs | 2 +- crates/oxc_semantic/src/symbol.rs | 32 ++++++++++++++----- crates/oxc_syntax/src/symbol.rs | 18 +++++++++++ 5 files changed, 46 insertions(+), 12 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/no_redeclare.rs b/crates/oxc_linter/src/rules/eslint/no_redeclare.rs index 3b2c426ba692a..d9c0040e1a027 100644 --- a/crates/oxc_linter/src/rules/eslint/no_redeclare.rs +++ b/crates/oxc_linter/src/rules/eslint/no_redeclare.rs @@ -66,7 +66,7 @@ impl Rule for NoRedeclare { AstKind::VariableDeclarator(var) => { if let BindingPatternKind::BindingIdentifier(ident) = &var.id.kind { if symbol_name == ident.name.as_str() { - for span in ctx.symbols().get_redeclare_variables(symbol_id) { + for span in ctx.symbols().get_redeclarations(symbol_id) { self.report_diagnostic(ctx, *span, ident); } } @@ -75,7 +75,7 @@ impl Rule for NoRedeclare { AstKind::FormalParameter(param) => { if let BindingPatternKind::BindingIdentifier(ident) = ¶m.pattern.kind { if symbol_name == ident.name.as_str() { - for span in ctx.symbols().get_redeclare_variables(symbol_id) { + for span in ctx.symbols().get_redeclarations(symbol_id) { self.report_diagnostic(ctx, *span, ident); } } diff --git a/crates/oxc_linter/src/rules/eslint/no_shadow_restricted_names.rs b/crates/oxc_linter/src/rules/eslint/no_shadow_restricted_names.rs index 33edced1781f8..e00c19700f847 100644 --- a/crates/oxc_linter/src/rules/eslint/no_shadow_restricted_names.rs +++ b/crates/oxc_linter/src/rules/eslint/no_shadow_restricted_names.rs @@ -64,7 +64,7 @@ impl Rule for NoShadowRestrictedNames { } check_and_diagnostic(name, ctx.symbols().get_span(symbol_id), ctx); - for span in ctx.symbols().get_redeclare_variables(symbol_id) { + for span in ctx.symbols().get_redeclarations(symbol_id) { check_and_diagnostic(name, *span, ctx); } }); diff --git a/crates/oxc_semantic/src/builder.rs b/crates/oxc_semantic/src/builder.rs index 46ce59d4a0fb3..7e6bb8e66fc91 100644 --- a/crates/oxc_semantic/src/builder.rs +++ b/crates/oxc_semantic/src/builder.rs @@ -465,7 +465,7 @@ impl<'a> SemanticBuilder<'a> { } pub fn add_redeclare_variable(&mut self, symbol_id: SymbolId, span: Span) { - self.symbols.add_redeclare_variable(symbol_id, span); + self.symbols.add_redeclaration(symbol_id, span); } fn add_export_flag_to_export_identifiers(&mut self, program: &Program<'a>) { diff --git a/crates/oxc_semantic/src/symbol.rs b/crates/oxc_semantic/src/symbol.rs index 0f547d2253632..1d43ae6551882 100644 --- a/crates/oxc_semantic/src/symbol.rs +++ b/crates/oxc_semantic/src/symbol.rs @@ -5,7 +5,7 @@ use oxc_index::IndexVec; use oxc_span::{CompactStr, Span}; pub use oxc_syntax::{ scope::ScopeId, - symbol::{SymbolFlags, SymbolId}, + symbol::{RedeclarationId, SymbolFlags, SymbolId}, }; #[cfg(feature = "serialize")] use serde::Serialize; @@ -26,6 +26,10 @@ export type IndexVec = Array; /// Symbol Table /// /// `SoA` (Struct of Arrays) for memory efficiency. +/// +/// Most symbols won't have redeclarations, so instead of storing `Vec` directly in +/// `redeclare_variables` (32 bytes per symbol), store `Option` (4 bytes). +/// That ID indexes into `redeclarations` where the actual `Vec` is stored. #[derive(Debug, Default)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify), serde(rename_all = "camelCase"))] pub struct SymbolTable { @@ -36,7 +40,9 @@ pub struct SymbolTable { /// Pointer to the AST Node where this symbol is declared pub declarations: IndexVec, pub resolved_references: IndexVec>, - pub redeclare_variables: IndexVec>, + redeclarations: IndexVec>, + + redeclaration_spans: IndexVec>, pub references: IndexVec, } @@ -90,8 +96,13 @@ impl SymbolTable { self.flags[symbol_id] } - pub fn get_redeclare_variables(&self, symbol_id: SymbolId) -> &Vec { - &self.redeclare_variables[symbol_id] + pub fn get_redeclarations(&self, symbol_id: SymbolId) -> &[Span] { + if let Some(redeclaration_id) = self.redeclarations[symbol_id] { + &self.redeclaration_spans[redeclaration_id] + } else { + static EMPTY: &[Span] = &[]; + EMPTY + } } pub fn union_flag(&mut self, symbol_id: SymbolId, includes: SymbolFlags) { @@ -128,11 +139,16 @@ impl SymbolTable { self.scope_ids.push(scope_id); self.declarations.push(node_id); self.resolved_references.push(vec![]); - self.redeclare_variables.push(vec![]) + self.redeclarations.push(None) } - pub fn add_redeclare_variable(&mut self, symbol_id: SymbolId, span: Span) { - self.redeclare_variables[symbol_id].push(span); + pub fn add_redeclaration(&mut self, symbol_id: SymbolId, span: Span) { + if let Some(redeclaration_id) = self.redeclarations[symbol_id] { + self.redeclaration_spans[redeclaration_id].push(span); + } else { + let redeclaration_id = self.redeclaration_spans.push(vec![span]); + self.redeclarations[symbol_id] = Some(redeclaration_id); + }; } pub fn create_reference(&mut self, reference: Reference) -> ReferenceId { @@ -200,7 +216,7 @@ impl SymbolTable { self.scope_ids.reserve(additional_symbols); self.declarations.reserve(additional_symbols); self.resolved_references.reserve(additional_symbols); - self.redeclare_variables.reserve(additional_symbols); + self.redeclarations.reserve(additional_symbols); self.references.reserve(additional_references); } diff --git a/crates/oxc_syntax/src/symbol.rs b/crates/oxc_syntax/src/symbol.rs index fb52c986425f8..6688e84ec7171 100644 --- a/crates/oxc_syntax/src/symbol.rs +++ b/crates/oxc_syntax/src/symbol.rs @@ -23,11 +23,29 @@ impl Idx for SymbolId { } } +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub struct RedeclarationId(NonZeroU32); + +impl Idx for RedeclarationId { + #[allow(clippy::cast_possible_truncation)] + fn from_usize(idx: usize) -> Self { + // SAFETY: + 1 is always non-zero. + + unsafe { Self(NonZeroU32::new_unchecked(idx as u32 + 1)) } + } + + fn index(self) -> usize { + self.0.get() as usize - 1 + } +} + #[cfg(feature = "serialize")] #[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)] const TS_APPEND_CONTENT: &'static str = r#" export type SymbolId = number; export type SymbolFlags = unknown; +export type RedeclarationId = unknown; "#; bitflags! {