Skip to content

Commit

Permalink
Optimize iteration over sibling nodes by reusing NodeData if possible
Browse files Browse the repository at this point in the history
When possible, reuse the allocated NodeData instead of allocating a
new one for each iteration. This can be done as long as the refcount
is 1 - we can then just rewire the values in NodeData to point to the
new item.

This removes ~220k allocations when compiling a largish slint file,
about half of all rowan allocations that happen during iteration,
i.e. we go from 450k down to 230k.
  • Loading branch information
milianw committed Oct 28, 2024
1 parent f06a2c9 commit 0b1a6ec
Showing 1 changed file with 126 additions and 8 deletions.
134 changes: 126 additions & 8 deletions src/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,20 @@ impl SyntaxNode {
unsafe { self.ptr.as_ref() }
}

#[inline]
fn can_take_ptr(&self) -> bool {
self.data().rc.get() == 1 && !self.data().mutable
}

#[inline]
fn take_ptr(self) -> ptr::NonNull<NodeData> {
assert!(self.can_take_ptr());
let ret = self.ptr;
// don't change the refcount when self gets dropped
std::mem::forget(self);
ret
}

pub fn replace_with(&self, replacement: GreenNode) -> GreenNode {
assert_eq!(self.kind(), replacement.kind());
match &self.parent() {
Expand Down Expand Up @@ -744,6 +758,43 @@ impl SyntaxNode {
})
}

// if possible (i.e. unshared), consume self and advance it to point to the next sibling
// this way, we can reuse the previously allocated buffer
pub fn to_next_sibling(self) -> Option<SyntaxNode> {
if !self.can_take_ptr() {
// cannot mutate in-place
return self.next_sibling();
}

let mut ptr = self.take_ptr();
let data = unsafe { ptr.as_mut() };
assert!(data.rc.get() == 1);

let parent = data.parent_node()?;
let parent_offset = parent.offset();
let siblings = parent.green_ref().children().raw.enumerate();
let index = data.index() as usize;

siblings
.skip(index + 1)
.find_map(|(index, child)| {
child
.as_ref()
.into_node()
.and_then(|green| Some((green, index as u32, child.rel_offset())))
})
.and_then(|(green, index, rel_offset)| {
data.index.set(index);
data.offset = parent_offset + rel_offset;
data.green = Green::Node { ptr: Cell::new(green.into()) };
Some(SyntaxNode { ptr })
})
.or_else(|| {
unsafe { free(ptr) };
None
})
}

pub fn next_sibling(&self) -> Option<SyntaxNode> {
self.data().next_sibling()
}
Expand Down Expand Up @@ -937,6 +988,20 @@ impl SyntaxToken {
unsafe { self.ptr.as_ref() }
}

#[inline]
fn can_take_ptr(&self) -> bool {
self.data().rc.get() == 1 && !self.data().mutable
}

#[inline]
fn take_ptr(self) -> ptr::NonNull<NodeData> {
assert!(self.can_take_ptr());
let ret = self.ptr;
// don't change the refcount when self gets dropped
std::mem::forget(self);
ret
}

pub fn replace_with(&self, replacement: GreenToken) -> GreenNode {
assert_eq!(self.kind(), replacement.kind());
let parent = self.parent().unwrap();
Expand Down Expand Up @@ -1121,6 +1186,59 @@ impl SyntaxElement {
}
}

fn can_take_ptr(&self) -> bool {
match self {
NodeOrToken::Node(it) => it.can_take_ptr(),
NodeOrToken::Token(it) => it.can_take_ptr(),
}
}

fn take_ptr(self) -> ptr::NonNull<NodeData> {
match self {
NodeOrToken::Node(it) => it.take_ptr(),
NodeOrToken::Token(it) => it.take_ptr(),
}
}

// if possible (i.e. unshared), consume self and advance it to point to the next sibling
// this way, we can reuse the previously allocated buffer
pub fn to_next_sibling_or_token(self) -> Option<SyntaxElement> {
if !self.can_take_ptr() {
// cannot mutate in-place
return self.next_sibling_or_token();
}

let mut ptr = self.take_ptr();
let data = unsafe { ptr.as_mut() };

let parent = data.parent_node()?;
let parent_offset = parent.offset();
let siblings = parent.green_ref().children().raw.enumerate();
let index = data.index() as usize;

siblings
.skip(index + 1)
.find_map(|(index, green)| {
data.index.set(index as u32);
data.offset = parent_offset + green.rel_offset();

match green.as_ref() {
NodeOrToken::Node(node) => {
data.green = Green::Node { ptr: Cell::new(node.into()) };
Some(SyntaxElement::Node(SyntaxNode { ptr }))
}
NodeOrToken::Token(token) => {
data.green = Green::Token { ptr: token.into() };
Some(SyntaxElement::Token(SyntaxToken { ptr }))
}
}
})
.or_else(|| {
unsafe { free(ptr) };
None
})
}

pub fn next_sibling_or_token_by_kind(
&self,
matcher: &impl Fn(SyntaxKind) -> bool,
Expand Down Expand Up @@ -1270,11 +1388,11 @@ impl Iterator for SyntaxNodeChildren {
if !self.next_initialized {
self.next = self.parent.first_child();
self.next_initialized = true;
} else {
self.next = self.next.take().and_then(|next| next.to_next_sibling());
}
self.next.take().map(|next| {
self.next = next.next_sibling();
next
})

self.next.clone()
}
}

Expand Down Expand Up @@ -1333,11 +1451,11 @@ impl Iterator for SyntaxElementChildren {
if !self.next_initialized {
self.next = self.parent.first_child_or_token();
self.next_initialized = true;
} else {
self.next = self.next.take().and_then(|next| next.to_next_sibling_or_token());
}
self.next.take().map(|next| {
self.next = next.next_sibling_or_token();
next
})

self.next.clone()
}
}

Expand Down

0 comments on commit 0b1a6ec

Please sign in to comment.