Skip to content
This repository has been archived by the owner on Aug 31, 2023. It is now read-only.

Commit

Permalink
perf(rome_js_semantic): POC Reduce writes
Browse files Browse the repository at this point in the history
Improve the performance of the semantic model by writting less data:

* Only store the nodes that can be queried by the semantic model
* Only store declaration reads/writes once instead of twice

## Alternatives:

Keep `declaration_all_writes` and `declaration_all_reads` and compute `declaration_all_references` by concatenating the two list.

Downside: Doesn't maintain read/write order
Upside: Cheaper read of reads/writes because it doesn't require any filtering
  • Loading branch information
Micha Reiser committed Nov 6, 2022
1 parent b2831b7 commit 9771f1d
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 54 deletions.
56 changes: 55 additions & 1 deletion crates/rome_js_analyze/src/semantic_services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@ use rome_analyze::{
RuleKey, ServiceBag, Visitor, VisitorContext, VisitorFinishContext,
};
use rome_js_semantic::{SemanticEventExtractor, SemanticModel, SemanticModelBuilder};
use rome_js_syntax::JsSyntaxKind::{
JSX_REFERENCE_IDENTIFIER, JS_ARROW_FUNCTION_EXPRESSION, JS_BLOCK_STATEMENT, JS_CALL_EXPRESSION,
JS_CATCH_CLAUSE, JS_CLASS_DECLARATION, JS_CLASS_EXPORT_DEFAULT_DECLARATION,
JS_CLASS_EXPRESSION, JS_CONSTRUCTOR_CLASS_MEMBER, JS_FOR_IN_STATEMENT, JS_FOR_OF_STATEMENT,
JS_FOR_STATEMENT, JS_FUNCTION_BODY, JS_FUNCTION_DECLARATION,
JS_FUNCTION_EXPORT_DEFAULT_DECLARATION, JS_FUNCTION_EXPRESSION, JS_GETTER_CLASS_MEMBER,
JS_GETTER_OBJECT_MEMBER, JS_IDENTIFIER_ASSIGNMENT, JS_IDENTIFIER_BINDING,
JS_METHOD_CLASS_MEMBER, JS_METHOD_OBJECT_MEMBER, JS_MODULE, JS_REFERENCE_IDENTIFIER, JS_SCRIPT,
JS_SETTER_CLASS_MEMBER, JS_SETTER_OBJECT_MEMBER, JS_SWITCH_STATEMENT, TS_ENUM_DECLARATION,
TS_FUNCTION_TYPE, TS_IDENTIFIER_BINDING, TS_INTERFACE_DECLARATION, TS_TYPE_ALIAS_DECLARATION,
};
use rome_js_syntax::{JsAnyRoot, JsLanguage, WalkEvent};
use rome_rowan::{AstNode, SyntaxNode};

Expand Down Expand Up @@ -101,7 +112,50 @@ impl Visitor for SemanticModelBuilderVisitor {
) {
match event {
WalkEvent::Enter(node) => {
self.builder.push_node(node);
match node.kind() {
JS_IDENTIFIER_BINDING | TS_IDENTIFIER_BINDING => {
self.builder.push_node(node);
}
JS_REFERENCE_IDENTIFIER | JSX_REFERENCE_IDENTIFIER => {
self.builder.push_node(node);
}
JS_IDENTIFIER_ASSIGNMENT => {
self.builder.push_node(node);
}
JS_CALL_EXPRESSION => {
self.builder.push_node(node);
}

JS_MODULE | JS_SCRIPT => self.builder.push_node(node),
JS_FUNCTION_DECLARATION
| JS_FUNCTION_EXPORT_DEFAULT_DECLARATION
| JS_FUNCTION_EXPRESSION
| JS_ARROW_FUNCTION_EXPRESSION
| JS_CLASS_DECLARATION
| JS_CLASS_EXPORT_DEFAULT_DECLARATION
| JS_CLASS_EXPRESSION
| JS_CONSTRUCTOR_CLASS_MEMBER
| JS_METHOD_CLASS_MEMBER
| JS_GETTER_CLASS_MEMBER
| JS_SETTER_CLASS_MEMBER
| JS_METHOD_OBJECT_MEMBER
| JS_GETTER_OBJECT_MEMBER
| JS_SETTER_OBJECT_MEMBER
| JS_FUNCTION_BODY
| TS_INTERFACE_DECLARATION
| TS_ENUM_DECLARATION
| TS_TYPE_ALIAS_DECLARATION
| TS_FUNCTION_TYPE => {
self.builder.push_node(node);
}

JS_BLOCK_STATEMENT | JS_FOR_STATEMENT | JS_FOR_OF_STATEMENT
| JS_FOR_IN_STATEMENT | JS_SWITCH_STATEMENT | JS_CATCH_CLAUSE => {
self.builder.push_node(node);
}
_ => {}
}

self.extractor.enter(node);
}
WalkEvent::Leave(node) => {
Expand Down
154 changes: 101 additions & 53 deletions crates/rome_js_semantic/src/semantic_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use rome_rowan::{AstNode, SyntaxTokenText};
use rust_lapper::{Interval, Lapper};
use rustc_hash::{FxHashMap, FxHashSet};
use std::{
collections::{BTreeSet, HashSet, VecDeque},
collections::{BTreeSet, VecDeque},
iter::FusedIterator,
sync::Arc,
};
Expand Down Expand Up @@ -138,10 +138,6 @@ struct SemanticModelData {
declared_at_by_range: FxHashMap<TextRange, TextRange>,
// Maps a declaration range to the range of its references
declaration_all_references: FxHashMap<TextRange, Vec<(ReferenceType, TextRange)>>,
// Maps a declaration range to the range of its "reads"
declaration_all_reads: FxHashMap<TextRange, Vec<(ReferenceType, TextRange)>>,
// Maps a declaration range to the range of its "writes"
declaration_all_writes: FxHashMap<TextRange, Vec<(ReferenceType, TextRange)>>,
// All bindings that were exported
exported: FxHashSet<TextRange>,
/// All references that could not be resolved
Expand Down Expand Up @@ -182,25 +178,19 @@ impl SemanticModelData {
}
}

pub fn all_reads_iter(
&self,
range: &TextRange,
) -> std::slice::Iter<'_, (ReferenceType, TextRange)> {
if let Some(v) = self.declaration_all_reads.get(range) {
v.iter()
pub fn all_reads_iter(&self, range: &TextRange) -> ReadsIter {
if let Some(v) = self.declaration_all_references.get(range) {
ReadsIter { iter: v.iter() }
} else {
[].iter()
ReadsIter { iter: [].iter() }
}
}

pub fn all_writes_iter(
&self,
range: &TextRange,
) -> std::slice::Iter<'_, (ReferenceType, TextRange)> {
if let Some(v) = self.declaration_all_writes.get(range) {
v.iter()
pub fn all_writes_iter(&self, range: &TextRange) -> WritesIter {
if let Some(v) = self.declaration_all_references.get(range) {
WritesIter { iter: v.iter() }
} else {
[].iter()
WritesIter { iter: [].iter() }
}
}

Expand Down Expand Up @@ -376,18 +366,18 @@ impl Binding {
}

/// Returns an iterator to all reads references of this binding.
pub fn all_reads(&self) -> ReferencesIter {
pub fn all_reads(&self) -> ReadReferencesIter {
let range = self.node.text_range();
ReferencesIter {
ReadReferencesIter {
data: self.data.clone(),
iter: self.data.all_reads_iter(&range),
}
}

/// Returns an iterator to all write references of this binding.
pub fn all_writes(&self) -> ReferencesIter {
pub fn all_writes(&self) -> WriteReferencesIter {
let range = self.node.text_range();
ReferencesIter {
WriteReferencesIter {
data: self.data.clone(),
iter: self.data.all_writes_iter(&range),
}
Expand Down Expand Up @@ -448,6 +438,86 @@ impl Reference {
}
}

struct ReadsIter<'a> {
iter: std::slice::Iter<'a, (ReferenceType, TextRange)>,
}

impl<'a> Iterator for ReadsIter<'a> {
type Item = (ReferenceType, TextRange);

fn next(&mut self) -> Option<Self::Item> {
let result = loop {
match self.iter.next()? {
(read @ ReferenceType::Read { .. }, range) => {
break (*read, *range);
}
_ => {}
}
};
Some(result)
}
}

pub struct ReadReferencesIter<'a> {
data: Arc<SemanticModelData>,
iter: ReadsIter<'a>,
}

impl<'a> Iterator for ReadReferencesIter<'a> {
type Item = Reference;

fn next(&mut self) -> Option<Self::Item> {
let (ty, range) = self.iter.next()?;
let node = self.data.node_by_range.get(&range)?;
Some(Reference {
data: self.data.clone(),
node: node.clone(),
range,
ty,
})
}
}

struct WritesIter<'a> {
iter: std::slice::Iter<'a, (ReferenceType, TextRange)>,
}

impl<'a> Iterator for WritesIter<'a> {
type Item = (ReferenceType, TextRange);

fn next(&mut self) -> Option<Self::Item> {
let result = loop {
match self.iter.next()? {
(read @ ReferenceType::Write { .. }, range) => {
break (*read, *range);
}
_ => {}
}
};
Some(result)
}
}

pub struct WriteReferencesIter<'a> {
data: Arc<SemanticModelData>,
iter: WritesIter<'a>,
}

impl<'a> Iterator for WriteReferencesIter<'a> {
type Item = Reference;

fn next(&mut self) -> Option<Self::Item> {
let (ty, range) = self.iter.next()?;
let node = self.data.node_by_range.get(&range)?;
Some(Reference {
data: self.data.clone(),
node: node.clone(),
range,
ty,
})
}
}

/// Iterate all references of a particular declaration.
pub struct ReferencesIter<'a> {
data: Arc<SemanticModelData>,
Expand Down Expand Up @@ -723,21 +793,21 @@ impl SemanticModel {

/// Returns a list with all read [Reference] of the specified declaration.
/// Can also be called from "all_reads" extension method.
pub fn all_reads<'a>(&'a self, declaration: &impl IsDeclarationAstNode) -> ReferencesIter<'a> {
pub fn all_reads(&self, declaration: &impl IsDeclarationAstNode) -> ReadReferencesIter {
let node = declaration.node();
let range = node.syntax().text_range();
ReferencesIter {
ReadReferencesIter {
data: self.data.clone(),
iter: self.data.all_reads_iter(&range),
}
}

/// Returns a list with all write [Reference] of the specified declaration.
/// Can also be called from "all_writes" extension method.
pub fn all_writes<'a>(&'a self, declaration: &impl IsDeclarationAstNode) -> ReferencesIter<'a> {
pub fn all_writes(&self, declaration: &impl IsDeclarationAstNode) -> WriteReferencesIter {
let node = declaration.node();
let range = node.syntax().text_range();
ReferencesIter {
WriteReferencesIter {
data: self.data.clone(),
iter: self.data.all_writes_iter(&range),
}
Expand Down Expand Up @@ -847,14 +917,14 @@ pub trait AllReferencesExtensions {
model.all_references(self)
}

fn all_reads<'a>(&self, model: &'a SemanticModel) -> ReferencesIter<'a>
fn all_reads<'a>(&self, model: &'a SemanticModel) -> ReadReferencesIter<'a>
where
Self: IsDeclarationAstNode,
{
model.all_reads(self)
}

fn all_writes<'a>(&self, model: &'a SemanticModel) -> ReferencesIter<'a>
fn all_writes<'a>(&self, model: &'a SemanticModel) -> WriteReferencesIter<'a>
where
Self: IsDeclarationAstNode,
{
Expand Down Expand Up @@ -890,8 +960,6 @@ pub struct SemanticModelBuilder {
scope_hoisted_to_by_range: FxHashMap<TextSize, usize>,
declarations_by_range: FxHashMap<TextRange, TextRange>,
declaration_all_references: FxHashMap<TextRange, Vec<(ReferenceType, TextRange)>>,
declaration_all_reads: FxHashMap<TextRange, Vec<(ReferenceType, TextRange)>>,
declaration_all_writes: FxHashMap<TextRange, Vec<(ReferenceType, TextRange)>>,
exported: FxHashSet<TextRange>,
unresolved_references: Vec<(ReferenceType, TextRange)>,
global_references: Vec<(ReferenceType, TextRange)>,
Expand All @@ -908,8 +976,7 @@ impl SemanticModelBuilder {
scope_hoisted_to_by_range: FxHashMap::default(),
declarations_by_range: FxHashMap::default(),
declaration_all_references: FxHashMap::default(),
declaration_all_reads: FxHashMap::default(),
declaration_all_writes: FxHashMap::default(),

exported: FxHashSet::default(),
unresolved_references: Vec::new(),
global_references: Vec::new(),
Expand Down Expand Up @@ -997,10 +1064,6 @@ impl SemanticModelBuilder {
.entry(declaration_at)
.or_default()
.push((ReferenceType::Read { hoisted: false }, range));
self.declaration_all_reads
.entry(declaration_at)
.or_default()
.push((ReferenceType::Read { hoisted: false }, range));

let scope = &mut self.scopes[scope_id];
scope.read_references.push(ScopeReference { range });
Expand All @@ -1015,10 +1078,6 @@ impl SemanticModelBuilder {
.entry(declaration_at)
.or_default()
.push((ReferenceType::Read { hoisted: true }, range));
self.declaration_all_reads
.entry(declaration_at)
.or_default()
.push((ReferenceType::Read { hoisted: true }, range));

let scope = &mut self.scopes[scope_id];
scope.read_references.push(ScopeReference { range });
Expand All @@ -1033,10 +1092,6 @@ impl SemanticModelBuilder {
.entry(declaration_at)
.or_default()
.push((ReferenceType::Write { hoisted: false }, range));
self.declaration_all_writes
.entry(declaration_at)
.or_default()
.push((ReferenceType::Write { hoisted: false }, range));

let scope = &mut self.scopes[scope_id];
scope.write_references.push(ScopeReference { range });
Expand All @@ -1051,11 +1106,6 @@ impl SemanticModelBuilder {
.entry(declaration_at)
.or_default()
.push((ReferenceType::Write { hoisted: true }, range));
self.declaration_all_writes
.entry(declaration_at)
.or_default()
.push((ReferenceType::Write { hoisted: true }, range));

let scope = &mut self.scopes[scope_id];
scope.write_references.push(ScopeReference { range });
}
Expand Down Expand Up @@ -1097,8 +1147,6 @@ impl SemanticModelBuilder {
node_by_range: self.node_by_range,
declared_at_by_range: self.declarations_by_range,
declaration_all_references: self.declaration_all_references,
declaration_all_reads: self.declaration_all_reads,
declaration_all_writes: self.declaration_all_writes,
exported: self.exported,
unresolved_references: self.unresolved_references,
global_references: self.global_references,
Expand All @@ -1111,7 +1159,7 @@ impl SemanticModelBuilder {
/// Extra options for the [SemanticModel] creation.
pub struct SemanticModelOptions {
/// All the allowed globals names
pub globals: HashSet<String>,
pub globals: FxHashSet<String>,
}

/// Build the complete [SemanticModel] of a parsed file.
Expand Down

0 comments on commit 9771f1d

Please sign in to comment.