Skip to content

Add #[loop_match] for improved DFA codegen #138780

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
Jun 25, 2025

Conversation

folkertdev
Copy link
Contributor

@folkertdev folkertdev commented Mar 21, 2025

tracking issue: #132306
project goal: rust-lang/rust-project-goals#258

This PR adds the #[loop_match] attribute, which aims to improve code generation for state machines. For some (very exciting) benchmarks, see rust-lang/rust-project-goals#258 (comment)

Currently, a very restricted syntax pattern is accepted. We'd like to get feedback and merge this now before we go too far in a direction that others have concerns with.

current state

We accept code that looks like this

#[loop_match]
loop {
    state = 'blk: {
        match state {
            State::A => {
                #[const_continue]
                break 'blk State::B
            }
            State::B => { /* ... */ }
            /* ... */
        }
    }
}
  • a loop should have the same semantics with and without #[loop_match]: normal continue and break continue to work
  • #[const_continue] is only allowed in loops annotated with #[loop_match]
  • the loop body needs to have this particular shape (a single assignment to the match scrutinee, with the body a labelled block containing just a match)

future work

  • perform const evaluation on the break value
  • support more state/scrutinee types

maybe future work

  • allow continue 'label value syntax, which #[const_continue] could then use.
  • allow the match to be on an arbitrary expression (e.g. State::Initial)
  • attempt to also optimize break/continue expressions that are not marked with #[const_continue]

r? @traviscross

@rustbot rustbot added A-attributes Area: Attributes (`#[…]`, `#![…]`) S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Mar 21, 2025
@rustbot
Copy link
Collaborator

rustbot commented Mar 21, 2025

Some changes occurred in match checking

cc @Nadrieril

Some changes occurred in compiler/rustc_passes/src/check_attr.rs

cc @jdonszelmann

Some changes occurred in rustc_ty_utils::consts.rs

cc @BoxyUwU

Copy link
Contributor

@traviscross traviscross left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @folkertdev for putting up this PR. The big picture looks right, in terms of the behavior of the tests and how to approach the experiment in terms of starting with the attributes for thiis.

This is a first partial pass on the details.

@rustbot author

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Mar 22, 2025
@rust-log-analyzer

This comment has been minimized.

Copy link
Contributor Author

@folkertdev folkertdev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the detailed review!

I've fixed a bunch of the low-hanging fruit (e.g. in the tests). For the actual pattern matching logic, I have a branch with what I believe is a better solution that re-uses more existing pattern matching infra. We'll come back to that here once björn has had a chance to look at it.

@rustbot
Copy link
Collaborator

rustbot commented Mar 24, 2025

Some changes occurred in exhaustiveness checking

cc @Nadrieril

Some changes occurred in match lowering

cc @Nadrieril

@bors
Copy link
Collaborator

bors commented Mar 26, 2025

☔ The latest upstream changes (presumably #138974) made this pull request unmergeable. Please resolve the merge conflicts.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@folkertdev
Copy link
Contributor Author

We've done a bunch of work here, and I believe all of the earlier review comments have now been dealt with.

@rustbot ready

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Apr 4, 2025
@traviscross
Copy link
Contributor

traviscross commented Apr 6, 2025

@rustbot author

As a lang matter, this is looking reasonable to me in terms of a lang experiment.

As an impl matter, this is starting to look not unreasonable to me, but I'd like for @Nadrieril to also have a look if he's able.

r? @Nadrieril

@Nadrieril: I still need to raise this in a lang meeting to confirm that everyone is happy to see the experiment here in light of earlier objections, so please don't merge this just yet. You can leave it back in my hands after you're happy with the impl.

Also CC @oli-obk as this work is carrying over some FIXME items you have marked.

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Apr 6, 2025
@rustbot
Copy link
Collaborator

rustbot commented Apr 6, 2025

Reminder, once the PR becomes ready for a review, use @rustbot ready.

@bors
Copy link
Collaborator

bors commented Jun 23, 2025

☔ The latest upstream changes (presumably #142906) made this pull request unmergeable. Please resolve the merge conflicts.

@lcnr
Copy link
Contributor

lcnr commented Jun 24, 2025

don't have too much time rn and properly reviewing this PR is effort

r? compiler

If this doesn't get reviewed over the next ~2 weeks reassign to me

@rustbot rustbot assigned oli-obk and unassigned lcnr Jun 24, 2025
Copy link
Contributor

@oli-obk oli-obk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems fine to me as an experiment, but I don't see how to turn it into an actual feature later without outright replacing most of it and its tests. That doesn't mean this is badly coded or anything, just that adding first class syntax will make a rewrite very desirable to carry the higher level information properly downwards and avoid lots of situations that have to recover that information in lossy ways

@@ -742,6 +816,190 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
self.cfg.start_new_block().unit()
}

/// Based on `FunctionCx::eval_unevaluated_mir_constant_to_valtree`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this duplicated and not shared?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We didn't see a good way to share the code. The original is a private method on FunctionCx in rustc_codegen_ssa, we're in rustc_mir_build, so that function does some things (like .monomorphize) that we can't yet do here.

};

let Some(real_target) =
self.static_pattern_match(&cx, valtree, &*scope.arms, &scope.built_match_tree)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea I don't really have an idea for how to make this work well in general without redesigning the feature from scratch independently from pattern matching. Not sure how and what parts of the CTFE machinery we could extract out to have something reusable as it works on MIR and thinks only switch statements on integers exist.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only need to evaluate the scrutinee into some sort of value representation. I guess I don't really understand what you think the problem is here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my "complaint" is that this is new logic not shared with anything else in the compiler and that we likely won't share with anything else, even tho we do similar things e.g. in const eval. But there we do it post-mir-building and "just" process the built MIR. I just worry we'll have weird bugs if we accidentally diverge in some less tested use cases

let pat = cx.lower_pat(&*self.thir.arms[arm_id].pattern);

// Peel off or-patterns if they exist.
if let rustc_pattern_analysis::rustc::Constructor::Or = pat.ctor() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

couldn't this be part of static_pattern_match_inner and just recurse on itself in Or patterns?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This peels off only the outer layer. Nested or-patterns are currently impossible to handle, because we can't always associate the pattern with the correct branch. @Nadrieril had some mid/long-term ideas of how to refactor the or-pattern logic so that this connection can be made.

Given the types that we accept now, only handling top-level or-patterns isn't a limitation, and so we're again defensive here and only implement that which we can guarantee will work.

Copy link
Contributor Author

@folkertdev folkertdev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review!

You're right that we'd need to make a bunch of changes if syntax were added. However, discussions about syntax are just endless (everyone can have an opinion about syntax, way fewer people weigh in on the technical details). So syntax is not something that I think we'll push for for a while: the technical side has to be promising before we'll bother with that.

Having loop_match on nightly will make it a lot easier to experiment with.

@@ -742,6 +816,190 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
self.cfg.start_new_block().unit()
}

/// Based on `FunctionCx::eval_unevaluated_mir_constant_to_valtree`.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We didn't see a good way to share the code. The original is a private method on FunctionCx in rustc_codegen_ssa, we're in rustc_mir_build, so that function does some things (like .monomorphize) that we can't yet do here.

};

let Some(real_target) =
self.static_pattern_match(&cx, valtree, &*scope.arms, &scope.built_match_tree)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only need to evaluate the scrutinee into some sort of value representation. I guess I don't really understand what you think the problem is here?

let pat = cx.lower_pat(&*self.thir.arms[arm_id].pattern);

// Peel off or-patterns if they exist.
if let rustc_pattern_analysis::rustc::Constructor::Or = pat.ctor() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This peels off only the outer layer. Nested or-patterns are currently impossible to handle, because we can't always associate the pattern with the correct branch. @Nadrieril had some mid/long-term ideas of how to refactor the or-pattern logic so that this connection can be made.

Given the types that we accept now, only handling top-level or-patterns isn't a limitation, and so we're again defensive here and only implement that which we can guarantee will work.

@oli-obk
Copy link
Contributor

oli-obk commented Jun 24, 2025

@bors r+

@bors
Copy link
Collaborator

bors commented Jun 24, 2025

📌 Commit ba5556d has been approved by oli-obk

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jun 24, 2025
workingjubilee added a commit to workingjubilee/rustc that referenced this pull request Jun 24, 2025
…attr, r=oli-obk

Add `#[loop_match]` for improved DFA codegen

tracking issue: rust-lang#132306
project goal: rust-lang/rust-project-goals#258

This PR adds the `#[loop_match]` attribute, which aims to improve code generation for state machines. For some (very exciting) benchmarks, see rust-lang/rust-project-goals#258 (comment)

Currently, a very restricted syntax pattern is accepted. We'd like to get feedback and merge this now before we go too far in a direction that others have concerns with.

## current state

We accept code that looks like this

```rust
#[loop_match]
loop {
    state = 'blk: {
        match state {
            State::A => {
                #[const_continue]
                break 'blk State::B
            }
            State::B => { /* ... */ }
            /* ... */
        }
    }
}
```

- a loop should have the same semantics with and without `#[loop_match]`: normal `continue` and `break` continue to work
- `#[const_continue]` is only allowed in loops annotated with `#[loop_match]`
- the loop body needs to have this particular shape (a single assignment to the match scrutinee, with the body a labelled block containing just a match)

## future work

- perform const evaluation on the `break` value
- support more state/scrutinee types

## maybe future work

- allow `continue 'label value` syntax, which `#[const_continue]` could then use.
- allow the match to be on an arbitrary expression (e.g. `State::Initial`)
- attempt to also optimize `break`/`continue` expressions that are not marked with `#[const_continue]`

r? `@traviscross`
@traviscross
Copy link
Contributor

@bors r-
@bors r=oli-obk,traviscross

@bors bors added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels Jun 24, 2025
@bors
Copy link
Collaborator

bors commented Jun 24, 2025

📌 Commit ba5556d has been approved by oli-obk,traviscross

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Jun 24, 2025
workingjubilee added a commit to workingjubilee/rustc that referenced this pull request Jun 25, 2025
…attr, r=oli-obk,traviscross

Add `#[loop_match]` for improved DFA codegen

tracking issue: rust-lang#132306
project goal: rust-lang/rust-project-goals#258

This PR adds the `#[loop_match]` attribute, which aims to improve code generation for state machines. For some (very exciting) benchmarks, see rust-lang/rust-project-goals#258 (comment)

Currently, a very restricted syntax pattern is accepted. We'd like to get feedback and merge this now before we go too far in a direction that others have concerns with.

## current state

We accept code that looks like this

```rust
#[loop_match]
loop {
    state = 'blk: {
        match state {
            State::A => {
                #[const_continue]
                break 'blk State::B
            }
            State::B => { /* ... */ }
            /* ... */
        }
    }
}
```

- a loop should have the same semantics with and without `#[loop_match]`: normal `continue` and `break` continue to work
- `#[const_continue]` is only allowed in loops annotated with `#[loop_match]`
- the loop body needs to have this particular shape (a single assignment to the match scrutinee, with the body a labelled block containing just a match)

## future work

- perform const evaluation on the `break` value
- support more state/scrutinee types

## maybe future work

- allow `continue 'label value` syntax, which `#[const_continue]` could then use.
- allow the match to be on an arbitrary expression (e.g. `State::Initial`)
- attempt to also optimize `break`/`continue` expressions that are not marked with `#[const_continue]`

r? `@traviscross`
bors added a commit that referenced this pull request Jun 25, 2025
Rollup of 15 pull requests

Successful merges:

 - #135731 (Implement parsing of pinned borrows)
 - #138780 (Add `#[loop_match]` for improved DFA codegen)
 - #142453 (Windows: make `read_dir` stop iterating after the first error is encountered)
 - #142633 (Error on invalid signatures for interrupt ABIs)
 - #142768 (Avoid a bitcast FFI call in transmuting)
 - #142825 (Port `#[track_caller]` to the new attribute system)
 - #142844 (Enable short-ice for Windows)
 - #142934 (Tweak `-Zmacro-stats` measurement.)
 - #142955 (Couple of test suite fixes for cg_clif)
 - #142977 (rustdoc: Don't mark `#[target_feature]` functions as ⚠)
 - #142980 (Reduce mismatched-lifetime-syntaxes suggestions to MaybeIncorrect)
 - #142982 (Corrected spelling mistake in c_str.rs)
 - #142983 (Taint body on invalid call ABI)
 - #142988 (Update wasm-component-ld to 0.5.14)
 - #142993 (Update cargo)

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit f542909 into rust-lang:master Jun 25, 2025
20 checks passed
@rustbot rustbot added this to the 1.90.0 milestone Jun 25, 2025
rust-timer added a commit that referenced this pull request Jun 25, 2025
Rollup merge of #138780 - trifectatechfoundation:loop_match_attr, r=oli-obk,traviscross

Add `#[loop_match]` for improved DFA codegen

tracking issue: #132306
project goal: rust-lang/rust-project-goals#258

This PR adds the `#[loop_match]` attribute, which aims to improve code generation for state machines. For some (very exciting) benchmarks, see rust-lang/rust-project-goals#258 (comment)

Currently, a very restricted syntax pattern is accepted. We'd like to get feedback and merge this now before we go too far in a direction that others have concerns with.

## current state

We accept code that looks like this

```rust
#[loop_match]
loop {
    state = 'blk: {
        match state {
            State::A => {
                #[const_continue]
                break 'blk State::B
            }
            State::B => { /* ... */ }
            /* ... */
        }
    }
}
```

- a loop should have the same semantics with and without `#[loop_match]`: normal `continue` and `break` continue to work
- `#[const_continue]` is only allowed in loops annotated with `#[loop_match]`
- the loop body needs to have this particular shape (a single assignment to the match scrutinee, with the body a labelled block containing just a match)

## future work

- perform const evaluation on the `break` value
- support more state/scrutinee types

## maybe future work

- allow `continue 'label value` syntax, which `#[const_continue]` could then use.
- allow the match to be on an arbitrary expression (e.g. `State::Initial`)
- attempt to also optimize `break`/`continue` expressions that are not marked with `#[const_continue]`

r? ``@traviscross``
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-attributes Area: Attributes (`#[…]`, `#![…]`) I-lang-radar Items that are on lang's radar and will need eventual work or consideration. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.