perf(semantic): store unresolved refs in a stack (#4162)

This tweaks `SemanticBuilder` logic in order to accumulate unresolved
references in a stack, getting rid of the previous index-vector which
is not required under the current access pattern.

Ref: https://github.com/oxc-project/oxc/pull/4107#issuecomment-2214167393
This commit is contained in:
lucab 2024-07-10 14:59:15 +00:00
parent f2b32731df
commit 22031430b1

View file

@ -9,7 +9,6 @@ use oxc_cfg::{
IterationInstructionKind, ReturnInstructionKind, IterationInstructionKind, ReturnInstructionKind,
}; };
use oxc_diagnostics::OxcDiagnostic; use oxc_diagnostics::OxcDiagnostic;
use oxc_index::{index_vec, IndexVec};
use oxc_span::{CompactStr, SourceType, Span}; use oxc_span::{CompactStr, SourceType, Span};
use oxc_syntax::{module_record::ModuleRecord, operator::AssignmentOperator}; use oxc_syntax::{module_record::ModuleRecord, operator::AssignmentOperator};
@ -66,9 +65,9 @@ pub struct SemanticBuilder<'a> {
pub nodes: AstNodes<'a>, pub nodes: AstNodes<'a>,
pub scope: ScopeTree, pub scope: ScopeTree,
pub symbols: SymbolTable, pub symbols: SymbolTable,
/// NOTE(lucab): lazy vector, always access this through the
/// `unresolved_references_by_scope()` helper. // LIFO stack used to accumulate unresolved refs while traversing scopes.
unresolved_references: IndexVec<ScopeId, UnresolvedReferences>, unresolved_references: Vec<UnresolvedReferences>,
pub(crate) module_record: Arc<ModuleRecord>, pub(crate) module_record: Arc<ModuleRecord>,
@ -112,7 +111,7 @@ impl<'a> SemanticBuilder<'a> {
nodes: AstNodes::default(), nodes: AstNodes::default(),
scope, scope,
symbols: SymbolTable::default(), symbols: SymbolTable::default(),
unresolved_references: index_vec![UnresolvedReferences::default()], unresolved_references: vec![],
module_record: Arc::new(ModuleRecord::default()), module_record: Arc::new(ModuleRecord::default()),
label_builder: LabelBuilder::default(), label_builder: LabelBuilder::default(),
build_jsdoc: false, build_jsdoc: false,
@ -167,9 +166,13 @@ impl<'a> SemanticBuilder<'a> {
self self
} }
/// Finalize the builder.
///
/// # Panics
pub fn build(mut self, program: &Program<'a>) -> SemanticBuilderReturn<'a> { pub fn build(mut self, program: &Program<'a>) -> SemanticBuilderReturn<'a> {
if self.source_type.is_typescript_definition() { if self.source_type.is_typescript_definition() {
let scope_id = self.scope.add_scope(None, ScopeFlags::Top); let scope_id = self.scope.add_scope(None, ScopeFlags::Top);
self.unresolved_references.push(UnresolvedReferences::default());
program.scope_id.set(Some(scope_id)); program.scope_id.set(Some(scope_id));
} else { } else {
self.visit_program(program); self.visit_program(program);
@ -179,8 +182,9 @@ impl<'a> SemanticBuilder<'a> {
checker::check_module_record(&self); checker::check_module_record(&self);
} }
} }
self.scope.root_unresolved_references =
self.unresolved_references.swap_remove(self.scope.root_scope_id()); debug_assert_eq!(self.unresolved_references.len(), 1);
self.scope.root_unresolved_references = self.unresolved_references.pop().unwrap();
let jsdoc = if self.build_jsdoc { self.jsdoc.build() } else { JSDocFinder::default() }; let jsdoc = if self.build_jsdoc { self.jsdoc.build() } else { JSDocFinder::default() };
@ -321,11 +325,16 @@ impl<'a> SemanticBuilder<'a> {
Some(symbol_id) Some(symbol_id)
} }
/// Declare an unresolved reference in the current scope.
///
/// # Panics
pub fn declare_reference(&mut self, reference: Reference) -> ReferenceId { pub fn declare_reference(&mut self, reference: Reference) -> ReferenceId {
let reference_name = reference.name().clone(); let reference_name = reference.name().clone();
let reference_id = self.symbols.create_reference(reference); let reference_id = self.symbols.create_reference(reference);
self.unresolved_references_by_scope(self.current_scope_id) self.unresolved_references
.last_mut()
.unwrap()
.entry(reference_name) .entry(reference_name)
.or_default() .or_default()
.push(reference_id); .push(reference_id);
@ -350,10 +359,8 @@ impl<'a> SemanticBuilder<'a> {
} }
fn resolve_references_for_current_scope(&mut self) { fn resolve_references_for_current_scope(&mut self) {
let all_references = self let all_references =
.unresolved_references_by_scope(self.current_scope_id) self.unresolved_references.last_mut().unwrap().drain().collect::<Vec<(_, Vec<_>)>>();
.drain()
.collect::<Vec<(_, Vec<_>)>>();
for (name, reference_ids) in all_references { for (name, reference_ids) in all_references {
self.resolve_reference_ids(name, reference_ids); self.resolve_reference_ids(name, reference_ids);
@ -361,19 +368,21 @@ impl<'a> SemanticBuilder<'a> {
} }
fn resolve_reference_ids(&mut self, name: CompactStr, reference_ids: Vec<ReferenceId>) { fn resolve_reference_ids(&mut self, name: CompactStr, reference_ids: Vec<ReferenceId>) {
let parent_scope_id =
self.scope.get_parent_id(self.current_scope_id).unwrap_or(self.current_scope_id);
if let Some(symbol_id) = self.scope.get_binding(self.current_scope_id, &name) { if let Some(symbol_id) = self.scope.get_binding(self.current_scope_id, &name) {
for reference_id in &reference_ids { for reference_id in &reference_ids {
self.symbols.references[*reference_id].set_symbol_id(symbol_id); self.symbols.references[*reference_id].set_symbol_id(symbol_id);
} }
self.symbols.resolved_references[symbol_id].extend(reference_ids); self.symbols.resolved_references[symbol_id].extend(reference_ids);
} else { } else {
self.unresolved_references_by_scope(parent_scope_id) let index = if self.scope.get_parent_id(self.current_scope_id).is_some() {
.entry(name) // Parent of last item in the stack.
.or_default() self.unresolved_references.len().checked_sub(2).unwrap()
.extend(reference_ids); } else {
// Last (and only) item in the stack.
0
};
let refs = &mut self.unresolved_references[index];
refs.entry(name).or_default().extend(reference_ids);
} }
} }
@ -405,14 +414,6 @@ impl<'a> SemanticBuilder<'a> {
self.symbols.union_flag(symbol_id, SymbolFlags::Export); self.symbols.union_flag(symbol_id, SymbolFlags::Export);
} }
} }
fn unresolved_references_by_scope(&mut self, scope_id: ScopeId) -> &mut UnresolvedReferences {
let min_new_len = scope_id + 1;
if self.unresolved_references.len() < min_new_len {
self.unresolved_references.resize_with(min_new_len.into(), Default::default);
}
&mut self.unresolved_references[scope_id]
}
} }
impl<'a> Visit<'a> for SemanticBuilder<'a> { impl<'a> Visit<'a> for SemanticBuilder<'a> {
@ -426,11 +427,13 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
} }
self.current_scope_id = self.scope.add_scope(parent_scope_id, flags); self.current_scope_id = self.scope.add_scope(parent_scope_id, flags);
self.unresolved_references.push(UnresolvedReferences::default());
} }
fn leave_scope(&mut self) { fn leave_scope(&mut self) {
self.resolve_references_for_current_scope(); self.resolve_references_for_current_scope();
if let Some(parent_id) = self.scope.get_parent_id(self.current_scope_id) { if let Some(parent_id) = self.scope.get_parent_id(self.current_scope_id) {
self.unresolved_references.pop();
self.current_scope_id = parent_id; self.current_scope_id = parent_id;
} }
} }