Skip to content

Commit 9f3cedd

Browse files
authored
Shrink ParserState (#457)
* tokenizer: Shrink current_line_start_position. We already truncate column numbers to u32, so this gives the same answer it would otherwise give, and saves some storage (not in this patch because ParserState is still 24 bytes, but we can improve on that in a bit). * parser: Shrink ParserState by collapsing line number and opening block type. The line number range gets a bit smaller, but it pays off by making state restoration faster, which is a lot hotter.
1 parent 3a1d1c7 commit 9f3cedd

3 files changed

Lines changed: 93 additions & 28 deletions

File tree

‎src/parser.rs‎

Lines changed: 80 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,33 @@ use std::ops::BitOr;
1313
///
1414
/// Can be used with the `Parser::reset` method to restore that state.
1515
/// Should only be used with the `Parser` instance it came from.
16-
#[derive(Debug, Clone, Default)]
16+
#[derive(Clone, Default)]
1717
pub struct ParserState {
1818
pub(crate) position: usize,
19-
pub(crate) current_line_start_position: usize,
20-
pub(crate) current_line_number: u32,
21-
pub(crate) at_start_of: Option<BlockType>,
19+
pub(crate) current_line_start_position: u32,
20+
/// Current line number shifted by `Self::BLOCK_TYPE_BITS`, with the low 2 bits holding the
21+
/// `BlockType` discriminant of the block the last returned token opened (if any).
22+
line_number_and_block_type: u32,
23+
}
24+
25+
impl fmt::Debug for ParserState {
26+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27+
f.debug_struct("ParserState")
28+
.field("position", &self.position)
29+
.field(
30+
"current_line_start_position",
31+
&self.current_line_start_position,
32+
)
33+
.field("current_line_number", &self.line_number())
34+
.field("at_start_of", &self.at_start_of())
35+
.finish()
36+
}
2237
}
2338

2439
impl ParserState {
40+
const BLOCK_TYPE_BITS: u32 = 2;
41+
const BLOCK_TYPE_MASK: u32 = (1 << Self::BLOCK_TYPE_BITS) - 1;
42+
2543
/// The position from the start of the input, counted in UTF-8 bytes.
2644
#[inline]
2745
pub fn position(&self) -> SourcePosition {
@@ -32,10 +50,42 @@ impl ParserState {
3250
#[inline]
3351
pub fn source_location(&self) -> SourceLocation {
3452
SourceLocation {
35-
line: self.current_line_number,
36-
column: (self.position - self.current_line_start_position + 1) as u32,
53+
line: self.line_number(),
54+
column: (self.position as u32)
55+
.wrapping_sub(self.current_line_start_position)
56+
.wrapping_add(1),
3757
}
3858
}
59+
60+
#[inline]
61+
pub(crate) fn line_number(&self) -> u32 {
62+
self.line_number_and_block_type >> Self::BLOCK_TYPE_BITS
63+
}
64+
65+
#[inline]
66+
pub(crate) fn advance_line_number(&mut self, count: u32) {
67+
self.line_number_and_block_type += count << Self::BLOCK_TYPE_BITS;
68+
}
69+
70+
#[inline]
71+
pub(crate) fn at_start_of(&self) -> Option<BlockType> {
72+
let block_type = (self.line_number_and_block_type & Self::BLOCK_TYPE_MASK) as u8;
73+
BlockType::from_byte(block_type)
74+
}
75+
76+
#[inline]
77+
pub(crate) fn set_at_start_of(&mut self, block_type: BlockType) {
78+
debug_assert!(self.at_start_of().is_none());
79+
self.line_number_and_block_type |= block_type as u32;
80+
debug_assert_eq!(self.at_start_of(), Some(block_type));
81+
}
82+
83+
#[inline]
84+
pub(crate) fn take_at_start_of(&mut self) -> Option<BlockType> {
85+
let block_type = self.at_start_of();
86+
self.line_number_and_block_type &= !Self::BLOCK_TYPE_MASK;
87+
block_type
88+
}
3989
}
4090

4191
/// When parsing until a given token, sometimes the caller knows that parsing is going to restart
@@ -241,20 +291,35 @@ struct CachedToken<'i> {
241291
}
242292

243293
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
294+
#[repr(u8)]
244295
pub(crate) enum BlockType {
245-
Parenthesis,
296+
// NOTE: We rely on the discriminants being 1, 2, 3 for the `ParserState`
297+
// bit packing (and from_byte).
298+
Parenthesis = 1,
246299
SquareBracket,
247300
CurlyBracket,
248301
}
249302

250303
impl BlockType {
304+
#[inline]
305+
fn from_byte(b: u8) -> Option<BlockType> {
306+
let ty = match b {
307+
1 => BlockType::Parenthesis,
308+
2 => BlockType::SquareBracket,
309+
3 => BlockType::CurlyBracket,
310+
_ => return None,
311+
};
312+
debug_assert_eq!(ty as u8, b);
313+
Some(ty)
314+
}
315+
251316
fn closing(token: &Token) -> Option<BlockType> {
252-
match *token {
253-
Token::CloseParenthesis => Some(BlockType::Parenthesis),
254-
Token::CloseSquareBracket => Some(BlockType::SquareBracket),
255-
Token::CloseCurlyBracket => Some(BlockType::CurlyBracket),
256-
_ => None,
257-
}
317+
Some(match *token {
318+
Token::CloseParenthesis => BlockType::Parenthesis,
319+
Token::CloseSquareBracket => BlockType::SquareBracket,
320+
Token::CloseCurlyBracket => BlockType::CurlyBracket,
321+
_ => return None,
322+
})
258323
}
259324
}
260325

@@ -939,7 +1004,7 @@ pub fn parse_nested_block<'i, F, T, E>(
9391004
where
9401005
F: FnOnce(&mut Parser<'i>) -> Result<T, ParseError<E>>,
9411006
{
942-
let block_type = parser.state.at_start_of.take().expect(
1007+
let block_type = parser.state.take_at_start_of().expect(
9431008
"\
9441009
A nested parser can only be created when a Function, \
9451010
ParenthesisBlock, SquareBracketBlock, or CurlyBracketBlock \
@@ -980,7 +1045,7 @@ impl Parser<'_> {
9801045
// FIXME: have a special-purpose tokenizer method for this that does less work.
9811046
while !self.is_eof() {
9821047
let token = self.next_unchecked();
983-
if let Some(nested_block_type) = self.state.at_start_of.take() {
1048+
if let Some(nested_block_type) = self.state.take_at_start_of() {
9841049
stack.push(nested_block_type);
9851050
continue;
9861051
}

‎src/size_of_tests.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ size_of_test!(token, Token, 32);
4242
size_of_test!(std_cow_str, std::borrow::Cow<'static, str>, 24, 32);
4343
size_of_test!(cow_rc_str, CowRcStr, 16);
4444

45-
size_of_test!(parser, crate::parser::Parser, 168);
45+
size_of_test!(parser, crate::parser::Parser, 152);
4646
size_of_test!(source_position, crate::SourcePosition, 8);
47-
size_of_test!(parser_state, crate::ParserState, 24);
47+
size_of_test!(parser_state, crate::ParserState, 16);
4848

4949
size_of_test!(basic_parse_error, crate::BasicParseError, 1);
5050
size_of_test!(parse_error_lower_bound, crate::ParseError<()>, 1);

‎src/tokenizer.rs‎

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -238,14 +238,14 @@ impl<'a> Parser<'a> {
238238
/// Assumes non-EOF.
239239
#[inline]
240240
pub(crate) fn next_unchecked(&mut self) -> Token<'a> {
241-
debug_assert!(self.state.at_start_of.is_none());
241+
debug_assert!(self.state.at_start_of().is_none());
242242
next_token_unchecked(self)
243243
}
244244

245245
/// If the last token returned opened a block, skip until after the end of that block.
246246
#[inline]
247247
pub(crate) fn skip_block_at_start(&mut self) {
248-
if let Some(block_type) = self.state.at_start_of.take() {
248+
if let Some(block_type) = self.state.take_at_start_of() {
249249
self.consume_until_end_of_block(block_type);
250250
}
251251
}
@@ -456,8 +456,8 @@ impl<'a> Parser<'a> {
456456
if byte == b'\r' && self.next_byte() == Some(b'\n') {
457457
self.state.position += 1;
458458
}
459-
self.state.current_line_start_position = self.state.position;
460-
self.state.current_line_number += 1;
459+
self.state.current_line_start_position = self.state.position as u32;
460+
self.state.advance_line_number(1);
461461
}
462462

463463
#[inline]
@@ -476,7 +476,7 @@ impl<'a> Parser<'a> {
476476
self.state.current_line_start_position = self
477477
.state
478478
.current_line_start_position
479-
.wrapping_add(len_utf8 - c.len_utf16());
479+
.wrapping_add((len_utf8 - c.len_utf16()) as u32);
480480
c
481481
}
482482

@@ -602,7 +602,7 @@ fn next_token_unchecked<'a>(parser: &mut Parser<'a>) -> Token<'a> {
602602
b'\'' => consume_string(parser, true),
603603
b'(' => {
604604
parser.advance(1);
605-
parser.state.at_start_of = Some(BlockType::Parenthesis);
605+
parser.state.set_at_start_of(BlockType::Parenthesis);
606606
ParenthesisBlock
607607
},
608608
b')' => { parser.advance(1); CloseParenthesis },
@@ -683,7 +683,7 @@ fn next_token_unchecked<'a>(parser: &mut Parser<'a>) -> Token<'a> {
683683
b'a'..=b'z' | b'A'..=b'Z' | b'_' | b'\0' => consume_ident_like(parser),
684684
b'[' => {
685685
parser.advance(1);
686-
parser.state.at_start_of = Some(BlockType::SquareBracket);
686+
parser.state.set_at_start_of(BlockType::SquareBracket);
687687
SquareBracketBlock
688688
},
689689
b'\\' => {
@@ -697,7 +697,7 @@ fn next_token_unchecked<'a>(parser: &mut Parser<'a>) -> Token<'a> {
697697
},
698698
b'{' => {
699699
parser.advance(1);
700-
parser.state.at_start_of = Some(BlockType::CurlyBracket);
700+
parser.state.set_at_start_of(BlockType::CurlyBracket);
701701
CurlyBracketBlock
702702
},
703703
b'|' => {
@@ -945,7 +945,7 @@ fn consume_ident_like<'a>(parser: &mut Parser<'a>) -> Token<'a> {
945945
return url;
946946
}
947947
}
948-
parser.state.at_start_of = Some(BlockType::Parenthesis);
948+
parser.state.set_at_start_of(BlockType::Parenthesis);
949949
parser.arbitrary_substitution_functions.see_function(&value);
950950
Function(value)
951951
} else {
@@ -1215,10 +1215,10 @@ fn consume_unquoted_url<'a>(parser: &mut Parser<'a>) -> Result<Token<'a>, ()> {
12151215
}
12161216

12171217
if newlines > 0 {
1218-
parser.state.current_line_number += newlines;
1218+
parser.state.advance_line_number(newlines);
12191219
// No need for wrapping_add here, because there's no possible
12201220
// way to wrap.
1221-
parser.state.current_line_start_position = start_position + last_newline + 1;
1221+
parser.state.current_line_start_position = (start_position + last_newline + 1) as u32;
12221222
}
12231223

12241224
if found_printable_char {

0 commit comments

Comments
 (0)