/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 ® 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 | } |