Skip to content

Shrink ParseResult in the hot path. #106416

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions compiler/rustc_expand/src/mbe/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ impl BestFailure {
}

impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx, 'matcher> {
type Failure = (Token, usize, &'static str);

fn build_failure(tok: Token, position: usize, msg: &'static str) -> Self::Failure {
(tok, position, msg)
}

fn before_match_loc(&mut self, parser: &TtParser, matcher: &'matcher MatcherLoc) {
if self.remaining_matcher.is_none()
|| (parser.has_no_remaining_items_for_step() && *matcher != MatcherLoc::Eof)
Expand All @@ -122,7 +128,7 @@ impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx,
}
}

fn after_arm(&mut self, result: &NamedParseResult) {
fn after_arm(&mut self, result: &NamedParseResult<Self::Failure>) {
match result {
Success(_) => {
// Nonterminal parser recovery might turn failed matches into successful ones,
Expand All @@ -132,7 +138,7 @@ impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx,
"should not collect detailed info for successful macro match",
);
}
Failure(token, approx_position, msg) => {
Failure((token, approx_position, msg)) => {
debug!(?token, ?msg, "a new failure of an arm");

if self
Expand Down Expand Up @@ -175,6 +181,21 @@ impl<'a, 'cx> CollectTrackerAndEmitter<'a, 'cx, '_> {
}
}

/// Currently used by macro_rules! compilation to extract a little information from the `Failure` case.
pub struct FailureForwarder;

impl<'matcher> Tracker<'matcher> for FailureForwarder {
type Failure = (Token, usize, &'static str);

fn build_failure(tok: Token, position: usize, msg: &'static str) -> Self::Failure {
(tok, position, msg)
}

fn description() -> &'static str {
"failure-forwarder"
}
}

pub(super) fn emit_frag_parse_err(
mut e: DiagnosticBuilder<'_, rustc_errors::ErrorGuaranteed>,
parser: &Parser<'_>,
Expand Down
26 changes: 13 additions & 13 deletions compiler/rustc_expand/src/mbe/macro_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,13 +305,13 @@ enum EofMatcherPositions {
}

/// Represents the possible results of an attempted parse.
pub(crate) enum ParseResult<T> {
pub(crate) enum ParseResult<T, F> {
/// Parsed successfully.
Success(T),
/// Arm failed to match. If the second parameter 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.
Failure(Token, usize, &'static str),
Failure(F),
/// Fatal error (malformed macro?). Abort compilation.
Error(rustc_span::Span, String),
ErrorReported(ErrorGuaranteed),
Expand All @@ -320,7 +320,7 @@ pub(crate) enum ParseResult<T> {
/// A `ParseResult` where the `Success` variant contains a mapping of
/// `MacroRulesNormalizedIdent`s to `NamedMatch`es. This represents the mapping
/// of metavars to the token trees they bind to.
pub(crate) type NamedParseResult = ParseResult<NamedMatches>;
pub(crate) type NamedParseResult<F> = ParseResult<NamedMatches, F>;

/// Contains a mapping of `MacroRulesNormalizedIdent`s to `NamedMatch`es.
/// This represents the mapping of metavars to the token trees they bind to.
Expand Down Expand Up @@ -458,7 +458,7 @@ impl TtParser {
token: &Token,
approx_position: usize,
track: &mut T,
) -> Option<NamedParseResult> {
) -> Option<NamedParseResult<T::Failure>> {
// Matcher positions that would be valid if the macro invocation was over now. Only
// modified if `token == Eof`.
let mut eof_mps = EofMatcherPositions::None;
Expand Down Expand Up @@ -595,14 +595,14 @@ impl TtParser {
EofMatcherPositions::Multiple => {
Error(token.span, "ambiguity: multiple successful parses".to_string())
}
EofMatcherPositions::None => Failure(
EofMatcherPositions::None => Failure(T::build_failure(
Token::new(
token::Eof,
if token.span.is_dummy() { token.span } else { token.span.shrink_to_hi() },
),
approx_position,
"missing tokens in macro arguments",
),
)),
})
} else {
None
Expand All @@ -615,7 +615,7 @@ impl TtParser {
parser: &mut Cow<'_, Parser<'_>>,
matcher: &'matcher [MatcherLoc],
track: &mut T,
) -> NamedParseResult {
) -> NamedParseResult<T::Failure> {
// A queue of possible matcher positions. We initialize it with the matcher position in
// which the "dot" is before the first token of the first token tree in `matcher`.
// `parse_tt_inner` then processes all of these possible matcher positions and produces
Expand Down Expand Up @@ -648,11 +648,11 @@ impl TtParser {
(0, 0) => {
// There are no possible next positions AND we aren't waiting for the black-box
// parser: syntax error.
return Failure(
return Failure(T::build_failure(
parser.token.clone(),
parser.approx_token_stream_pos(),
"no rules expected this token in macro call",
);
));
}

(_, 0) => {
Expand Down Expand Up @@ -711,11 +711,11 @@ impl TtParser {
}
}

fn ambiguity_error(
fn ambiguity_error<F>(
&self,
matcher: &[MatcherLoc],
token_span: rustc_span::Span,
) -> NamedParseResult {
) -> NamedParseResult<F> {
let nts = self
.bb_mps
.iter()
Expand All @@ -741,11 +741,11 @@ impl TtParser {
)
}

fn nameize<I: Iterator<Item = NamedMatch>>(
fn nameize<I: Iterator<Item = NamedMatch>, F>(
&self,
matcher: &[MatcherLoc],
mut res: I,
) -> NamedParseResult {
) -> NamedParseResult<F> {
// Make that each metavar has _exactly one_ binding. If so, insert the binding into the
// `NamedParseResult`. Otherwise, it's an error.
let mut ret_val = FxHashMap::default();
Expand Down
60 changes: 45 additions & 15 deletions compiler/rustc_expand/src/mbe/macro_rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,31 +141,40 @@ fn trace_macros_note(cx_expansions: &mut FxIndexMap<Span, Vec<String>>, sp: Span
}

pub(super) trait Tracker<'matcher> {
/// The contents of `ParseResult::Failure`.
type Failure;

/// 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.
fn build_failure(tok: Token, position: usize, msg: &'static str) -> Self::Failure;

/// This is called before trying to match next MatcherLoc on the current token.
fn before_match_loc(&mut self, parser: &TtParser, matcher: &'matcher MatcherLoc);
fn before_match_loc(&mut self, _parser: &TtParser, _matcher: &'matcher MatcherLoc) {}

/// This is called after an arm has been parsed, either successfully or unsuccessfully. When this is called,
/// `before_match_loc` was called at least once (with a `MatcherLoc::Eof`).
fn after_arm(&mut self, result: &NamedParseResult);
fn after_arm(&mut self, _result: &NamedParseResult<Self::Failure>) {}

/// For tracing.
fn description() -> &'static str;

fn recovery() -> Recovery;
fn recovery() -> Recovery {
Recovery::Forbidden
}
}

/// A noop tracker that is used in the hot path of the expansion, has zero overhead thanks to monomorphization.
pub(super) struct NoopTracker;

impl<'matcher> Tracker<'matcher> for NoopTracker {
fn before_match_loc(&mut self, _: &TtParser, _: &'matcher MatcherLoc) {}
fn after_arm(&mut self, _: &NamedParseResult) {}
type Failure = ();

fn build_failure(_tok: Token, _position: usize, _msg: &'static str) -> Self::Failure {}

fn description() -> &'static str {
"none"
}
fn recovery() -> Recovery {
Recovery::Forbidden
}
}

/// Expands the rules based macro defined by `lhses` and `rhses` for a given
Expand Down Expand Up @@ -326,8 +335,8 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>(

return Ok((i, named_matches));
}
Failure(_, reached_position, _) => {
trace!(%reached_position, "Failed to match arm, trying the next one");
Failure(_) => {
trace!("Failed to match arm, trying the next one");
// Try the next arm.
}
Error(_, _) => {
Expand Down Expand Up @@ -381,11 +390,13 @@ pub fn compile_declarative_macro(
let rhs_nm = Ident::new(sym::rhs, def.span);
let tt_spec = Some(NonterminalKind::TT);

// Parse the macro_rules! invocation
let (macro_rules, body) = match &def.kind {
ast::ItemKind::MacroDef(def) => (def.macro_rules, def.body.tokens.clone()),
let macro_def = match &def.kind {
ast::ItemKind::MacroDef(def) => def,
_ => unreachable!(),
};
let macro_rules = macro_def.macro_rules;

// Parse the macro_rules! invocation

// The pattern that macro_rules matches.
// The grammar for macro_rules! is:
Expand Down Expand Up @@ -426,13 +437,32 @@ pub fn compile_declarative_macro(
// Convert it into `MatcherLoc` form.
let argument_gram = mbe::macro_parser::compute_locs(&argument_gram);

let parser = Parser::new(&sess.parse_sess, body, true, rustc_parse::MACRO_ARGUMENTS);
let create_parser = || {
let body = macro_def.body.tokens.clone();
Parser::new(&sess.parse_sess, body, true, rustc_parse::MACRO_ARGUMENTS)
};

let parser = create_parser();
let mut tt_parser =
TtParser::new(Ident::with_dummy_span(if macro_rules { kw::MacroRules } else { kw::Macro }));
let argument_map =
match tt_parser.parse_tt(&mut Cow::Owned(parser), &argument_gram, &mut NoopTracker) {
Success(m) => m,
Failure(token, _, msg) => {
Failure(()) => {
// The fast `NoopTracker` doesn't have any info on failure, so we need to retry it with another one
// that gives us the information we need.
// For this we need to reclone the macro body as the previous parser consumed it.
let retry_parser = create_parser();

let parse_result = tt_parser.parse_tt(
&mut Cow::Owned(retry_parser),
&argument_gram,
&mut diagnostics::FailureForwarder,
);
let Failure((token, _, msg)) = parse_result else {
unreachable!("matcher returned something other than Failure after retry");
};

let s = parse_failure_msg(&token);
let sp = token.span.substitute_dummy(def.span);
let mut err = sess.parse_sess.span_diagnostic.struct_span_err(sp, &s);
Expand Down