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

Emit StorageDead along unwind paths for generators #61373

Merged
merged 6 commits into from
Jun 6, 2019

Conversation

tmandry
Copy link
Member

@tmandry tmandry commented May 30, 2019

Completion of the work done in #60840. That PR made a change to implicitly consider a local StorageDead after Drop, but that was incorrect for DropAndReplace (see also #61060 which tried to fix this in a different way).

This finally enables the optimization implemented in #60187.

r? @eddyb
cc @Zoxc @cramertj @RalfJung

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label May 30, 2019
@tmandry tmandry force-pushed the emit-storagedead-along-unwind branch from 5b84d01 to a09f605 Compare May 30, 2019 20:57
@Centril
Copy link
Contributor

Centril commented May 30, 2019

@bors try

@bors
Copy link
Contributor

bors commented May 30, 2019

⌛ Trying commit a09f605 with merge ac8797a...

bors added a commit that referenced this pull request May 30, 2019
Emit StorageDead along unwind paths for generators

Completion of the work done in #60840. That PR made a change to implicitly consider a local `StorageDead` after Drop, but that was incorrect for DropAndReplace (see also #61060 which tried to fix this in a different way).

This finally enables the optimization implemented in #60187.

r? @eddyb
cc @Zoxc @cramertj @RalfJung
@tmandry tmandry force-pushed the emit-storagedead-along-unwind branch from ec51065 to e9ad4fe Compare May 30, 2019 22:13
@cramertj
Copy link
Member

@bors try

(looks like the last one was cancelled somehow?)

@bors
Copy link
Contributor

bors commented May 31, 2019

⌛ Trying commit ca5eaa7 with merge 611f90c...

bors added a commit that referenced this pull request May 31, 2019
Emit StorageDead along unwind paths for generators

Completion of the work done in #60840. That PR made a change to implicitly consider a local `StorageDead` after Drop, but that was incorrect for DropAndReplace (see also #61060 which tried to fix this in a different way).

This finally enables the optimization implemented in #60187.

r? @eddyb
cc @Zoxc @cramertj @RalfJung
@bors
Copy link
Contributor

bors commented May 31, 2019

☀️ Try build successful - checks-travis
Build commit: 611f90c

@tmandry
Copy link
Member Author

tmandry commented May 31, 2019

@rust-timer build 611f90c

@rust-timer
Copy link
Collaborator

Success: Queued 611f90c with parent 75f4644, comparison URL.

@rust-timer
Copy link
Collaborator

Finished benchmarking try commit 611f90c, comparison URL.

@cramertj
Copy link
Member

cramertj commented Jun 1, 2019

Looks like noise.

@tmandry
Copy link
Member Author

tmandry commented Jun 1, 2019

The test I just added feels slightly brittle, but I documented what can safely change. I'm open to suggestions on improving this.

I think it's good to have a test that shows the effects of this PR, and it might catch things that the optimization integration test won't.

Copy link
Member

@eddyb eddyb left a comment

Choose a reason for hiding this comment

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

LGTM, but if possible I'd like @arielb1, @pnkfelix or @nikomatsakis to take a look at this.

@arielb1
Copy link
Contributor

arielb1 commented Jun 2, 2019

Is it possible to do the same merging approach I did in LLVM, where 2 locals are not considered as overlapping if there is no path between their StorageLive statements?

@arielb1
Copy link
Contributor

arielb1 commented Jun 2, 2019

@tmandry
Copy link
Member Author

tmandry commented Jun 3, 2019

@arielb1 hmm, I actually briefly thought about doing something like this, but decided it must be unsound in some way.

Given that it works in LLVM, it might work here. I can't think of a reason why not, at least...

@RalfJung
Copy link
Member

RalfJung commented Jun 3, 2019

Well, any execution where they are both live must have executed a StorageLive for each of them -- thus witnessing a path from one StorageLive to another. With no such path, there can be no such execution. Right?

@tmandry
Copy link
Member Author

tmandry commented Jun 3, 2019

Well, any execution where they are both live must have executed a StorageLive for each of them

@eddyb pointed out to me earlier, this is true as long as the program is not doing any sort of dynamic trickery where it keeps track of what's StorageLive at runtime. e.g.

if pred { StorageLive(a); } else { StorageLive(b); }
// ...later...
if pred { a = 42; } else { b = 99; }

I don't think there's a way to actually write this in Rust, and it should almost certainly be UB. But we should be verifying this in MIR. It can be verified by checking that every usage of a is dominated by StorageLive(a). (also @eddyb's idea)

Given that the optimization that depends on this PR is blocking async/await stabilization, I'm inclined to land this as-is, and then revert it and update the analysis in a follow-up change. This PR and #60187 are correct in their current state, albeit suboptimal. I fear that what should be a "quick fix" might stir up more controversy, or end up being less quick than I'm anticipating :)

EDIT: The TL;DR is that with the analysis @arielb1 is proposing, we don't need the StorageDead statements added by this PR. But there's nothing wrong with adding them for now and removing them later. I'd rather not update the analysis before merging some version of the optimization.

@eddyb
Copy link
Member

eddyb commented Jun 4, 2019

@bors r+

@bors
Copy link
Contributor

bors commented Jun 4, 2019

📌 Commit 00fb0bd has been approved by eddyb

@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 4, 2019
Co-Authored-By: Ralf Jung <post@ralfj.de>
@tmandry tmandry force-pushed the emit-storagedead-along-unwind branch from 00fb0bd to 20fffc8 Compare June 4, 2019 23:52
@tmandry
Copy link
Member Author

tmandry commented Jun 5, 2019

I don't follow -- in your example, it is impossible for a and ´b` to be live in the same run. So how is that a counterexample to my statement?

You're right, it's not a counterexample. We probably shouldn't support it, though.

@tmandry
Copy link
Member Author

tmandry commented Jun 5, 2019

Failure isn’t reproducing after rebase, resubmitting.

@bors r=eddyb

@bors
Copy link
Contributor

bors commented Jun 5, 2019

📌 Commit 20fffc8 has been approved by eddyb

@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 5, 2019
@bors
Copy link
Contributor

bors commented Jun 5, 2019

⌛ Testing commit 20fffc8 with merge bc4e958...

bors added a commit that referenced this pull request Jun 5, 2019
Emit StorageDead along unwind paths for generators

Completion of the work done in #60840. That PR made a change to implicitly consider a local `StorageDead` after Drop, but that was incorrect for DropAndReplace (see also #61060 which tried to fix this in a different way).

This finally enables the optimization implemented in #60187.

r? @eddyb
cc @Zoxc @cramertj @RalfJung
@bors
Copy link
Contributor

bors commented Jun 5, 2019

💔 Test failed - checks-travis

@rust-highfive
Copy link
Collaborator

The job test-various of your PR failed on Travis (raw log). Through arcane magic we have determined that the following fragments from the build log may contain information about the problem.

Click to expand the log.
[01:19:10] thread 'main' panicked at 'Some tests failed', src/tools/compiletest/src/main.rs:521:22
[01:19:10] 
[01:19:10] failures:
[01:19:10] 
[01:19:10] ---- [mir-opt] mir-opt/generator-storage-dead-unwind.rs stdout ----
[01:19:10] thread '[mir-opt] mir-opt/generator-storage-dead-unwind.rs' panicked at 'Did not find expected line, error: ran out of mir dump to match against
[01:19:10] Expected Line: "    _1 = suspend(move _5) -> [resume: bb2, drop: bb4];"
[01:19:10] Test Name: rustc.main-{{closure}}.StateTransform.before.mir
[01:19:10] ... (elided)
[01:19:10] ... (elided)
[01:19:10] let _2: Foo;
[01:19:10] ... (elided)
[01:19:10] ... (elided)
[01:19:10] let mut _7: Foo;
[01:19:10] ... (elided)
[01:19:10] let mut _9: Bar;
[01:19:10] scope 1 {
[01:19:10]     let _3: Bar;
[01:19:10]     scope 2 {
[01:19:10]     }
[01:19:10] }
[01:19:10] bb0: {
[01:19:10]     StorageLive(_2);
[01:19:10]     _2 = Foo(const 5i32,);
[01:19:10]     StorageLive(_3);
[01:19:10]     _3 = Bar(const 6i32,);
[01:19:10] ... (elided)
[01:19:10]     _1 = suspend(move _5) -> [resume: bb2, drop: bb4];
[01:19:10] }
[01:19:10] bb1 (cleanup): {
[01:19:10]     resume;
[01:19:10] }
[01:19:10] bb2: {
[01:19:10] ... (elided)
[01:19:10]     StorageLive(_7);
[01:19:10]     _7 = move _2;
[01:19:10]     _6 = const take::<Foo>(move _7) -> [return: bb9, unwind: bb8];
[01:19:10] }
[01:19:10] bb3 (cleanup): {
[01:19:10]     StorageDead(_2);
[01:19:10]     drop(_1) -> bb1;
[01:19:10] }
[01:19:10] bb4: {
[01:19:10] ... (elided)
[01:19:10]     StorageDead(_3);
[01:19:10]     drop(_2) -> [return: bb5, unwind: bb3];
[01:19:10] }
[01:19:10] bb5: {
[01:19:10]     StorageDead(_2);
[01:19:10]     drop(_1) -> [return: bb6, unwind: bb1];
[01:19:10] }
[01:19:10] bb6: {
[01:19:10]     generator_drop;
[01:19:10] }
[01:19:10] bb7 (cleanup): {
[01:19:10]     StorageDead(_3);
[01:19:10]     StorageDead(_2);
[01:19:10]     drop(_1) -> bb1;
[01:19:10] }
[01:19:10] bb8 (cleanup): {
[01:19:10]     StorageDead(_7);
[01:19:10]     goto -> bb7;
[01:19:10] }
[01:19:10] bb9: {
[01:19:10]     StorageDead(_7);
[01:19:10]     StorageLive(_9);
[01:19:10]     _9 = move _3;
[01:19:10]     _8 = const take::<Bar>(move _9) -> [return: bb10, unwind: bb11];
[01:19:10] }
[01:19:10] bb10: {
[01:19:10]     StorageDead(_9);
[01:19:10] ... (elided)
[01:19:10]     StorageDead(_3);
[01:19:10]     StorageDead(_2);
[01:19:10]     drop(_1) -> [return: bb12, unwind: bb1];
[01:19:10] }
[01:19:10] bb11 (cleanup): {
[01:19:10]     StorageDead(_9);
[01:19:10]     goto -> bb7;
[01:19:10] }
[01:19:10] bb12: {
[01:19:10]     return;
[01:19:10] }
[01:19:10] Actual:
[01:19:10] fn  main::{{closure}}#0(_1: [generator@/checkout/src/test/mir-opt/generator-storage-dead-unwind.rs:19:16: 25:6 {Foo, Bar, ()}]) -> ()
[01:19:10] yields ()
[01:19:10]  {
[01:19:10]     let mut _0: ();
[01:19:10]     let _2: Foo;
[01:19:10]     let mut _4: ();
[01:19:10]     let mut _5: ();
[01:19:10]     let mut _6: ();
[01:19:10]     let mut _7: Foo;
[01:19:10]     let mut _8: ();
[01:19:10]     let mut _9: Bar;
[01:19:10]     scope 1 {
[01:19:10]         let _3: Bar;
[01:19:10]         scope 2 {
[01:19:10]     }
[01:19:10]     bb0: {
[01:19:10]     bb0: {
[01:19:10]         StorageLive(_2);
[01:19:10]         _2 = Foo(const 5i32,);
[01:19:10]         StorageLive(_3);
[01:19:10]         _3 = Bar(const 6i32,);
[01:19:10]         StorageLive(_5);
[01:19:10]         _5 = ();
[01:19:10]         _1 = suspend(move _5) -> [resume: bb1, drop: bb2];
[01:19:10]     bb1: {
[01:19:10]         _4 = ();
[01:19:10]         _4 = ();
[01:19:10]         StorageDead(_5);
[01:19:10]         StorageLive(_7);
[01:19:10]         _7 = move _2;
[01:19:10]         _6 = const take::<Foo>(move _7) -> bb5;
[01:19:10]     bb2: {
[01:19:10]     bb2: {
[01:19:10]         StorageDead(_5);
[01:19:10]         StorageDead(_3);
[01:19:10]         drop(_2) -> bb3;
[01:19:10]     bb3: {
[01:19:10]     bb3: {
[01:19:10]         StorageDead(_2);
[01:19:10]         drop(_1) -> bb4;
[01:19:10]     bb4: {
[01:19:10]         generator_drop;
[01:19:10]     }
[01:19:10]     bb5: {
[01:19:10]     bb5: {
[01:19:10]         StorageDead(_7);
[01:19:10]         StorageLive(_9);
[01:19:10]         _9 = move _3;
[01:19:10]         _8 = const take::<Bar>(move _9) -> bb6;
[01:19:10]     bb6: {
[01:19:10]     bb6: {
[01:19:10]         StorageDead(_9);
[01:19:10]         _0 = ();
[01:19:10]         StorageDead(_3);
[01:19:10]         StorageDead(_2);
[01:19:10]         drop(_1) -> bb7;
[01:19:10]     bb7: {
[01:19:10]         return;
[01:19:10]     }
[01:19:10]     }
[01:19:10]     bb8 (cleanup): {
[01:19:10]         resume;
[01:19:10] }', src/tools/compiletest/src/runtest.rs:3196:13
[01:19:10] note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
[01:19:10] 
[01:19:10] 
[01:19:10] 
[01:19:10] failures:
[01:19:10]     [mir-opt] mir-opt/generator-storage-dead-unwind.rs
[01:19:10] 
[01:19:10] test result: FAILED. 45 passed; 1 failed; 6 ignored; 0 measured; 0 filtered out
[01:19:10] 
[01:19:10] 
[01:19:10] 
[01:19:10] command did not execute successfully: "/checkout/obj/build/x86_64-unknown-linux-gnu/stage0-tools-bin/compiletest" "--compile-lib-path" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/lib" "--run-lib-path" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/lib/rustlib/wasm32-unknown-unknown/lib" "--rustc-path" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/bin/rustc" "--src-base" "/checkout/src/test/mir-opt" "--build-base" "/checkout/obj/build/x86_64-unknown-linux-gnu/test/mir-opt" "--stage-id" "stage2-wasm32-unknown-unknown" "--mode" "mir-opt" "--target" "wasm32-unknown-unknown" "--host" "x86_64-unknown-linux-gnu" "--llvm-filecheck" "/checkout/obj/build/x86_64-unknown-linux-gnu/llvm/build/bin/FileCheck" "--nodejs" "/node-v9.2.0-linux-x64/bin/node" "--host-rustcflags" "-Crpath -O -Cdebuginfo=0 -Zunstable-options  -Lnative=/checkout/obj/build/x86_64-unknown-linux-gnu/native/rust-test-helpers" "--target-rustcflags" "-Crpath -O -Cdebuginfo=0 -Zunstable-options  -Lnative=/checkout/obj/build/wasm32-unknown-unknown/native/rust-test-helpers" "--docck-python" "/usr/bin/python2.7" "--lldb-python" "/usr/bin/python2.7" "--gdb" "/usr/bin/gdb" "--llvm-version" "8.0.0-rust-1.37.0-dev\n" "--cc" "" "--cxx" "" "--cflags" "" "--llvm-components" "" "--llvm-cxxflags" "" "--adb-path" "adb" "--adb-test-dir" "/data/tmp/work" "--android-cross-path" "" "--color" "always"
[01:19:10] 
[01:19:10] 
[01:19:10] failed to run: /checkout/obj/build/bootstrap/debug/bootstrap test --target wasm32-unknown-unknown src/test/run-make src/test/ui src/test/run-pass src/test/compile-fail src/test/mir-opt src/test/codegen-units src/libcore
[01:19:10] Build completed unsuccessfully in 1:10:13
---
travis_time:end:0e65e290:start=1559738131857985710,finish=1559738131876192641,duration=18206931
travis_fold:end:after_failure.3
travis_fold:start:after_failure.4
travis_time:start:0941a639
$ ln -s . checkout && for CORE in obj/cores/core.*; do EXE=$(echo $CORE | sed 's|obj/cores/core\.[0-9]*\.!checkout!\(.*\)|\1|;y|!|/|'); if [ -f "$EXE" ]; then printf travis_fold":start:crashlog\n\033[31;1m%s\033[0m\n" "$CORE"; gdb --batch -q -c "$CORE" "$EXE" -iex 'set auto-load off' -iex 'dir src/' -iex 'set sysroot .' -ex bt -ex q; echo travis_fold":"end:crashlog; fi; done || true
travis_fold:end:after_failure.4
travis_fold:start:after_failure.5
travis_time:start:152bda08
travis_time:start:152bda08
$ cat ./obj/build/x86_64-unknown-linux-gnu/native/asan/build/lib/asan/clang_rt.asan-dynamic-i386.vers || true
cat: ./obj/build/x86_64-unknown-linux-gnu/native/asan/build/lib/asan/clang_rt.asan-dynamic-i386.vers: No such file or directory
travis_fold:end:after_failure.5
travis_fold:start:after_failure.6
travis_time:start:00b06ab1
$ dmesg | grep -i kill

I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact @TimNN. (Feature Requests)

@bors bors added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels Jun 5, 2019
@tmandry tmandry force-pushed the emit-storagedead-along-unwind branch from 20fffc8 to 7718b14 Compare June 5, 2019 22:10
@tmandry
Copy link
Member Author

tmandry commented Jun 5, 2019

Ignoring wasm targets in mir-opt test now.

@bors r=eddyb

@bors
Copy link
Contributor

bors commented Jun 5, 2019

📌 Commit 7718b14 has been approved by eddyb

@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 5, 2019
@bors
Copy link
Contributor

bors commented Jun 6, 2019

⌛ Testing commit 7718b14 with merge daf1ed0...

bors added a commit that referenced this pull request Jun 6, 2019
Emit StorageDead along unwind paths for generators

Completion of the work done in #60840. That PR made a change to implicitly consider a local `StorageDead` after Drop, but that was incorrect for DropAndReplace (see also #61060 which tried to fix this in a different way).

This finally enables the optimization implemented in #60187.

r? @eddyb
cc @Zoxc @cramertj @RalfJung
@bors
Copy link
Contributor

bors commented Jun 6, 2019

☀️ Test successful - checks-travis, status-appveyor
Approved by: eddyb
Pushing daf1ed0 to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label Jun 6, 2019
@bors bors merged commit 7718b14 into rust-lang:master Jun 6, 2019
bors added a commit that referenced this pull request Jun 12, 2019
Generator optimization: Overlap locals that never have storage live at the same time

The specific goal of this optimization is to optimize async fns which use `await!`. Notably, `await!` has an enclosing scope around the futures it awaits ([definition](https://github.com/rust-lang/rust/blob/08bfe16129b0621bc90184f8704523d4929695ef/src/libstd/macros.rs#L365-L381)), which we rely on to implement the optimization.

More generally, the optimization allows overlapping the storage of some locals which are never storage-live at the same time. **We care about storage-liveness when computing the layout, because knowing a field is `StorageDead` is the only way to prove it will not be accessed, either directly or through a reference.**

To determine whether we can overlap two locals in the generator layout, we look at whether they might *both* be `StorageLive` at any point in the MIR. We use the `MaybeStorageLive` dataflow analysis for this. We iterate over every location in the MIR, and build a bitset for each local of the locals it might potentially conflict with.

Next, we assign every saved local to one or more variants. The variants correspond to suspension points, and we include the set of locals live across a given suspension point in the variant. (Note that we use liveness instead of storage-liveness here; this ensures that the local has actually been initialized in each variant it has been included in. If the local is not live across a suspension point, then it doesn't need to be included in that variant.). It's important to note that the variants are a "view" into our layout.

For the layout computation, we use a simplified approach.

1. Start with the set of locals assigned to only one variant. The rest are disqualified.
2. For each pair of locals which may conflict *and are not assigned to the same variant*, we pick one local to disqualify from overlapping.

Disqualified locals go into a non-overlapping "prefix" at the beginning of our layout. This means they always have space reserved for them. All the locals that are allowed to overlap in each variant are then laid out after this prefix, in the "overlap zone".

So, if A and B were disqualified, and X, Y, and Z were all eligible for overlap, our generator might look something like this:

You can think of a generator as an enum, where some fields are shared between variants. e.g.

```rust
enum Generator {
  Unresumed,
  Poisoned,
  Returned,
  Suspend0(A, B, X),
  Suspend1(B),
  Suspend2(A, Y, Z),
}
```

where every mention of `A` and `B` refer to the same field, which does not move when changing variants. Note that `A` and `B` would automatically be sent to the prefix in this example. Assuming that `X` is never `StorageLive` at the same time as either `Y` or `Z`, it would be allowed to overlap with them.

Note that if two locals (`Y` and `Z` in this case) are assigned to the same variant in our generator, their memory would never overlap in the layout. Thus they can both be eligible for the overlapping section, even if they are storage-live at the same time.

---

Depends on:
- [x] #59897 Multi-variant layouts for generators
- [x] #60840 Preserve local scopes in generator MIR
- [x] #61373 Emit StorageDead along unwind paths for generators

Before merging:

- [x] ~Wrap the types of all generator fields in `MaybeUninitialized` in layout::ty::field~ (opened #60889)
- [x] Make PR description more complete (e.g. explain why storage liveness is important and why we have to check every location)
- [x] Clean up TODO
- [x] Fix the layout code to enforce that the same field never moves around in the generator
- [x] Add tests for async/await
- [x] ~Reduce # bits we store by half, since the conflict relation is symmetric~ (note: decided not to do this, for simplicity)
- [x] Store liveness information for each yield point in our `GeneratorLayout`, that way we can emit more useful debuginfo AND tell miri which fields are definitely initialized for a given variant (see discussion at #59897 (comment))
Centril added a commit to Centril/rust that referenced this pull request Jun 12, 2019
…ddyb

Generator optimization: Overlap locals that never have storage live at the same time

The specific goal of this optimization is to optimize async fns which use `await!`. Notably, `await!` has an enclosing scope around the futures it awaits ([definition](https://github.com/rust-lang/rust/blob/08bfe16129b0621bc90184f8704523d4929695ef/src/libstd/macros.rs#L365-L381)), which we rely on to implement the optimization.

More generally, the optimization allows overlapping the storage of some locals which are never storage-live at the same time. **We care about storage-liveness when computing the layout, because knowing a field is `StorageDead` is the only way to prove it will not be accessed, either directly or through a reference.**

To determine whether we can overlap two locals in the generator layout, we look at whether they might *both* be `StorageLive` at any point in the MIR. We use the `MaybeStorageLive` dataflow analysis for this. We iterate over every location in the MIR, and build a bitset for each local of the locals it might potentially conflict with.

Next, we assign every saved local to one or more variants. The variants correspond to suspension points, and we include the set of locals live across a given suspension point in the variant. (Note that we use liveness instead of storage-liveness here; this ensures that the local has actually been initialized in each variant it has been included in. If the local is not live across a suspension point, then it doesn't need to be included in that variant.). It's important to note that the variants are a "view" into our layout.

For the layout computation, we use a simplified approach.

1. Start with the set of locals assigned to only one variant. The rest are disqualified.
2. For each pair of locals which may conflict *and are not assigned to the same variant*, we pick one local to disqualify from overlapping.

Disqualified locals go into a non-overlapping "prefix" at the beginning of our layout. This means they always have space reserved for them. All the locals that are allowed to overlap in each variant are then laid out after this prefix, in the "overlap zone".

So, if A and B were disqualified, and X, Y, and Z were all eligible for overlap, our generator might look something like this:

You can think of a generator as an enum, where some fields are shared between variants. e.g.

```rust
enum Generator {
  Unresumed,
  Poisoned,
  Returned,
  Suspend0(A, B, X),
  Suspend1(B),
  Suspend2(A, Y, Z),
}
```

where every mention of `A` and `B` refer to the same field, which does not move when changing variants. Note that `A` and `B` would automatically be sent to the prefix in this example. Assuming that `X` is never `StorageLive` at the same time as either `Y` or `Z`, it would be allowed to overlap with them.

Note that if two locals (`Y` and `Z` in this case) are assigned to the same variant in our generator, their memory would never overlap in the layout. Thus they can both be eligible for the overlapping section, even if they are storage-live at the same time.

---

Depends on:
- [x] rust-lang#59897 Multi-variant layouts for generators
- [x] rust-lang#60840 Preserve local scopes in generator MIR
- [x] rust-lang#61373 Emit StorageDead along unwind paths for generators

Before merging:

- [x] ~Wrap the types of all generator fields in `MaybeUninitialized` in layout::ty::field~ (opened rust-lang#60889)
- [x] Make PR description more complete (e.g. explain why storage liveness is important and why we have to check every location)
- [x] Clean up TODO
- [x] Fix the layout code to enforce that the same field never moves around in the generator
- [x] Add tests for async/await
- [x] ~Reduce # bits we store by half, since the conflict relation is symmetric~ (note: decided not to do this, for simplicity)
- [x] Store liveness information for each yield point in our `GeneratorLayout`, that way we can emit more useful debuginfo AND tell miri which fields are definitely initialized for a given variant (see discussion at rust-lang#59897 (comment))
@tmandry tmandry deleted the emit-storagedead-along-unwind branch July 14, 2022 23:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
merged-by-bors This PR was explicitly merged by bors. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants