Skip to content

Commit ff1838f

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 ff1838f

File tree

94 files changed

+2408
-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.

94 files changed

+2408
-490
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: 118 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
2222
use std::iter;
2323

24+
use rustc_data_structures::unord::UnordSet;
2425
use rustc_index::bit_set::{BitMatrix, DenseBitSet};
2526
use rustc_index::{Idx, IndexSlice, IndexVec};
2627
use tracing::{debug, trace};
@@ -30,6 +31,17 @@ use crate::{
3031
StructKind, TagEncoding, Variants, WrappingRange,
3132
};
3233

34+
/// This option controls how coroutine saved locals are packed
35+
/// into the coroutine state data
36+
#[derive(Debug, Clone, Copy)]
37+
pub enum PackCoroutineLayout {
38+
/// The classic layout where captures are always promoted to coroutine state prefix
39+
Classic,
40+
/// Captures are first saved into the `UNRESUME` state and promoted
41+
/// when they are used across more than one suspension
42+
CapturesOnly,
43+
}
44+
3345
/// Overlap eligibility and variant assignment for each CoroutineSavedLocal.
3446
#[derive(Clone, Debug, PartialEq)]
3547
enum SavedLocalEligibility<VariantIdx, FieldIdx> {
@@ -74,6 +86,7 @@ fn coroutine_saved_local_eligibility<VariantIdx: Idx, FieldIdx: Idx, LocalIdx: I
7486
}
7587
}
7688
}
89+
debug!(?ineligible_locals, "after counting variants containing a saved local");
7790

7891
// Next, check every pair of eligible locals to see if they
7992
// conflict.
@@ -103,6 +116,7 @@ fn coroutine_saved_local_eligibility<VariantIdx: Idx, FieldIdx: Idx, LocalIdx: I
103116
trace!("removing local {:?} due to conflict with {:?}", remove, other);
104117
}
105118
}
119+
debug!(?ineligible_locals, "after checking conflicts");
106120

107121
// Count the number of variants in use. If only one of them, then it is
108122
// impossible to overlap any locals in our layout. In this case it's
@@ -122,6 +136,7 @@ fn coroutine_saved_local_eligibility<VariantIdx: Idx, FieldIdx: Idx, LocalIdx: I
122136
}
123137
ineligible_locals.insert_all();
124138
}
139+
debug!(?ineligible_locals, "after checking used variants");
125140
}
126141

127142
// Write down the order of our locals that will be promoted to the prefix.
@@ -145,20 +160,24 @@ pub(super) fn layout<
145160
>(
146161
calc: &super::LayoutCalculator<impl HasDataLayout>,
147162
local_layouts: &IndexSlice<LocalIdx, F>,
148-
mut prefix_layouts: IndexVec<FieldIdx, F>,
163+
relocated_upvars: &IndexSlice<LocalIdx, Option<LocalIdx>>,
164+
upvar_layouts: IndexVec<FieldIdx, F>,
149165
variant_fields: &IndexSlice<VariantIdx, IndexVec<FieldIdx, LocalIdx>>,
150166
storage_conflicts: &BitMatrix<LocalIdx, LocalIdx>,
167+
pack: PackCoroutineLayout,
151168
tag_to_layout: impl Fn(Scalar) -> F,
152169
) -> super::LayoutCalculatorResult<FieldIdx, VariantIdx, F> {
153170
use SavedLocalEligibility::*;
154171

155172
let (ineligible_locals, assignments) =
156173
coroutine_saved_local_eligibility(local_layouts.len(), variant_fields, storage_conflicts);
174+
debug!(?ineligible_locals);
157175

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

163182
// `variant_fields` already accounts for the reserved variants, so no need to add them.
164183
let max_discr = (variant_fields.len() - 1) as u128;
@@ -168,18 +187,38 @@ pub(super) fn layout<
168187
valid_range: WrappingRange { start: 0, end: max_discr },
169188
};
170189

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

177216
let (prefix_size, prefix_align) = (prefix.size, prefix.align);
178217

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

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

232322
let mut variant = calc.univariant(
233-
&variant_only_tys.collect::<IndexVec<_, _>>(),
323+
&fields,
234324
&ReprOptions::default(),
235325
StructKind::Prefixed(prefix_size, prefix_align.abi),
236326
)?;
@@ -254,19 +344,14 @@ pub(super) fn layout<
254344
IndexVec::from_elem_n(FieldIdx::new(invalid_field_idx), invalid_field_idx);
255345

256346
let mut offsets_and_memory_index = iter::zip(offsets, memory_index);
257-
let combined_offsets = variant_fields
347+
let combined_offsets = is_ineligible
258348
.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-
}
349+
.map(|(i, &is_ineligible)| {
350+
let (offset, memory_index) = if let Some(field_idx) = is_ineligible {
351+
(promoted_offsets[field_idx], promoted_memory_index[field_idx])
352+
} else {
353+
let (offset, memory_index) = offsets_and_memory_index.next().unwrap();
354+
(offset, promoted_memory_index.len() as u32 + memory_index)
270355
};
271356
combined_inverse_memory_index[memory_index] = i;
272357
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
}

compiler/rustc_codegen_ssa/src/mir/operand.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -934,13 +934,12 @@ impl<'a, 'tcx, V: CodegenObject> OperandValue<V> {
934934
}
935935

936936
impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
937+
#[instrument(level = "debug", skip(self, bx), ret)]
937938
fn maybe_codegen_consume_direct(
938939
&mut self,
939940
bx: &mut Bx,
940941
place_ref: mir::PlaceRef<'tcx>,
941942
) -> Option<OperandRef<'tcx, Bx::Value>> {
942-
debug!("maybe_codegen_consume_direct(place_ref={:?})", place_ref);
943-
944943
match self.locals[place_ref.local] {
945944
LocalRef::Operand(mut o) => {
946945
// We only need to handle the projections that
@@ -980,13 +979,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
980979
}
981980
}
982981

982+
#[instrument(level = "debug", skip(self, bx), ret)]
983983
pub fn codegen_consume(
984984
&mut self,
985985
bx: &mut Bx,
986986
place_ref: mir::PlaceRef<'tcx>,
987987
) -> OperandRef<'tcx, Bx::Value> {
988-
debug!("codegen_consume(place_ref={:?})", place_ref);
989-
990988
let ty = self.monomorphized_place_ty(place_ref);
991989
let layout = bx.cx().layout_of(ty);
992990

@@ -1005,13 +1003,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10051003
bx.load_operand(place)
10061004
}
10071005

1006+
#[instrument(level = "debug", skip(self, bx), ret)]
10081007
pub fn codegen_operand(
10091008
&mut self,
10101009
bx: &mut Bx,
10111010
operand: &mir::Operand<'tcx>,
10121011
) -> OperandRef<'tcx, Bx::Value> {
1013-
debug!("codegen_operand(operand={:?})", operand);
1014-
10151012
match *operand {
10161013
mir::Operand::Copy(ref place) | mir::Operand::Move(ref place) => {
10171014
self.codegen_consume(bx, place.as_ref())

0 commit comments

Comments
 (0)