Coverage Report

Created: 2021-03-22 08:29

/src/regalloc.rs/lib/src/linear_scan/resolve_moves.rs
Line
Count
Source (jump to first uncovered line)
1
use super::{analysis::BlockPos, next_use, IntId, Location, RegUses, VirtualInterval};
2
use crate::{
3
    analysis_control_flow::CFGInfo,
4
    data_structures::{BlockIx, InstPoint, Point},
5
    inst_stream::{InstExtPoint, InstToInsert, InstToInsertAndExtPoint},
6
    sparse_set::SparseSet,
7
    Function, RealReg, Reg, SpillSlot, TypedIxVec, VirtualReg, Writable,
8
};
9
10
use log::{debug, info, trace};
11
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
12
use smallvec::SmallVec;
13
use std::fmt;
14
15
0
fn resolve_moves_in_block<F: Function>(
16
0
    func: &F,
17
0
    intervals: &Vec<VirtualInterval>,
18
0
    reg_uses: &RegUses,
19
0
    scratches_by_rc: &[Option<RealReg>],
20
0
    spill_slot: &mut u32,
21
0
    moves_in_blocks: &mut Vec<InstToInsertAndExtPoint>,
22
0
    tmp_ordered_moves: &mut Vec<MoveOp>,
23
0
    tmp_stack: &mut Vec<MoveOp>,
24
0
) {
25
0
    let mut block_ends = HashSet::default();
26
0
    let mut block_starts = HashSet::default();
27
0
    for bix in func.blocks() {
28
0
        let insts = func.block_insns(bix);
29
0
        block_ends.insert(insts.last());
30
0
        block_starts.insert(insts.first());
31
0
    }
32
33
0
    let mut reloads_at_inst = HashMap::default();
34
0
    let mut spills_at_inst = Vec::new();
35
36
0
    for interval in intervals {
37
0
        let parent_id = match interval.parent {
38
0
            Some(pid) => pid,
39
0
            None => {
40
0
                // In unreachable code, it's possible that a given interval has no
41
0
                // parents and is assigned to a stack location for its whole lifetime.
42
0
                //
43
0
                // In reachable code, the analysis only create intervals for virtual
44
0
                // registers with at least one register use, so a parentless interval (=
45
0
                // hasn't ever been split) can't live in a stack slot.
46
0
                #[cfg(debug_assertions)]
47
0
                debug_assert!(
48
0
                    interval.location.spill().is_none()
49
0
                        || (next_use(interval, InstPoint::min_value(), reg_uses,).is_none())
50
0
                );
51
0
                continue;
52
0
            }
53
0
        };
54
0
55
0
        let parent = &intervals[parent_id.0];
56
57
        // If this is a move between blocks, handle it as such.
58
0
        if parent.end.pt() == Point::Def
59
0
            && interval.start.pt() == Point::Use
60
0
            && block_ends.contains(&parent.end.iix())
61
0
            && block_starts.contains(&interval.start.iix())
62
        {
63
            continue;
64
0
        }
65
0
66
0
        let child_start = interval.start;
67
0
        let vreg = interval.vreg;
68
0
69
0
        match interval.location {
70
0
            Location::None => panic!("interval has no location after regalloc!"),
71
72
0
            Location::Reg(rreg) => {
73
                // Reconnect with the parent location, by adding a move if needed.
74
0
                if let Some(next_use) = next_use(interval, child_start, reg_uses) {
75
                    // No need to reload before a new definition.
76
0
                    if next_use.pt() == Point::Def {
77
                        continue;
78
0
                    }
79
0
                };
80
81
0
                let mut at_inst = child_start;
82
0
                match at_inst.pt() {
83
0
                    Point::Use => {
84
0
                        at_inst.set_pt(Point::Reload);
85
0
                    }
86
0
                    Point::Def => {
87
0
                        at_inst.set_pt(Point::Spill);
88
0
                    }
89
0
                    _ => unreachable!(),
90
                }
91
92
0
                let entry = reloads_at_inst.entry(at_inst).or_insert_with(|| Vec::new());
93
0
94
0
                match parent.location {
95
0
                    Location::None => unreachable!(),
96
97
0
                    Location::Reg(from_rreg) => {
98
0
                        if from_rreg != rreg {
99
                            debug!(
100
0
                                "inblock fixup: {:?} move {:?} -> {:?} at {:?}",
101
0
                                interval.id, from_rreg, rreg, at_inst
102
                            );
103
0
                            entry.push(MoveOp::new_move(from_rreg, rreg, vreg));
104
0
                        }
105
                    }
106
107
0
                    Location::Stack(spill) => {
108
                        debug!(
109
0
                            "inblock fixup: {:?} reload {:?} -> {:?} at {:?}",
110
0
                            interval.id, spill, rreg, at_inst
111
                        );
112
0
                        entry.push(MoveOp::new_reload(spill, rreg, vreg));
113
                    }
114
                }
115
            }
116
117
0
            Location::Stack(spill) => {
118
0
                // This interval has been spilled (i.e. split). Spill after the last def or before
119
0
                // the last use.
120
0
                let mut at_inst = parent.end;
121
0
                at_inst.set_pt(if at_inst.pt() == Point::Use {
122
0
                    Point::Reload
123
                } else {
124
                    debug_assert!(at_inst.pt() == Point::Def);
125
0
                    Point::Spill
126
                });
127
128
0
                match parent.location {
129
0
                    Location::None => unreachable!(),
130
131
0
                    Location::Reg(rreg) => {
132
                        debug!(
133
0
                            "inblock fixup: {:?} spill {:?} -> {:?} at {:?}",
134
0
                            interval.id, rreg, spill, at_inst
135
                        );
136
0
                        spills_at_inst.push(InstToInsertAndExtPoint::new(
137
0
                            InstToInsert::Spill {
138
0
                                to_slot: spill,
139
0
                                from_reg: rreg,
140
0
                                for_vreg: Some(vreg),
141
0
                            },
142
0
                            InstExtPoint::from_inst_point(at_inst),
143
0
                        ));
144
                    }
145
146
0
                    Location::Stack(parent_spill) => {
147
0
                        debug_assert_eq!(parent_spill, spill);
148
                    }
149
                }
150
            }
151
        }
152
    }
153
154
    // Flush the memory moves caused by in-block fixups. Conceptually, the spills
155
    // must happen after the right locations have been set, that is, after the
156
    // reloads. Reloads may include several moves that must happen in parallel
157
    // (e.g. if two real regs must be swapped), so process them first. Once all
158
    // the parallel assignments have been done, push forward all the spills.
159
0
    for (at_inst, mut pending_moves) in reloads_at_inst {
160
0
        schedule_moves(&mut pending_moves, tmp_ordered_moves, tmp_stack);
161
0
        emit_moves(
162
0
            at_inst,
163
0
            &tmp_ordered_moves,
164
0
            spill_slot,
165
0
            scratches_by_rc,
166
0
            moves_in_blocks,
167
0
        );
168
0
    }
169
170
0
    moves_in_blocks.append(&mut spills_at_inst);
171
0
}
Unexecuted instantiation: regalloc::linear_scan::resolve_moves::resolve_moves_in_block::<minira::test_framework::Func>
Unexecuted instantiation: regalloc::linear_scan::resolve_moves::resolve_moves_in_block::<regalloc::snapshot::IRFunction>
172
173
0
#[derive(Default, Clone)]
174
struct BlockInfo {
175
    start: SmallVec<[(VirtualReg, IntId); 4]>,
176
    end: SmallVec<[(VirtualReg, IntId); 4]>,
177
}
178
179
static UNSORTED_THRESHOLD: usize = 8;
180
181
impl BlockInfo {
182
    #[inline(never)]
183
0
    fn insert(&mut self, pos: BlockPos, vreg: VirtualReg, id: IntId) {
184
0
        match pos {
185
0
            BlockPos::Start => {
186
0
                #[cfg(debug_assertions)]
187
0
                debug_assert!(self.start.iter().find(|prev| prev.0 == vreg).is_none());
188
0
                self.start.push((vreg, id));
189
0
            }
190
0
            BlockPos::End => {
191
0
                #[cfg(debug_assertions)]
192
0
                debug_assert!(self.end.iter().find(|prev| prev.0 == vreg).is_none());
193
0
                self.end.push((vreg, id));
194
0
            }
195
        }
196
0
    }
197
198
    #[inline(never)]
199
0
    fn finish(&mut self) {
200
0
        if self.start.len() >= UNSORTED_THRESHOLD {
201
0
            self.start.sort_unstable_by_key(|pair| pair.0);
202
0
        }
203
0
        if self.end.len() >= UNSORTED_THRESHOLD {
204
0
            self.end.sort_unstable_by_key(|pair| pair.0);
205
0
        }
206
0
    }
207
208
    #[inline(never)]
209
0
    fn lookup(&self, pos: BlockPos, vreg: &VirtualReg) -> IntId {
210
0
        let array = match pos {
211
0
            BlockPos::Start => &self.start,
212
0
            BlockPos::End => &self.end,
213
        };
214
0
        if array.len() >= UNSORTED_THRESHOLD {
215
0
            array[array.binary_search_by_key(vreg, |pair| pair.0).unwrap()].1
216
        } else {
217
0
            array
218
0
                .iter()
219
0
                .find(|el| el.0 == *vreg)
220
0
                .expect("should have found target reg")
221
0
                .1
222
        }
223
0
    }
224
}
225
226
/// For each block, collect a mapping of block_{start, end} -> actual location, to make the
227
/// across-blocks fixup phase fast.
228
#[inline(never)]
229
0
fn collect_block_infos<F: Function>(
230
0
    func: &F,
231
0
    intervals: &Vec<VirtualInterval>,
232
0
    liveins: &TypedIxVec<BlockIx, SparseSet<Reg>>,
233
0
    liveouts: &TypedIxVec<BlockIx, SparseSet<Reg>>,
234
0
) -> Vec<BlockInfo> {
235
0
    // Preallocate the block information, with the final size of each vector.
236
0
    let mut infos = Vec::with_capacity(func.blocks().len());
237
0
    for bix in func.blocks() {
238
0
        infos.push(BlockInfo {
239
0
            start: SmallVec::with_capacity(liveins[bix].card()),
240
0
            end: SmallVec::with_capacity(liveouts[bix].card()),
241
0
        });
242
0
    }
243
244
    // For each interval, add the boundary information to the block info data structure.
245
0
    for int in intervals {
246
0
        let vreg = int.vreg;
247
0
        let id = int.id;
248
0
        for boundary in &int.block_boundaries {
249
0
            let bix = boundary.bix;
250
0
            let pos = boundary.pos;
251
0
            match pos {
252
0
                BlockPos::Start => {
253
                    // In theory, this could be an assertion, if analysis was precise and meaning
254
                    // that RangeFragKind::Thru/LiveIn really meant that (it actually means that
255
                    // the first fragment inst coincided with the block's first inst).
256
0
                    if !liveins[bix].contains(vreg.to_reg()) {
257
                        continue;
258
0
                    }
259
                }
260
                BlockPos::End => {
261
                    // See above comment.
262
0
                    if !liveouts[bix].contains(vreg.to_reg()) {
263
                        continue;
264
0
                    }
265
                }
266
            }
267
0
            infos[bix.get() as usize].insert(pos, vreg, id);
268
        }
269
    }
270
271
0
    for info in infos.iter_mut() {
272
0
        info.finish();
273
0
    }
274
275
0
    infos
276
0
}
Unexecuted instantiation: regalloc::linear_scan::resolve_moves::collect_block_infos::<minira::test_framework::Func>
Unexecuted instantiation: regalloc::linear_scan::resolve_moves::collect_block_infos::<regalloc::snapshot::IRFunction>
277
278
/// Figure the sequence of parallel moves to insert at block boundaries:
279
/// - for each block
280
///  - for each liveout vreg in this block
281
///    - for each successor of this block
282
///      - if the locations allocated in the block and its successor don't
283
///      match, insert a pending move from one location to the other.
284
///
285
/// Once that's done:
286
/// - resolve cycles in the pending moves
287
/// - generate real moves from the pending moves.
288
#[inline(never)]
289
0
fn resolve_moves_across_blocks<F: Function>(
290
0
    func: &F,
291
0
    cfg: &CFGInfo,
292
0
    liveins: &TypedIxVec<BlockIx, SparseSet<Reg>>,
293
0
    liveouts: &TypedIxVec<BlockIx, SparseSet<Reg>>,
294
0
    intervals: &Vec<VirtualInterval>,
295
0
    scratches_by_rc: &[Option<RealReg>],
296
0
    spill_slot: &mut u32,
297
0
    moves_at_block_starts: &mut Vec<InstToInsertAndExtPoint>,
298
0
    moves_at_block_ends: &mut Vec<InstToInsertAndExtPoint>,
299
0
    tmp_ordered_moves: &mut Vec<MoveOp>,
300
0
    tmp_stack: &mut Vec<MoveOp>,
301
0
) {
302
0
    let mut parallel_move_map = HashMap::default();
303
0
304
0
    let block_info = collect_block_infos(func, intervals, liveins, liveouts);
305
0
306
0
    let mut seen_successors = HashSet::default();
307
0
    for block in func.blocks() {
308
0
        let successors = &cfg.succ_map[block];
309
0
310
0
        // Where to insert the fixup move, if needed? Per API contract, there are no more critical
311
0
        // edges: so this block is the only successor to its predecessors, or the only predecessor
312
0
        // to its successors in the control flow graph.
313
0
        // If there's more than one successor to the current block, then inserting the fix-up moves
314
0
        // in the current block may impact all the other successors: in this case, we should insert
315
0
        // the instruction at the end of the current block. If the successors all have only one
316
0
        // predecessor (the current block), then we can insert the fixup move at the beginning of
317
0
        // the successors safely.
318
0
        let all_succ_have_one_pred = successors
319
0
            .iter()
320
0
            .all(|succ| cfg.pred_map[*succ].card() == 1);
321
0
        assert!(successors.card() == 1 || all_succ_have_one_pred);
322
323
0
        for &reg in liveouts[block].iter() {
324
0
            let vreg = if let Some(vreg) = reg.as_virtual_reg() {
325
0
                vreg
326
0
            } else {
327
0
                continue;
328
0
            };
329
0
330
0
            seen_successors.clear();
331
0
332
0
            let cur_id = block_info[block.get() as usize].lookup(BlockPos::End, &vreg);
333
0
            let cur_int = &intervals[cur_id.0];
334
0
            let loc_at_cur_end = cur_int.location;
335
336
0
            for &succ in successors.iter() {
337
0
                if !liveins[succ].contains(reg) {
338
                    // This variable isn't live in this block.
339
                    continue;
340
0
                }
341
0
                if !seen_successors.insert(succ) {
342
                    continue;
343
0
                }
344
0
345
0
                let succ_id = block_info[succ.get() as usize].lookup(BlockPos::Start, &vreg);
346
0
                let succ_int = &intervals[succ_id.0];
347
0
348
0
                // If the two intervals aren't related to the same virtual range, then the move is
349
0
                // not required.
350
0
                if cur_int.ancestor != succ_int.ancestor {
351
                    continue;
352
0
                }
353
0
354
0
                let loc_at_succ_start = succ_int.location;
355
356
0
                let (at_inst, block_pos) = if all_succ_have_one_pred {
357
                    // At the beginning of the successors (each only has a single predecessor).
358
0
                    let pos = InstPoint::new_reload(func.block_insns(succ).first());
359
0
                    (pos, BlockPos::Start)
360
                } else {
361
                    // Before the control flow instruction.
362
0
                    let pos = InstPoint::new_reload(func.block_insns(block).last());
363
0
                    (pos, BlockPos::End)
364
                };
365
366
0
                let pending_moves = parallel_move_map
367
0
                    .entry(at_inst)
368
0
                    .or_insert_with(|| (Vec::new(), block_pos));
369
0
370
0
                match (loc_at_cur_end, loc_at_succ_start) {
371
0
                    (Location::Reg(cur_rreg), Location::Reg(succ_rreg)) => {
372
0
                        if cur_rreg == succ_rreg {
373
                            continue;
374
0
                        }
375
                        debug!(
376
0
                          "boundary fixup: move {:?} -> {:?} at {:?} for {:?} between {:?} and {:?}",
377
0
                          cur_rreg,
378
0
                          succ_rreg,
379
0
                          at_inst,
380
0
                          vreg,
381
0
                          block,
382
0
                          succ
383
                        );
384
0
                        pending_moves
385
0
                            .0
386
0
                            .push(MoveOp::new_move(cur_rreg, succ_rreg, vreg));
387
                    }
388
389
0
                    (Location::Reg(cur_rreg), Location::Stack(spillslot)) => {
390
                        debug!(
391
0
                          "boundary fixup: spill {:?} -> {:?} at {:?} for {:?} between {:?} and {:?}",
392
0
                          cur_rreg,
393
0
                          spillslot,
394
0
                          at_inst,
395
0
                          vreg,
396
0
                          block,
397
0
                          succ
398
                        );
399
0
                        pending_moves
400
0
                            .0
401
0
                            .push(MoveOp::new_spill(cur_rreg, spillslot, vreg));
402
                    }
403
404
0
                    (Location::Stack(spillslot), Location::Reg(rreg)) => {
405
                        debug!(
406
0
                          "boundary fixup: reload {:?} -> {:?} at {:?} for {:?} between {:?} and {:?}",
407
0
                          spillslot,
408
0
                          rreg,
409
0
                          at_inst,
410
0
                          vreg,
411
0
                          block,
412
0
                          succ
413
                        );
414
0
                        pending_moves
415
0
                            .0
416
0
                            .push(MoveOp::new_reload(spillslot, rreg, vreg));
417
                    }
418
419
0
                    (Location::Stack(left_spill_slot), Location::Stack(right_spill_slot)) => {
420
                        // Stack to stack should not happen here, since two ranges for the
421
                        // same vreg can't be intersecting, so the same stack slot ought to
422
                        // be reused in this case.
423
                        debug_assert_eq!(
424
                          left_spill_slot, right_spill_slot,
425
                          "Moves from stack to stack only happen on the same vreg, thus the same stack slot"
426
                        );
427
                        continue;
428
                    }
429
430
                    (_, _) => {
431
0
                        panic!("register or stack slots must have been allocated.");
432
                    }
433
                };
434
            }
435
        }
436
437
        // Flush the memory moves caused by block fixups for this block.
438
0
        for (at_inst, (move_insts, block_pos)) in parallel_move_map.iter_mut() {
439
0
            schedule_moves(move_insts, tmp_ordered_moves, tmp_stack);
440
0
441
0
            match block_pos {
442
0
                BlockPos::Start => {
443
0
                    emit_moves(
444
0
                        *at_inst,
445
0
                        &tmp_ordered_moves,
446
0
                        spill_slot,
447
0
                        scratches_by_rc,
448
0
                        moves_at_block_starts,
449
0
                    );
450
0
                }
451
0
                BlockPos::End => {
452
0
                    emit_moves(
453
0
                        *at_inst,
454
0
                        &tmp_ordered_moves,
455
0
                        spill_slot,
456
0
                        scratches_by_rc,
457
0
                        moves_at_block_ends,
458
0
                    );
459
0
                }
460
            };
461
        }
462
463
0
        parallel_move_map.clear();
464
    }
465
466
0
    debug!("");
467
0
}
Unexecuted instantiation: regalloc::linear_scan::resolve_moves::resolve_moves_across_blocks::<minira::test_framework::Func>
Unexecuted instantiation: regalloc::linear_scan::resolve_moves::resolve_moves_across_blocks::<regalloc::snapshot::IRFunction>
468
469
#[inline(never)]
470
pub(crate) fn run<F: Function>(
471
    func: &F,
472
    cfg: &CFGInfo,
473
    reg_uses: &RegUses,
474
    intervals: &Vec<VirtualInterval>,
475
    liveins: &TypedIxVec<BlockIx, SparseSet<Reg>>,
476
    liveouts: &TypedIxVec<BlockIx, SparseSet<Reg>>,
477
    spill_slot: &mut u32,
478
    scratches_by_rc: &[Option<RealReg>],
479
) -> Vec<InstToInsertAndExtPoint> {
480
    info!("resolve_moves");
481
482
    // Keep three lists of moves to insert:
483
    // - moves across blocks, that must happen at the start of blocks,
484
    // - moves within a given block,
485
    // - moves across blocks, that must happen at the end of blocks.
486
    //
487
    // To maintain the property that these moves are eventually sorted at the end, we'll compute
488
    // the final array of moves by concatenating these three arrays. `inst_stream` uses a stable
489
    // sort, making sure the at-block-start/within-block/at-block-end will be respected.
490
    let mut moves_at_block_starts = Vec::new();
491
    let mut moves_at_block_ends = Vec::new();
492
    let mut moves_in_blocks = Vec::new();
493
494
    let mut tmp_stack = Vec::new();
495
    let mut tmp_ordered_moves = Vec::new();
496
    resolve_moves_in_block(
497
        func,
498
        intervals,
499
        reg_uses,
500
        scratches_by_rc,
501
        spill_slot,
502
        &mut moves_in_blocks,
503
        &mut tmp_ordered_moves,
504
        &mut tmp_stack,
505
    );
506
507
    resolve_moves_across_blocks(
508
        func,
509
        cfg,
510
        liveins,
511
        liveouts,
512
        intervals,
513
        scratches_by_rc,
514
        spill_slot,
515
        &mut moves_at_block_starts,
516
        &mut moves_at_block_ends,
517
        &mut tmp_ordered_moves,
518
        &mut tmp_stack,
519
    );
520
521
    let mut insts_and_points = moves_at_block_starts;
522
    insts_and_points.reserve(moves_in_blocks.len() + moves_at_block_ends.len());
523
    insts_and_points.append(&mut moves_in_blocks);
524
    insts_and_points.append(&mut moves_at_block_ends);
525
526
    insts_and_points
527
}
528
529
0
#[derive(PartialEq, Debug)]
530
enum MoveOperand {
531
    Reg(RealReg),
532
    Stack(SpillSlot),
533
}
534
535
impl MoveOperand {
536
    fn aliases(&self, other: &Self) -> bool {
537
        self == other
538
    }
539
}
540
541
struct MoveOp {
542
    from: MoveOperand,
543
    to: MoveOperand,
544
    vreg: VirtualReg,
545
    cycle_begin: Option<usize>,
546
    cycle_end: Option<usize>,
547
}
548
549
impl fmt::Debug for MoveOp {
550
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
551
0
        write!(fmt, "{:?}: {:?} -> {:?}", self.vreg, self.from, self.to)?;
552
0
        if let Some(ref begin) = self.cycle_begin {
553
0
            write!(fmt, ", start of cycle #{}", begin)?;
554
0
        }
555
0
        if let Some(ref end) = self.cycle_end {
556
0
            write!(fmt, ", end of cycle #{}", end)?;
557
0
        }
558
0
        Ok(())
559
0
    }
560
}
561
562
impl MoveOp {
563
0
    fn new_move(from: RealReg, to: RealReg, vreg: VirtualReg) -> Self {
564
0
        Self {
565
0
            from: MoveOperand::Reg(from),
566
0
            to: MoveOperand::Reg(to),
567
0
            vreg,
568
0
            cycle_begin: None,
569
0
            cycle_end: None,
570
0
        }
571
0
    }
572
573
    fn new_spill(from: RealReg, to: SpillSlot, vreg: VirtualReg) -> Self {
574
        Self {
575
            from: MoveOperand::Reg(from),
576
            to: MoveOperand::Stack(to),
577
            vreg,
578
            cycle_begin: None,
579
            cycle_end: None,
580
        }
581
    }
582
583
    fn new_reload(from: SpillSlot, to: RealReg, vreg: VirtualReg) -> Self {
584
        Self {
585
            from: MoveOperand::Stack(from),
586
            to: MoveOperand::Reg(to),
587
            vreg,
588
            cycle_begin: None,
589
            cycle_end: None,
590
        }
591
    }
592
593
0
    fn gen_inst(&self) -> InstToInsert {
594
0
        match self.from {
595
0
            MoveOperand::Reg(from) => match self.to {
596
0
                MoveOperand::Reg(to) => InstToInsert::Move {
597
0
                    to_reg: Writable::from_reg(to),
598
0
                    from_reg: from,
599
0
                    for_vreg: self.vreg,
600
0
                },
601
0
                MoveOperand::Stack(to) => InstToInsert::Spill {
602
0
                    to_slot: to,
603
0
                    from_reg: from,
604
0
                    for_vreg: Some(self.vreg),
605
0
                },
606
            },
607
0
            MoveOperand::Stack(from) => match self.to {
608
0
                MoveOperand::Reg(to) => InstToInsert::Reload {
609
0
                    to_reg: Writable::from_reg(to),
610
0
                    from_slot: from,
611
0
                    for_vreg: Some(self.vreg),
612
0
                },
613
0
                MoveOperand::Stack(_to) => unreachable!("stack to stack move"),
614
            },
615
        }
616
0
    }
617
}
618
619
0
fn find_blocking_move<'a>(
620
0
    pending: &'a mut Vec<MoveOp>,
621
0
    last: &MoveOp,
622
0
) -> Option<(usize, &'a mut MoveOp)> {
623
0
    for (i, other) in pending.iter_mut().enumerate() {
624
0
        if other.from.aliases(&last.to) {
625
0
            return Some((i, other));
626
0
        }
627
    }
628
0
    None
629
0
}
630
631
0
fn find_cycled_move<'a>(
632
0
    stack: &'a mut Vec<MoveOp>,
633
0
    from: &mut usize,
634
0
    last: &MoveOp,
635
0
) -> Option<&'a mut MoveOp> {
636
0
    for i in *from..stack.len() {
637
0
        *from += 1;
638
0
        let other = &stack[i];
639
0
        if other.from.aliases(&last.to) {
640
0
            return Some(&mut stack[i]);
641
0
        }
642
    }
643
0
    None
644
0
}
645
646
/// Given a pending list of moves, returns a list of moves ordered in a correct
647
/// way, i.e., no move clobbers another one.
648
#[inline(never)]
649
0
fn schedule_moves(
650
0
    pending: &mut Vec<MoveOp>,
651
0
    ordered_moves: &mut Vec<MoveOp>,
652
0
    stack: &mut Vec<MoveOp>,
653
0
) {
654
0
    ordered_moves.clear();
655
0
656
0
    let mut num_cycles = 0;
657
0
    let mut cur_cycles = 0;
658
659
0
    trace!("pending moves: {:#?}", pending);
660
661
0
    while let Some(pm) = pending.pop() {
662
0
        trace!("handling pending move {:?}", pm);
663
        debug_assert!(
664
            pm.from != pm.to,
665
            "spurious moves should not have been inserted"
666
        );
667
668
0
        stack.clear();
669
0
        stack.push(pm);
670
671
0
        while !stack.is_empty() {
672
0
            let blocking_pair = find_blocking_move(pending, stack.last().unwrap());
673
674
0
            if let Some((blocking_idx, blocking)) = blocking_pair {
675
0
                trace!("found blocker: {:?}", blocking);
676
0
                let mut stack_cur = 0;
677
678
0
                let has_cycles =
679
0
                    if let Some(mut cycled) = find_cycled_move(stack, &mut stack_cur, blocking) {
680
0
                        trace!("found cycle: {:?}", cycled);
681
                        debug_assert!(cycled.cycle_end.is_none());
682
0
                        cycled.cycle_end = Some(cur_cycles);
683
0
                        true
684
                    } else {
685
0
                        false
686
                    };
687
688
0
                if has_cycles {
689
0
                    loop {
690
0
                        match find_cycled_move(stack, &mut stack_cur, blocking) {
691
0
                            Some(ref mut cycled) => {
692
0
                                trace!("found more cycles ending on blocker: {:?}", cycled);
693
                                debug_assert!(cycled.cycle_end.is_none());
694
0
                                cycled.cycle_end = Some(cur_cycles);
695
                            }
696
0
                            None => break,
697
                        }
698
                    }
699
700
                    debug_assert!(blocking.cycle_begin.is_none());
701
0
                    blocking.cycle_begin = Some(cur_cycles);
702
0
                    cur_cycles += 1;
703
0
                }
704
705
0
                let blocking = pending.remove(blocking_idx);
706
0
                stack.push(blocking);
707
0
            } else {
708
0
                // There's no blocking move! We can push this in the ordered list of
709
0
                // moves.
710
0
                // TODO IonMonkey has more optimizations for this case.
711
0
                let last = stack.pop().unwrap();
712
0
                ordered_moves.push(last);
713
0
            }
714
        }
715
716
0
        if num_cycles < cur_cycles {
717
0
            num_cycles = cur_cycles;
718
0
        }
719
0
        cur_cycles = 0;
720
    }
721
0
}
722
723
#[inline(never)]
724
0
fn emit_moves(
725
0
    at_inst: InstPoint,
726
0
    ordered_moves: &Vec<MoveOp>,
727
0
    num_spill_slots: &mut u32,
728
0
    scratches_by_rc: &[Option<RealReg>],
729
0
    moves_in_blocks: &mut Vec<InstToInsertAndExtPoint>,
730
0
) {
731
0
    let mut spill_slot = None;
732
0
    let mut in_cycle = false;
733
734
0
    trace!("emit_moves");
735
736
0
    for mov in ordered_moves {
737
0
        if let Some(_) = &mov.cycle_end {
738
            debug_assert!(in_cycle);
739
740
            // There is some pattern:
741
            //   (A -> B)
742
            //   (B -> A)
743
            // This case handles (B -> A), which we reach last. We emit a move from
744
            // the saved value of B, to A.
745
0
            match mov.to {
746
0
                MoveOperand::Reg(dst_reg) => {
747
0
                    let inst = InstToInsert::Reload {
748
0
                        to_reg: Writable::from_reg(dst_reg),
749
0
                        from_slot: spill_slot.expect("should have a cycle spill slot"),
750
0
                        for_vreg: Some(mov.vreg),
751
0
                    };
752
0
                    moves_in_blocks.push(InstToInsertAndExtPoint::new(
753
0
                        inst,
754
0
                        InstExtPoint::from_inst_point(at_inst),
755
0
                    ));
756
                    trace!(
757
0
                        "finishing cycle: {:?} -> {:?}",
758
0
                        spill_slot.unwrap(),
759
0
                        dst_reg
760
                    );
761
                }
762
0
                MoveOperand::Stack(dst_spill) => {
763
0
                    let scratch = scratches_by_rc[mov.vreg.get_class() as usize]
764
0
                        .expect("missing scratch reg");
765
0
                    let inst = InstToInsert::Reload {
766
0
                        to_reg: Writable::from_reg(scratch),
767
0
                        from_slot: spill_slot.expect("should have a cycle spill slot"),
768
0
                        for_vreg: Some(mov.vreg),
769
0
                    };
770
0
                    moves_in_blocks.push(InstToInsertAndExtPoint::new(
771
0
                        inst,
772
0
                        InstExtPoint::from_inst_point(at_inst),
773
0
                    ));
774
0
                    let inst = InstToInsert::Spill {
775
0
                        to_slot: dst_spill,
776
0
                        from_reg: scratch,
777
0
                        for_vreg: Some(mov.vreg),
778
0
                    };
779
0
                    moves_in_blocks.push(InstToInsertAndExtPoint::new(
780
0
                        inst,
781
0
                        InstExtPoint::from_inst_point(at_inst),
782
0
                    ));
783
                    trace!(
784
0
                        "finishing cycle: {:?} -> {:?} -> {:?}",
785
0
                        spill_slot.unwrap(),
786
0
                        scratch,
787
0
                        dst_spill
788
                    );
789
                }
790
            };
791
792
0
            in_cycle = false;
793
            continue;
794
0
        }
795
0
796
0
        if let Some(_) = &mov.cycle_begin {
797
            debug_assert!(!in_cycle);
798
799
            // There is some pattern:
800
            //   (A -> B)
801
            //   (B -> A)
802
            // This case handles (A -> B), which we reach first. We save B, then allow
803
            // the original move to continue.
804
0
            match spill_slot {
805
0
                Some(_) => {}
806
0
                None => {
807
0
                    spill_slot = Some(SpillSlot::new(*num_spill_slots));
808
0
                    *num_spill_slots += 1;
809
0
                }
810
            }
811
812
0
            match mov.to {
813
0
                MoveOperand::Reg(src_reg) => {
814
0
                    let inst = InstToInsert::Spill {
815
0
                        to_slot: spill_slot.unwrap(),
816
0
                        from_reg: src_reg,
817
0
                        for_vreg: Some(mov.vreg),
818
0
                    };
819
0
                    moves_in_blocks.push(InstToInsertAndExtPoint::new(
820
0
                        inst,
821
0
                        InstExtPoint::from_inst_point(at_inst),
822
0
                    ));
823
0
                    trace!("starting cycle: {:?} -> {:?}", src_reg, spill_slot.unwrap());
824
                }
825
0
                MoveOperand::Stack(src_spill) => {
826
0
                    let scratch = scratches_by_rc[mov.vreg.get_class() as usize]
827
0
                        .expect("missing scratch reg");
828
0
                    let inst = InstToInsert::Reload {
829
0
                        to_reg: Writable::from_reg(scratch),
830
0
                        from_slot: src_spill,
831
0
                        for_vreg: Some(mov.vreg),
832
0
                    };
833
0
                    moves_in_blocks.push(InstToInsertAndExtPoint::new(
834
0
                        inst,
835
0
                        InstExtPoint::from_inst_point(at_inst),
836
0
                    ));
837
0
                    let inst = InstToInsert::Spill {
838
0
                        to_slot: spill_slot.expect("should have a cycle spill slot"),
839
0
                        from_reg: scratch,
840
0
                        for_vreg: Some(mov.vreg),
841
0
                    };
842
0
                    moves_in_blocks.push(InstToInsertAndExtPoint::new(
843
0
                        inst,
844
0
                        InstExtPoint::from_inst_point(at_inst),
845
0
                    ));
846
                    trace!(
847
0
                        "starting cycle: {:?} -> {:?} -> {:?}",
848
0
                        src_spill,
849
0
                        scratch,
850
0
                        spill_slot.unwrap()
851
                    );
852
                }
853
            };
854
855
0
            in_cycle = true;
856
0
        }
857
858
        // A normal move which is not part of a cycle.
859
0
        let inst = mov.gen_inst();
860
0
        moves_in_blocks.push(InstToInsertAndExtPoint::new(
861
0
            inst,
862
0
            InstExtPoint::from_inst_point(at_inst),
863
0
        ));
864
0
        trace!("moving {:?} -> {:?}", mov.from, mov.to);
865
    }
866
0
}