Skip to content

Commit c3f3205

Browse files
mir-opt; docstr
1 parent d1e6488 commit c3f3205

6 files changed

+482
-6
lines changed

compiler/rustc_mir_transform/src/coroutine/relocate_upvars.rs

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,57 @@
1-
//! MIR rewrite pass to promote upvars into native locals in the coroutine body
1+
//! MIR rewrite pass to relocate upvars into native locals in the coroutine body
22
//!
33
//! # Summary
4+
//! The current contract of coroutine upvars is as follows.
5+
//! Coroutines are constructed, initially in state UNRESUMED, by copying or moving
6+
//! captures into the `struct`-fields, which are also called prefix fields,
7+
//! taking necessary references as per capture specification.
8+
//!
9+
//! ```text
10+
//! Low address High address
11+
//! ┌─────────┬─────────┬─────┬─────────────────────┬───────────────────────────────────────────────────────┬──────────────┐
12+
//! │ │ │ │ │ │ │
13+
//! │ Upvar 1 │ Upvar 2 │ ... │ Coroutine State Tag │ Ineligibles, aka. saved locals alive across 2+ states │ Other states │
14+
//! │ │ │ │ │ │ │
15+
//! └─────────┴─────────┴─────┴─────────────────────┴───────────────────────────────────────────────────────┴──────────────┘
16+
//! ```
17+
//!
18+
//! In case some upvars are large and short-lived, the classic layout scheme can be wasteful.
19+
//! One way to reduce the memory footprint is to
20+
//!
421
//! This pass performs the following transformations.
522
//! 1. It generates a fresh batch of locals for each captured upvars.
623
//!
724
//! For each upvar, whether used or not, a fresh local is created with the same type.
25+
//! The types respect the nature of the captures, being by-ref, by-ref-mut or by-value.
26+
//! This is reflected in the results in the upvar analysis conducted in the HIR type-checking phase.
827
//!
928
//! 2. It replaces the places pointing into those upvars with places pointing into those locals instead
1029
//!
1130
//! Each place that starts with access into the coroutine structure `_1` is replaced with the fresh local as
12-
//! the base. For instance, `(_1.4 as Some).0` is rewritten into `(_34 as Some).0` when `_34` is the fresh local
31+
//! the base.
32+
//! For instance, `(_1.4 as Some).0` is rewritten into `(_34 as Some).0` when `_34` is the fresh local
1333
//! corresponding to the captured upvar stored in `_1.4`.
1434
//!
35+
//! This phase assumes that the initial built MIR respects the nature of captures.
36+
//! For instance, if the upvar `_1.4` is instead a by-ref-mut capture of a value of type `T`,
37+
//! this phase assumes that all access correctly built as operating on the place `(*_1.4)`.
38+
//! Based on the assumption, this phase replaces `_1.4` with a fresh local `_34: &mut T` and
39+
//! the correctness is still upheld.
40+
//!
1541
//! 3. It assembles an prologue to replace the current entry block.
1642
//!
1743
//! This prologue block transfers every captured upvar into its corresponding fresh local, *via scratch locals*.
1844
//! The upvars are first completely moved into the scratch locals in batch, and then moved into the destination
1945
//! locals in batch.
2046
//! The reason is that it is possible that coroutine layout may change and the source memory location of
21-
//! an upvar may not necessarily be mapped exactly to the same place as in the `Unresumed` state.
22-
//! While coroutine layout ensures that the same saved local has stable offsets throughout its lifetime,
23-
//! technically the upvar in `Unresumed` state and their fresh locals are different saved locals.
24-
//! This scratch locals re-estabilish safety so that the correct data permutation can take place.
47+
//! an upvar may not necessarily be mapped exactly to the same place as in the `UNRESUMED` state.
48+
//! This is very possible, because the coroutine layout scheme at this moment remains opaque,
49+
//! other than the contract that a saved local has a stable internal offset throughout its liveness span.
50+
//!
51+
//! While the current coroutine layout ensures that the same saved local has stable offsets throughout its lifetime,
52+
//! technically the upvar in `UNRESUMED` state and their fresh locals are different saved locals.
53+
//! This scratch locals re-establish safety so that the correct data permutation can take place,
54+
//! when a future coroutine layout calculator sees the permutation fit.
2555
2656
use std::borrow::Cow;
2757

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// skip-filecheck
2+
//@ compile-flags: -Zpack-coroutine-layout=captures-only
3+
#![feature(coroutines, coroutine_trait, stmt_expr_attributes)]
4+
5+
// EMIT_MIR_FOR_EACH_PANIC_STRATEGY
6+
7+
// EMIT_MIR coroutine_relocate_upvars.main-{closure#0}.RelocateUpvars.before.mir
8+
// EMIT_MIR coroutine_relocate_upvars.main-{closure#0}.RelocateUpvars.after.mir
9+
fn main() {
10+
let mut x = String::new();
11+
let gen_ = #[coroutine]
12+
|| {
13+
x = String::new();
14+
yield;
15+
};
16+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// MIR for `main::{closure#0}` after RelocateUpvars
2+
3+
fn main::{closure#0}(_1: {coroutine@$DIR/coroutine-relocate-upvars.rs:12:5: 12:7}, _2: ()) -> ()
4+
yields ()
5+
{
6+
debug x => (*_6);
7+
debug x => _6;
8+
let mut _0: ();
9+
let mut _3: std::string::String;
10+
let _4: ();
11+
let mut _5: ();
12+
let mut _6: &mut std::string::String;
13+
let _7: &mut std::string::String;
14+
15+
bb0: {
16+
StorageLive(_6);
17+
StorageLive(_7);
18+
_7 = move (_1.0: &mut std::string::String);
19+
_6 = move _7;
20+
StorageDead(_7);
21+
goto -> bb24;
22+
}
23+
24+
bb1: {
25+
drop((*_6)) -> [return: bb2, unwind: bb3];
26+
}
27+
28+
bb2: {
29+
(*_6) = move _3;
30+
drop(_3) -> [return: bb4, unwind: bb11, drop: bb8];
31+
}
32+
33+
bb3 (cleanup): {
34+
(*_6) = move _3;
35+
drop(_3) -> [return: bb11, unwind terminate(cleanup)];
36+
}
37+
38+
bb4: {
39+
StorageDead(_3);
40+
StorageLive(_4);
41+
StorageLive(_5);
42+
_5 = ();
43+
_4 = yield(move _5) -> [resume: bb5, drop: bb7];
44+
}
45+
46+
bb5: {
47+
StorageDead(_5);
48+
StorageDead(_4);
49+
_0 = const ();
50+
goto -> bb16;
51+
}
52+
53+
bb6: {
54+
return;
55+
}
56+
57+
bb7: {
58+
StorageDead(_5);
59+
StorageDead(_4);
60+
goto -> bb9;
61+
}
62+
63+
bb8: {
64+
StorageDead(_3);
65+
goto -> bb9;
66+
}
67+
68+
bb9: {
69+
goto -> bb20;
70+
}
71+
72+
bb10: {
73+
coroutine_drop;
74+
}
75+
76+
bb11 (cleanup): {
77+
StorageDead(_3);
78+
goto -> bb23;
79+
}
80+
81+
bb12 (cleanup): {
82+
resume;
83+
}
84+
85+
bb13 (cleanup): {
86+
StorageDead(_6);
87+
goto -> bb12;
88+
}
89+
90+
bb14 (cleanup): {
91+
drop(_6) -> [return: bb13, unwind terminate(cleanup)];
92+
}
93+
94+
bb15: {
95+
StorageDead(_6);
96+
goto -> bb6;
97+
}
98+
99+
bb16: {
100+
drop(_6) -> [return: bb15, unwind: bb12, drop: bb10];
101+
}
102+
103+
bb17 (cleanup): {
104+
StorageDead(_6);
105+
goto -> bb12;
106+
}
107+
108+
bb18 (cleanup): {
109+
drop(_6) -> [return: bb17, unwind terminate(cleanup)];
110+
}
111+
112+
bb19: {
113+
StorageDead(_6);
114+
goto -> bb10;
115+
}
116+
117+
bb20: {
118+
drop(_6) -> [return: bb19, unwind: bb12];
119+
}
120+
121+
bb21 (cleanup): {
122+
terminate(cleanup);
123+
}
124+
125+
bb22 (cleanup): {
126+
StorageDead(_6);
127+
goto -> bb12;
128+
}
129+
130+
bb23 (cleanup): {
131+
drop(_6) -> [return: bb22, unwind terminate(cleanup)];
132+
}
133+
134+
bb24: {
135+
StorageLive(_3);
136+
_3 = String::new() -> [return: bb1, unwind: bb11];
137+
}
138+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// MIR for `main::{closure#0}` after RelocateUpvars
2+
3+
fn main::{closure#0}(_1: {coroutine@$DIR/coroutine-relocate-upvars.rs:12:5: 12:7}, _2: ()) -> ()
4+
yields ()
5+
{
6+
debug x => (*_6);
7+
debug x => _6;
8+
let mut _0: ();
9+
let mut _3: std::string::String;
10+
let _4: ();
11+
let mut _5: ();
12+
let mut _6: &mut std::string::String;
13+
let _7: &mut std::string::String;
14+
15+
bb0: {
16+
StorageLive(_6);
17+
StorageLive(_7);
18+
_7 = move (_1.0: &mut std::string::String);
19+
_6 = move _7;
20+
StorageDead(_7);
21+
goto -> bb24;
22+
}
23+
24+
bb1: {
25+
drop((*_6)) -> [return: bb2, unwind: bb3];
26+
}
27+
28+
bb2: {
29+
(*_6) = move _3;
30+
drop(_3) -> [return: bb4, unwind: bb11, drop: bb8];
31+
}
32+
33+
bb3 (cleanup): {
34+
(*_6) = move _3;
35+
drop(_3) -> [return: bb11, unwind terminate(cleanup)];
36+
}
37+
38+
bb4: {
39+
StorageDead(_3);
40+
StorageLive(_4);
41+
StorageLive(_5);
42+
_5 = ();
43+
_4 = yield(move _5) -> [resume: bb5, drop: bb7];
44+
}
45+
46+
bb5: {
47+
StorageDead(_5);
48+
StorageDead(_4);
49+
_0 = const ();
50+
goto -> bb16;
51+
}
52+
53+
bb6: {
54+
return;
55+
}
56+
57+
bb7: {
58+
StorageDead(_5);
59+
StorageDead(_4);
60+
goto -> bb9;
61+
}
62+
63+
bb8: {
64+
StorageDead(_3);
65+
goto -> bb9;
66+
}
67+
68+
bb9: {
69+
goto -> bb20;
70+
}
71+
72+
bb10: {
73+
coroutine_drop;
74+
}
75+
76+
bb11 (cleanup): {
77+
StorageDead(_3);
78+
goto -> bb23;
79+
}
80+
81+
bb12 (cleanup): {
82+
resume;
83+
}
84+
85+
bb13 (cleanup): {
86+
StorageDead(_6);
87+
goto -> bb12;
88+
}
89+
90+
bb14 (cleanup): {
91+
drop(_6) -> [return: bb13, unwind terminate(cleanup)];
92+
}
93+
94+
bb15: {
95+
StorageDead(_6);
96+
goto -> bb6;
97+
}
98+
99+
bb16: {
100+
drop(_6) -> [return: bb15, unwind: bb12, drop: bb10];
101+
}
102+
103+
bb17 (cleanup): {
104+
StorageDead(_6);
105+
goto -> bb12;
106+
}
107+
108+
bb18 (cleanup): {
109+
drop(_6) -> [return: bb17, unwind terminate(cleanup)];
110+
}
111+
112+
bb19: {
113+
StorageDead(_6);
114+
goto -> bb10;
115+
}
116+
117+
bb20: {
118+
drop(_6) -> [return: bb19, unwind: bb12];
119+
}
120+
121+
bb21 (cleanup): {
122+
terminate(cleanup);
123+
}
124+
125+
bb22 (cleanup): {
126+
StorageDead(_6);
127+
goto -> bb12;
128+
}
129+
130+
bb23 (cleanup): {
131+
drop(_6) -> [return: bb22, unwind terminate(cleanup)];
132+
}
133+
134+
bb24: {
135+
StorageLive(_3);
136+
_3 = String::new() -> [return: bb1, unwind: bb11];
137+
}
138+
}

0 commit comments

Comments
 (0)