@@ -26,10 +26,21 @@ use rustc_index::{Idx, IndexSlice, IndexVec};
2626use tracing:: { debug, trace} ;
2727
2828use crate :: {
29- BackendRepr , FieldsShape , HasDataLayout , Integer , LayoutData , Primitive , ReprOptions , Scalar ,
30- StructKind , TagEncoding , Variants , WrappingRange ,
29+ Align , BackendRepr , FieldsShape , HasDataLayout , Integer , LayoutData , Primitive , ReprOptions ,
30+ Scalar , 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 ) ]
3546enum 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,19 +186,39 @@ 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 ! ( ?pack, "prefix_layouts={prefix_layouts:#?}" ) ;
174212 let prefix =
175213 calc. univariant ( & prefix_layouts, & ReprOptions :: default ( ) , StructKind :: AlwaysSized ) ?;
176214
177- let ( prefix_size, prefix_align ) = ( prefix. size , prefix . align ) ;
215+ let prefix_size = prefix. size ;
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.
183- debug ! ( "prefix = { :#?}" , prefix ) ;
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.
221+ debug ! ( "prefix={prefix :#?}" ) ;
184222 let ( outer_fields, promoted_offsets, promoted_memory_index) = match prefix. fields {
185223 FieldsShape :: Arbitrary { mut offsets, in_memory_order } => {
186224 // "a" (`0..b_start`) and "b" (`b_start..`) correspond to
@@ -209,26 +247,74 @@ pub(super) fn layout<
209247 _ => unreachable ! ( ) ,
210248 } ;
211249
250+ // Here we start to compute layout of each state variant
212251 let mut size = prefix. size ;
213252 let mut align = prefix. align ;
214253 let variants = variant_fields
215254 . iter_enumerated ( )
216255 . map ( |( index, variant_fields) | {
256+ // Special case: UNRESUMED overlaps with the upvar region of the prefix,
257+ // so that moving upvars may eventually become a no-op.
258+ let is_unresumed = index. index ( ) == 0 ;
259+ if is_unresumed && matches ! ( pack, PackCoroutineLayout :: Classic ) {
260+ let fields = FieldsShape :: Arbitrary {
261+ offsets : ( 0 ..tag_index. index ( ) ) . map ( |i| outer_fields. offset ( i) ) . collect ( ) ,
262+ in_memory_order : ( 0 ..tag_index. index ( ) ) . map ( FieldIdx :: new) . collect ( ) ,
263+ } ;
264+ let align = prefix. align ;
265+ let size = prefix. size ;
266+ return Ok ( LayoutData {
267+ fields,
268+ variants : Variants :: Single { index } ,
269+ backend_repr : BackendRepr :: Memory { sized : true } ,
270+ largest_niche : None ,
271+ uninhabited : false ,
272+ align,
273+ size,
274+ max_repr_align : None ,
275+ unadjusted_abi_align : align. abi ,
276+ randomization_seed : Default :: default ( ) ,
277+ } ) ;
278+ }
279+ let mut is_ineligible = IndexVec :: from_elem_n ( None , variant_fields. len ( ) ) ;
280+ for ( field, & local) in variant_fields. iter_enumerated ( ) {
281+ if is_unresumed {
282+ if let Some ( inner_local) = relocated_upvars[ local]
283+ && inner_local != local
284+ && let Ineligible ( Some ( promoted_field) ) = assignments[ inner_local]
285+ {
286+ is_ineligible. insert ( field, promoted_field) ;
287+ continue ;
288+ }
289+ }
290+ match assignments[ local] {
291+ Assigned ( v) if v == index => { }
292+ Ineligible ( Some ( promoted_field) ) => {
293+ is_ineligible. insert ( field, promoted_field) ;
294+ }
295+ Ineligible ( None ) => {
296+ panic ! ( "an ineligible local should have been promoted into the prefix" )
297+ }
298+ Assigned ( _) => {
299+ panic ! ( "an eligible local should have been assigned to exactly one variant" )
300+ }
301+ Unassigned => {
302+ panic ! ( "each saved local should have been inspected at least once" )
303+ }
304+ }
305+ }
217306 // Only include overlap-eligible fields when we compute our variant layout.
218- let variant_only_tys = variant_fields
219- . iter ( )
220- . filter ( |local| match assignments[ * * local] {
221- Unassigned => unreachable ! ( ) ,
222- Assigned ( v) if v == index => true ,
223- Assigned ( _) => unreachable ! ( "assignment does not match variant" ) ,
224- Ineligible ( _) => false ,
307+ let fields: IndexVec < _ , _ > = variant_fields
308+ . iter_enumerated ( )
309+ . filter_map ( |( field, & local) | {
310+ if is_ineligible. contains ( field) { None } else { Some ( local_layouts[ local] ) }
225311 } )
226- . map ( |local| local_layouts [ * local ] ) ;
312+ . collect ( ) ;
227313
228314 let mut variant = calc. univariant (
229- & variant_only_tys . collect :: < IndexVec < _ , _ > > ( ) ,
315+ & fields ,
230316 & ReprOptions :: default ( ) ,
231- StructKind :: Prefixed ( prefix_size, prefix_align . abi ) ,
317+ StructKind :: Prefixed ( prefix_size, Align :: ONE ) ,
232318 ) ?;
233319 variant. variants = Variants :: Single { index } ;
234320
@@ -251,19 +337,14 @@ pub(super) fn layout<
251337 IndexVec :: from_elem_n ( FieldIdx :: new ( invalid_field_idx) , invalid_field_idx) ;
252338
253339 let mut offsets_and_memory_index = iter:: zip ( offsets, memory_index) ;
254- let combined_offsets = variant_fields
340+ let combined_offsets = is_ineligible
255341 . iter_enumerated ( )
256- . map ( |( i, local) | {
257- let ( offset, memory_index) = match assignments[ * local] {
258- Unassigned => unreachable ! ( ) ,
259- Assigned ( _) => {
260- let ( offset, memory_index) = offsets_and_memory_index. next ( ) . unwrap ( ) ;
261- ( offset, promoted_memory_index. len ( ) as u32 + memory_index)
262- }
263- Ineligible ( field_idx) => {
264- let field_idx = field_idx. unwrap ( ) ;
265- ( promoted_offsets[ field_idx] , promoted_memory_index[ field_idx] )
266- }
342+ . map ( |( i, & is_ineligible) | {
343+ let ( offset, memory_index) = if let Some ( field_idx) = is_ineligible {
344+ ( promoted_offsets[ field_idx] , promoted_memory_index[ field_idx] )
345+ } else {
346+ let ( offset, memory_index) = offsets_and_memory_index. next ( ) . unwrap ( ) ;
347+ ( offset, promoted_memory_index. len ( ) as u32 + memory_index)
267348 } ;
268349 combined_in_memory_order[ memory_index] = i;
269350 offset
0 commit comments