2121
2222use std:: iter;
2323
24+ use rustc_data_structures:: unord:: UnordSet ;
2425use rustc_index:: bit_set:: { BitMatrix , DenseBitSet } ;
2526use rustc_index:: { Idx , IndexSlice , IndexVec } ;
2627use 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 ) ]
3547enum 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
0 commit comments