Preparatory changes for macro parsing BFS->DFS#158974
Conversation
|
The parser was modified, potentially altering the grammar of (stable) Rust cc @fmease |
|
Thanks for the pull request, and welcome! The Rust Project is excited to review your changes, and you should hear from @nnethercote (or someone else) some time within the next two weeks. Please see the contribution instructions for more information. Namely, in order to ensure the minimum review times lag, PR authors and assigned reviewers should ensure that the review label (
|
Could you tell more, what is the BFS->DFS change and its motivation and goals? |
|
Hey @petrochenkov! I initially got interested in the macro parser when I learned about degenerate cases causing exponential-time (and exponential-space) complexity here. I wrote a blog post about it with more details: https://bal-e.org/blog/2026/oops-cubic-macro/. In short, the macro parser explores ambiguity using a BFS, but it only cares about one result (if there are other results, it only cares about their existence, not the data within; at least on the fast path). A DFS feels simpler here:
I'm quite confident the macro parser can be changed to DFS without affecting user behavior (#158894 added a user-visible change, but only for a minor detail in error messages). In any case, I'll write about it more when I get to the main PR. I think these preparatory PRs are worth merging independently of the BFS->DFS change. Happy to answer in more detail too :) |
|
@bors try @rust-timer queue |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Preparatory changes for macro parsing BFS->DFS
This comment has been minimized.
This comment has been minimized.
|
Finished benchmarking commit (71875d2): comparison URL. Overall result: ❌✅ regressions and improvements - please read:Benchmarking means the PR may be perf-sensitive. It's automatically marked not fit for rolling up. Overriding is possible but disadvised: it risks changing compiler perf. Next, please: If you can, justify the regressions found in this try perf run in writing along with @bors rollup=never Instruction countOur most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.
Max RSS (memory usage)Results (primary 0.8%, secondary 4.8%)A less reliable metric. May be of interest, but not used to determine the overall result above.
CyclesResults (primary -2.6%, secondary -6.9%)A less reliable metric. May be of interest, but not used to determine the overall result above.
Binary sizeThis perf run didn't have relevant results for this metric. Bootstrap: 489.147s -> 487.834s (-0.27%) |
In the next commit, the work done in `Tracker::after_arm()` upon a `Failure` will be moved into `Tracker::build_failure()`. For this to work, the `WhichMatcher` parameter to `after_arm()` needs to be available to `build_failure()`. Rather than threading it through `TtParser`, this commit stores it in `CollectTrackerAndEmitter` and keeps it updated using a new `Tracker::prepare()` method. `tests/ui/macros` pass.
Rather than preparing a failure using `Tracker::build_failure()` and consuming it from `Tracker::after_arm()`, the parser now calls `Tracker::failure()` the moment a failure is detected, and this will eagerly perform all the necessary processing. This makes the contents of `Failure` redundant, so they can be removed in the next commit.
Now that everything is processed in `Tracker::failure()`, this data does not need to be propagated. This also removes some generics.
The appropriate token (span) and the diagnostic message are now computed in `diagnostics.rs`. Following this commit, _all_ diagnostic messages have been moved from `macro_parser.rs` to `diagnostics.rs`. Yay!
It is being used by `Parser::nonterminal_may_begin_with()`.
This commit deliberately causes panics. See the next commit for more details and a fix.
Until now, `:tt`, `:item`, and `:stmt` metavars could be attempted against `token::Eof`. *HOWEVER*, when a `token::Eof` is processed, `parse_tt_inner()` ignores `bb_mps`, not even checking for ambiguity. It's as if those `bb_mps` never existed; as if `nonterminal_may_begin_with()` returned `false` for `Eof`. This commit makes that behavior more explicit. This does not change user visible behavior. `tests/ui/macros` pass.
The previous commit fixed a case where some meta-variables were being treated as if they *could* match `token::Eof` and then silently ignored. This commit adds a test to ensure meta-variables continue to have a consistent response to an `Eof` (in theory, they could vary between fatal non-terminal parsing errors and non-fatal matching failures).
|
💔 Test for f402c2a failed: CI. Failed job:
|
|
@bors retry |
This comment has been minimized.
This comment has been minimized.
Preparatory changes for macro parsing BFS->DFS This PR contains some miscellaneous commits I accumulated while working on the BFS->DFS change. Their goal is to simplify the code and clarifying existing behavior. It is a conceptual follow-up to #158577. High-level overview: - Adds context about the current match arm to `Tracker` through the new `Tracker::prepare()`, so that the `WhichMatcher` parameter is available implicitly. In a later PR, this will be used to reference `MatcherLoc`s by index. - Reformulates matching failures to work more like ambiguity errors wrt. `Tracker`; `Tracker::build_failure()` (which would build a failure consumed by `Tracker::after_arm()`) becomes `Tracker::failure()` which eagerly processes the error. This removes the need for `ParseResult::Failure` to store any data at all. This relies on the match arm context provided by `Tracker::prepare()`. - There is a subtle edge case involving `token::Eof` and non-terminal parsing; `Parser::nonterminal_may_begin_with()` would sometimes return `true` for `token::Eof`, even though the non-terminal parse would never be attempted. `TtParser` did not check `bb_mps` when handling `token::Eof`, so non-terminal parses at EOF were being silently dropped. I changed `Parser::nonterminal_may_begin_with()` to always return `false` for `token::Eof`, making this behavior much more visible. I added a test case to make sure meta-variables return the same error uniformly when parsed against EOF. Contains #158894, which should be merged soon (after which I'll rebase onto `main`). Best reviewed commit-by-commit. r? @nnethercote
|
💔 Test for ea14f2e failed: CI. Failed job:
|
|
The job Click to see the possible cause of the failure (guessed by this bot) |
This comment has been minimized.
This comment has been minimized.
Preparatory changes for macro parsing BFS->DFS try-job: i686-gnu-nopt-1
|
@bors p=6 |
This comment has been minimized.
This comment has been minimized.
What is this?This is an experimental post-merge analysis report that shows differences in test outcomes between the merged PR and its parent PR.Comparing f0b782b (parent) -> 77cf889 (this PR) Test differencesShow 5 test diffsStage 1
Stage 2
Additionally, 2 doctest diffs were found. These are ignored, as they are noisy. Job group index
Test dashboardRun cargo run --manifest-path src/ci/citool/Cargo.toml -- \
test-dashboard 77cf889bc178ddb44d6a1c78e5a820b5abb31d8d --output-dir test-dashboardAnd then open Job duration changes
How to interpret the job duration changes?Job durations can vary a lot, based on the actual runner instance |
|
Finished benchmarking commit (77cf889): comparison URL. Overall result: ❌✅ regressions and improvements - please read:Our benchmarks found a performance regression caused by this PR. Next Steps:
@rustbot label: +perf-regression Instruction countOur most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.
Max RSS (memory usage)Results (primary 2.0%, secondary 1.1%)A less reliable metric. May be of interest, but not used to determine the overall result above.
CyclesResults (secondary -0.0%)A less reliable metric. May be of interest, but not used to determine the overall result above.
Binary sizeThis perf run didn't have relevant results for this metric. Bootstrap: 489.629s -> 489.838s (0.04%) |
Eagerly check for ambiguity in macro parsing This PR implements an important step leading up to the BFS->DFS change: it makes ambiguity detection (as occurs when parsing meta-variables and reaching EOF) eager. Rather than accumulating the `bb_mps` and `eof_mps` lists, then checking that a single valid parse exists, this PR introduces a `check_for_ambiguity()` method that gets called as soon as the relevant `MatcherLoc` is observed. `check_for_ambiguity()` immediately drains `cur_mps` instead of waiting for the outer loop to do so, and evaluates the mps within a "checking for ambiguity" context. This change was complicated by the fact that `bb_mps` was relied on for error messages; it was passed along to `Tracker::ambiguity()`. The same is true for `next_mps`. The first few commits of this PR (after pending rebases) add state to `Tracker` so that it can compute the relevant diagnostic information independently of `bb_mps`. This PR complicates the control flow around `parse_tt_inner()` in subtle ways. I'm not super happy about it, but I think it will become easier to understand again once a DFS approach is implemented. ~This PR contains #158894 and #158974. I'll rebase on top of `main` once they're both merged.~ Best reviewed commit-by-commit. r? @nnethercote
Eagerly check for ambiguity in macro parsing This PR implements an important step leading up to the BFS->DFS change: it makes ambiguity detection (as occurs when parsing meta-variables and reaching EOF) eager. Rather than accumulating the `bb_mps` and `eof_mps` lists, then checking that a single valid parse exists, this PR introduces a `check_for_ambiguity()` method that gets called as soon as the relevant `MatcherLoc` is observed. `check_for_ambiguity()` immediately drains `cur_mps` instead of waiting for the outer loop to do so, and evaluates the mps within a "checking for ambiguity" context. This change was complicated by the fact that `bb_mps` was relied on for error messages; it was passed along to `Tracker::ambiguity()`. The same is true for `next_mps`. The first few commits of this PR (after pending rebases) add state to `Tracker` so that it can compute the relevant diagnostic information independently of `bb_mps`. This PR complicates the control flow around `parse_tt_inner()` in subtle ways. I'm not super happy about it, but I think it will become easier to understand again once a DFS approach is implemented. ~This PR contains rust-lang/rust#158894 and rust-lang/rust#158974. I'll rebase on top of `main` once they're both merged.~ Best reviewed commit-by-commit. r? @nnethercote
Eagerly check for ambiguity in macro parsing This PR implements an important step leading up to the BFS->DFS change: it makes ambiguity detection (as occurs when parsing meta-variables and reaching EOF) eager. Rather than accumulating the `bb_mps` and `eof_mps` lists, then checking that a single valid parse exists, this PR introduces a `check_for_ambiguity()` method that gets called as soon as the relevant `MatcherLoc` is observed. `check_for_ambiguity()` immediately drains `cur_mps` instead of waiting for the outer loop to do so, and evaluates the mps within a "checking for ambiguity" context. This change was complicated by the fact that `bb_mps` was relied on for error messages; it was passed along to `Tracker::ambiguity()`. The same is true for `next_mps`. The first few commits of this PR (after pending rebases) add state to `Tracker` so that it can compute the relevant diagnostic information independently of `bb_mps`. This PR complicates the control flow around `parse_tt_inner()` in subtle ways. I'm not super happy about it, but I think it will become easier to understand again once a DFS approach is implemented. ~This PR contains rust-lang/rust#158894 and rust-lang/rust#158974. I'll rebase on top of `main` once they're both merged.~ Best reviewed commit-by-commit. r? @nnethercote
View all comments
This PR contains some miscellaneous commits I accumulated while working on the BFS->DFS change. Their goal is to simplify the code and clarifying existing behavior. It is a conceptual follow-up to #158577. High-level overview:
Adds context about the current match arm to
Trackerthrough the newTracker::prepare(), so that theWhichMatcherparameter is available implicitly. In a later PR, this will be used to referenceMatcherLocs by index.Reformulates matching failures to work more like ambiguity errors wrt.
Tracker;Tracker::build_failure()(which would build a failure consumed byTracker::after_arm()) becomesTracker::failure()which eagerly processes the error. This removes the need forParseResult::Failureto store any data at all. This relies on the match arm context provided byTracker::prepare().There is a subtle edge case involving
token::Eofand non-terminal parsing;Parser::nonterminal_may_begin_with()would sometimes returntruefortoken::Eof, even though the non-terminal parse would never be attempted.TtParserdid not checkbb_mpswhen handlingtoken::Eof, so non-terminal parses at EOF were being silently dropped. I changedParser::nonterminal_may_begin_with()to always returnfalsefortoken::Eof, making this behavior much more visible. I added a test case to make sure meta-variables return the same error uniformly when parsed against EOF.Contains #158894, which should be merged soon (after which I'll rebase onto
main).Best reviewed commit-by-commit.
r? @nnethercote