Skip to content

MIR move elimination#3943

Open
Amanieu wants to merge 3 commits intorust-lang:masterfrom
Amanieu:mir-move-elimination
Open

MIR move elimination#3943
Amanieu wants to merge 3 commits intorust-lang:masterfrom
Amanieu:mir-move-elimination

Conversation

@Amanieu
Copy link
Copy Markdown
Member

@Amanieu Amanieu commented Apr 3, 2026

This RFC proposes changes to Rust's operational semantics and MIR representation to enable elimination of unnecessary copies of local variables. Specifically, it makes accessing memory after a move undefined behavior, and redefines the allocation lifetime of local variables to be tied to their initialized state rather than their lexical scope. Finally, it introduces a new MIR optimization pass which exploits these guarantees to eliminate copies between locals when it is safe to do so.

Important

Since RFCs involve many conversations at once that can be difficult to follow, please use review comment threads on the text changes instead of direct comments on the RFC.

If you don't have a particular section of the RFC to comment on, you can click on the "Comment on this file" button on the top-right corner of the diff, to the right of the "Viewed" checkbox. This will create a separate thread even if others have commented on the file too.

Rendered

@NobodyXu
Copy link
Copy Markdown

NobodyXu commented Apr 3, 2026

For Copy-iable types, can rust mir just drop them after the last usage, given that it cannot have a Drop implementation?

It seems to be extremely strange to say I need a move keyword on a Copy-iable type just so the compiler can optimize it

@Amanieu
Copy link
Copy Markdown
Member Author

Amanieu commented Apr 3, 2026

For Copy-iable types, can rust mir just drop them after the last usage, given that it cannot have a Drop implementation?

It seems to be extremely strange to say I need a move keyword on a Copy-iable type just so the compiler can optimize it

We can drop Copy types after the last usage as long as they've not been borrowed. If they have then the compiler would need to additionally prove through alias analysis that the borrow has ended. This is necessary because stack/tree borrows allows a pointer/reference to continue accessing a local after it has been copied (but not moved).

The purpose of a move keyword would be to forcibly end the borrows of a local early, which allows the local to be freed at that point. This would also be enforced by the borrow checker for references.

@NobodyXu
Copy link
Copy Markdown

NobodyXu commented Apr 3, 2026

The purpose of a move keyword would be to forcibly end the borrows of a local early, which allows the local to be freed at that point. This would also be enforced by the borrow checker for references.

Wouldn't it make more sense to have something similar to drop to force drop it, and that can also work on non-Copy-iable type as well, for generic functions?

@Amanieu
Copy link
Copy Markdown
Member Author

Amanieu commented Apr 3, 2026

Calling drop(x) on a Copy type doesn't do anything since x is copied. The new keyword would allow you to write drop(move x) which forces x to be moved. Here's an example where this matters:

let x = 1;
let y = &x;
drop(move x);
let z = *y; // Fails because x was moved. Removing `move` fixes this.

Anyways, move isn't even being proposed in this RFC, it's a possible future extension.

@PoignardAzur
Copy link
Copy Markdown

Since RFCs involve many conversations at once that can be difficult to follow, please use review comment threads on the text changes instead of direct comments on the RFC.

@Noratrieb Noratrieb added T-compiler Relevant to the compiler team, which will review and decide on the RFC. T-opsem Relevant to the operational semantics team, which will review and decide on the RFC. labels Apr 5, 2026
@Amanieu Amanieu added the T-lang Relevant to the language team, which will review and decide on the RFC. label Apr 5, 2026

The proposed behavior of freeing a local variable's allocation on move only applies when the entire variable is moved. This is not the case when only a part of the variable is moved (e.g. only one field of a struct) because a re-initialized field must retain the address it had before, reintroducing the same NB issue.

Even in the case where all of the fields of a local variable have been moved out one-by-one, the local will not be freed.
Copy link
Copy Markdown
Contributor

@Jules-Bertholet Jules-Bertholet Apr 7, 2026

Choose a reason for hiding this comment

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

Even in the case where all of the fields of a local variable have been moved out one-by-one, the local will not be freed.

Why not?


Even in the case where all of the fields of a local variable have been moved out one-by-one, the local will not be freed.

With that said, we would like to keep the door open for potentially switching to operational semantics with NB in the future. So although the proposed opsem does not consider accessing a moved field as UB, we would like users to avoid relying on this behavior since it may change in the future.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why can't we say that accessing the moved field is UB? Because we don't have NB, the compiler can't exploit that UB by stashing another allocation with an observable address in the empty space. But that doesn't mean it can't still be UB detected by Miri, if we want users to avoid relying on it. And the compiler could even make use of the UB, to stash an allocation whose address it can prove is never observed.

Copy link
Copy Markdown
Member Author

@Amanieu Amanieu Apr 7, 2026

Choose a reason for hiding this comment

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

There's no reason we can't do it, it's just that I am not doing so in this RFC and instead leaving to future work (I will update the future possibilities section with this). There are 2 main reasons for this:

  1. Adding support for partial moves makes the opsem (and by extension Miri) much more complex since we now needs to track which bytes of a local have been moved out and become "inactive" (UB to access). What happens to the padding of a struct if one field is moved out? What happens to the discriminant of an enum like Option<T> if the Some value has been moved out and the layout is optimized (the discriminant occupied the same bytes as the value)? These are all questions that would have to be answered.

  2. The proposed MIR optimization pass can't easily take advantage of this, and even if it could (while respecting address observation rules) then I expect the benefit over the existing proposed pass would be minimal. It's just not worth the extra complexity of tracking lifetimes separately for every field of a local.

@Amanieu Amanieu removed the T-lang Relevant to the language team, which will review and decide on the RFC. label Apr 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-compiler Relevant to the compiler team, which will review and decide on the RFC. T-opsem Relevant to the operational semantics team, which will review and decide on the RFC.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants