Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 61d4bbd

Browse files
dingxiangfei2009Dirbaio
andcommittedFeb 9, 2025··
new layout
Co-authored-by: Dario Nieuwenhuis <[email protected]>
1 parent 53fb637 commit 61d4bbd

File tree

11 files changed

+402
-38
lines changed

11 files changed

+402
-38
lines changed
 

‎compiler/rustc_abi/src/lib.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -1555,8 +1555,10 @@ pub enum Variants<FieldIdx: Idx, VariantIdx: Idx> {
15551555
/// a struct, and they all have space reserved for the tag.
15561556
/// For enums, the tag is the sole field of the layout.
15571557
Multiple {
1558+
/// Tag definition
15581559
tag: Scalar,
15591560
tag_encoding: TagEncoding<VariantIdx>,
1561+
/// Index of tag among fields
15601562
tag_field: usize,
15611563
variants: IndexVec<VariantIdx, LayoutData<FieldIdx, VariantIdx>>,
15621564
},
@@ -1600,6 +1602,7 @@ pub enum TagEncoding<VariantIdx: Idx> {
16001602
pub struct Niche {
16011603
pub offset: Size,
16021604
pub value: Primitive,
1605+
/// A range of valid values with both endpoints being inclusive
16031606
pub valid_range: WrappingRange,
16041607
}
16051608

@@ -1610,17 +1613,23 @@ impl Niche {
16101613
if niche.available(cx) > 0 { Some(niche) } else { None }
16111614
}
16121615

1616+
/// Compute how many values are outside the valid range, available for optimisation through niche filling
16131617
pub fn available<C: HasDataLayout>(&self, cx: &C) -> u128 {
16141618
let Self { value, valid_range: v, .. } = *self;
16151619
let size = value.size(cx);
16161620
assert!(size.bits() <= 128);
16171621
let max_value = size.unsigned_int_max();
16181622

1619-
// Find out how many values are outside the valid range.
16201623
let niche = v.end.wrapping_add(1)..v.start;
16211624
niche.end.wrapping_sub(niche.start) & max_value
16221625
}
16231626

1627+
/// Try to enlarge the valid value range to include another `count` values,
1628+
/// so that they can be used for niche-filling optimisation.
1629+
/// `None` signals impossibility of reservation.
1630+
/// Otherwise, `Some((start, scalar))` signifies that a reservation is possible,
1631+
/// the first value in the reservation is `start`, and the new scalar including
1632+
/// the reserved values is defined in `scalar`.
16241633
pub fn reserve<C: HasDataLayout>(&self, cx: &C, count: u128) -> Option<(u128, Scalar)> {
16251634
assert!(count > 0);
16261635

‎compiler/rustc_feature/src/unstable.rs

+2
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,8 @@ declare_features! (
453453
(internal, contracts_internals, "CURRENT_RUSTC_VERSION", Some(128044)),
454454
/// Allows coroutines to be cloned.
455455
(unstable, coroutine_clone, "1.65.0", Some(95360)),
456+
/// Allows aggressive merging coroutine saved slots
457+
(unstable, coroutine_new_layout, "CURRENT_RUSTC_VERSION", Some(99999)),
456458
/// Allows defining coroutines.
457459
(unstable, coroutines, "1.21.0", Some(43122)),
458460
/// Allows function attribute `#[coverage(on/off)]`, to control coverage

‎compiler/rustc_middle/src/mir/query.rs

+5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use rustc_index::bit_set::BitMatrix;
1010
use rustc_index::{Idx, IndexVec};
1111
use rustc_macros::{HashStable, TyDecodable, TyEncodable, TypeFoldable, TypeVisitable};
1212
use rustc_span::{Span, Symbol};
13+
use rustc_type_ir::data_structures::IndexMap;
1314
use smallvec::SmallVec;
1415

1516
use super::{ConstValue, SourceInfo};
@@ -57,6 +58,10 @@ pub struct CoroutineLayout<'tcx> {
5758
#[type_foldable(identity)]
5859
#[type_visitable(ignore)]
5960
pub storage_conflicts: BitMatrix<CoroutineSavedLocal, CoroutineSavedLocal>,
61+
62+
#[type_foldable(identity)]
63+
#[type_visitable(ignore)]
64+
pub relocated_upvars: IndexMap<CoroutineSavedLocal, CoroutineSavedLocal>,
6065
}
6166

6267
impl Debug for CoroutineLayout<'_> {

‎compiler/rustc_mir_transform/src/coroutine.rs

+21-3
Original file line numberDiff line numberDiff line change
@@ -976,9 +976,17 @@ fn compute_layout<'tcx>(
976976
suspension_point_at_block,
977977
} = liveness;
978978

979+
// We need to later establish the map between upvars in UNRESUMED and locals in other states.
980+
let local_upvar_map: UnordMap<_, _> = body
981+
.local_upvar_map
982+
.iter_enumerated()
983+
.filter_map(|(field, local)| local.map(|local| (local, field)))
984+
.collect();
985+
979986
// Gather live local types and their indices.
980987
let mut locals = IndexVec::<CoroutineSavedLocal, _>::new();
981988
let mut tys = IndexVec::<CoroutineSavedLocal, _>::new();
989+
let mut saved_local_upvar_map = UnordMap::default();
982990
for (saved_local, local) in saved_locals.iter_enumerated() {
983991
debug!("coroutine saved local {:?} => {:?}", saved_local, local);
984992

@@ -1006,6 +1014,10 @@ fn compute_layout<'tcx>(
10061014
debug!(?decl);
10071015

10081016
tys.push(decl);
1017+
1018+
if let Some(&field) = local_upvar_map.get(&local) {
1019+
saved_local_upvar_map.insert(field, saved_local);
1020+
}
10091021
}
10101022
// These are the "saved locals" sourced from the UNRESUMED state.
10111023
let upvar_saved_locals: IndexVec<FieldIdx, CoroutineSavedLocal> = upvar_tys
@@ -1058,8 +1070,7 @@ fn compute_layout<'tcx>(
10581070
SourceInfo::outermost(body_span.shrink_to_hi()),
10591071
SourceInfo::outermost(body_span.shrink_to_hi()),
10601072
]
1061-
.iter()
1062-
.copied()
1073+
.into_iter()
10631074
.collect();
10641075

10651076
// Build the coroutine variant field list.
@@ -1101,17 +1112,24 @@ fn compute_layout<'tcx>(
11011112
field_names.get_or_insert_with(saved_local, || var.name);
11021113
}
11031114
}
1104-
for (capture, saved_local) in upvar_infos.iter().zip(upvar_saved_locals) {
1115+
for (capture, &saved_local) in upvar_infos.iter().zip(&upvar_saved_locals) {
11051116
field_names.get_or_insert_with(saved_local, || capture.var_ident.name);
11061117
}
11071118
debug!(field_names = ?field_names.debug_map_view());
11081119

1120+
let relocated_upvars = upvar_saved_locals
1121+
.iter_enumerated()
1122+
.filter_map(|(field, &source)| {
1123+
saved_local_upvar_map.get(&field).map(|&dest| (source, dest))
1124+
})
1125+
.collect();
11091126
let layout = CoroutineLayout {
11101127
field_tys: tys,
11111128
field_names,
11121129
variant_fields,
11131130
variant_source_info,
11141131
storage_conflicts,
1132+
relocated_upvars,
11151133
};
11161134
debug!(?layout);
11171135

‎compiler/rustc_mir_transform/src/coroutine/relocate_upvars.rs

+26-12
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,38 @@
44
//! This pass performs the following transformations.
55
//! 1. It generates a fresh batch of locals for each captured upvars.
66
//!
7-
//! For each upvar, whether used or not, a fresh local is created with the same type.
7+
//! For each upvar, whether used or not, a fresh local is created with the same
8+
//! type.
89
//!
9-
//! 2. It replaces the places pointing into those upvars with places pointing into those locals instead
10+
//! 2. It replaces the places pointing into those upvars with places pointing
11+
//! into those locals instead
1012
//!
11-
//! Each place that starts with access into the coroutine structure `_1` is replaced with the fresh local as
12-
//! the base. For instance, `(_1.4 as Some).0` is rewritten into `(_34 as Some).0` when `_34` is the fresh local
13+
//! Each place that starts with access into the coroutine structure `_1` is
14+
//! replaced with the fresh local as the base. For instance, `(_1.4 as Some).0`
15+
//! is rewritten into `(_34 as Some).0` when `_34` is the fresh local
1316
//! corresponding to the captured upvar stored in `_1.4`.
1417
//!
1518
//! 3. It assembles an prologue to replace the current entry block.
1619
//!
17-
//! This prologue block transfers every captured upvar into its corresponding fresh local, *via scratch locals*.
18-
//! The upvars are first completely moved into the scratch locals in batch, and then moved into the destination
19-
//! locals in batch.
20-
//! The reason is that it is possible that coroutine layout may change and the source memory location of
21-
//! an upvar may not necessarily be mapped exactly to the same place as in the `Unresumed` state.
22-
//! While coroutine layout ensures that the same saved local has stable offsets throughout its lifetime,
23-
//! technically the upvar in `Unresumed` state and their fresh locals are different saved locals.
24-
//! This scratch locals re-estabilish safety so that the correct data permutation can take place.
20+
//! This prologue block transfers every captured upvar into its corresponding
21+
//! fresh local, *via scratch locals*.
22+
//! The upvars are first completely moved into the scratch locals in batch,
23+
//! and then moved into the destination locals in batch.
24+
//! The reason is that it is possible that coroutine layout may change and the
25+
//! source memory location of an upvar may not necessarily be mapped exactly to
26+
//! the same place as in the `Unresumed` state.
27+
//! While coroutine layout ensures that the same saved local has stable offsets
28+
//! throughout its lifetime, technically the upvar in `Unresumed` state and
29+
//! their fresh locals are different saved locals.
30+
//! This scratch locals re-estabilish safety so that the correct data
31+
//! permutation can take place.
32+
//!
33+
//! By enabling the feature gate `new_coroutine_layout`, the new coroutine
34+
//! layout calculator enters in effect and further guarantee that the upvars in
35+
//! the `Unresumed` state will share the same memory offsets as
36+
//! their corresponding saved locals, if exist.
37+
//! This policy enables further optimisation opportunities, so that the
38+
//! copies inserted by this pass will be elided away beyond the codegen phase.
2539
2640
use rustc_abi::FieldIdx;
2741
use rustc_index::{IndexSlice, IndexVec};

‎compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,7 @@ symbols! {
704704
core_panic_macro,
705705
coroutine,
706706
coroutine_clone,
707+
coroutine_new_layout,
707708
coroutine_resume,
708709
coroutine_return,
709710
coroutine_state,

‎compiler/rustc_ty_utils/src/layout.rs

+16-7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#![allow(unused)]
12
use std::fmt::Debug;
23
use std::iter;
34

@@ -31,6 +32,7 @@ use crate::errors::{
3132
MultipleArrayFieldsSimdType, NonPrimitiveSimdType, OversizedSimdType, ZeroLengthSimdType,
3233
};
3334

35+
mod coroutine;
3436
mod invariant;
3537

3638
pub(crate) fn provide(providers: &mut Providers) {
@@ -430,7 +432,13 @@ fn layout_of_uncached<'tcx>(
430432
tcx.mk_layout(unit)
431433
}
432434

433-
ty::Coroutine(def_id, args) => coroutine_layout(cx, ty, def_id, args)?,
435+
ty::Coroutine(def_id, args) => {
436+
// if tcx.features().coroutine_new_layout() {
437+
coroutine::coroutine_layout(cx, ty, def_id, args)?
438+
// } else {
439+
// coroutine_layout(cx, ty, def_id, args)?
440+
// }
441+
}
434442

435443
ty::Closure(_, args) => {
436444
let tys = args.as_closure().upvar_tys();
@@ -1216,8 +1224,10 @@ fn variant_info_for_coroutine<'tcx>(
12161224
.zip_eq(upvar_names)
12171225
.enumerate()
12181226
.map(|(field_idx, (_, name))| {
1219-
let field_layout = layout.field(cx, field_idx);
1220-
let offset = layout.fields.offset(field_idx);
1227+
// Upvars occupies the Unresumed variant at index zero
1228+
let variant_layout = layout.for_variant(cx, VariantIdx::ZERO);
1229+
let field_layout = variant_layout.field(cx, field_idx);
1230+
let offset = variant_layout.fields.offset(field_idx);
12211231
upvars_size = upvars_size.max(offset + field_layout.size);
12221232
FieldInfo {
12231233
kind: FieldKind::Upvar,
@@ -1237,12 +1247,11 @@ fn variant_info_for_coroutine<'tcx>(
12371247
let variant_layout = layout.for_variant(cx, variant_idx);
12381248
let mut variant_size = Size::ZERO;
12391249
let fields = variant_def
1240-
.iter()
1241-
.enumerate()
1250+
.iter_enumerated()
12421251
.map(|(field_idx, local)| {
12431252
let field_name = coroutine.field_names[*local];
1244-
let field_layout = variant_layout.field(cx, field_idx);
1245-
let offset = variant_layout.fields.offset(field_idx);
1253+
let field_layout = variant_layout.field(cx, field_idx.index());
1254+
let offset = variant_layout.fields.offset(field_idx.index());
12461255
// The struct is as large as the last field's end
12471256
variant_size = variant_size.max(offset + field_layout.size);
12481257
FieldInfo {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
use std::cmp::Reverse;
2+
use std::collections::BTreeSet;
3+
use std::fmt::Debug;
4+
use std::ops::Bound;
5+
6+
use rustc_abi::{
7+
BackendRepr, FieldIdx, FieldsShape, Integer, Layout, LayoutData, Primitive, Scalar, Size,
8+
TagEncoding, TyAndLayout, Variants, WrappingRange,
9+
};
10+
use rustc_index::bit_set::DenseBitSet;
11+
use rustc_index::{Idx, IndexVec};
12+
use rustc_middle::mir::CoroutineSavedLocal;
13+
use rustc_middle::ty::layout::{HasTyCtxt, IntegerExt, LayoutCx, LayoutError, LayoutOf};
14+
use rustc_middle::ty::{EarlyBinder, GenericArgsRef, Ty};
15+
use tracing::{debug, instrument};
16+
17+
use super::error;
18+
19+
#[instrument(level = "debug", skip(cx))]
20+
pub(super) fn coroutine_layout<'tcx>(
21+
cx: &LayoutCx<'tcx>,
22+
ty: Ty<'tcx>,
23+
def_id: rustc_hir::def_id::DefId,
24+
args: GenericArgsRef<'tcx>,
25+
) -> Result<Layout<'tcx>, &'tcx LayoutError<'tcx>> {
26+
let tcx = cx.tcx();
27+
let Some(info) = tcx.coroutine_layout(def_id, args.as_coroutine().kind_ty()) else {
28+
return Err(error(cx, LayoutError::Unknown(ty)));
29+
};
30+
let mut relocated_upvars = info.relocated_upvars.clone();
31+
for (&source, &dest) in &info.relocated_upvars {
32+
relocated_upvars.insert(dest, source);
33+
}
34+
35+
let tcx = cx.tcx();
36+
let field_layouts: IndexVec<CoroutineSavedLocal, _> = info
37+
.field_tys
38+
.iter_enumerated()
39+
.map(|(saved_local, ty)| {
40+
let ty = EarlyBinder::bind(ty.ty).instantiate(tcx, args);
41+
cx.spanned_layout_of(ty, info.field_tys[saved_local].source_info.span)
42+
})
43+
.try_collect()?;
44+
let layouts: IndexVec<CoroutineSavedLocal, _> =
45+
field_layouts.iter().map(|data| data.layout.clone()).collect();
46+
47+
let field_sort_keys: IndexVec<CoroutineSavedLocal, _>;
48+
let mut saved_locals: Vec<_>;
49+
// ## The heuristic on which saved locals get allocation first ##
50+
// 1. the alignment
51+
// Intuitively data with a larger alignment asks for a larger block of contiguous memory.
52+
// It is easier to get large blocks early in the beginning, but it will get harder to
53+
// recover them as fragmentation creeps in when data with smaller alignment occupies
54+
// the large chunks.
55+
// 2. the size
56+
// The size also poses restriction on layout, but not as potent as alignment.
57+
// 3. the degree of conflicts
58+
// This metric is the number of confliciting saved locals with a given saved local.
59+
// Preferring allocating highly conflicting data over those that are less and more
60+
// transient in nature will keep the fragmentation contained in neighbourhoods of a layout.
61+
(saved_locals, field_sort_keys) = field_layouts
62+
.iter_enumerated()
63+
.map(|(saved_local, ty)| {
64+
(
65+
saved_local,
66+
(
67+
Reverse(ty.align.abi),
68+
Reverse(ty.size),
69+
Reverse(info.storage_conflicts.count(saved_local)),
70+
),
71+
)
72+
})
73+
.unzip();
74+
let mut uninhabited_or_zst = DenseBitSet::new_empty(saved_locals.len());
75+
for (saved_local, ty) in field_layouts.iter_enumerated() {
76+
if ty.backend_repr.is_uninhabited() || ty.is_zst() {
77+
uninhabited_or_zst.insert(saved_local);
78+
}
79+
}
80+
saved_locals.sort_by_key(|&idx| &field_sort_keys[idx]);
81+
let max_discr = (info.variant_fields.len() - 1) as u128;
82+
let discr_int = Integer::fit_unsigned(max_discr);
83+
let tag = Scalar::Initialized {
84+
value: Primitive::Int(discr_int, false),
85+
valid_range: WrappingRange { start: 0, end: max_discr },
86+
};
87+
let tag_layout = TyAndLayout {
88+
ty: discr_int.to_ty(tcx, false),
89+
layout: tcx.mk_layout(LayoutData::scalar(cx, tag)),
90+
};
91+
// This will be *the* align of the entire coroutine,
92+
// which is the maximal alignment of all saved locals.
93+
// We need to also consider the tag layout alignment.
94+
let align = saved_locals
95+
.get(0)
96+
.map(|&idx| layouts[idx].align.max(tag_layout.align))
97+
.unwrap_or(tag_layout.align);
98+
99+
// ## The blocked map, or the reservation map ##
100+
// This map from saved locals to memory layout records the reservation
101+
// status of the coroutine state memory, down to the byte granularity.
102+
// `Slot`s are inserted to mark ranges of memory that a particular saved local
103+
// shall not have overlapping memory allocation, due to the liveness of
104+
// other conflicting saved locals.
105+
// Therefore, we can try to make reservation for this saved local
106+
// by inspecting the gaps before, between, and after those blocked-out memory ranges.
107+
let mut blocked: IndexVec<CoroutineSavedLocal, BTreeSet<Slot>> =
108+
IndexVec::from_elem_n(BTreeSet::new(), saved_locals.len());
109+
let mut has_assignment = DenseBitSet::new_empty(saved_locals.len());
110+
let mut tag_blocked = BTreeSet::new();
111+
let mut assignment = IndexVec::from_elem_n(Slot { start: 0, end: 0 }, saved_locals.len());
112+
for (idx, &current_local) in saved_locals.iter().enumerate() {
113+
if uninhabited_or_zst.contains(current_local) {
114+
// Do not bother to compute on uninhabited data.
115+
// They will not get allocation after all.
116+
// By default, a ZST occupies the beginning of the coroutine state.
117+
continue;
118+
}
119+
let layout_data = &field_layouts[current_local];
120+
121+
let candidate = if let Some(&another_local) = relocated_upvars.get(&current_local)
122+
&& has_assignment.contains(another_local)
123+
{
124+
assignment[another_local]
125+
} else {
126+
find_lowest_viable_allocation(&layout_data, &blocked[current_local])
127+
};
128+
// The discriminant is certainly conflicting with all the saved locals
129+
merge_slot_in(&mut tag_blocked, candidate);
130+
for &other_local in &saved_locals[idx + 1..] {
131+
if info.storage_conflicts.contains(current_local, other_local) {
132+
merge_slot_in(&mut blocked[other_local], candidate);
133+
}
134+
}
135+
// Adjustment to the layout of this field by shifting them into the chosen slot
136+
assignment[current_local] = candidate;
137+
has_assignment.insert(current_local);
138+
}
139+
debug!(assignment = ?assignment.debug_map_view());
140+
141+
// Find a slot for discriminant, also known as the tag.
142+
let tag_candidate = find_lowest_viable_allocation(&tag_layout, &tag_blocked);
143+
debug!(tag = ?tag_candidate);
144+
145+
// Assemble the layout for each coroutine state
146+
let variants: IndexVec<_, LayoutData<_, _>> = info
147+
.variant_fields
148+
.iter_enumerated()
149+
.map(|(index, fields)| {
150+
let size = Size::from_bytes(
151+
fields
152+
.iter()
153+
.map(|&saved_local| assignment[saved_local].end)
154+
.max()
155+
.unwrap_or(0)
156+
.max(tag_candidate.end),
157+
)
158+
.align_to(align.abi);
159+
let offsets: IndexVec<_, _> = fields
160+
.iter()
161+
.map(|&saved_local| Size::from_bytes(assignment[saved_local].start))
162+
.collect();
163+
let memory_index = IndexVec::from_fn_n(|n: FieldIdx| (n.index() as u32), offsets.len());
164+
LayoutData {
165+
// We are aware of specialized layouts such as scalar pairs but this is still
166+
// in development.
167+
// Let us hold off from further optimisation until more information is available.
168+
fields: FieldsShape::Arbitrary { offsets, memory_index },
169+
variants: Variants::Single { index },
170+
backend_repr: BackendRepr::Memory { sized: true },
171+
largest_niche: None,
172+
align,
173+
size,
174+
max_repr_align: None,
175+
unadjusted_abi_align: align.abi,
176+
randomization_seed: 0,
177+
}
178+
})
179+
.collect();
180+
let size = variants
181+
.iter()
182+
.map(|data| data.size)
183+
.max()
184+
.unwrap_or(Size::ZERO)
185+
.max(Size::from_bytes(tag_candidate.end))
186+
.align_to(align.abi);
187+
let layout = tcx.mk_layout(LayoutData {
188+
fields: FieldsShape::Arbitrary {
189+
offsets: [Size::from_bytes(tag_candidate.start)].into(),
190+
memory_index: [0].into(),
191+
},
192+
variants: Variants::Multiple {
193+
tag,
194+
tag_encoding: TagEncoding::Direct,
195+
tag_field: 0,
196+
variants,
197+
},
198+
backend_repr: BackendRepr::Memory { sized: true },
199+
// Suppress niches inside coroutines. If the niche is inside a field that is aliased (due to
200+
// self-referentiality), getting the discriminant can cause aliasing violations.
201+
// `UnsafeCell` blocks niches for the same reason, but we don't yet have `UnsafePinned` that
202+
// would do the same for us here.
203+
// See <https://github.com/rust-lang/rust/issues/63818>, <https://github.com/rust-lang/miri/issues/3780>.
204+
// FIXME(#125735): Remove when <https://github.com/rust-lang/rust/issues/125735>
205+
// is implemented and aliased coroutine fields are wrapped in `UnsafePinned`.
206+
// NOTE(@dingxiangfei2009): I believe there is still niche, which is the tag,
207+
// but I am not sure how much benefit is there for us to grab.
208+
largest_niche: None,
209+
align,
210+
size,
211+
max_repr_align: None,
212+
unadjusted_abi_align: align.abi,
213+
randomization_seed: 0,
214+
});
215+
debug!("coroutine layout ({:?}): {:#?}", ty, layout);
216+
Ok(layout)
217+
}
218+
219+
/// An occupied slot in the coroutine memory at some yield point
220+
#[derive(PartialEq, Eq, Copy, Clone)]
221+
struct Slot {
222+
/// Beginning of the memory slot, inclusive
223+
start: u64,
224+
/// End of the memory slot, exclusive or one byte past the data
225+
end: u64,
226+
}
227+
228+
impl PartialOrd for Slot {
229+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
230+
(self.start, self.end).partial_cmp(&(other.start, other.end))
231+
}
232+
}
233+
234+
impl Ord for Slot {
235+
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
236+
(self.start, self.end).cmp(&(other.start, other.end))
237+
}
238+
}
239+
240+
impl Debug for Slot {
241+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
242+
f.debug_tuple("Slot").field(&self.start).field(&self.end).finish()
243+
}
244+
}
245+
246+
impl Slot {
247+
fn overlap_with(&self, other: &Self) -> bool {
248+
if self.start == self.end || other.start == other.end {
249+
return false;
250+
}
251+
self.contains_point(other.start) || other.contains_point(self.start)
252+
}
253+
fn contains_point(&self, point: u64) -> bool {
254+
self.start <= point && point < self.end
255+
}
256+
}
257+
258+
fn merge_slot_in(slots: &mut BTreeSet<Slot>, slot: Slot) {
259+
let start = Slot { start: slot.start, end: slot.start };
260+
let end = Slot { start: slot.end, end: slot.end };
261+
let one_past_end = Slot { start: slot.end + 1, end: slot.end + 1 };
262+
let (range_start, replace_start) = if let Some(prev) = slots.range(..start).next_back()
263+
&& (prev.end == slot.start || prev.contains_point(slot.start))
264+
{
265+
(Bound::Included(prev), prev.start)
266+
} else {
267+
(Bound::Included(&start), slot.start)
268+
};
269+
let (range_end, replace_end) = if let Some(next) = slots.range(..one_past_end).next_back()
270+
&& next.start == slot.end
271+
{
272+
(Bound::Included(next), next.end)
273+
} else if let Some(prev) = slots.range(..end).next_back()
274+
&& prev.contains_point(slot.end)
275+
{
276+
(Bound::Included(prev), prev.end)
277+
} else {
278+
(Bound::Included(&end), slot.end)
279+
};
280+
let to_remove: Vec<_> = slots.range((range_start, range_end)).copied().collect();
281+
for slot in to_remove {
282+
slots.remove(&slot);
283+
}
284+
slots.insert(Slot { start: replace_start, end: replace_end });
285+
}
286+
287+
fn find_lowest_viable_allocation<F: Idx, V: Idx>(
288+
layout: &LayoutData<F, V>,
289+
blocked: &BTreeSet<Slot>,
290+
) -> Slot {
291+
let size = layout.size.bytes();
292+
let align = layout.align.abi;
293+
let mut candidate = Slot { start: 0, end: size };
294+
for slot in blocked {
295+
if slot.overlap_with(&candidate) {
296+
let start = Size::from_bytes(slot.end).align_to(align).bytes();
297+
candidate = Slot { start, end: start + size };
298+
} else {
299+
break;
300+
}
301+
}
302+
candidate
303+
}

‎tests/ui/async-await/future-sizes/async-awaiting-fut.stdout

+6-6
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ print-type-size field `.value`: 2052 bytes
1414
print-type-size type: `{async fn body of calls_fut<{async fn body of big_fut()}>()}`: 2052 bytes, alignment: 1 bytes
1515
print-type-size discriminant: 1 bytes
1616
print-type-size variant `Unresumed`: 2051 bytes
17-
print-type-size upvar `.fut`: 1 bytes, offset: 0 bytes, alignment: 1 bytes
1817
print-type-size padding: 1026 bytes
1918
print-type-size local `.fut`: 1025 bytes, alignment: 1 bytes
19+
print-type-size upvar `.fut`: 1025 bytes, offset: 1027 bytes, alignment: 1 bytes
2020
print-type-size variant `Suspend0`: 1027 bytes
2121
print-type-size local `.fut`: 1025 bytes
2222
print-type-size local `..coroutine_field4`: 1 bytes, type: bool
@@ -29,8 +29,8 @@ print-type-size variant `Suspend2`: 1027 bytes
2929
print-type-size local `.fut`: 1025 bytes
3030
print-type-size local `..coroutine_field4`: 1 bytes, type: bool
3131
print-type-size local `.__awaitee`: 1 bytes, type: {async fn body of wait()}
32-
print-type-size variant `Returned`: 0 bytes
33-
print-type-size variant `Panicked`: 0 bytes
32+
print-type-size variant `Returned`: 2051 bytes
33+
print-type-size variant `Panicked`: 2051 bytes
3434
print-type-size type: `std::mem::ManuallyDrop<{async fn body of big_fut()}>`: 1025 bytes, alignment: 1 bytes
3535
print-type-size field `.value`: 1025 bytes
3636
print-type-size type: `std::mem::MaybeUninit<{async fn body of big_fut()}>`: 1025 bytes, alignment: 1 bytes
@@ -40,10 +40,10 @@ print-type-size field `.value`: 1025 bytes
4040
print-type-size type: `{async fn body of big_fut()}`: 1025 bytes, alignment: 1 bytes
4141
print-type-size discriminant: 1 bytes
4242
print-type-size variant `Unresumed`: 1024 bytes
43-
print-type-size upvar `.arg`: 1 bytes, offset: 0 bytes, alignment: 1 bytes
4443
print-type-size local `.arg`: 1024 bytes
45-
print-type-size variant `Returned`: 0 bytes
46-
print-type-size variant `Panicked`: 0 bytes
44+
print-type-size upvar `.arg`: 1024 bytes, offset: 1 bytes, alignment: 1 bytes
45+
print-type-size variant `Returned`: 1024 bytes
46+
print-type-size variant `Panicked`: 1024 bytes
4747
print-type-size type: `std::mem::ManuallyDrop<[u8; 1024]>`: 1024 bytes, alignment: 1 bytes
4848
print-type-size field `.value`: 1024 bytes
4949
print-type-size type: `std::mem::MaybeUninit<[u8; 1024]>`: 1024 bytes, alignment: 1 bytes

‎tests/ui/async-await/future-sizes/large-arg.stdout

+9-9
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ print-type-size field `.value`: 1027 bytes
1414
print-type-size type: `{async fn body of a<[u8; 1024]>()}`: 1027 bytes, alignment: 1 bytes
1515
print-type-size discriminant: 1 bytes
1616
print-type-size variant `Unresumed`: 1024 bytes
17-
print-type-size upvar `.t`: 1 bytes, offset: 0 bytes, alignment: 1 bytes
1817
print-type-size local `.t`: 1024 bytes
18+
print-type-size upvar `.t`: 1024 bytes, offset: 1 bytes, alignment: 1 bytes
1919
print-type-size variant `Suspend0`: 1026 bytes
2020
print-type-size local `.__awaitee`: 1026 bytes, type: {async fn body of b<[u8; 1024]>()}
21-
print-type-size variant `Returned`: 0 bytes
22-
print-type-size variant `Panicked`: 0 bytes
21+
print-type-size variant `Returned`: 1024 bytes
22+
print-type-size variant `Panicked`: 1024 bytes
2323
print-type-size type: `std::mem::ManuallyDrop<{async fn body of b<[u8; 1024]>()}>`: 1026 bytes, alignment: 1 bytes
2424
print-type-size field `.value`: 1026 bytes
2525
print-type-size type: `std::mem::MaybeUninit<{async fn body of b<[u8; 1024]>()}>`: 1026 bytes, alignment: 1 bytes
@@ -29,12 +29,12 @@ print-type-size field `.value`: 1026 bytes
2929
print-type-size type: `{async fn body of b<[u8; 1024]>()}`: 1026 bytes, alignment: 1 bytes
3030
print-type-size discriminant: 1 bytes
3131
print-type-size variant `Unresumed`: 1024 bytes
32-
print-type-size upvar `.t`: 1 bytes, offset: 0 bytes, alignment: 1 bytes
3332
print-type-size local `.t`: 1024 bytes
33+
print-type-size upvar `.t`: 1024 bytes, offset: 1 bytes, alignment: 1 bytes
3434
print-type-size variant `Suspend0`: 1025 bytes
3535
print-type-size local `.__awaitee`: 1025 bytes, type: {async fn body of c<[u8; 1024]>()}
36-
print-type-size variant `Returned`: 0 bytes
37-
print-type-size variant `Panicked`: 0 bytes
36+
print-type-size variant `Returned`: 1024 bytes
37+
print-type-size variant `Panicked`: 1024 bytes
3838
print-type-size type: `std::mem::ManuallyDrop<{async fn body of c<[u8; 1024]>()}>`: 1025 bytes, alignment: 1 bytes
3939
print-type-size field `.value`: 1025 bytes
4040
print-type-size type: `std::mem::MaybeUninit<{async fn body of c<[u8; 1024]>()}>`: 1025 bytes, alignment: 1 bytes
@@ -49,10 +49,10 @@ print-type-size variant `Pending`: 0 bytes
4949
print-type-size type: `{async fn body of c<[u8; 1024]>()}`: 1025 bytes, alignment: 1 bytes
5050
print-type-size discriminant: 1 bytes
5151
print-type-size variant `Unresumed`: 1024 bytes
52-
print-type-size upvar `.t`: 1 bytes, offset: 0 bytes, alignment: 1 bytes
5352
print-type-size local `.t`: 1024 bytes
54-
print-type-size variant `Returned`: 0 bytes
55-
print-type-size variant `Panicked`: 0 bytes
53+
print-type-size upvar `.t`: 1024 bytes, offset: 1 bytes, alignment: 1 bytes
54+
print-type-size variant `Returned`: 1024 bytes
55+
print-type-size variant `Panicked`: 1024 bytes
5656
print-type-size type: `std::mem::ManuallyDrop<[u8; 1024]>`: 1024 bytes, alignment: 1 bytes
5757
print-type-size field `.value`: 1024 bytes
5858
print-type-size type: `std::mem::MaybeUninit<[u8; 1024]>`: 1024 bytes, alignment: 1 bytes
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fn main() {
2+
compile_error!("this is an experimental flag that has no obvious user-facing changes");
3+
}

0 commit comments

Comments
 (0)
Please sign in to comment.