Skip to content

Commit 7cc2dd8

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]> Signed-off-by: Xiangfei Ding <[email protected]>
1 parent 82224f6 commit 7cc2dd8

File tree

106 files changed

+2891
-662
lines changed

Some content is hidden

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

106 files changed

+2891
-662
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
@@ -67,7 +67,9 @@ pub use canon_abi::{ArmCall, CanonAbi, InterruptKind, X86Call};
6767
pub use extern_abi::CVariadicStatus;
6868
pub use extern_abi::{ExternAbi, all_names};
6969
#[cfg(feature = "nightly")]
70-
pub use layout::{FIRST_VARIANT, FieldIdx, Layout, TyAbiInterface, TyAndLayout, VariantIdx};
70+
pub use layout::{
71+
FIRST_VARIANT, FieldIdx, Layout, PackCoroutineLayout, TyAbiInterface, TyAndLayout, VariantIdx,
72+
};
7173
pub use layout::{LayoutCalculator, LayoutCalculatorError};
7274

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

compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs

Lines changed: 53 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -421,49 +421,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
421421
Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty
422422
));
423423

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

469427
// Complete hack to approximate old AST-borrowck diagnostic: if the span starts
@@ -569,6 +527,58 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
569527
}
570528
}
571529

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

compiler/rustc_borrowck/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2495,7 +2495,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
24952495
// If the local may have been initialized, and it is now currently being
24962496
// mutated, then it is justified to be annotated with the `mut`
24972497
// keyword, since the mutation may be a possible reassignment.
2498-
if is_local_mutation_allowed != LocalMutationIsAllowed::Yes
2498+
if !matches!(is_local_mutation_allowed, LocalMutationIsAllowed::Yes)
24992499
&& self.is_local_ever_initialized(local, state).is_some()
25002500
{
25012501
self.used_mut.insert(local);

compiler/rustc_borrowck/src/path_utils.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::ops::ControlFlow;
33
use rustc_abi::FieldIdx;
44
use rustc_data_structures::graph::dominators::Dominators;
55
use rustc_middle::mir::{BasicBlock, Body, Location, Place, PlaceRef, ProjectionElem};
6-
use rustc_middle::ty::TyCtxt;
6+
use rustc_middle::ty::{CapturedPlace, TyCtxt};
77
use tracing::debug;
88

99
use crate::borrow_set::{BorrowData, BorrowSet, TwoPhaseActivation};
@@ -133,7 +133,7 @@ pub(super) fn borrow_of_local_data(place: Place<'_>) -> bool {
133133
/// of a closure type.
134134
pub(crate) fn is_upvar_field_projection<'tcx>(
135135
tcx: TyCtxt<'tcx>,
136-
upvars: &[&rustc_middle::ty::CapturedPlace<'tcx>],
136+
upvars: &[&CapturedPlace<'tcx>],
137137
place_ref: PlaceRef<'tcx>,
138138
body: &Body<'tcx>,
139139
) -> Option<FieldIdx> {

0 commit comments

Comments
 (0)