Skip to content

Commit 0088ab8

Browse files
authored
perf(es/parser): Remove OneDirectionalList and reduce allocation of Vec (#11000)
**Description:** 1. Separate `CommentsBuffer` into legacy and new ones with `CommentsBufferTrait`. 2. For the new one, remove `OneDirectionalList` and use `Vec`. Record positions in `LexerCheckpoint` for recovery. There's regression for legacy lexer. I'm not sure the reason.
1 parent a4aa0ec commit 0088ab8

File tree

8 files changed

+222
-178
lines changed

8 files changed

+222
-178
lines changed
Lines changed: 7 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,21 @@
1-
use std::{iter::Rev, rc::Rc, vec::IntoIter};
2-
31
use swc_common::{comments::Comment, BytePos};
42

5-
#[derive(Clone)]
3+
#[derive(Debug, Clone)]
64
pub struct BufferedComment {
75
pub kind: BufferedCommentKind,
86
pub pos: BytePos,
97
pub comment: Comment,
108
}
119

12-
#[derive(Clone)]
10+
#[derive(Debug, Clone, Copy)]
1311
pub enum BufferedCommentKind {
1412
Leading,
1513
Trailing,
1614
}
1715

18-
#[derive(Clone)]
19-
pub struct CommentsBuffer {
20-
comments: OneDirectionalList<BufferedComment>,
21-
pending_leading: OneDirectionalList<Comment>,
22-
}
23-
24-
impl Default for CommentsBuffer {
25-
fn default() -> Self {
26-
Self::new()
27-
}
28-
}
29-
30-
impl CommentsBuffer {
31-
pub fn new() -> Self {
32-
Self {
33-
comments: OneDirectionalList::new(),
34-
pending_leading: OneDirectionalList::new(),
35-
}
36-
}
37-
38-
pub fn push(&mut self, comment: BufferedComment) {
39-
self.comments.push(comment);
40-
}
41-
42-
pub fn push_pending_leading(&mut self, comment: Comment) {
43-
self.pending_leading.push(comment);
44-
}
45-
46-
pub fn take_comments(&mut self) -> Rev<IntoIter<BufferedComment>> {
47-
self.comments.take_all()
48-
}
49-
50-
pub fn take_pending_leading(&mut self) -> Rev<IntoIter<Comment>> {
51-
self.pending_leading.take_all()
52-
}
53-
}
54-
55-
/// A one direction linked list that can be cheaply
56-
/// cloned with the clone maintaining its position in the list.
57-
#[derive(Clone)]
58-
struct OneDirectionalList<T: Clone> {
59-
last_node: Option<Rc<OneDirectionalListNode<T>>>,
60-
}
61-
62-
impl<T: Clone> OneDirectionalList<T> {
63-
pub fn new() -> Self {
64-
Self { last_node: None }
65-
}
66-
67-
pub fn take_all(&mut self) -> Rev<IntoIter<T>> {
68-
// these are stored in reverse, so we need to reverse them back
69-
let mut items = Vec::new();
70-
let mut current_node = self.last_node.take();
71-
while let Some(node) = current_node {
72-
let mut node = match Rc::try_unwrap(node) {
73-
Ok(n) => n,
74-
Err(n) => n.as_ref().clone(),
75-
};
76-
items.push(node.item);
77-
current_node = node.previous.take();
78-
}
79-
items.into_iter().rev()
80-
}
81-
82-
pub fn push(&mut self, item: T) {
83-
let previous = self.last_node.take();
84-
let new_item = OneDirectionalListNode { item, previous };
85-
self.last_node = Some(Rc::new(new_item));
86-
}
87-
}
88-
89-
#[derive(Clone)]
90-
struct OneDirectionalListNode<T: Clone> {
91-
item: T,
92-
previous: Option<Rc<OneDirectionalListNode<T>>>,
16+
pub trait CommentsBufferTrait {
17+
fn push_comment(&mut self, comment: BufferedComment);
18+
fn push_pending(&mut self, comment: Comment);
19+
fn pending_to_comment(&mut self, kind: BufferedCommentKind, pos: BytePos);
20+
fn take_comments(&mut self) -> impl Iterator<Item = BufferedComment> + '_;
9321
}

crates/swc_ecma_lexer/src/common/lexer/mod.rs

Lines changed: 31 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use std::borrow::Cow;
22

33
use char::{Char, CharExt};
4-
use comments_buffer::{BufferedComment, BufferedCommentKind};
54
use either::Either::{self, Left, Right};
65
use num_bigint::BigInt as BigIntValue;
76
use smartstring::{LazyCompact, SmartString};
@@ -18,7 +17,7 @@ use self::jsx::xhtml;
1817
use super::{context::Context, input::Tokens};
1918
use crate::{
2019
common::lexer::{
21-
comments_buffer::CommentsBuffer,
20+
comments_buffer::{BufferedComment, BufferedCommentKind, CommentsBufferTrait},
2221
number::{parse_integer, LazyInteger},
2322
},
2423
error::SyntaxError,
@@ -78,14 +77,15 @@ fn remove_underscore(s: &str, has_underscore: bool) -> Cow<'_, str> {
7877
pub trait Lexer<'a, TokenAndSpan>: Tokens<TokenAndSpan> + Sized {
7978
type State: self::state::State;
8079
type Token: token::TokenFactory<'a, TokenAndSpan, Self, Lexer = Self>;
80+
type CommentsBuffer: CommentsBufferTrait;
8181

8282
fn input(&self) -> &StringInput<'a>;
8383
fn input_mut(&mut self) -> &mut StringInput<'a>;
8484
fn state(&self) -> &Self::State;
8585
fn state_mut(&mut self) -> &mut Self::State;
8686
fn comments(&self) -> Option<&'a dyn swc_common::comments::Comments>;
87-
fn comments_buffer(&self) -> Option<&CommentsBuffer>;
88-
fn comments_buffer_mut(&mut self) -> Option<&mut CommentsBuffer>;
87+
fn comments_buffer(&self) -> Option<&Self::CommentsBuffer>;
88+
fn comments_buffer_mut(&mut self) -> Option<&mut Self::CommentsBuffer>;
8989
/// # Safety
9090
///
9191
/// We know that the start and the end are valid
@@ -272,10 +272,10 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens<TokenAndSpan> + Sized {
272272
};
273273

274274
if is_for_next {
275-
self.comments_buffer_mut().unwrap().push_pending_leading(cmt);
275+
self.comments_buffer_mut().unwrap().push_pending(cmt);
276276
} else {
277277
let pos = self.state().prev_hi();
278-
self.comments_buffer_mut().unwrap().push(BufferedComment {
278+
self.comments_buffer_mut().unwrap().push_comment(BufferedComment {
279279
kind: BufferedCommentKind::Trailing,
280280
pos,
281281
comment: cmt,
@@ -303,16 +303,16 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens<TokenAndSpan> + Sized {
303303
};
304304

305305
if is_for_next {
306-
self.comments_buffer_mut()
307-
.unwrap()
308-
.push_pending_leading(cmt);
306+
self.comments_buffer_mut().unwrap().push_pending(cmt);
309307
} else {
310308
let pos = self.state().prev_hi();
311-
self.comments_buffer_mut().unwrap().push(BufferedComment {
312-
kind: BufferedCommentKind::Trailing,
313-
pos,
314-
comment: cmt,
315-
});
309+
self.comments_buffer_mut()
310+
.unwrap()
311+
.push_comment(BufferedComment {
312+
kind: BufferedCommentKind::Trailing,
313+
pos,
314+
comment: cmt,
315+
});
316316
}
317317
}
318318

@@ -412,16 +412,16 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens<TokenAndSpan> + Sized {
412412
};
413413

414414
if is_for_next {
415-
self.comments_buffer_mut()
416-
.unwrap()
417-
.push_pending_leading(cmt);
415+
self.comments_buffer_mut().unwrap().push_pending(cmt);
418416
} else {
419417
let pos = self.state().prev_hi();
420-
self.comments_buffer_mut().unwrap().push(BufferedComment {
421-
kind: BufferedCommentKind::Trailing,
422-
pos,
423-
comment: cmt,
424-
});
418+
self.comments_buffer_mut()
419+
.unwrap()
420+
.push_comment(BufferedComment {
421+
kind: BufferedCommentKind::Trailing,
422+
pos,
423+
comment: cmt,
424+
});
425425
}
426426
}
427427

@@ -887,25 +887,16 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens<TokenAndSpan> + Sized {
887887
let start_pos = self.start_pos();
888888
let comments_buffer = self.comments_buffer_mut().unwrap();
889889

890+
// if the file had no tokens and no shebang, then treat any
891+
// comments in the leading comments buffer as leading.
892+
// Otherwise treat them as trailing.
893+
let kind = if last == start_pos {
894+
BufferedCommentKind::Leading
895+
} else {
896+
BufferedCommentKind::Trailing
897+
};
890898
// move the pending to the leading or trailing
891-
for c in comments_buffer.take_pending_leading() {
892-
// if the file had no tokens and no shebang, then treat any
893-
// comments in the leading comments buffer as leading.
894-
// Otherwise treat them as trailing.
895-
if last == start_pos {
896-
comments_buffer.push(BufferedComment {
897-
kind: BufferedCommentKind::Leading,
898-
pos: last,
899-
comment: c,
900-
});
901-
} else {
902-
comments_buffer.push(BufferedComment {
903-
kind: BufferedCommentKind::Trailing,
904-
pos: last,
905-
comment: c,
906-
});
907-
}
908-
}
899+
comments_buffer.pending_to_comment(kind, last);
909900

910901
// now fill the user's passed in comments
911902
for comment in comments_buffer.take_comments() {
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use std::{iter::Rev, rc::Rc, vec::IntoIter};
2+
3+
use swc_common::{comments::Comment, BytePos};
4+
5+
use crate::common::lexer::comments_buffer::{
6+
BufferedComment, BufferedCommentKind, CommentsBufferTrait,
7+
};
8+
9+
#[derive(Clone)]
10+
pub struct CommentsBuffer {
11+
comments: OneDirectionalList<BufferedComment>,
12+
pending_leading: OneDirectionalList<Comment>,
13+
}
14+
15+
impl Default for CommentsBuffer {
16+
fn default() -> Self {
17+
Self::new()
18+
}
19+
}
20+
21+
impl CommentsBuffer {
22+
pub fn new() -> Self {
23+
Self {
24+
comments: OneDirectionalList::new(),
25+
pending_leading: OneDirectionalList::new(),
26+
}
27+
}
28+
}
29+
30+
impl CommentsBufferTrait for CommentsBuffer {
31+
#[inline(always)]
32+
fn push_comment(&mut self, comment: BufferedComment) {
33+
self.comments.push(comment);
34+
}
35+
36+
#[inline(always)]
37+
fn push_pending(&mut self, comment: Comment) {
38+
self.pending_leading.push(comment);
39+
}
40+
41+
#[inline(always)]
42+
fn take_comments(&mut self) -> impl Iterator<Item = BufferedComment> + '_ {
43+
self.comments.take_all()
44+
}
45+
46+
#[inline(always)]
47+
fn pending_to_comment(&mut self, kind: BufferedCommentKind, pos: BytePos) {
48+
for comment in self.pending_leading.take_all() {
49+
let comment = BufferedComment { kind, pos, comment };
50+
self.comments.push(comment);
51+
}
52+
}
53+
}
54+
55+
/// A one direction linked list that can be cheaply
56+
/// cloned with the clone maintaining its position in the list.
57+
#[derive(Clone)]
58+
struct OneDirectionalList<T: Clone> {
59+
last_node: Option<Rc<OneDirectionalListNode<T>>>,
60+
}
61+
62+
impl<T: Clone> OneDirectionalList<T> {
63+
pub fn new() -> Self {
64+
Self { last_node: None }
65+
}
66+
67+
pub fn take_all(&mut self) -> Rev<IntoIter<T>> {
68+
// these are stored in reverse, so we need to reverse them back
69+
let mut items = Vec::new();
70+
let mut current_node = self.last_node.take();
71+
while let Some(node) = current_node {
72+
let mut node = match Rc::try_unwrap(node) {
73+
Ok(n) => n,
74+
Err(n) => n.as_ref().clone(),
75+
};
76+
items.push(node.item);
77+
current_node = node.previous.take();
78+
}
79+
items.into_iter().rev()
80+
}
81+
82+
pub fn push(&mut self, item: T) {
83+
let previous = self.last_node.take();
84+
let new_item = OneDirectionalListNode { item, previous };
85+
self.last_node = Some(Rc::new(new_item));
86+
}
87+
}
88+
89+
#[derive(Clone)]
90+
struct OneDirectionalListNode<T: Clone> {
91+
item: T,
92+
previous: Option<Rc<OneDirectionalListNode<T>>>,
93+
}

crates/swc_ecma_lexer/src/lexer/mod.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,17 @@ pub use self::state::{TokenContext, TokenContexts, TokenFlags, TokenType};
1414
use self::table::{ByteHandler, BYTE_HANDLERS};
1515
use crate::{
1616
common::{
17-
lexer::{
18-
char::CharExt, comments_buffer::CommentsBuffer, fixed_len_span, pos_span, LexResult,
19-
Lexer as LexerTrait,
20-
},
17+
lexer::{char::CharExt, fixed_len_span, pos_span, LexResult, Lexer as LexerTrait},
2118
syntax::{Syntax, SyntaxFlags},
2219
},
2320
error::{Error, SyntaxError},
21+
lexer::comments_buffer::CommentsBuffer,
2422
tok,
2523
token::{BinOpToken, Token, TokenAndSpan},
2624
Context,
2725
};
2826

27+
mod comments_buffer;
2928
mod jsx;
3029
mod number;
3130
mod state;
@@ -56,6 +55,7 @@ pub struct Lexer<'a> {
5655
impl FusedIterator for Lexer<'_> {}
5756

5857
impl<'a> crate::common::lexer::Lexer<'a, TokenAndSpan> for Lexer<'a> {
58+
type CommentsBuffer = CommentsBuffer;
5959
type State = self::state::State;
6060
type Token = self::Token;
6161

@@ -90,14 +90,12 @@ impl<'a> crate::common::lexer::Lexer<'a, TokenAndSpan> for Lexer<'a> {
9090
}
9191

9292
#[inline(always)]
93-
fn comments_buffer(&self) -> Option<&crate::common::lexer::comments_buffer::CommentsBuffer> {
93+
fn comments_buffer(&self) -> Option<&Self::CommentsBuffer> {
9494
self.comments_buffer.as_ref()
9595
}
9696

9797
#[inline(always)]
98-
fn comments_buffer_mut(
99-
&mut self,
100-
) -> Option<&mut crate::common::lexer::comments_buffer::CommentsBuffer> {
98+
fn comments_buffer_mut(&mut self) -> Option<&mut Self::CommentsBuffer> {
10199
self.comments_buffer.as_mut()
102100
}
103101

crates/swc_ecma_lexer/src/lexer/state.rs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::{
1111
input::Tokens,
1212
lexer::{
1313
char::CharExt,
14-
comments_buffer::{BufferedComment, BufferedCommentKind},
14+
comments_buffer::{BufferedCommentKind, CommentsBufferTrait},
1515
state::{
1616
State as StateTrait, TokenKind as TokenKindTrait, TokenType as TokenTypeTrait,
1717
},
@@ -870,13 +870,7 @@ impl Iterator for Lexer<'_> {
870870
let span = self.span(start);
871871
if !matches!(token, Token::Eof) {
872872
if let Some(comments) = self.comments_buffer.as_mut() {
873-
for comment in comments.take_pending_leading() {
874-
comments.push(BufferedComment {
875-
kind: BufferedCommentKind::Leading,
876-
pos: start,
877-
comment,
878-
});
879-
}
873+
comments.pending_to_comment(BufferedCommentKind::Leading, start);
880874
}
881875

882876
self.state.update(start, token.kind());

0 commit comments

Comments
 (0)