diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index a80842c8def18..44951c18d4051 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -169,6 +169,8 @@ impl BestFailure { impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'matcher> { type Failure = (Token, u32, &'static str); + const NEEDS_TRACKING: bool = true; + fn build_failure(tok: Token, position: u32, msg: &'static str) -> Self::Failure { (tok, position, msg) } diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index b8325e3ce7756..69b9cdacc5ef1 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -447,6 +447,49 @@ pub(crate) struct TtParser { empty_matches: Rc>, } + +/// Matches a run of literal-token (and delimiter-marker) matcher locations +/// directly against the input, advancing `mp` and the parser past every +/// matched token. Stops at input Eof or at the first location the queue +/// machinery must handle; a literal mismatch is a definitive failure of the +/// arm, returned as `Err`. Callers ensure `mp` is the only live position and +/// that the tracker does not need to observe individual locations. +fn match_literal_run<'matcher, T: Tracker<'matcher>>( + mp: &mut MatcherPos, + parser: &mut Cow<'_, Parser<'_>>, + matcher: &'matcher [MatcherLoc], +) -> Result<(), NamedParseResult> { + loop { + if parser.token == token::Eof { + return Ok(()); + } + match &matcher[mp.idx] { + MatcherLoc::Token { token: t } => { + // Doc comments in the matcher are skipped, see `parse_tt_inner`. + if matches!(t, Token { kind: DocComment(..), .. }) { + mp.idx += 1; + } else if token_name_eq(t, &parser.token) { + mp.idx += 1; + parser.to_mut().bump(); + } else { + // The only position failed on a literal token, so the arm + // cannot match. + return Err(Failure(T::build_failure( + parser.token, + parser.approx_token_stream_pos(), + "no rules expected this token in macro call", + ))); + } + } + MatcherLoc::Delimited => { + // Entering the delimiter is trivial. + mp.idx += 1; + } + _ => return Ok(()), + } + } +} + impl TtParser { pub(super) fn new(macro_name: Ident) -> TtParser { TtParser { @@ -635,6 +678,15 @@ impl TtParser { self.cur_mps.clear(); self.cur_mps.push(MatcherPos { idx: 0, matches: Rc::clone(&self.empty_matches) }); + // Match any leading run of literal-token locations directly; matchers + // that fail on a leading literal (the common case when a macro tries + // its rules in order) never enter the queue machinery at all. + if !T::NEEDS_TRACKING + && let Err(failure) = match_literal_run::(&mut self.cur_mps[0], parser, matcher) + { + return failure; + } + loop { self.next_mps.clear(); self.bb_mps.clear(); @@ -667,6 +719,20 @@ impl TtParser { )); } + (1, 0) if !T::NEEDS_TRACKING => { + // A single next position: advance past the matched token and, if + // the position sits at a run of literal-token (and delimiter- + // marker) locations, match the whole run directly against the + // input without going through the queue machinery. Literal-heavy + // matchers spend most of their steps in this state. + let mut mp = self.next_mps.pop().unwrap(); + parser.to_mut().bump(); + if let Err(failure) = match_literal_run::(&mut mp, parser, matcher) { + return failure; + } + self.cur_mps.push(mp); + } + (_, 0) => { // Dump all possible `next_mps` into `cur_mps` for the next iteration. Then // process the next token. diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index ef0404d0a8fbe..4b8c301c0020c 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -334,6 +334,11 @@ pub(super) trait Tracker<'matcher> { /// The contents of `ParseResult::Failure`. type Failure; + /// Whether this tracker needs to observe every matcher location via + /// `before_match_loc`. When `false`, the matcher may take fast paths that + /// skip those callbacks. + const NEEDS_TRACKING: bool; + /// Arm failed to match. If the token is `token::Eof`, it indicates an unexpected /// end of macro invocation. Otherwise, it indicates that no rules expected the given token. /// The usize is the approximate position of the token in the input token stream. @@ -361,6 +366,8 @@ pub(super) struct NoopTracker; impl<'matcher> Tracker<'matcher> for NoopTracker { type Failure = (); + const NEEDS_TRACKING: bool = false; + fn build_failure(_tok: Token, _position: u32, _msg: &'static str) -> Self::Failure {} fn description() -> &'static str { diff --git a/compiler/rustc_expand/src/mbe/transcribe.rs b/compiler/rustc_expand/src/mbe/transcribe.rs index a8a49dff88dcb..89003259101a7 100644 --- a/compiler/rustc_expand/src/mbe/transcribe.rs +++ b/compiler/rustc_expand/src/mbe/transcribe.rs @@ -85,6 +85,10 @@ impl<'psess> TranscrCtx<'psess, '_> { struct Marker { expand_id: LocalExpnId, transparency: Transparency, + /// One-entry cache in front of `cache`: virtually all tokens in a macro + /// body share one syntax context, so this hits on a plain compare without + /// hashing. + last: Option<(SyntaxContext, SyntaxContext)>, cache: FxHashMap, } @@ -96,10 +100,17 @@ impl Marker { // it's some advanced case with macro-generated macros. So if we cache the marked version // of that context once, we'll typically have a 100% cache hit rate after that. *span = span.map_ctxt(|ctxt| { - *self + if let Some((from, to)) = self.last + && from == ctxt + { + return to; + } + let to = *self .cache .entry(ctxt) - .or_insert_with(|| ctxt.apply_mark(self.expand_id.to_expn_id(), self.transparency)) + .or_insert_with(|| ctxt.apply_mark(self.expand_id.to_expn_id(), self.transparency)); + self.last = Some((ctxt, to)); + to }); } } @@ -370,7 +381,7 @@ pub(super) fn transcribe<'a>( let mut tscx = TranscrCtx { psess, interp, - marker: Marker { expand_id, transparency, cache: Default::default() }, + marker: Marker { expand_id, transparency, last: None, cache: Default::default() }, repeats: Vec::new(), lookup_cache: SmallVec::new(), stack: smallvec![ExecFrame::Root { segs: &template.segs, idx: 0 }],