Skip to content

Commit d305ea1

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 <dirbaio@dirbaio.net> Signed-off-by: Xiangfei Ding <dingxiangfei2009@protonmail.ch>
1 parent ea49d7c commit d305ea1

File tree

96 files changed

+2415
-490
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

+2415
-490
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3338,6 +3338,7 @@ dependencies = [
33383338
"bitflags",
33393339
"rand 0.9.2",
33403340
"rand_xoshiro",
3341+
"rustc-hash 2.1.1",
33413342
"rustc_data_structures",
33423343
"rustc_error_messages",
33433344
"rustc_hashes",

compiler/rustc_abi/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ rand_xoshiro = { version = "0.7.0", optional = true }
1111
rustc_data_structures = { path = "../rustc_data_structures", optional = true }
1212
rustc_error_messages = { path = "../rustc_error_messages", optional = true }
1313
rustc_hashes = { path = "../rustc_hashes" }
14+
rustc-hash = "2.0.0"
1415
rustc_index = { path = "../rustc_index", default-features = false }
1516
rustc_macros = { path = "../rustc_macros", optional = true }
1617
rustc_serialize = { path = "../rustc_serialize", optional = true }

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: 117 additions & 33 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> {
@@ -74,6 +85,7 @@ fn coroutine_saved_local_eligibility<VariantIdx: Idx, FieldIdx: Idx, LocalIdx: I
7485
}
7586
}
7687
}
88+
debug!(?ineligible_locals, "after counting variants containing a saved local");
7789

7890
// Next, check every pair of eligible locals to see if they
7991
// conflict.
@@ -103,6 +115,7 @@ fn coroutine_saved_local_eligibility<VariantIdx: Idx, FieldIdx: Idx, LocalIdx: I
103115
trace!("removing local {:?} due to conflict with {:?}", remove, other);
104116
}
105117
}
118+
debug!(?ineligible_locals, "after checking conflicts");
106119

107120
// Count the number of variants in use. If only one of them, then it is
108121
// impossible to overlap any locals in our layout. In this case it's
@@ -122,6 +135,7 @@ fn coroutine_saved_local_eligibility<VariantIdx: Idx, FieldIdx: Idx, LocalIdx: I
122135
}
123136
ineligible_locals.insert_all();
124137
}
138+
debug!(?ineligible_locals, "after checking used variants");
125139
}
126140

127141
// Write down the order of our locals that will be promoted to the prefix.
@@ -145,20 +159,24 @@ pub(super) fn layout<
145159
>(
146160
calc: &super::LayoutCalculator<impl HasDataLayout>,
147161
local_layouts: &IndexSlice<LocalIdx, F>,
148-
mut prefix_layouts: IndexVec<FieldIdx, F>,
162+
relocated_upvars: &IndexSlice<LocalIdx, Option<LocalIdx>>,
163+
upvar_layouts: IndexVec<FieldIdx, F>,
149164
variant_fields: &IndexSlice<VariantIdx, IndexVec<FieldIdx, LocalIdx>>,
150165
storage_conflicts: &BitMatrix<LocalIdx, LocalIdx>,
166+
pack: PackCoroutineLayout,
151167
tag_to_layout: impl Fn(Scalar) -> F,
152168
) -> super::LayoutCalculatorResult<FieldIdx, VariantIdx, F> {
153169
use SavedLocalEligibility::*;
154170

155171
let (ineligible_locals, assignments) =
156172
coroutine_saved_local_eligibility(local_layouts.len(), variant_fields, storage_conflicts);
173+
debug!(?ineligible_locals);
157174

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();
175+
// Build a prefix layout, consisting of only the state tag and, as per request, upvars
176+
let tag_index = match pack {
177+
PackCoroutineLayout::CapturesOnly => FieldIdx::new(0),
178+
PackCoroutineLayout::Classic => upvar_layouts.next_index(),
179+
};
162180

163181
// `variant_fields` already accounts for the reserved variants, so no need to add them.
164182
let max_discr = (variant_fields.len() - 1) as u128;
@@ -168,18 +186,38 @@ pub(super) fn layout<
168186
valid_range: WrappingRange { start: 0, end: max_discr },
169187
};
170188

171-
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);
189+
let upvars_in_unresumed: rustc_hash::FxHashSet<_> =
190+
variant_fields[VariantIdx::new(0)].iter().copied().collect();
191+
let promoted_layouts = ineligible_locals.iter().filter_map(|local| {
192+
if matches!(pack, PackCoroutineLayout::Classic) && upvars_in_unresumed.contains(&local) {
193+
// We do not need to promote upvars, they are already in the upvar region
194+
None
195+
} else {
196+
Some(local_layouts[local])
197+
}
198+
});
199+
// FIXME: when we introduce more pack scheme, we need to change the prefix layout here
200+
let prefix_layouts: IndexVec<_, _> = match pack {
201+
PackCoroutineLayout::Classic => {
202+
// Classic scheme packs the states as follows
203+
// [ <upvars>.. , <state tag>, <promoted ineligibles>] ++ <variant data>
204+
// In addition, UNRESUME overlaps with the <upvars> part
205+
upvar_layouts.into_iter().chain([tag_to_layout(tag)]).chain(promoted_layouts).collect()
206+
}
207+
PackCoroutineLayout::CapturesOnly => {
208+
[tag_to_layout(tag)].into_iter().chain(promoted_layouts).collect()
209+
}
210+
};
211+
debug!(?prefix_layouts, ?pack);
174212
let prefix =
175213
calc.univariant(&prefix_layouts, &ReprOptions::default(), StructKind::AlwaysSized)?;
176214

177215
let (prefix_size, prefix_align) = (prefix.size, prefix.align);
178216

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.
217+
// Split the prefix layout into the discriminant and
218+
// the "promoted" fields.
219+
// Promoted fields will get included in each variant
220+
// that requested them in CoroutineLayout.
183221
debug!("prefix = {:#?}", prefix);
184222
let (outer_fields, promoted_offsets, promoted_memory_index) = match prefix.fields {
185223
FieldsShape::Arbitrary { mut offsets, memory_index } => {
@@ -213,24 +251,75 @@ pub(super) fn layout<
213251
_ => unreachable!(),
214252
};
215253

254+
// Here we start to compute layout of each state variant
216255
let mut size = prefix.size;
217256
let mut align = prefix.align;
218257
let variants = variant_fields
219258
.iter_enumerated()
220259
.map(|(index, variant_fields)| {
260+
// Special case: UNRESUMED overlaps with the upvar region of the prefix,
261+
// so that moving upvars may eventually become a no-op.
262+
let is_unresumed = index.index() == 0;
263+
if is_unresumed && matches!(pack, PackCoroutineLayout::Classic) {
264+
let fields = FieldsShape::Arbitrary {
265+
offsets: (0..tag_index.index()).map(|i| outer_fields.offset(i)).collect(),
266+
memory_index: (0..tag_index.index())
267+
.map(|i| {
268+
(outer_fields.memory_index(i) + promoted_memory_index.len()) as u32
269+
})
270+
.collect(),
271+
};
272+
let align = prefix.align;
273+
let size = prefix.size;
274+
return Ok(LayoutData {
275+
fields,
276+
variants: Variants::Single { index },
277+
backend_repr: BackendRepr::Memory { sized: true },
278+
largest_niche: None,
279+
uninhabited: false,
280+
align,
281+
size,
282+
max_repr_align: None,
283+
unadjusted_abi_align: align.abi,
284+
randomization_seed: Default::default(),
285+
});
286+
}
287+
let mut is_ineligible = IndexVec::from_elem_n(None, variant_fields.len());
288+
for (field, &local) in variant_fields.iter_enumerated() {
289+
if is_unresumed {
290+
if let Some(inner_local) = relocated_upvars[local]
291+
&& let Ineligible(Some(promoted_field)) = assignments[inner_local]
292+
{
293+
is_ineligible.insert(field, promoted_field);
294+
continue;
295+
}
296+
}
297+
match assignments[local] {
298+
Assigned(v) if v == index => {}
299+
Ineligible(Some(promoted_field)) => {
300+
is_ineligible.insert(field, promoted_field);
301+
}
302+
Ineligible(None) => {
303+
panic!("an ineligible local should have been promoted into the prefix")
304+
}
305+
Assigned(_) => {
306+
panic!("an eligible local should have been assigned to exactly one variant")
307+
}
308+
Unassigned => {
309+
panic!("each saved local should have been inspected at least once")
310+
}
311+
}
312+
}
221313
// 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,
314+
let fields: IndexVec<_, _> = variant_fields
315+
.iter_enumerated()
316+
.filter_map(|(field, &local)| {
317+
if is_ineligible.contains(field) { None } else { Some(local_layouts[local]) }
229318
})
230-
.map(|local| local_layouts[*local]);
319+
.collect();
231320

232321
let mut variant = calc.univariant(
233-
&variant_only_tys.collect::<IndexVec<_, _>>(),
322+
&fields,
234323
&ReprOptions::default(),
235324
StructKind::Prefixed(prefix_size, prefix_align.abi),
236325
)?;
@@ -254,19 +343,14 @@ pub(super) fn layout<
254343
IndexVec::from_elem_n(FieldIdx::new(invalid_field_idx), invalid_field_idx);
255344

256345
let mut offsets_and_memory_index = iter::zip(offsets, memory_index);
257-
let combined_offsets = variant_fields
346+
let combined_offsets = is_ineligible
258347
.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-
}
348+
.map(|(i, &is_ineligible)| {
349+
let (offset, memory_index) = if let Some(field_idx) = is_ineligible {
350+
(promoted_offsets[field_idx], promoted_memory_index[field_idx])
351+
} else {
352+
let (offset, memory_index) = offsets_and_memory_index.next().unwrap();
353+
(offset, promoted_memory_index.len() as u32 + memory_index)
270354
};
271355
combined_inverse_memory_index[memory_index] = i;
272356
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/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2478,7 +2478,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
24782478
// If the local may have been initialized, and it is now currently being
24792479
// mutated, then it is justified to be annotated with the `mut`
24802480
// keyword, since the mutation may be a possible reassignment.
2481-
if is_local_mutation_allowed != LocalMutationIsAllowed::Yes
2481+
if !matches!(is_local_mutation_allowed, LocalMutationIsAllowed::Yes)
24822482
&& self.is_local_ever_initialized(local, state).is_some()
24832483
{
24842484
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> {

compiler/rustc_codegen_cranelift/src/base.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,9 @@ fn codegen_stmt<'tcx>(fx: &mut FunctionCx<'_, '_, 'tcx>, cur_block: Block, stmt:
891891
let variant_dest = lval.downcast_variant(fx, variant_index);
892892
(variant_index, variant_dest, active_field_index)
893893
}
894+
mir::AggregateKind::Coroutine(_def_id, _args) => {
895+
(FIRST_VARIANT, lval.downcast_variant(fx, FIRST_VARIANT), None)
896+
}
894897
_ => (FIRST_VARIANT, lval, None),
895898
};
896899
if active_field_index.is_some() {

compiler/rustc_codegen_llvm/src/debuginfo/metadata/enums/cpp_like.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -721,7 +721,6 @@ fn build_union_fields_for_direct_tag_coroutine<'ll, 'tcx>(
721721

722722
let coroutine_layout = cx.tcx.coroutine_layout(coroutine_def_id, coroutine_args.args).unwrap();
723723

724-
let common_upvar_names = cx.tcx.closure_saved_names_of_captured_variables(coroutine_def_id);
725724
let variant_range = coroutine_args.variant_range(coroutine_def_id, cx.tcx);
726725
let variant_count = (variant_range.start.as_u32()..variant_range.end.as_u32()).len();
727726

@@ -761,7 +760,6 @@ fn build_union_fields_for_direct_tag_coroutine<'ll, 'tcx>(
761760
coroutine_type_and_layout,
762761
coroutine_type_di_node,
763762
coroutine_layout,
764-
common_upvar_names,
765763
);
766764

767765
let span = coroutine_layout.variant_source_info[variant_index].span;

compiler/rustc_codegen_llvm/src/debuginfo/metadata/enums/native.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,6 @@ pub(super) fn build_coroutine_di_node<'ll, 'tcx>(
186186
)
187187
};
188188

189-
let common_upvar_names =
190-
cx.tcx.closure_saved_names_of_captured_variables(coroutine_def_id);
191-
192189
// Build variant struct types
193190
let variant_struct_type_di_nodes: SmallVec<_> = variants
194191
.indices()
@@ -216,7 +213,6 @@ pub(super) fn build_coroutine_di_node<'ll, 'tcx>(
216213
coroutine_type_and_layout,
217214
coroutine_type_di_node,
218215
coroutine_layout,
219-
common_upvar_names,
220216
),
221217
source_info,
222218
}

0 commit comments

Comments
 (0)