|
| 1 | +//! Coroutine layout logic. |
| 2 | +//! |
| 3 | +//! When laying out coroutines, we divide our saved local fields into two |
| 4 | +//! categories: overlap-eligible and overlap-ineligible. |
| 5 | +//! |
| 6 | +//! Those fields which are ineligible for overlap go in a "prefix" at the |
| 7 | +//! beginning of the layout, and always have space reserved for them. |
| 8 | +//! |
| 9 | +//! Overlap-eligible fields are only assigned to one variant, so we lay |
| 10 | +//! those fields out for each variant and put them right after the |
| 11 | +//! prefix. |
| 12 | +//! |
| 13 | +//! Finally, in the layout details, we point to the fields from the |
| 14 | +//! variants they are assigned to. It is possible for some fields to be |
| 15 | +//! included in multiple variants. No field ever "moves around" in the |
| 16 | +//! layout; its offset is always the same. |
| 17 | +//! |
| 18 | +//! Also included in the layout are the upvars and the discriminant. |
| 19 | +//! These are included as fields on the "outer" layout; they are not part |
| 20 | +//! of any variant. |
| 21 | +
|
| 22 | +use std::iter; |
| 23 | + |
| 24 | +use rustc_index::bit_set::{BitMatrix, DenseBitSet}; |
| 25 | +use rustc_index::{Idx, IndexSlice, IndexVec}; |
| 26 | +use tracing::{debug, trace}; |
| 27 | + |
| 28 | +use crate::{ |
| 29 | + BackendRepr, FieldsShape, HasDataLayout, Integer, LayoutData, Primitive, ReprOptions, Scalar, |
| 30 | + StructKind, TagEncoding, Variants, WrappingRange, |
| 31 | +}; |
| 32 | + |
| 33 | +/// Overlap eligibility and variant assignment for each CoroutineSavedLocal. |
| 34 | +#[derive(Clone, Debug, PartialEq)] |
| 35 | +enum SavedLocalEligibility<VariantIdx, FieldIdx> { |
| 36 | + Unassigned, |
| 37 | + Assigned(VariantIdx), |
| 38 | + Ineligible(Option<FieldIdx>), |
| 39 | +} |
| 40 | + |
| 41 | +/// Compute the eligibility and assignment of each local. |
| 42 | +fn coroutine_saved_local_eligibility<VariantIdx: Idx, FieldIdx: Idx, LocalIdx: Idx>( |
| 43 | + nb_locals: usize, |
| 44 | + variant_fields: &IndexSlice<VariantIdx, IndexVec<FieldIdx, LocalIdx>>, |
| 45 | + storage_conflicts: &BitMatrix<LocalIdx, LocalIdx>, |
| 46 | +) -> (DenseBitSet<LocalIdx>, IndexVec<LocalIdx, SavedLocalEligibility<VariantIdx, FieldIdx>>) { |
| 47 | + use SavedLocalEligibility::*; |
| 48 | + |
| 49 | + let mut assignments: IndexVec<LocalIdx, _> = IndexVec::from_elem_n(Unassigned, nb_locals); |
| 50 | + |
| 51 | + // The saved locals not eligible for overlap. These will get |
| 52 | + // "promoted" to the prefix of our coroutine. |
| 53 | + let mut ineligible_locals = DenseBitSet::new_empty(nb_locals); |
| 54 | + |
| 55 | + // Figure out which of our saved locals are fields in only |
| 56 | + // one variant. The rest are deemed ineligible for overlap. |
| 57 | + for (variant_index, fields) in variant_fields.iter_enumerated() { |
| 58 | + for local in fields { |
| 59 | + match assignments[*local] { |
| 60 | + Unassigned => { |
| 61 | + assignments[*local] = Assigned(variant_index); |
| 62 | + } |
| 63 | + Assigned(idx) => { |
| 64 | + // We've already seen this local at another suspension |
| 65 | + // point, so it is no longer a candidate. |
| 66 | + trace!( |
| 67 | + "removing local {:?} in >1 variant ({:?}, {:?})", |
| 68 | + local, variant_index, idx |
| 69 | + ); |
| 70 | + ineligible_locals.insert(*local); |
| 71 | + assignments[*local] = Ineligible(None); |
| 72 | + } |
| 73 | + Ineligible(_) => {} |
| 74 | + } |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + // Next, check every pair of eligible locals to see if they |
| 79 | + // conflict. |
| 80 | + for local_a in storage_conflicts.rows() { |
| 81 | + let conflicts_a = storage_conflicts.count(local_a); |
| 82 | + if ineligible_locals.contains(local_a) { |
| 83 | + continue; |
| 84 | + } |
| 85 | + |
| 86 | + for local_b in storage_conflicts.iter(local_a) { |
| 87 | + // local_a and local_b are storage live at the same time, therefore they |
| 88 | + // cannot overlap in the coroutine layout. The only way to guarantee |
| 89 | + // this is if they are in the same variant, or one is ineligible |
| 90 | + // (which means it is stored in every variant). |
| 91 | + if ineligible_locals.contains(local_b) || assignments[local_a] == assignments[local_b] { |
| 92 | + continue; |
| 93 | + } |
| 94 | + |
| 95 | + // If they conflict, we will choose one to make ineligible. |
| 96 | + // This is not always optimal; it's just a greedy heuristic that |
| 97 | + // seems to produce good results most of the time. |
| 98 | + let conflicts_b = storage_conflicts.count(local_b); |
| 99 | + let (remove, other) = |
| 100 | + if conflicts_a > conflicts_b { (local_a, local_b) } else { (local_b, local_a) }; |
| 101 | + ineligible_locals.insert(remove); |
| 102 | + assignments[remove] = Ineligible(None); |
| 103 | + trace!("removing local {:?} due to conflict with {:?}", remove, other); |
| 104 | + } |
| 105 | + } |
| 106 | + |
| 107 | + // Count the number of variants in use. If only one of them, then it is |
| 108 | + // impossible to overlap any locals in our layout. In this case it's |
| 109 | + // always better to make the remaining locals ineligible, so we can |
| 110 | + // lay them out with the other locals in the prefix and eliminate |
| 111 | + // unnecessary padding bytes. |
| 112 | + { |
| 113 | + let mut used_variants = DenseBitSet::new_empty(variant_fields.len()); |
| 114 | + for assignment in &assignments { |
| 115 | + if let Assigned(idx) = assignment { |
| 116 | + used_variants.insert(*idx); |
| 117 | + } |
| 118 | + } |
| 119 | + if used_variants.count() < 2 { |
| 120 | + for assignment in assignments.iter_mut() { |
| 121 | + *assignment = Ineligible(None); |
| 122 | + } |
| 123 | + ineligible_locals.insert_all(); |
| 124 | + } |
| 125 | + } |
| 126 | + |
| 127 | + // Write down the order of our locals that will be promoted to the prefix. |
| 128 | + { |
| 129 | + for (idx, local) in ineligible_locals.iter().enumerate() { |
| 130 | + assignments[local] = Ineligible(Some(FieldIdx::new(idx))); |
| 131 | + } |
| 132 | + } |
| 133 | + debug!("coroutine saved local assignments: {:?}", assignments); |
| 134 | + |
| 135 | + (ineligible_locals, assignments) |
| 136 | +} |
| 137 | + |
| 138 | +/// Compute the full coroutine layout. |
| 139 | +pub(super) fn layout< |
| 140 | + 'a, |
| 141 | + F: core::ops::Deref<Target = &'a LayoutData<FieldIdx, VariantIdx>> + core::fmt::Debug + Copy, |
| 142 | + VariantIdx: Idx, |
| 143 | + FieldIdx: Idx, |
| 144 | + LocalIdx: Idx, |
| 145 | +>( |
| 146 | + calc: &super::LayoutCalculator<impl HasDataLayout>, |
| 147 | + local_layouts: &IndexSlice<LocalIdx, F>, |
| 148 | + mut prefix_layouts: IndexVec<FieldIdx, F>, |
| 149 | + variant_fields: &IndexSlice<VariantIdx, IndexVec<FieldIdx, LocalIdx>>, |
| 150 | + storage_conflicts: &BitMatrix<LocalIdx, LocalIdx>, |
| 151 | + tag_to_layout: impl Fn(Scalar) -> F, |
| 152 | +) -> super::LayoutCalculatorResult<FieldIdx, VariantIdx, F> { |
| 153 | + use SavedLocalEligibility::*; |
| 154 | + |
| 155 | + let (ineligible_locals, assignments) = |
| 156 | + coroutine_saved_local_eligibility(local_layouts.len(), variant_fields, storage_conflicts); |
| 157 | + |
| 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.len(); |
| 162 | + |
| 163 | + // `variant_fields` already accounts for the reserved variants, so no need to add them. |
| 164 | + let max_discr = (variant_fields.len() - 1) as u128; |
| 165 | + let discr_int = Integer::fit_unsigned(max_discr); |
| 166 | + let tag = Scalar::Initialized { |
| 167 | + value: Primitive::Int(discr_int, /* signed = */ false), |
| 168 | + valid_range: WrappingRange { start: 0, end: max_discr }, |
| 169 | + }; |
| 170 | + |
| 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); |
| 174 | + let prefix = |
| 175 | + calc.univariant(&prefix_layouts, &ReprOptions::default(), StructKind::AlwaysSized)?; |
| 176 | + |
| 177 | + let (prefix_size, prefix_align) = (prefix.size, prefix.align); |
| 178 | + |
| 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. |
| 183 | + debug!("prefix = {:#?}", prefix); |
| 184 | + let (outer_fields, promoted_offsets, promoted_memory_index) = match prefix.fields { |
| 185 | + FieldsShape::Arbitrary { mut offsets, memory_index } => { |
| 186 | + let mut inverse_memory_index = memory_index.invert_bijective_mapping(); |
| 187 | + |
| 188 | + // "a" (`0..b_start`) and "b" (`b_start..`) correspond to |
| 189 | + // "outer" and "promoted" fields respectively. |
| 190 | + let b_start = FieldIdx::new(tag_index + 1); |
| 191 | + let offsets_b = IndexVec::from_raw(offsets.raw.split_off(b_start.index())); |
| 192 | + let offsets_a = offsets; |
| 193 | + |
| 194 | + // Disentangle the "a" and "b" components of `inverse_memory_index` |
| 195 | + // by preserving the order but keeping only one disjoint "half" each. |
| 196 | + // FIXME(eddyb) build a better abstraction for permutations, if possible. |
| 197 | + let inverse_memory_index_b: IndexVec<u32, FieldIdx> = inverse_memory_index |
| 198 | + .iter() |
| 199 | + .filter_map(|&i| i.index().checked_sub(b_start.index()).map(FieldIdx::new)) |
| 200 | + .collect(); |
| 201 | + inverse_memory_index.raw.retain(|&i| i.index() < b_start.index()); |
| 202 | + let inverse_memory_index_a = inverse_memory_index; |
| 203 | + |
| 204 | + // Since `inverse_memory_index_{a,b}` each only refer to their |
| 205 | + // respective fields, they can be safely inverted |
| 206 | + let memory_index_a = inverse_memory_index_a.invert_bijective_mapping(); |
| 207 | + let memory_index_b = inverse_memory_index_b.invert_bijective_mapping(); |
| 208 | + |
| 209 | + let outer_fields = |
| 210 | + FieldsShape::Arbitrary { offsets: offsets_a, memory_index: memory_index_a }; |
| 211 | + (outer_fields, offsets_b, memory_index_b) |
| 212 | + } |
| 213 | + _ => unreachable!(), |
| 214 | + }; |
| 215 | + |
| 216 | + let mut size = prefix.size; |
| 217 | + let mut align = prefix.align; |
| 218 | + let variants = variant_fields |
| 219 | + .iter_enumerated() |
| 220 | + .map(|(index, variant_fields)| { |
| 221 | + // 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, |
| 229 | + }) |
| 230 | + .map(|local| local_layouts[*local]); |
| 231 | + |
| 232 | + let mut variant = calc.univariant( |
| 233 | + &variant_only_tys.collect::<IndexVec<_, _>>(), |
| 234 | + &ReprOptions::default(), |
| 235 | + StructKind::Prefixed(prefix_size, prefix_align.abi), |
| 236 | + )?; |
| 237 | + variant.variants = Variants::Single { index }; |
| 238 | + |
| 239 | + let FieldsShape::Arbitrary { offsets, memory_index } = variant.fields else { |
| 240 | + unreachable!(); |
| 241 | + }; |
| 242 | + |
| 243 | + // Now, stitch the promoted and variant-only fields back together in |
| 244 | + // the order they are mentioned by our CoroutineLayout. |
| 245 | + // Because we only use some subset (that can differ between variants) |
| 246 | + // of the promoted fields, we can't just pick those elements of the |
| 247 | + // `promoted_memory_index` (as we'd end up with gaps). |
| 248 | + // So instead, we build an "inverse memory_index", as if all of the |
| 249 | + // promoted fields were being used, but leave the elements not in the |
| 250 | + // subset as `invalid_field_idx`, which we can filter out later to |
| 251 | + // obtain a valid (bijective) mapping. |
| 252 | + let invalid_field_idx = promoted_memory_index.len() + memory_index.len(); |
| 253 | + let mut combined_inverse_memory_index = |
| 254 | + IndexVec::from_elem_n(FieldIdx::new(invalid_field_idx), invalid_field_idx); |
| 255 | + |
| 256 | + let mut offsets_and_memory_index = iter::zip(offsets, memory_index); |
| 257 | + let combined_offsets = variant_fields |
| 258 | + .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 | + } |
| 270 | + }; |
| 271 | + combined_inverse_memory_index[memory_index] = i; |
| 272 | + offset |
| 273 | + }) |
| 274 | + .collect(); |
| 275 | + |
| 276 | + // Remove the unused slots and invert the mapping to obtain the |
| 277 | + // combined `memory_index` (also see previous comment). |
| 278 | + combined_inverse_memory_index.raw.retain(|&i| i.index() != invalid_field_idx); |
| 279 | + let combined_memory_index = combined_inverse_memory_index.invert_bijective_mapping(); |
| 280 | + |
| 281 | + variant.fields = FieldsShape::Arbitrary { |
| 282 | + offsets: combined_offsets, |
| 283 | + memory_index: combined_memory_index, |
| 284 | + }; |
| 285 | + |
| 286 | + size = size.max(variant.size); |
| 287 | + align = align.max(variant.align); |
| 288 | + Ok(variant) |
| 289 | + }) |
| 290 | + .collect::<Result<IndexVec<VariantIdx, _>, _>>()?; |
| 291 | + |
| 292 | + size = size.align_to(align.abi); |
| 293 | + |
| 294 | + let uninhabited = prefix.uninhabited || variants.iter().all(|v| v.is_uninhabited()); |
| 295 | + let abi = BackendRepr::Memory { sized: true }; |
| 296 | + |
| 297 | + Ok(LayoutData { |
| 298 | + variants: Variants::Multiple { |
| 299 | + tag, |
| 300 | + tag_encoding: TagEncoding::Direct, |
| 301 | + tag_field: tag_index, |
| 302 | + variants, |
| 303 | + }, |
| 304 | + fields: outer_fields, |
| 305 | + backend_repr: abi, |
| 306 | + // Suppress niches inside coroutines. If the niche is inside a field that is aliased (due to |
| 307 | + // self-referentiality), getting the discriminant can cause aliasing violations. |
| 308 | + // `UnsafeCell` blocks niches for the same reason, but we don't yet have `UnsafePinned` that |
| 309 | + // would do the same for us here. |
| 310 | + // See <https://github.com/rust-lang/rust/issues/63818>, <https://github.com/rust-lang/miri/issues/3780>. |
| 311 | + // FIXME: Remove when <https://github.com/rust-lang/rust/issues/125735> is implemented and aliased coroutine fields are wrapped in `UnsafePinned`. |
| 312 | + largest_niche: None, |
| 313 | + uninhabited, |
| 314 | + size, |
| 315 | + align, |
| 316 | + max_repr_align: None, |
| 317 | + unadjusted_abi_align: align.abi, |
| 318 | + randomization_seed: Default::default(), |
| 319 | + }) |
| 320 | +} |
0 commit comments