Skip to content
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

DROP to DROP_IF #558

Closed
1 of 3 tasks
nikomatsakis opened this issue Sep 16, 2022 · 3 comments
Closed
1 of 3 tasks

DROP to DROP_IF #558

nikomatsakis opened this issue Sep 16, 2022 · 3 comments
Labels
major-change A proposal to make a major change to rustc major-change-accepted A major change proposal that was accepted T-compiler Add this label so rfcbot knows to poll the compiler team

Comments

@nikomatsakis
Copy link
Contributor

nikomatsakis commented Sep 16, 2022

Proposal

This is a proposal to modify the MIR such that we can run "drop elaboration" before borrow check. This eliminates duplicate logic and simplifies borrow check, moving us closer to our goal of having borrow check run against MIR that is as close as possible to the MIR that we will use for codegen. It also establishes a single "analysis MIR" that is used for both borrow check and const-promotion/const-safety, and potentially other future analyses (e.g., user-provided analyses based on the stable MIR effort).

Background: How MIR and drop-evaluation works today

In MIR today, the semantics of DROP in MIR change over time:

  • After MIR build, DROP(x) means "drop x if it is initialized at runtime".
    • This allows MIR build to insert DROP(x) for every variable that is going out of scope, without doing any sort of flow-sensitive analysis.
  • When borrowck runs, DROP(x) has this semantics.
    • The borrow check computes a "maybe initialized" analysis to figure out which drops may take effect and which are no-ops.
    • This is in addition to the "definitely initialized" analysis the borrow checker has to use to detect uses of (potentially) moved values.
  • Drop elaboration runs, performing various refinements:
    • DROP(x) where x is not initialized are removed.
    • DROP(x) may be expanded to statements like DROP(x.y) if only some fields are initialized.
    • For conditional drops, temporary variables are introduced and DROP(x) is converted to if tmp { DROP(x) }. This requires modifying the control-flow graph in non-trivial ways.
  • We then run const-eval and potentially other analyses.
  • Finally, we perform optimizations and do codegen.

The MIR itself has the following drop-related terminators:

  • Drop(Place) -- drop the value at place, if it is initialized; after drop-elaboration, the place will always be initialized
  • DropAndReplace(Place, Value) -- drop the value at place, if it is initialized, and stuff in Value if there is a panic (I think?)

Proposal

Alter the MIR to have the following terminators:

  • DropIfInit(Place) -- emitted only by MIR build, indicates we should drop Place if it is (dynamically) initialized
  • DropIf(Operand, Place) -- if operand is true, drop place (which must be initialized)
  • DropAndReplaceIf(Operand, Place, Value) -- if operand is true, drop place (which must be initialized)

During MIR build, we create DropIfInit.

Drop elaboration will convert to the other two, and hence most code can expect DropIfInit to never occur:

  • if the drop is not not needed, it can be removed
  • if only some fields are needed, the drop can be converted to DropIf(True, Place.F1) etc
  • if the drop is conditional, a flag F can be introduced and we can then change to DropIf(F, Place)
    • It's possible to combine the previous two cases, of course, resulting in DropIf(F, Place.F1).
    • One side-effect of this is that drop-elaboration can be simplified: it never needs to make drastic edits to the the control-flow-graph structure, but can simply alter basic-blocks in place (though it may have to insert new blocks onto an edge, if we are going to convert DropIf(True, X) to DropIf(True, X.F) and DropIf(True, X.G)).

Borrow check will run on this "fixed" IR. It can treat the operands to DropIf as trusted, meaning that it does not verify that the flag F is true iff the value is initialized, but rather assumes that is true. Borrow check should, I believe, no longer need the "maybe initialized" analysis, however, because drop elaboration will already have removed or refined the drops using those results.

Other analyses and codegen become very mildly more complicated, in that they have to take into account that DropIf now introduces a small bit of control-flow. But this control-flow is very simple, and amounts to if !Flag { goto next_block }; else Drop(). In other words, the basic block is now an "extended basic block". I've not looked at codegen recently, so I don't know how much complexity is introduced here. If this is a problem, it could be reduced by a MIR->MIR transformation that ensures that converts DropIf(F, P) to if F { DropIf(True, P) }.

Update to the above: DropAndReplace

As discussed on Zulip, we need to investigate the best way to manage DropAndReplace. However, it is likely that we can remove it altogether in drop elaboration, just as we do today, and replace it with DropIf and an assignment. This will require borrow check to treat drop as an &mut access and not a move, but this is believed to be ok. The idea is that the borrow checker trusts that...

  • DropIf statements only apply to "maybe initialized" content -- there should be no Drop or DropAndReplace statements by the time the borrow check runs.
  • After a value is dropped, it will not be used again; further, drop will either (a) only be used on owned places or (b) the CFG will ensure that those places are filled again before they can possibly be used.
  • The condition in DROP_IF will evaluate to true when the place is initialized or false if it is not.

Mentors or Reviewers

nikomatsakis to mentor. Giacomo Pasini is interested in implementing.

cc @oli-obk @RalfJung @ecstatic-morse and I guess @rust-lang/wg-mir-opt

This was previously discussed on Zulip around here.

Process

The main points of the Major Change Process are as follows:

  • File an issue describing the proposal.
  • A compiler team member or contributor who is knowledgeable in the area can second by writing @rustbot second.
    • Finding a "second" suffices for internal changes. If however, you are proposing a new public-facing feature, such as a -C flag, then full team check-off is required.
    • Compiler team members can initiate a check-off via @rfcbot fcp merge on either the MCP or the PR.
  • Once an MCP is seconded, the Final Comment Period begins. If no objections are raised after 10 days, the MCP is considered approved.

You can read more about Major Change Proposals on forge.

Comments

This issue is not meant to be used for technical discussion. There is a Zulip stream for that. Use this issue to leave procedural comments, such as volunteering to review, indicating that you second the proposal (or third, etc), or raising a concern that you would like to be addressed.

History

  • Initial version of the MCP proposed generating DropIf(true) during MIR build. This was changed to a distinct DropIfInit terminator at the suggestion of @bjorn3 so as to make the constructor more robust.
  • Fixed a typo in definition of DropAndReplaceIf.
@nikomatsakis nikomatsakis added T-compiler Add this label so rfcbot knows to poll the compiler team major-change A proposal to make a major change to rustc labels Sep 16, 2022
@rustbot
Copy link
Collaborator

rustbot commented Sep 16, 2022

This issue is not meant to be used for technical discussion. There is a Zulip stream for that. Use this issue to leave procedural comments, such as volunteering to review, indicating that you second the proposal (or third, etc), or raising a concern that you would like to be addressed.

cc @rust-lang/compiler @rust-lang/compiler-contributors

@rustbot rustbot added the to-announce Announce this issue on triage meeting label Sep 16, 2022
@pnkfelix
Copy link
Member

@rustbot second

@rustbot rustbot added the final-comment-period The FCP has started, most (if not all) team members are in agreement label Sep 21, 2022
@apiraino apiraino removed the to-announce Announce this issue on triage meeting label Sep 22, 2022
@apiraino
Copy link
Contributor

apiraino commented Oct 4, 2022

@rustbot label -final-comment-period +major-change-accepted

@apiraino apiraino closed this as completed Oct 4, 2022
@rustbot rustbot added major-change-accepted A major change proposal that was accepted to-announce Announce this issue on triage meeting and removed final-comment-period The FCP has started, most (if not all) team members are in agreement labels Oct 4, 2022
@apiraino apiraino removed the to-announce Announce this issue on triage meeting label Oct 18, 2022
Dylan-DPC added a commit to Dylan-DPC/rust that referenced this issue Feb 8, 2023
Treat Drop as a rmw operation

Previously, a Drop terminator was considered a move in MIR. This commit changes the behavior to only treat Drop as a mutable access to the dropped place.

In order for this change to be correct, we need to guarantee that

1.  A dropped value won't be used again
   2.  Places that appear in a drop won't be used again before a
     subsequent initialization.

We can ensure this to be correct at MIR construction because Drop will only be emitted when a variable goes out of scope, thus having:
*   (1) as there is no way of reaching the old value. drop-elaboration
     will also remove any uninitialized drop.
 * (2) as the place can't be named following the end of the scope.

However, the initialization status, previously tracked by moves, should also be tied to the execution of a Drop, hence the additional logic in the dataflow analyses.

From discussion in [this thread](https://rust-lang.zulipchat.com/#narrow/stream/233931-t-compiler.2Fmajor-changes/topic/.60DROP.60.20to.20.60DROP_IF.60.20compiler-team.23558), originating from rust-lang/compiler-team#558.
See also rust-lang#104488 (comment)
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Feb 8, 2023
Treat Drop as a rmw operation

Previously, a Drop terminator was considered a move in MIR. This commit changes the behavior to only treat Drop as a mutable access to the dropped place.

In order for this change to be correct, we need to guarantee that

1.  A dropped value won't be used again
   2.  Places that appear in a drop won't be used again before a
     subsequent initialization.

We can ensure this to be correct at MIR construction because Drop will only be emitted when a variable goes out of scope, thus having:
*   (1) as there is no way of reaching the old value. drop-elaboration
     will also remove any uninitialized drop.
 * (2) as the place can't be named following the end of the scope.

However, the initialization status, previously tracked by moves, should also be tied to the execution of a Drop, hence the additional logic in the dataflow analyses.

From discussion in [this thread](https://rust-lang.zulipchat.com/#narrow/stream/233931-t-compiler.2Fmajor-changes/topic/.60DROP.60.20to.20.60DROP_IF.60.20compiler-team.23558), originating from rust-lang/compiler-team#558.
See also rust-lang#104488 (comment)
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Feb 8, 2023
Treat Drop as a rmw operation

Previously, a Drop terminator was considered a move in MIR. This commit changes the behavior to only treat Drop as a mutable access to the dropped place.

In order for this change to be correct, we need to guarantee that

1.  A dropped value won't be used again
   2.  Places that appear in a drop won't be used again before a
     subsequent initialization.

We can ensure this to be correct at MIR construction because Drop will only be emitted when a variable goes out of scope, thus having:
*   (1) as there is no way of reaching the old value. drop-elaboration
     will also remove any uninitialized drop.
 * (2) as the place can't be named following the end of the scope.

However, the initialization status, previously tracked by moves, should also be tied to the execution of a Drop, hence the additional logic in the dataflow analyses.

From discussion in [this thread](https://rust-lang.zulipchat.com/#narrow/stream/233931-t-compiler.2Fmajor-changes/topic/.60DROP.60.20to.20.60DROP_IF.60.20compiler-team.23558), originating from rust-lang/compiler-team#558.
See also rust-lang#104488 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
major-change A proposal to make a major change to rustc major-change-accepted A major change proposal that was accepted T-compiler Add this label so rfcbot knows to poll the compiler team
Projects
None yet
Development

No branches or pull requests

4 participants