Skip to content

Commit 4c26e2e

Browse files
committed
Auto merge of #50855 - nnethercote:fewer-macro_parser-allocs, r=petrochenkov
Speed up the macro parser These three commits reduce the number of allocations done by the macro parser, in some cases dramatically. For example, for a clean check builds of html5ever, the number of allocations is reduced by 40%. Here are the rustc-benchmarks that are sped up by at least 1%. ``` html5ever-check avg: -6.6% min: -10.3% max: -4.1% html5ever avg: -5.2% min: -9.5% max: -2.8% html5ever-opt avg: -4.3% min: -9.3% max: -1.6% crates.io-check avg: -1.8% min: -2.9% max: -0.6% crates.io-opt avg: -1.0% min: -2.2% max: -0.1% crates.io avg: -1.1% min: -2.2% max: -0.2% ```
2 parents ccb5e97 + ad47145 commit 4c26e2e

File tree

5 files changed

+87
-35
lines changed

5 files changed

+87
-35
lines changed

src/libsyntax/ext/tt/macro_parser.rs

+71-23
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
8383
pub use self::NamedMatch::*;
8484
pub use self::ParseResult::*;
85-
use self::TokenTreeOrTokenTreeVec::*;
85+
use self::TokenTreeOrTokenTreeSlice::*;
8686

8787
use ast::Ident;
8888
use syntax_pos::{self, BytePos, Span};
@@ -97,6 +97,7 @@ use tokenstream::TokenStream;
9797
use util::small_vector::SmallVector;
9898

9999
use std::mem;
100+
use std::ops::{Deref, DerefMut};
100101
use std::rc::Rc;
101102
use std::collections::HashMap;
102103
use std::collections::hash_map::Entry::{Occupied, Vacant};
@@ -106,12 +107,12 @@ use std::collections::hash_map::Entry::{Occupied, Vacant};
106107
/// Either a sequence of token trees or a single one. This is used as the representation of the
107108
/// sequence of tokens that make up a matcher.
108109
#[derive(Clone)]
109-
enum TokenTreeOrTokenTreeVec {
110+
enum TokenTreeOrTokenTreeSlice<'a> {
110111
Tt(TokenTree),
111-
TtSeq(Vec<TokenTree>),
112+
TtSeq(&'a [TokenTree]),
112113
}
113114

114-
impl TokenTreeOrTokenTreeVec {
115+
impl<'a> TokenTreeOrTokenTreeSlice<'a> {
115116
/// Returns the number of constituent top-level token trees of `self` (top-level in that it
116117
/// will not recursively descend into subtrees).
117118
fn len(&self) -> usize {
@@ -135,19 +136,19 @@ impl TokenTreeOrTokenTreeVec {
135136
/// This is used by `inner_parse_loop` to keep track of delimited submatchers that we have
136137
/// descended into.
137138
#[derive(Clone)]
138-
struct MatcherTtFrame {
139+
struct MatcherTtFrame<'a> {
139140
/// The "parent" matcher that we are descending into.
140-
elts: TokenTreeOrTokenTreeVec,
141+
elts: TokenTreeOrTokenTreeSlice<'a>,
141142
/// The position of the "dot" in `elts` at the time we descended.
142143
idx: usize,
143144
}
144145

145146
/// Represents a single "position" (aka "matcher position", aka "item"), as described in the module
146147
/// documentation.
147148
#[derive(Clone)]
148-
struct MatcherPos {
149+
struct MatcherPos<'a> {
149150
/// The token or sequence of tokens that make up the matcher
150-
top_elts: TokenTreeOrTokenTreeVec,
151+
top_elts: TokenTreeOrTokenTreeSlice<'a>,
151152
/// The position of the "dot" in this matcher
152153
idx: usize,
153154
/// The beginning position in the source that the beginning of this matcher corresponds to. In
@@ -186,7 +187,7 @@ struct MatcherPos {
186187
sep: Option<Token>,
187188
/// The "parent" matcher position if we are in a repetition. That is, the matcher position just
188189
/// before we enter the sequence.
189-
up: Option<Box<MatcherPos>>,
190+
up: Option<MatcherPosHandle<'a>>,
190191

191192
// Specifically used to "unzip" token trees. By "unzip", we mean to unwrap the delimiters from
192193
// a delimited token tree (e.g. something wrapped in `(` `)`) or to get the contents of a doc
@@ -195,17 +196,60 @@ struct MatcherPos {
195196
/// pat ) pat`), we need to keep track of the matchers we are descending into. This stack does
196197
/// that where the bottom of the stack is the outermost matcher.
197198
// Also, throughout the comments, this "descent" is often referred to as "unzipping"...
198-
stack: Vec<MatcherTtFrame>,
199+
stack: Vec<MatcherTtFrame<'a>>,
199200
}
200201

201-
impl MatcherPos {
202+
impl<'a> MatcherPos<'a> {
202203
/// Add `m` as a named match for the `idx`-th metavar.
203204
fn push_match(&mut self, idx: usize, m: NamedMatch) {
204205
let matches = Rc::make_mut(&mut self.matches[idx]);
205206
matches.push(m);
206207
}
207208
}
208209

210+
// Lots of MatcherPos instances are created at runtime. Allocating them on the
211+
// heap is slow. Furthermore, using SmallVec<MatcherPos> to allocate them all
212+
// on the stack is also slow, because MatcherPos is quite a large type and
213+
// instances get moved around a lot between vectors, which requires lots of
214+
// slow memcpy calls.
215+
//
216+
// Therefore, the initial MatcherPos is always allocated on the stack,
217+
// subsequent ones (of which there aren't that many) are allocated on the heap,
218+
// and this type is used to encapsulate both cases.
219+
enum MatcherPosHandle<'a> {
220+
Ref(&'a mut MatcherPos<'a>),
221+
Box(Box<MatcherPos<'a>>),
222+
}
223+
224+
impl<'a> Clone for MatcherPosHandle<'a> {
225+
// This always produces a new Box.
226+
fn clone(&self) -> Self {
227+
MatcherPosHandle::Box(match *self {
228+
MatcherPosHandle::Ref(ref r) => Box::new((**r).clone()),
229+
MatcherPosHandle::Box(ref b) => b.clone(),
230+
})
231+
}
232+
}
233+
234+
impl<'a> Deref for MatcherPosHandle<'a> {
235+
type Target = MatcherPos<'a>;
236+
fn deref(&self) -> &Self::Target {
237+
match *self {
238+
MatcherPosHandle::Ref(ref r) => r,
239+
MatcherPosHandle::Box(ref b) => b,
240+
}
241+
}
242+
}
243+
244+
impl<'a> DerefMut for MatcherPosHandle<'a> {
245+
fn deref_mut(&mut self) -> &mut MatcherPos<'a> {
246+
match *self {
247+
MatcherPosHandle::Ref(ref mut r) => r,
248+
MatcherPosHandle::Box(ref mut b) => b,
249+
}
250+
}
251+
}
252+
209253
/// Represents the possible results of an attempted parse.
210254
pub enum ParseResult<T> {
211255
/// Parsed successfully.
@@ -241,10 +285,10 @@ fn create_matches(len: usize) -> Vec<Rc<Vec<NamedMatch>>> {
241285

242286
/// Generate the top-level matcher position in which the "dot" is before the first token of the
243287
/// matcher `ms` and we are going to start matching at position `lo` in the source.
244-
fn initial_matcher_pos(ms: Vec<TokenTree>, lo: BytePos) -> Box<MatcherPos> {
245-
let match_idx_hi = count_names(&ms[..]);
288+
fn initial_matcher_pos(ms: &[TokenTree], lo: BytePos) -> MatcherPos {
289+
let match_idx_hi = count_names(ms);
246290
let matches = create_matches(match_idx_hi);
247-
Box::new(MatcherPos {
291+
MatcherPos {
248292
// Start with the top level matcher given to us
249293
top_elts: TtSeq(ms), // "elts" is an abbr. for "elements"
250294
// The "dot" is before the first token of the matcher
@@ -267,7 +311,7 @@ fn initial_matcher_pos(ms: Vec<TokenTree>, lo: BytePos) -> Box<MatcherPos> {
267311
seq_op: None,
268312
sep: None,
269313
up: None,
270-
})
314+
}
271315
}
272316

273317
/// `NamedMatch` is a pattern-match result for a single `token::MATCH_NONTERMINAL`:
@@ -394,12 +438,12 @@ fn token_name_eq(t1: &Token, t2: &Token) -> bool {
394438
/// # Returns
395439
///
396440
/// A `ParseResult`. Note that matches are kept track of through the items generated.
397-
fn inner_parse_loop(
441+
fn inner_parse_loop<'a>(
398442
sess: &ParseSess,
399-
cur_items: &mut SmallVector<Box<MatcherPos>>,
400-
next_items: &mut Vec<Box<MatcherPos>>,
401-
eof_items: &mut SmallVector<Box<MatcherPos>>,
402-
bb_items: &mut SmallVector<Box<MatcherPos>>,
443+
cur_items: &mut SmallVector<MatcherPosHandle<'a>>,
444+
next_items: &mut Vec<MatcherPosHandle<'a>>,
445+
eof_items: &mut SmallVector<MatcherPosHandle<'a>>,
446+
bb_items: &mut SmallVector<MatcherPosHandle<'a>>,
403447
token: &Token,
404448
span: syntax_pos::Span,
405449
) -> ParseResult<()> {
@@ -502,7 +546,7 @@ fn inner_parse_loop(
502546
}
503547

504548
let matches = create_matches(item.matches.len());
505-
cur_items.push(Box::new(MatcherPos {
549+
cur_items.push(MatcherPosHandle::Box(Box::new(MatcherPos {
506550
stack: vec![],
507551
sep: seq.separator.clone(),
508552
seq_op: Some(seq.op),
@@ -514,7 +558,7 @@ fn inner_parse_loop(
514558
up: Some(item),
515559
sp_lo: sp.lo(),
516560
top_elts: Tt(TokenTree::Sequence(sp, seq)),
517-
}));
561+
})));
518562
}
519563

520564
// We need to match a metavar (but the identifier is invalid)... this is an error
@@ -596,7 +640,11 @@ pub fn parse(
596640
// processes all of these possible matcher positions and produces posible next positions into
597641
// `next_items`. After some post-processing, the contents of `next_items` replenish `cur_items`
598642
// and we start over again.
599-
let mut cur_items = SmallVector::one(initial_matcher_pos(ms.to_owned(), parser.span.lo()));
643+
//
644+
// This MatcherPos instance is allocated on the stack. All others -- and
645+
// there are frequently *no* others! -- are allocated on the heap.
646+
let mut initial = initial_matcher_pos(ms, parser.span.lo());
647+
let mut cur_items = SmallVector::one(MatcherPosHandle::Ref(&mut initial));
600648
let mut next_items = Vec::new();
601649

602650
loop {

src/libsyntax/ext/tt/macro_rules.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use parse::token::Token::*;
2727
use symbol::Symbol;
2828
use tokenstream::{TokenStream, TokenTree};
2929

30+
use std::borrow::Cow;
3031
use std::collections::HashMap;
3132
use std::collections::hash_map::Entry;
3233

@@ -142,7 +143,7 @@ fn generic_extension<'cx>(cx: &'cx mut ExtCtxt,
142143
}
143144

144145
let directory = Directory {
145-
path: cx.current_expansion.module.directory.clone(),
146+
path: Cow::from(cx.current_expansion.module.directory.as_path()),
146147
ownership: cx.current_expansion.directory_ownership,
147148
};
148149
let mut p = Parser::new(cx.parse_sess(), tts, Some(directory), true, false);

src/libsyntax/parse/mod.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use symbol::Symbol;
2323
use tokenstream::{TokenStream, TokenTree};
2424
use diagnostics::plugin::ErrorMap;
2525

26+
use std::borrow::Cow;
2627
use std::collections::HashSet;
2728
use std::iter;
2829
use std::path::{Path, PathBuf};
@@ -89,8 +90,8 @@ impl ParseSess {
8990
}
9091

9192
#[derive(Clone)]
92-
pub struct Directory {
93-
pub path: PathBuf,
93+
pub struct Directory<'a> {
94+
pub path: Cow<'a, Path>,
9495
pub ownership: DirectoryOwnership,
9596
}
9697

src/libsyntax/parse/parser.rs

+9-8
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ use tokenstream::{self, Delimited, ThinTokenStream, TokenTree, TokenStream};
5757
use symbol::{Symbol, keywords};
5858
use util::ThinVec;
5959

60+
use std::borrow::Cow;
6061
use std::cmp;
6162
use std::mem;
6263
use std::path::{self, Path, PathBuf};
@@ -228,7 +229,7 @@ pub struct Parser<'a> {
228229
prev_token_kind: PrevTokenKind,
229230
pub restrictions: Restrictions,
230231
/// Used to determine the path to externally loaded source files
231-
pub directory: Directory,
232+
pub directory: Directory<'a>,
232233
/// Whether to parse sub-modules in other files.
233234
pub recurse_into_file_modules: bool,
234235
/// Name of the root module this parser originated from. If `None`, then the
@@ -535,7 +536,7 @@ enum TokenExpectType {
535536
impl<'a> Parser<'a> {
536537
pub fn new(sess: &'a ParseSess,
537538
tokens: TokenStream,
538-
directory: Option<Directory>,
539+
directory: Option<Directory<'a>>,
539540
recurse_into_file_modules: bool,
540541
desugar_doc_comments: bool)
541542
-> Self {
@@ -549,7 +550,7 @@ impl<'a> Parser<'a> {
549550
restrictions: Restrictions::empty(),
550551
recurse_into_file_modules,
551552
directory: Directory {
552-
path: PathBuf::new(),
553+
path: Cow::from(PathBuf::new()),
553554
ownership: DirectoryOwnership::Owned { relative: None }
554555
},
555556
root_module_name: None,
@@ -572,9 +573,9 @@ impl<'a> Parser<'a> {
572573
if let Some(directory) = directory {
573574
parser.directory = directory;
574575
} else if !parser.span.source_equal(&DUMMY_SP) {
575-
if let FileName::Real(path) = sess.codemap().span_to_unmapped_path(parser.span) {
576-
parser.directory.path = path;
577-
parser.directory.path.pop();
576+
if let FileName::Real(mut path) = sess.codemap().span_to_unmapped_path(parser.span) {
577+
path.pop();
578+
parser.directory.path = Cow::from(path);
578579
}
579580
}
580581

@@ -6008,10 +6009,10 @@ impl<'a> Parser<'a> {
60086009

60096010
fn push_directory(&mut self, id: Ident, attrs: &[Attribute]) {
60106011
if let Some(path) = attr::first_attr_value_str_by_name(attrs, "path") {
6011-
self.directory.path.push(&path.as_str());
6012+
self.directory.path.to_mut().push(&path.as_str());
60126013
self.directory.ownership = DirectoryOwnership::Owned { relative: None };
60136014
} else {
6014-
self.directory.path.push(&id.name.as_str());
6015+
self.directory.path.to_mut().push(&id.name.as_str());
60156016
}
60166017
}
60176018

src/libsyntax/tokenstream.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use print::pprust;
3131
use serialize::{Decoder, Decodable, Encoder, Encodable};
3232
use util::RcSlice;
3333

34+
use std::borrow::Cow;
3435
use std::{fmt, iter, mem};
3536
use std::hash::{self, Hash};
3637

@@ -106,7 +107,7 @@ impl TokenTree {
106107
-> macro_parser::NamedParseResult {
107108
// `None` is because we're not interpolating
108109
let directory = Directory {
109-
path: cx.current_expansion.module.directory.clone(),
110+
path: Cow::from(cx.current_expansion.module.directory.as_path()),
110111
ownership: cx.current_expansion.directory_ownership,
111112
};
112113
macro_parser::parse(cx.parse_sess(), tts, mtch, Some(directory), true)

0 commit comments

Comments
 (0)