perf(semantic): keep a single map of unresolved references (#4107)

This reworks `ScopeTree` in order to keep a single (root) map of
unresolved references. The `SemanticBuilder` keeps track of all
intermediate ones while walking scopes, and it can get rid of all
non-root ones once done.

Ref: https://github.com/oxc-project/backlog/issues/32
This commit is contained in:
Luca Bruno 2024-07-08 10:56:13 +02:00 committed by GitHub
parent 115ac3b81b
commit 9114c8e01c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 30 additions and 35 deletions

View file

@ -9,6 +9,7 @@ use oxc_cfg::{
IterationInstructionKind, ReturnInstructionKind,
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_index::{index_vec, IndexVec};
use oxc_span::{CompactStr, SourceType, Span};
use oxc_syntax::{module_record::ModuleRecord, operator::AssignmentOperator};
@ -22,7 +23,7 @@ use crate::{
module_record::ModuleRecordBuilder,
node::{AstNode, AstNodeId, AstNodes, NodeFlags},
reference::{Reference, ReferenceFlag, ReferenceId},
scope::{ScopeFlags, ScopeId, ScopeTree},
scope::{ScopeFlags, ScopeId, ScopeTree, UnresolvedReferences},
symbol::{SymbolFlags, SymbolId, SymbolTable},
JSDocFinder, Semantic,
};
@ -65,6 +66,9 @@ pub struct SemanticBuilder<'a> {
pub nodes: AstNodes<'a>,
pub scope: ScopeTree,
pub symbols: SymbolTable,
/// NOTE(lucab): lazy vector, always access this through the
/// `unresolved_references_by_scope()` helper.
unresolved_references: IndexVec<ScopeId, UnresolvedReferences>,
pub(crate) module_record: Arc<ModuleRecord>,
@ -108,6 +112,7 @@ impl<'a> SemanticBuilder<'a> {
nodes: AstNodes::default(),
scope,
symbols: SymbolTable::default(),
unresolved_references: index_vec![UnresolvedReferences::default()],
module_record: Arc::new(ModuleRecord::default()),
label_builder: LabelBuilder::default(),
build_jsdoc: false,
@ -174,6 +179,8 @@ impl<'a> SemanticBuilder<'a> {
checker::check_module_record(&self);
}
}
self.scope.root_unresolved_references =
self.unresolved_references.swap_remove(self.scope.root_scope_id());
let jsdoc = if self.build_jsdoc { self.jsdoc.build() } else { JSDocFinder::default() };
@ -317,7 +324,11 @@ impl<'a> SemanticBuilder<'a> {
pub fn declare_reference(&mut self, reference: Reference) -> ReferenceId {
let reference_name = reference.name().clone();
let reference_id = self.symbols.create_reference(reference);
self.scope.add_unresolved_reference(self.current_scope_id, reference_name, reference_id);
self.unresolved_references_by_scope(self.current_scope_id)
.entry(reference_name)
.or_default()
.push(reference_id);
reference_id
}
@ -340,8 +351,7 @@ impl<'a> SemanticBuilder<'a> {
fn resolve_references_for_current_scope(&mut self) {
let all_references = self
.scope
.unresolved_references_mut(self.current_scope_id)
.unresolved_references_by_scope(self.current_scope_id)
.drain()
.collect::<Vec<(_, Vec<_>)>>();
@ -360,7 +370,10 @@ impl<'a> SemanticBuilder<'a> {
}
self.symbols.resolved_references[symbol_id].extend(reference_ids);
} else {
self.scope.extend_unresolved_reference(parent_scope_id, name, reference_ids);
self.unresolved_references_by_scope(parent_scope_id)
.entry(name)
.or_default()
.extend(reference_ids);
}
}
@ -392,6 +405,14 @@ impl<'a> SemanticBuilder<'a> {
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> {

View file

@ -11,7 +11,7 @@ use crate::{reference::ReferenceId, symbol::SymbolId, AstNodeId};
type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
type Bindings = FxIndexMap<CompactStr, SymbolId>;
type UnresolvedReferences = FxHashMap<CompactStr, Vec<ReferenceId>>;
pub(crate) type UnresolvedReferences = FxHashMap<CompactStr, Vec<ReferenceId>>;
/// Scope Tree
///
@ -27,7 +27,7 @@ pub struct ScopeTree {
node_ids: FxHashMap<ScopeId, AstNodeId>,
flags: IndexVec<ScopeId, ScopeFlags>,
bindings: IndexVec<ScopeId, Bindings>,
unresolved_references: IndexVec<ScopeId, UnresolvedReferences>,
pub(crate) root_unresolved_references: UnresolvedReferences,
}
impl ScopeTree {
@ -88,7 +88,7 @@ impl ScopeTree {
}
pub fn root_unresolved_references(&self) -> &UnresolvedReferences {
&self.unresolved_references[self.root_scope_id()]
&self.root_unresolved_references
}
pub fn get_flags(&self, scope_id: ScopeId) -> ScopeFlags {
@ -142,7 +142,7 @@ impl ScopeTree {
}
pub fn add_root_unresolved_reference(&mut self, name: CompactStr, reference_id: ReferenceId) {
self.add_unresolved_reference(self.root_scope_id(), name, reference_id);
self.root_unresolved_references.entry(name).or_default().push(reference_id);
}
pub fn has_binding(&self, scope_id: ScopeId, name: &str) -> bool {
@ -184,7 +184,6 @@ impl ScopeTree {
let scope_id = self.parent_ids.push(parent_id);
_ = self.flags.push(flags);
_ = self.bindings.push(Bindings::default());
_ = self.unresolved_references.push(UnresolvedReferences::default());
if let Some(parent_id) = parent_id {
self.child_ids.entry(parent_id).or_default().push(scope_id);
@ -204,29 +203,4 @@ impl ScopeTree {
pub fn remove_binding(&mut self, scope_id: ScopeId, name: &CompactStr) {
self.bindings[scope_id].shift_remove(name);
}
pub(crate) fn add_unresolved_reference(
&mut self,
scope_id: ScopeId,
name: CompactStr,
reference_id: ReferenceId,
) {
self.unresolved_references[scope_id].entry(name).or_default().push(reference_id);
}
pub(crate) fn extend_unresolved_reference(
&mut self,
scope_id: ScopeId,
name: CompactStr,
reference_ids: Vec<ReferenceId>,
) {
self.unresolved_references[scope_id].entry(name).or_default().extend(reference_ids);
}
pub(crate) fn unresolved_references_mut(
&mut self,
scope_id: ScopeId,
) -> &mut UnresolvedReferences {
&mut self.unresolved_references[scope_id]
}
}