Skip to content

Commit d1e6488

Browse files
relocate upvars with saved locals for analysis
... and treat coroutine upvar captures as saved locals as well. This allows the liveness analysis to determine which captures are truly saved across a yield point and which are initially used but discarded at first yield points. In the event that upvar captures are promoted, most certainly because a coroutine suspends at least once, the slots in the promotion prefix shall be reused. This means that the copies emitted in the upvar relocation MIR pass will eventually elided and eliminated in the codegen phase, hence no additional runtime cost is realised. Additional MIR dumps are inserted so that it is easier to inspect the bodies of async closures, including those that captures the state by-value. Debug information is updated to point at the correct location for upvars in borrow checking errors and final debuginfo. A language change that this patch enables is now actually reverted, so that lifetimes on relocated upvars are invariant with the upvars outside of the coroutine body. We are deferring the language change to a later discussion. Co-authored-by: Dario Nieuwenhuis <[email protected]>
1 parent d127901 commit d1e6488

File tree

101 files changed

+2375
-681
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

101 files changed

+2375
-681
lines changed

compiler/rustc_abi/src/layout.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::fmt::{self, Write};
33
use std::ops::{Bound, Deref};
44
use std::{cmp, iter};
55

6+
pub use coroutine::PackCoroutineLayout;
67
use rustc_hashes::Hash64;
78
use rustc_index::Idx;
89
use rustc_index::bit_set::BitMatrix;
@@ -210,17 +211,21 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
210211
>(
211212
&self,
212213
local_layouts: &IndexSlice<LocalIdx, F>,
213-
prefix_layouts: IndexVec<FieldIdx, F>,
214+
relocated_upvars: &IndexSlice<LocalIdx, Option<LocalIdx>>,
215+
upvar_layouts: IndexVec<FieldIdx, F>,
214216
variant_fields: &IndexSlice<VariantIdx, IndexVec<FieldIdx, LocalIdx>>,
215217
storage_conflicts: &BitMatrix<LocalIdx, LocalIdx>,
218+
pack: PackCoroutineLayout,
216219
tag_to_layout: impl Fn(Scalar) -> F,
217220
) -> LayoutCalculatorResult<FieldIdx, VariantIdx, F> {
218221
coroutine::layout(
219222
self,
220223
local_layouts,
221-
prefix_layouts,
224+
relocated_upvars,
225+
upvar_layouts,
222226
variant_fields,
223227
storage_conflicts,
228+
pack,
224229
tag_to_layout,
225230
)
226231
}

compiler/rustc_abi/src/layout/coroutine.rs

Lines changed: 100 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ use crate::{
3030
StructKind, TagEncoding, Variants, WrappingRange,
3131
};
3232

33+
/// This option controls how coroutine saved locals are packed
34+
/// into the coroutine state data
35+
#[derive(Debug, Clone, Copy)]
36+
pub enum PackCoroutineLayout {
37+
/// The classic layout where captures are always promoted to coroutine state prefix
38+
Classic,
39+
/// Captures are first saved into the `UNRESUME` state and promoted
40+
/// when they are used across more than one suspension
41+
CapturesOnly,
42+
}
43+
3344
/// Overlap eligibility and variant assignment for each CoroutineSavedLocal.
3445
#[derive(Clone, Debug, PartialEq)]
3546
enum SavedLocalEligibility<VariantIdx, FieldIdx> {
@@ -145,20 +156,23 @@ pub(super) fn layout<
145156
>(
146157
calc: &super::LayoutCalculator<impl HasDataLayout>,
147158
local_layouts: &IndexSlice<LocalIdx, F>,
148-
mut prefix_layouts: IndexVec<FieldIdx, F>,
159+
relocated_upvars: &IndexSlice<LocalIdx, Option<LocalIdx>>,
160+
upvar_layouts: IndexVec<FieldIdx, F>,
149161
variant_fields: &IndexSlice<VariantIdx, IndexVec<FieldIdx, LocalIdx>>,
150162
storage_conflicts: &BitMatrix<LocalIdx, LocalIdx>,
163+
pack: PackCoroutineLayout,
151164
tag_to_layout: impl Fn(Scalar) -> F,
152165
) -> super::LayoutCalculatorResult<FieldIdx, VariantIdx, F> {
153166
use SavedLocalEligibility::*;
154167

155168
let (ineligible_locals, assignments) =
156169
coroutine_saved_local_eligibility(local_layouts.len(), variant_fields, storage_conflicts);
157170

158-
// Build a prefix layout, including "promoting" all ineligible
159-
// locals as part of the prefix. We compute the layout of all of
160-
// these fields at once to get optimal packing.
161-
let tag_index = prefix_layouts.next_index();
171+
// Build a prefix layout, consisting of only the state tag and, as per request, upvars
172+
let tag_index = match pack {
173+
PackCoroutineLayout::CapturesOnly => FieldIdx::new(0),
174+
PackCoroutineLayout::Classic => upvar_layouts.next_index(),
175+
};
162176

163177
// `variant_fields` already accounts for the reserved variants, so no need to add them.
164178
let max_discr = (variant_fields.len() - 1) as u128;
@@ -169,17 +183,28 @@ pub(super) fn layout<
169183
};
170184

171185
let promoted_layouts = ineligible_locals.iter().map(|local| local_layouts[local]);
172-
prefix_layouts.push(tag_to_layout(tag));
173-
prefix_layouts.extend(promoted_layouts);
186+
// FIXME: when we introduce more pack scheme, we need to change the prefix layout here
187+
let prefix_layouts: IndexVec<_, _> = match pack {
188+
PackCoroutineLayout::Classic => {
189+
// Classic scheme packs the states as follows
190+
// [ <upvars>.. , <state tag>, <promoted ineligibles>] ++ <variant data>
191+
// In addition, UNRESUME overlaps with the <upvars> part
192+
upvar_layouts.into_iter().chain([tag_to_layout(tag)]).chain(promoted_layouts).collect()
193+
}
194+
PackCoroutineLayout::CapturesOnly => {
195+
[tag_to_layout(tag)].into_iter().chain(promoted_layouts).collect()
196+
}
197+
};
198+
debug!(?prefix_layouts, ?pack);
174199
let prefix =
175200
calc.univariant(&prefix_layouts, &ReprOptions::default(), StructKind::AlwaysSized)?;
176201

177202
let (prefix_size, prefix_align) = (prefix.size, prefix.align);
178203

179-
// Split the prefix layout into the "outer" fields (upvars and
180-
// discriminant) and the "promoted" fields. Promoted fields will
181-
// get included in each variant that requested them in
182-
// CoroutineLayout.
204+
// Split the prefix layout into the discriminant and
205+
// the "promoted" fields.
206+
// Promoted fields will get included in each variant
207+
// that requested them in CoroutineLayout.
183208
debug!("prefix = {:#?}", prefix);
184209
let (outer_fields, promoted_offsets, promoted_memory_index) = match prefix.fields {
185210
FieldsShape::Arbitrary { mut offsets, memory_index } => {
@@ -218,19 +243,67 @@ pub(super) fn layout<
218243
let variants = variant_fields
219244
.iter_enumerated()
220245
.map(|(index, variant_fields)| {
246+
let is_unresumed = index == VariantIdx::new(0);
247+
if is_unresumed && matches!(pack, PackCoroutineLayout::Classic) {
248+
let fields = FieldsShape::Arbitrary {
249+
offsets: (0..tag_index.index()).map(|i| outer_fields.offset(i)).collect(),
250+
memory_index: (0..tag_index.index())
251+
.map(|i| {
252+
(outer_fields.memory_index(i) + promoted_memory_index.len()) as u32
253+
})
254+
.collect(),
255+
};
256+
let align = prefix.align;
257+
let size = prefix.size;
258+
return Ok(LayoutData {
259+
fields,
260+
variants: Variants::Single { index },
261+
backend_repr: BackendRepr::Memory { sized: true },
262+
largest_niche: None,
263+
uninhabited: false,
264+
align,
265+
size,
266+
max_repr_align: None,
267+
unadjusted_abi_align: align.abi,
268+
randomization_seed: Default::default(),
269+
});
270+
}
271+
let mut is_ineligible = IndexVec::from_elem_n(None, variant_fields.len());
272+
for (field, &local) in variant_fields.iter_enumerated() {
273+
if is_unresumed {
274+
if let Some(inner_local) = relocated_upvars[local]
275+
&& let Ineligible(Some(promoted_field)) = assignments[inner_local]
276+
{
277+
is_ineligible.insert(field, promoted_field);
278+
continue;
279+
}
280+
}
281+
match assignments[local] {
282+
Assigned(v) if v == index => {}
283+
Ineligible(Some(promoted_field)) => {
284+
is_ineligible.insert(field, promoted_field);
285+
}
286+
Ineligible(None) => {
287+
panic!("an ineligible local should have been promoted into the prefix")
288+
}
289+
Assigned(_) => {
290+
panic!("an eligible local should have been assigned to exactly one variant")
291+
}
292+
Unassigned => {
293+
panic!("each saved local should have been inspected at least once")
294+
}
295+
}
296+
}
221297
// Only include overlap-eligible fields when we compute our variant layout.
222-
let variant_only_tys = variant_fields
223-
.iter()
224-
.filter(|local| match assignments[**local] {
225-
Unassigned => unreachable!(),
226-
Assigned(v) if v == index => true,
227-
Assigned(_) => unreachable!("assignment does not match variant"),
228-
Ineligible(_) => false,
298+
let fields: IndexVec<_, _> = variant_fields
299+
.iter_enumerated()
300+
.filter_map(|(field, &local)| {
301+
if is_ineligible.contains(field) { None } else { Some(local_layouts[local]) }
229302
})
230-
.map(|local| local_layouts[*local]);
303+
.collect();
231304

232305
let mut variant = calc.univariant(
233-
&variant_only_tys.collect::<IndexVec<_, _>>(),
306+
&fields,
234307
&ReprOptions::default(),
235308
StructKind::Prefixed(prefix_size, prefix_align.abi),
236309
)?;
@@ -254,19 +327,14 @@ pub(super) fn layout<
254327
IndexVec::from_elem_n(FieldIdx::new(invalid_field_idx), invalid_field_idx);
255328

256329
let mut offsets_and_memory_index = iter::zip(offsets, memory_index);
257-
let combined_offsets = variant_fields
330+
let combined_offsets = is_ineligible
258331
.iter_enumerated()
259-
.map(|(i, local)| {
260-
let (offset, memory_index) = match assignments[*local] {
261-
Unassigned => unreachable!(),
262-
Assigned(_) => {
263-
let (offset, memory_index) = offsets_and_memory_index.next().unwrap();
264-
(offset, promoted_memory_index.len() as u32 + memory_index)
265-
}
266-
Ineligible(field_idx) => {
267-
let field_idx = field_idx.unwrap();
268-
(promoted_offsets[field_idx], promoted_memory_index[field_idx])
269-
}
332+
.map(|(i, &is_ineligible)| {
333+
let (offset, memory_index) = if let Some(field_idx) = is_ineligible {
334+
(promoted_offsets[field_idx], promoted_memory_index[field_idx])
335+
} else {
336+
let (offset, memory_index) = offsets_and_memory_index.next().unwrap();
337+
(offset, promoted_memory_index.len() as u32 + memory_index)
270338
};
271339
combined_inverse_memory_index[memory_index] = i;
272340
offset

compiler/rustc_abi/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ pub use callconv::{Heterogeneous, HomogeneousAggregate, Reg, RegKind};
6565
pub use canon_abi::{ArmCall, CanonAbi, InterruptKind, X86Call};
6666
pub use extern_abi::{ExternAbi, all_names};
6767
#[cfg(feature = "nightly")]
68-
pub use layout::{FIRST_VARIANT, FieldIdx, Layout, TyAbiInterface, TyAndLayout, VariantIdx};
68+
pub use layout::{
69+
FIRST_VARIANT, FieldIdx, Layout, PackCoroutineLayout, TyAbiInterface, TyAndLayout, VariantIdx,
70+
};
6971
pub use layout::{LayoutCalculator, LayoutCalculatorError};
7072

7173
/// Requirements for a `StableHashingContext` to be used in this crate.

compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs

Lines changed: 63 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -422,49 +422,18 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
422422
Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty
423423
));
424424

425-
let captured_place = self.upvars[upvar_index.index()];
426-
427-
err.span_label(span, format!("cannot {act}"));
428-
429-
let upvar_hir_id = captured_place.get_root_variable();
430-
431-
if let Node::Pat(pat) = self.infcx.tcx.hir_node(upvar_hir_id)
432-
&& let hir::PatKind::Binding(hir::BindingMode::NONE, _, upvar_ident, _) =
433-
pat.kind
434-
{
435-
if upvar_ident.name == kw::SelfLower {
436-
for (_, node) in self.infcx.tcx.hir_parent_iter(upvar_hir_id) {
437-
if let Some(fn_decl) = node.fn_decl() {
438-
if !matches!(
439-
fn_decl.implicit_self,
440-
hir::ImplicitSelfKind::RefImm | hir::ImplicitSelfKind::RefMut
441-
) {
442-
err.span_suggestion_verbose(
443-
upvar_ident.span.shrink_to_lo(),
444-
"consider changing this to be mutable",
445-
"mut ",
446-
Applicability::MachineApplicable,
447-
);
448-
break;
449-
}
450-
}
451-
}
452-
} else {
453-
err.span_suggestion_verbose(
454-
upvar_ident.span.shrink_to_lo(),
455-
"consider changing this to be mutable",
456-
"mut ",
457-
Applicability::MachineApplicable,
458-
);
459-
}
460-
}
425+
self.suggest_mutable_upvar(*upvar_index, the_place_err, &mut err, span, act);
426+
}
461427

462-
let tcx = self.infcx.tcx;
463-
if let ty::Ref(_, ty, Mutability::Mut) = the_place_err.ty(self.body, tcx).ty.kind()
464-
&& let ty::Closure(id, _) = *ty.kind()
465-
{
466-
self.show_mutating_upvar(tcx, id.expect_local(), the_place_err, &mut err);
467-
}
428+
PlaceRef { local, projection: [] }
429+
if let Some(upvar_index) = self
430+
.body
431+
.local_upvar_map
432+
.iter_enumerated()
433+
.filter_map(|(field, &local)| local.map(|local| (field, local)))
434+
.find_map(|(field, relocated)| (relocated == local).then_some(field)) =>
435+
{
436+
self.suggest_mutable_upvar(upvar_index, the_place_err, &mut err, span, act);
468437
}
469438

470439
// Complete hack to approximate old AST-borrowck diagnostic: if the span starts
@@ -570,6 +539,58 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
570539
}
571540
}
572541

542+
fn suggest_mutable_upvar(
543+
&self,
544+
upvar_index: FieldIdx,
545+
the_place_err: PlaceRef<'tcx>,
546+
err: &mut Diag<'infcx>,
547+
span: Span,
548+
act: &str,
549+
) {
550+
let captured_place = self.upvars[upvar_index.index()];
551+
552+
err.span_label(span, format!("cannot {act}"));
553+
554+
let upvar_hir_id = captured_place.get_root_variable();
555+
556+
if let Node::Pat(pat) = self.infcx.tcx.hir_node(upvar_hir_id)
557+
&& let hir::PatKind::Binding(hir::BindingMode::NONE, _, upvar_ident, _) = pat.kind
558+
{
559+
if upvar_ident.name == kw::SelfLower {
560+
for (_, node) in self.infcx.tcx.hir_parent_iter(upvar_hir_id) {
561+
if let Some(fn_decl) = node.fn_decl() {
562+
if !matches!(
563+
fn_decl.implicit_self,
564+
hir::ImplicitSelfKind::RefImm | hir::ImplicitSelfKind::RefMut
565+
) {
566+
err.span_suggestion_verbose(
567+
upvar_ident.span.shrink_to_lo(),
568+
"consider changing this to be mutable",
569+
"mut ",
570+
Applicability::MachineApplicable,
571+
);
572+
break;
573+
}
574+
}
575+
}
576+
} else {
577+
err.span_suggestion_verbose(
578+
upvar_ident.span.shrink_to_lo(),
579+
"consider changing this to be mutable",
580+
"mut ",
581+
Applicability::MachineApplicable,
582+
);
583+
}
584+
}
585+
586+
let tcx = self.infcx.tcx;
587+
if let ty::Ref(_, ty, Mutability::Mut) = the_place_err.ty(self.body, tcx).ty.kind()
588+
&& let ty::Closure(id, _) = *ty.kind()
589+
{
590+
self.show_mutating_upvar(tcx, id.expect_local(), the_place_err, err);
591+
}
592+
}
593+
573594
/// Suggest `map[k] = v` => `map.insert(k, v)` and the like.
574595
fn suggest_map_index_mut_alternatives(&self, ty: Ty<'tcx>, err: &mut Diag<'infcx>, span: Span) {
575596
let Some(adt) = ty.ty_adt_def() else { return };

0 commit comments

Comments
 (0)