Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ edition = "2018"

[dependencies]
erasable = "1.2.1"
rustc-hash = "1.0.1"
hashbrown = "0.8.0"
serde = { version = "1.0.89", optional = true, default-features = false }
slice-dst = "1.4.1"
smol_str = "0.1.10"
Expand Down
40 changes: 26 additions & 14 deletions src/green/builder.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use rustc_hash::FxHashSet;
use hashbrown::HashMap;

use crate::{
green::{GreenElement, GreenNode, GreenToken, SyntaxKind},
Expand All @@ -7,8 +7,8 @@ use crate::{

#[derive(Default, Debug)]
pub struct NodeCache {
nodes: FxHashSet<GreenNode>,
tokens: FxHashSet<GreenToken>,
nodes: HashMap<GreenNode, ()>,
tokens: HashMap<GreenToken, ()>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these HashMap just because of drain_filter?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes (unfortunately).

A further optimization step in the pipeline will take further advantage of HashMap::raw_entry_mut, so the switch to using set-like maps is probably inevitable, though.

}

impl NodeCache {
Expand All @@ -17,7 +17,7 @@ impl NodeCache {
I: IntoIterator<Item = GreenElement>,
I::IntoIter: ExactSizeIterator,
{
let mut node = GreenNode::new(kind, children);
let node = GreenNode::new(kind, children);
// Green nodes are fully immutable, so it's ok to deduplicate them.
// This is the same optimization that Roslyn does
// https://github.com/KirillOsenkov/Bliki/wiki/Roslyn-Immutable-Trees
Expand All @@ -27,21 +27,33 @@ impl NodeCache {
// 17% of the memory for green nodes!
// Future work: make hashing faster by avoiding rehashing of subtrees.
if node.children().len() <= 3 {
match self.nodes.get(&node) {
Some(existing) => node = existing.clone(),
None => assert!(self.nodes.insert(node.clone())),
}
self.nodes.raw_entry_mut().from_key(&node).or_insert(node, ()).0.clone()
} else {
node
}
node
}

fn token(&mut self, kind: SyntaxKind, text: SmolStr) -> GreenToken {
let mut token = GreenToken::new(kind, text);
match self.tokens.get(&token) {
Some(existing) => token = existing.clone(),
None => assert!(self.tokens.insert(token.clone())),
let token = GreenToken::new(kind, text);
self.tokens.raw_entry_mut().from_key(&token).or_insert(token, ()).0.clone()
}

fn turn_node_gc(&mut self) -> bool {
// NB: `drain_filter` is `retain` but with an iterator of the removed elements.
// i.e.: elements where the predicate is FALSE are removed and iterated over.
self.nodes.drain_filter(|node, ()| node.strong_count() > 1).any(|_| true)
}

fn turn_token_gc(&mut self) -> bool {
self.tokens.drain_filter(|token, ()| token.strong_count() > 1).any(|_| true)
}

/// Garbage collect any elements in this cache that are only held by this cache.
pub fn gc(&mut self) {
while self.turn_node_gc() {
continue;
}
token
self.turn_token_gc();
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/green/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ impl GreenNode {
let r: &SliceWithHeader<_, _> = &*self.data;
r as *const _ as _
}

pub(super) fn strong_count(&self) -> usize {
Thin::with(&self.data, |node| Arc::strong_count(node))
}
}

#[derive(Debug, Clone)]
Expand Down
6 changes: 6 additions & 0 deletions src/green/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ impl GreenToken {
pub fn text_len(&self) -> TextSize {
TextSize::try_from(self.text().len()).unwrap()
}

pub(super) fn strong_count(&self) -> usize {
let ptr = Self::remove_tag(self.ptr);
let arc = unsafe { ManuallyDrop::new(Arc::from_raw(ptr.as_ptr())) };
Arc::strong_count(&arc)
}
}

impl fmt::Debug for GreenToken {
Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ mod serde_impls;
// these, as a custom interner might work better, but `SmolStr` is a pretty good
// default.
pub use smol_str::SmolStr;
pub use text_size::{TextRange, TextSize, TextLen};
pub use text_size::{TextLen, TextRange, TextSize};

pub use crate::{
api::{
Language, SyntaxElement, SyntaxElementChildren, SyntaxNode, SyntaxNodeChildren, SyntaxToken,
},
green::{Checkpoint, Children, GreenNode, GreenNodeBuilder, GreenToken, SyntaxKind},
green::{Checkpoint, Children, GreenNode, GreenNodeBuilder, GreenToken, NodeCache, SyntaxKind},
syntax_text::SyntaxText,
utility_types::{Direction, NodeOrToken, TokenAtOffset, WalkEvent},
};