Skip to content

Commit faabda2

Browse files
dingxiangfei2009Dirbaio
authored andcommitted
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 <dirbaio@dirbaio.net>
1 parent cccf075 commit faabda2

File tree

96 files changed

+2543
-822
lines changed

Some content is hidden

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

96 files changed

+2543
-822
lines changed

compiler/rustc_abi/src/layout.rs

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

5+
pub use coroutine::PackCoroutineLayout;
56
use rustc_hashes::Hash64;
67
use rustc_index::Idx;
78
use rustc_index::bit_set::BitMatrix;
@@ -209,17 +210,21 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
209210
>(
210211
&self,
211212
local_layouts: &IndexSlice<LocalIdx, F>,
212-
prefix_layouts: IndexVec<FieldIdx, F>,
213+
relocated_upvars: &IndexSlice<LocalIdx, Option<LocalIdx>>,
214+
upvar_layouts: IndexVec<FieldIdx, F>,
213215
variant_fields: &IndexSlice<VariantIdx, IndexVec<FieldIdx, LocalIdx>>,
214216
storage_conflicts: &BitMatrix<LocalIdx, LocalIdx>,
217+
pack: PackCoroutineLayout,
215218
tag_to_layout: impl Fn(Scalar) -> F,
216219
) -> LayoutCalculatorResult<FieldIdx, VariantIdx, F> {
217220
coroutine::layout(
218221
self,
219222
local_layouts,
220-
prefix_layouts,
223+
relocated_upvars,
224+
upvar_layouts,
221225
variant_fields,
222226
storage_conflicts,
227+
pack,
223228
tag_to_layout,
224229
)
225230
}

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
@@ -393,49 +393,18 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
393393
Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty
394394
));
395395

396-
let captured_place = self.upvars[upvar_index.index()];
397-
398-
err.span_label(span, format!("cannot {act}"));
399-
400-
let upvar_hir_id = captured_place.get_root_variable();
401-
402-
if let Node::Pat(pat) = self.infcx.tcx.hir_node(upvar_hir_id)
403-
&& let hir::PatKind::Binding(hir::BindingMode::NONE, _, upvar_ident, _) =
404-
pat.kind
405-
{
406-
if upvar_ident.name == kw::SelfLower {
407-
for (_, node) in self.infcx.tcx.hir_parent_iter(upvar_hir_id) {
408-
if let Some(fn_decl) = node.fn_decl() {
409-
if !matches!(
410-
fn_decl.implicit_self,
411-
hir::ImplicitSelfKind::RefImm | hir::ImplicitSelfKind::RefMut
412-
) {
413-
err.span_suggestion_verbose(
414-
upvar_ident.span.shrink_to_lo(),
415-
"consider changing this to be mutable",
416-
"mut ",
417-
Applicability::MachineApplicable,
418-
);
419-
break;
420-
}
421-
}
422-
}
423-
} else {
424-
err.span_suggestion_verbose(
425-
upvar_ident.span.shrink_to_lo(),
426-
"consider changing this to be mutable",
427-
"mut ",
428-
Applicability::MachineApplicable,
429-
);
430-
}
431-
}
396+
self.suggest_mutable_upvar(*upvar_index, the_place_err, &mut err, span, act);
397+
}
432398

433-
let tcx = self.infcx.tcx;
434-
if let ty::Ref(_, ty, Mutability::Mut) = the_place_err.ty(self.body, tcx).ty.kind()
435-
&& let ty::Closure(id, _) = *ty.kind()
436-
{
437-
self.show_mutating_upvar(tcx, id.expect_local(), the_place_err, &mut err);
438-
}
399+
PlaceRef { local, projection: [] }
400+
if let Some(upvar_index) = self
401+
.body
402+
.local_upvar_map
403+
.iter_enumerated()
404+
.filter_map(|(field, &local)| local.map(|local| (field, local)))
405+
.find_map(|(field, relocated)| (relocated == local).then_some(field)) =>
406+
{
407+
self.suggest_mutable_upvar(upvar_index, the_place_err, &mut err, span, act);
439408
}
440409

441410
// complete hack to approximate old AST-borrowck
@@ -542,6 +511,58 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
542511
}
543512
}
544513

514+
fn suggest_mutable_upvar(
515+
&self,
516+
upvar_index: FieldIdx,
517+
the_place_err: PlaceRef<'tcx>,
518+
err: &mut Diag<'infcx>,
519+
span: Span,
520+
act: &str,
521+
) {
522+
let captured_place = self.upvars[upvar_index.index()];
523+
524+
err.span_label(span, format!("cannot {act}"));
525+
526+
let upvar_hir_id = captured_place.get_root_variable();
527+
528+
if let Node::Pat(pat) = self.infcx.tcx.hir_node(upvar_hir_id)
529+
&& let hir::PatKind::Binding(hir::BindingMode::NONE, _, upvar_ident, _) = pat.kind
530+
{
531+
if upvar_ident.name == kw::SelfLower {
532+
for (_, node) in self.infcx.tcx.hir_parent_iter(upvar_hir_id) {
533+
if let Some(fn_decl) = node.fn_decl() {
534+
if !matches!(
535+
fn_decl.implicit_self,
536+
hir::ImplicitSelfKind::RefImm | hir::ImplicitSelfKind::RefMut
537+
) {
538+
err.span_suggestion_verbose(
539+
upvar_ident.span.shrink_to_lo(),
540+
"consider changing this to be mutable",
541+
"mut ",
542+
Applicability::MachineApplicable,
543+
);
544+
break;
545+
}
546+
}
547+
}
548+
} else {
549+
err.span_suggestion_verbose(
550+
upvar_ident.span.shrink_to_lo(),
551+
"consider changing this to be mutable",
552+
"mut ",
553+
Applicability::MachineApplicable,
554+
);
555+
}
556+
}
557+
558+
let tcx = self.infcx.tcx;
559+
if let ty::Ref(_, ty, Mutability::Mut) = the_place_err.ty(self.body, tcx).ty.kind()
560+
&& let ty::Closure(id, _) = *ty.kind()
561+
{
562+
self.show_mutating_upvar(tcx, id.expect_local(), the_place_err, err);
563+
}
564+
}
565+
545566
/// Suggest `map[k] = v` => `map.insert(k, v)` and the like.
546567
fn suggest_map_index_mut_alternatives(&self, ty: Ty<'tcx>, err: &mut Diag<'infcx>, span: Span) {
547568
let Some(adt) = ty.ty_adt_def() else { return };

0 commit comments

Comments
 (0)