Skip to content

Commit 0f13bd4

Browse files
Add some helpful comments
1 parent 3674032 commit 0f13bd4

File tree

1 file changed

+56
-4
lines changed

1 file changed

+56
-4
lines changed

compiler/rustc_mir_transform/src/coroutine/by_move_body.rs

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,16 @@
5858
//! borrowing from the outer closure, and we simply peel off a `deref` projection
5959
//! from them. This second body is stored alongside the first body, and optimized
6060
//! with it in lockstep. When we need to resolve a body for `FnOnce` or `AsyncFnOnce`,
61-
//! we use this "by move" body instead.
61+
//! we use this "by-move" body instead.
62+
//!
63+
//! ## How does this work?
64+
//!
65+
//! This pass essentially remaps the body of the (child) closure of the coroutine-closure
66+
//! to take the set of upvars of the parent closure by value. This at least requires
67+
//! changing a by-ref upvar to be by-value in the case that the outer coroutine-closure
68+
//! captures something by value; however, it may also require renumbering field indices
69+
//! in case precise captures (edition 2021 closure capture rules) caused the inner coroutine
70+
//! to split one field capture into two.
6271
6372
use rustc_data_structures::unord::UnordMap;
6473
use rustc_hir as hir;
@@ -117,8 +126,15 @@ impl<'tcx> MirPass<'tcx> for ByMoveBody {
117126

118127
let mut field_remapping = UnordMap::default();
119128

129+
// One parent capture may correspond to several child captures if we end up
130+
// refining the set of captures via edition-2021 precise captures. We want to
131+
// match up any number of child captures with one parent capture, so we keep
132+
// peeking off this `Peekable` until the child doesn't match anymore.
120133
let mut parent_captures =
121134
tcx.closure_captures(parent_def_id).iter().copied().enumerate().peekable();
135+
// Make sure we use every field at least once, b/c why are we capturing something
136+
// if it's not used in the inner coroutine.
137+
let mut field_used_at_least_once = false;
122138

123139
for (child_field_idx, child_capture) in tcx
124140
.closure_captures(coroutine_def_id)
@@ -133,20 +149,36 @@ impl<'tcx> MirPass<'tcx> for ByMoveBody {
133149
bug!("we ran out of parent captures!")
134150
};
135151

152+
assert!(
153+
child_capture.place.projections.len() >= parent_capture.place.projections.len()
154+
);
155+
// A parent matches a child they share the same prefix of projections.
156+
// The child may have more, if it is capturing sub-fields out of
157+
// something that is captured by-move in the parent closure.
136158
if !std::iter::zip(
137159
&child_capture.place.projections,
138160
&parent_capture.place.projections,
139161
)
140162
.all(|(child, parent)| child.kind == parent.kind)
141163
{
164+
// Make sure the field was used at least once.
165+
assert!(
166+
field_used_at_least_once,
167+
"we captured {parent_capture:#?} but it was not used in the child coroutine?"
168+
);
169+
field_used_at_least_once = false;
142170
// Skip this field.
143171
let _ = parent_captures.next().unwrap();
144172
continue;
145173
}
146174

175+
// Store this set of additional projections (fields and derefs).
176+
// We need to re-apply them later.
147177
let child_precise_captures =
148178
&child_capture.place.projections[parent_capture.place.projections.len()..];
149179

180+
// If the parent captures by-move, and the child captures by-ref, then we
181+
// need to peel an additional `deref` off of the body of the child.
150182
let needs_deref = child_capture.is_by_ref() && !parent_capture.is_by_ref();
151183
if needs_deref {
152184
assert_ne!(
@@ -157,6 +189,8 @@ impl<'tcx> MirPass<'tcx> for ByMoveBody {
157189
);
158190
}
159191

192+
// Finally, store the type of the parent's captured place. We need
193+
// this when building the field projection in the MIR body later on.
160194
let mut parent_capture_ty = parent_capture.place.ty();
161195
parent_capture_ty = match parent_capture.info.capture_kind {
162196
ty::UpvarCapture::ByValue => parent_capture_ty,
@@ -178,6 +212,7 @@ impl<'tcx> MirPass<'tcx> for ByMoveBody {
178212
),
179213
);
180214

215+
field_used_at_least_once = true;
181216
break;
182217
}
183218
}
@@ -226,21 +261,34 @@ impl<'tcx> MutVisitor<'tcx> for MakeByMoveBody<'tcx> {
226261
context: mir::visit::PlaceContext,
227262
location: mir::Location,
228263
) {
264+
// Initializing an upvar local always starts with `CAPTURE_STRUCT_LOCAL` and a
265+
// field projection. If this is in `field_remapping`, then it must not be an
266+
// arg from calling the closure, but instead an upvar.
229267
if place.local == ty::CAPTURE_STRUCT_LOCAL
230268
&& let Some((&mir::ProjectionElem::Field(idx, _), projection)) =
231269
place.projection.split_first()
232270
&& let Some(&(remapped_idx, remapped_ty, needs_deref, additional_projections)) =
233271
self.field_remapping.get(&idx)
234272
{
273+
// As noted before, if the parent closure captures a field by value, and
274+
// the child captures a field by ref, then for the by-move body we're
275+
// generating, we also are taking that field by value. Peel off a deref,
276+
// since a layer of reffing has now become redundant.
235277
let final_deref = if needs_deref {
236-
let Some((mir::ProjectionElem::Deref, rest)) = projection.split_first() else {
237-
bug!();
278+
let [mir::ProjectionElem::Deref] = projection else {
279+
bug!("There should only be a single deref for an upvar local initialization");
238280
};
239-
rest
281+
&[]
240282
} else {
241283
projection
242284
};
243285

286+
// The only thing that should be left is a deref, if the parent captured
287+
// an upvar by-ref.
288+
std::assert_matches::assert_matches!(final_deref, [] | [mir::ProjectionElem::Deref]);
289+
290+
// For all of the additional projections that come out of precise capturing,
291+
// re-apply these projections.
244292
let additional_projections =
245293
additional_projections.iter().map(|elem| match elem.kind {
246294
ProjectionKind::Deref => mir::ProjectionElem::Deref,
@@ -250,6 +298,10 @@ impl<'tcx> MutVisitor<'tcx> for MakeByMoveBody<'tcx> {
250298
_ => unreachable!("precise captures only through fields and derefs"),
251299
});
252300

301+
// We start out with an adjusted field index (and ty), representing the
302+
// upvar that we get from our parent closure. We apply any of the additional
303+
// projections to make sure that to the rest of the body of the closure, the
304+
// place looks the same, and then apply that final deref if necessary.
253305
*place = mir::Place {
254306
local: place.local,
255307
projection: self.tcx.mk_place_elems_from_iter(

0 commit comments

Comments
 (0)