@@ -277,15 +277,6 @@ impl<T> MoveVecWithScratch<T> {
277
277
MoveVecWithScratch :: Scratch ( ..) => true ,
278
278
}
279
279
}
280
-
281
- /// Do any moves go from stack to stack?
282
- pub fn stack_to_stack ( & self , is_stack_alloc : impl Fn ( Allocation ) -> bool ) -> bool {
283
- match self {
284
- MoveVecWithScratch :: NoScratch ( moves) | MoveVecWithScratch :: Scratch ( moves) => moves
285
- . iter ( )
286
- . any ( |& ( src, dst, _) | is_stack_alloc ( src) && is_stack_alloc ( dst) ) ,
287
- }
288
- }
289
280
}
290
281
291
282
/// Final stage of move resolution: finding or using scratch
@@ -306,37 +297,28 @@ impl<T> MoveVecWithScratch<T> {
306
297
///
307
298
/// Then, we resolve stack-to-stack moves into stack-to-reg /
308
299
/// reg-to-stack pairs. For this, we try to allocate a second free
309
- /// register. If unavailable, we create another scratch stackslot, and
310
- /// we pick a "victim" register in the appropriate class, and we
311
- /// resolve into: victim -> extra-stackslot; stack-src -> victim;
312
- /// victim -> stack-dst; extra-stackslot -> victim.
313
- ///
314
- /// Sometimes move elision will be able to clean this up a bit. But,
315
- /// for simplicity reasons, let's keep the concerns separated! So we
316
- /// always do the full expansion above.
300
+ /// register. If unavailable, we create a new scratch stackslot to
301
+ /// serve as a backup of one of the in-use registers, then borrow that
302
+ /// register as the scratch register in the middle of stack-to-stack
303
+ /// moves.
317
304
pub struct MoveAndScratchResolver < GetReg , GetStackSlot , IsStackAlloc >
318
305
where
319
306
GetReg : FnMut ( ) -> Option < Allocation > ,
320
307
GetStackSlot : FnMut ( ) -> Allocation ,
321
308
IsStackAlloc : Fn ( Allocation ) -> bool ,
322
309
{
323
- /// Scratch register for stack-to-stack move expansion.
324
- stack_stack_scratch_reg : Option < Allocation > ,
325
- /// Stackslot into which we need to save the stack-to-stack
326
- /// scratch reg before doing any stack-to-stack moves, if we stole
327
- /// the reg.
328
- stack_stack_scratch_reg_save : Option < Allocation > ,
329
310
/// Closure that finds us a PReg at the current location.
330
- find_free_reg : GetReg ,
311
+ pub find_free_reg : GetReg ,
331
312
/// Closure that gets us a stackslot, if needed.
332
- get_stackslot : GetStackSlot ,
313
+ pub get_stackslot : GetStackSlot ,
333
314
/// Closure to determine whether an `Allocation` refers to a stack slot.
334
- is_stack_alloc : IsStackAlloc ,
335
- /// The victim PReg to evict to another stackslot at every
336
- /// stack-to-stack move if a free PReg is not otherwise
337
- /// available. Provided by caller and statically chosen. This is a
338
- /// very last-ditch option, so static choice is OK.
339
- victim : PReg ,
315
+ pub is_stack_alloc : IsStackAlloc ,
316
+ /// Use this register if no free register is available to use as a
317
+ /// temporary in stack-to-stack moves. If we do use this register
318
+ /// for that purpose, its value will be restored by the end of the
319
+ /// move sequence. Provided by caller and statically chosen. This is
320
+ /// a very last-ditch option, so static choice is OK.
321
+ pub borrowed_scratch_reg : PReg ,
340
322
}
341
323
342
324
impl < GetReg , GetStackSlot , IsStackAlloc > MoveAndScratchResolver < GetReg , GetStackSlot , IsStackAlloc >
@@ -345,88 +327,129 @@ where
345
327
GetStackSlot : FnMut ( ) -> Allocation ,
346
328
IsStackAlloc : Fn ( Allocation ) -> bool ,
347
329
{
348
- pub fn new (
349
- find_free_reg : GetReg ,
350
- get_stackslot : GetStackSlot ,
351
- is_stack_alloc : IsStackAlloc ,
352
- victim : PReg ,
353
- ) -> Self {
354
- Self {
355
- stack_stack_scratch_reg : None ,
356
- stack_stack_scratch_reg_save : None ,
357
- find_free_reg,
358
- get_stackslot,
359
- is_stack_alloc,
360
- victim,
361
- }
362
- }
330
+ pub fn compute < T : Debug + Default + Copy > (
331
+ mut self ,
332
+ moves : MoveVecWithScratch < T > ,
333
+ ) -> MoveVec < T > {
334
+ let moves = if moves. needs_scratch ( ) {
335
+ // Now, find a scratch allocation in order to resolve cycles.
336
+ let scratch = ( self . find_free_reg ) ( ) . unwrap_or_else ( || ( self . get_stackslot ) ( ) ) ;
337
+ trace ! ( "scratch resolver: scratch alloc {:?}" , scratch) ;
338
+
339
+ moves. with_scratch ( scratch)
340
+ } else {
341
+ moves. without_scratch ( ) . unwrap ( )
342
+ } ;
363
343
364
- pub fn compute < T : Debug + Copy > ( mut self , moves : MoveVecWithScratch < T > ) -> MoveVec < T > {
365
- // First, do we have a vec with no stack-to-stack moves or use
366
- // of a scratch register? Fast return if so.
367
- if !moves. needs_scratch ( ) && !moves. stack_to_stack ( & self . is_stack_alloc ) {
368
- return moves. without_scratch ( ) . unwrap ( ) ;
344
+ // Do we have any stack-to-stack moves? Fast return if not.
345
+ let stack_to_stack = moves
346
+ . iter ( )
347
+ . any ( |& ( src, dst, _) | self . is_stack_to_stack_move ( src, dst) ) ;
348
+ if !stack_to_stack {
349
+ return moves;
369
350
}
370
351
371
- let mut result = smallvec ! [ ] ;
372
-
373
- // Now, find a scratch allocation in order to resolve cycles.
374
- let scratch = ( self . find_free_reg ) ( ) . unwrap_or_else ( || ( self . get_stackslot ) ( ) ) ;
375
- trace ! ( "scratch resolver: scratch alloc {:?}" , scratch) ;
352
+ // Allocate a scratch register for stack-to-stack move expansion.
353
+ let ( scratch_reg, save_slot) = if let Some ( reg) = ( self . find_free_reg ) ( ) {
354
+ trace ! (
355
+ "scratch resolver: have free stack-to-stack scratch preg: {:?}" ,
356
+ reg
357
+ ) ;
358
+ ( reg, None )
359
+ } else {
360
+ let reg = Allocation :: reg ( self . borrowed_scratch_reg ) ;
361
+ // Stackslot into which we need to save the stack-to-stack
362
+ // scratch reg before doing any stack-to-stack moves, if we stole
363
+ // the reg.
364
+ let save = ( self . get_stackslot ) ( ) ;
365
+ trace ! (
366
+ "scratch resolver: stack-to-stack borrowing {:?} with save stackslot {:?}" ,
367
+ reg,
368
+ save
369
+ ) ;
370
+ ( reg, Some ( save) )
371
+ } ;
372
+
373
+ // Mutually exclusive flags for whether either scratch_reg or
374
+ // save_slot need to be restored from the other. Initially,
375
+ // scratch_reg has a value we should preserve and save_slot
376
+ // has garbage.
377
+ let mut scratch_dirty = false ;
378
+ let mut save_dirty = true ;
376
379
377
- let moves = moves . with_scratch ( scratch ) ;
380
+ let mut result = smallvec ! [ ] ;
378
381
for & ( src, dst, data) in & moves {
379
382
// Do we have a stack-to-stack move? If so, resolve.
380
- if ( self . is_stack_alloc ) ( src) && ( self . is_stack_alloc ) ( dst) {
383
+ if self . is_stack_to_stack_move ( src, dst) {
381
384
trace ! ( "scratch resolver: stack to stack: {:?} -> {:?}" , src, dst) ;
382
- // Lazily allocate a stack-to-stack scratch.
383
- if self . stack_stack_scratch_reg . is_none ( ) {
384
- if let Some ( reg) = ( self . find_free_reg ) ( ) {
385
- trace ! (
386
- "scratch resolver: have free stack-to-stack scratch preg: {:?}" ,
387
- reg
388
- ) ;
389
- self . stack_stack_scratch_reg = Some ( reg) ;
390
- } else {
391
- self . stack_stack_scratch_reg = Some ( Allocation :: reg ( self . victim ) ) ;
392
- self . stack_stack_scratch_reg_save = Some ( ( self . get_stackslot ) ( ) ) ;
393
- trace ! ( "scratch resolver: stack-to-stack using victim {:?} with save stackslot {:?}" ,
394
- self . stack_stack_scratch_reg,
395
- self . stack_stack_scratch_reg_save) ;
385
+
386
+ // If the selected scratch register is stolen from the
387
+ // set of in-use registers, then we need to save the
388
+ // current contents of the scratch register before using
389
+ // it as a temporary.
390
+ if let Some ( save_slot) = save_slot {
391
+ // However we may have already done so for an earlier
392
+ // stack-to-stack move in which case we don't need
393
+ // to do it again.
394
+ if save_dirty {
395
+ debug_assert ! ( !scratch_dirty) ;
396
+ result. push ( ( scratch_reg, save_slot, T :: default ( ) ) ) ;
397
+ save_dirty = false ;
396
398
}
397
399
}
398
400
399
- // If we have a "victimless scratch", then do a
400
- // stack-to-scratch / scratch-to-stack sequence.
401
- if self . stack_stack_scratch_reg_save . is_none ( ) {
402
- result. push ( ( src, self . stack_stack_scratch_reg . unwrap ( ) , data) ) ;
403
- result. push ( ( self . stack_stack_scratch_reg . unwrap ( ) , dst, data) ) ;
401
+ // We can't move directly from one stack slot to another
402
+ // on any architecture we care about, so stack-to-stack
403
+ // moves must go via a scratch register.
404
+ result. push ( ( src, scratch_reg, data) ) ;
405
+ result. push ( ( scratch_reg, dst, data) ) ;
406
+ scratch_dirty = true ;
407
+ } else {
408
+ // This is not a stack-to-stack move, but we need to
409
+ // make sure that the scratch register is in the correct
410
+ // state if this move interacts with that register.
411
+ if src == scratch_reg && scratch_dirty {
412
+ // We're copying from the scratch register so if
413
+ // it was stolen for a stack-to-stack move then we
414
+ // need to make sure it has the correct contents,
415
+ // not whatever was temporarily copied into it. If
416
+ // we got scratch_reg from find_free_reg then it
417
+ // had better not have been used as the source of
418
+ // a move. So if we're here it's because we fell
419
+ // back to the caller-provided last-resort scratch
420
+ // register, and we must therefore have a save-slot
421
+ // allocated too.
422
+ debug_assert ! ( !save_dirty) ;
423
+ let save_slot = save_slot. expect ( "move source should not be a free register" ) ;
424
+ result. push ( ( save_slot, scratch_reg, T :: default ( ) ) ) ;
425
+ scratch_dirty = false ;
404
426
}
405
- // Otherwise, save the current value in the
406
- // stack-to-stack scratch reg (which is our victim) to
407
- // the extra stackslot, then do the stack-to-scratch /
408
- // scratch-to-stack sequence, then restore it.
409
- else {
410
- result. push ( (
411
- self . stack_stack_scratch_reg . unwrap ( ) ,
412
- self . stack_stack_scratch_reg_save . unwrap ( ) ,
413
- data,
414
- ) ) ;
415
- result. push ( ( src, self . stack_stack_scratch_reg . unwrap ( ) , data) ) ;
416
- result. push ( ( self . stack_stack_scratch_reg . unwrap ( ) , dst, data) ) ;
417
- result. push ( (
418
- self . stack_stack_scratch_reg_save . unwrap ( ) ,
419
- self . stack_stack_scratch_reg . unwrap ( ) ,
420
- data,
421
- ) ) ;
427
+ if dst == scratch_reg {
428
+ // We are writing something to the scratch register
429
+ // so it doesn't matter what was there before. We
430
+ // can avoid restoring it, but we will need to save
431
+ // it again before the next stack-to-stack move.
432
+ scratch_dirty = false ;
433
+ save_dirty = true ;
422
434
}
423
- } else {
424
- // Normal move.
425
435
result. push ( ( src, dst, data) ) ;
426
436
}
427
437
}
428
438
439
+ // Now that all the stack-to-stack moves are done, restore the
440
+ // scratch register if necessary.
441
+ if let Some ( save_slot) = save_slot {
442
+ if scratch_dirty {
443
+ debug_assert ! ( !save_dirty) ;
444
+ result. push ( ( save_slot, scratch_reg, T :: default ( ) ) ) ;
445
+ }
446
+ }
447
+
429
448
trace ! ( "scratch resolver: got {:?}" , result) ;
430
449
result
431
450
}
451
+
452
+ fn is_stack_to_stack_move ( & self , src : Allocation , dst : Allocation ) -> bool {
453
+ ( self . is_stack_alloc ) ( src) && ( self . is_stack_alloc ) ( dst)
454
+ }
432
455
}
0 commit comments