/src/regalloc2/src/ion/liveranges.rs
Line | Count | Source |
1 | | /* |
2 | | * This file was initially derived from the files |
3 | | * `js/src/jit/BacktrackingAllocator.h` and |
4 | | * `js/src/jit/BacktrackingAllocator.cpp` in Mozilla Firefox, and was |
5 | | * originally licensed under the Mozilla Public License 2.0. We |
6 | | * subsequently relicensed it to Apache-2.0 WITH LLVM-exception (see |
7 | | * https://github.com/bytecodealliance/regalloc2/issues/7). |
8 | | * |
9 | | * Since the initial port, the design has been substantially evolved |
10 | | * and optimized. |
11 | | */ |
12 | | |
13 | | //! Live-range computation. |
14 | | |
15 | | use super::{ |
16 | | CodeRange, Env, LiveRangeFlag, LiveRangeIndex, LiveRangeKey, LiveRangeList, LiveRangeListEntry, |
17 | | LiveRangeSet, PRegData, PRegIndex, RegClass, Use, VRegData, VRegIndex, |
18 | | }; |
19 | | use crate::indexset::IndexSet; |
20 | | use crate::ion::data_structures::{ |
21 | | BlockparamIn, BlockparamOut, FixedRegFixupLevel, MultiFixedRegFixup, |
22 | | }; |
23 | | use crate::{ |
24 | | Allocation, Block, Function, Inst, InstPosition, Operand, OperandConstraint, OperandKind, |
25 | | OperandPos, PReg, ProgPoint, RegAllocError, VReg, VecExt, |
26 | | }; |
27 | | use core::convert::TryFrom; |
28 | | use core::usize; |
29 | | use smallvec::{smallvec, SmallVec}; |
30 | | |
31 | | /// A spill weight computed for a certain Use. |
32 | | #[derive(Clone, Copy, Debug)] |
33 | | pub struct SpillWeight(f32); |
34 | | |
35 | | #[inline(always)] |
36 | 0 | pub fn spill_weight_from_constraint( |
37 | 0 | constraint: OperandConstraint, |
38 | 0 | loop_depth: usize, |
39 | 0 | is_def: bool, |
40 | 0 | ) -> SpillWeight { |
41 | | // A bonus of 1000 for one loop level, 4000 for two loop levels, |
42 | | // 16000 for three loop levels, etc. Avoids exponentiation. |
43 | 0 | let loop_depth = core::cmp::min(10, loop_depth); |
44 | 0 | let hot_bonus: f32 = (0..loop_depth).fold(1000.0, |a, _| a * 4.0); |
45 | 0 | let def_bonus: f32 = if is_def { 2000.0 } else { 0.0 }; |
46 | 0 | let constraint_bonus: f32 = match constraint { |
47 | 0 | OperandConstraint::Any => 1000.0, |
48 | 0 | OperandConstraint::Reg | OperandConstraint::FixedReg(_) => 2000.0, |
49 | 0 | _ => 0.0, |
50 | | }; |
51 | 0 | SpillWeight(hot_bonus + def_bonus + constraint_bonus) |
52 | 0 | } |
53 | | |
54 | | impl SpillWeight { |
55 | | /// Convert a floating-point weight to a u16 that can be compactly |
56 | | /// stored in a `Use`. We simply take the top 16 bits of the f32; this |
57 | | /// is equivalent to the bfloat16 format |
58 | | /// (https://en.wikipedia.org/wiki/Bfloat16_floating-point_format). |
59 | 0 | pub fn to_bits(self) -> u16 { |
60 | 0 | (self.0.to_bits() >> 15) as u16 |
61 | 0 | } |
62 | | |
63 | | /// Convert a value that was returned from |
64 | | /// `SpillWeight::to_bits()` back into a `SpillWeight`. Note that |
65 | | /// some precision may be lost when round-tripping from a spill |
66 | | /// weight to packed bits and back. |
67 | 0 | pub fn from_bits(bits: u16) -> SpillWeight { |
68 | 0 | let x = f32::from_bits((bits as u32) << 15); |
69 | 0 | SpillWeight(x) |
70 | 0 | } |
71 | | |
72 | | /// Get a zero spill weight. |
73 | 0 | pub fn zero() -> SpillWeight { |
74 | 0 | SpillWeight(0.0) |
75 | 0 | } |
76 | | |
77 | | /// Convert to a raw floating-point value. |
78 | 0 | pub fn to_f32(self) -> f32 { |
79 | 0 | self.0 |
80 | 0 | } |
81 | | |
82 | | /// Create a `SpillWeight` from a raw floating-point value. |
83 | 0 | pub fn from_f32(x: f32) -> SpillWeight { |
84 | 0 | SpillWeight(x) |
85 | 0 | } |
86 | | |
87 | 0 | pub fn to_int(self) -> u32 { |
88 | 0 | self.0 as u32 |
89 | 0 | } |
90 | | } |
91 | | |
92 | | impl core::ops::Add<SpillWeight> for SpillWeight { |
93 | | type Output = SpillWeight; |
94 | 0 | fn add(self, other: SpillWeight) -> Self { |
95 | 0 | SpillWeight(self.0 + other.0) |
96 | 0 | } |
97 | | } |
98 | | |
99 | 0 | fn slot_idx(i: usize) -> Result<u16, RegAllocError> { |
100 | 0 | u16::try_from(i).map_err(|_| RegAllocError::TooManyOperands) |
101 | 0 | } |
102 | | |
103 | | impl<'a, F: Function> Env<'a, F> { |
104 | 0 | pub fn create_pregs_and_vregs(&mut self) { |
105 | | // Create PRegs from the env. |
106 | 0 | self.pregs.resize( |
107 | | PReg::NUM_INDEX, |
108 | 0 | PRegData { |
109 | 0 | allocations: LiveRangeSet::new(), |
110 | 0 | is_stack: false, |
111 | 0 | }, |
112 | | ); |
113 | 0 | for &preg in &self.env.fixed_stack_slots { |
114 | 0 | self.pregs[preg.index()].is_stack = true; |
115 | 0 | } |
116 | 0 | for class in 0..self.preferred_victim_by_class.len() { |
117 | 0 | self.preferred_victim_by_class[class] = self.env.non_preferred_regs_by_class[class] |
118 | 0 | .max_preg() |
119 | 0 | .or(self.env.preferred_regs_by_class[class].max_preg()) |
120 | 0 | .unwrap_or(PReg::invalid()); |
121 | 0 | } |
122 | | // Create VRegs from the vreg count. |
123 | 0 | for idx in 0..self.func.num_vregs() { |
124 | 0 | // We'll fill in the real details when we see the def. |
125 | 0 | self.ctx.vregs.add( |
126 | 0 | VReg::new(idx, RegClass::Int), |
127 | 0 | VRegData { |
128 | 0 | ranges: LiveRangeList::new_in(self.ctx.bump()), |
129 | 0 | blockparam: Block::invalid(), |
130 | 0 | // We'll learn the RegClass as we scan the code. |
131 | 0 | class: None, |
132 | 0 | }, |
133 | 0 | ); |
134 | 0 | } |
135 | | // Create allocations too. |
136 | 0 | for inst in 0..self.func.num_insts() { |
137 | 0 | let start = self.output.allocs.len() as u32; |
138 | 0 | self.output.inst_alloc_offsets.push(start); |
139 | 0 | for _ in 0..self.func.inst_operands(Inst::new(inst)).len() { |
140 | 0 | self.output.allocs.push(Allocation::none()); |
141 | 0 | } |
142 | | } |
143 | 0 | } |
144 | | |
145 | | /// Mark `range` as live for the given `vreg`. |
146 | | /// |
147 | | /// Returns the liverange that contains the given range. |
148 | 0 | pub fn add_liverange_to_vreg( |
149 | 0 | &mut self, |
150 | 0 | vreg: VRegIndex, |
151 | 0 | mut range: CodeRange, |
152 | 0 | ) -> LiveRangeIndex { |
153 | 0 | trace!("add_liverange_to_vreg: vreg {:?} range {:?}", vreg, range); |
154 | | |
155 | | // Invariant: as we are building liveness information, we |
156 | | // *always* process instructions bottom-to-top, and as a |
157 | | // consequence, new liveranges are always created before any |
158 | | // existing liveranges for a given vreg. We assert this here, |
159 | | // then use it to avoid an O(n) merge step (which would lead |
160 | | // to O(n^2) liveness construction cost overall). |
161 | | // |
162 | | // We store liveranges in reverse order in the `.ranges` |
163 | | // array, then reverse them at the end of |
164 | | // `compute_liveness()`. |
165 | | |
166 | 0 | if !self.vregs[vreg].ranges.is_empty() { |
167 | 0 | let last_range_index = self.vregs[vreg].ranges.last().unwrap().index; |
168 | 0 | let last_range = self.ranges[last_range_index].range; |
169 | 0 | if self.func.allow_multiple_vreg_defs() { |
170 | 0 | if last_range.contains(&range) { |
171 | | // Special case (may occur when multiple defs of pinned |
172 | | // physical regs occur): if this new range overlaps the |
173 | | // existing range, return it. |
174 | 0 | return last_range_index; |
175 | 0 | } |
176 | | // If this range's end falls in the middle of the last |
177 | | // range, truncate it to be contiguous so we can merge |
178 | | // below. |
179 | 0 | if range.to >= last_range.from && range.to <= last_range.to { |
180 | 0 | range.to = last_range.from; |
181 | 0 | } |
182 | 0 | } |
183 | 0 | debug_assert!( |
184 | 0 | range.to <= last_range.from, |
185 | | "range {:?}, last_range {:?}", |
186 | | range, |
187 | | last_range |
188 | | ); |
189 | 0 | } |
190 | | |
191 | 0 | if self.vregs[vreg].ranges.is_empty() |
192 | 0 | || range.to |
193 | 0 | < self.ranges[self.vregs[vreg].ranges.last().unwrap().index] |
194 | 0 | .range |
195 | 0 | .from |
196 | | { |
197 | | // Is not contiguous with previously-added (immediately |
198 | | // following) range; create a new range. |
199 | 0 | let lr = self.ctx.ranges.add(range, self.ctx.bump()); |
200 | 0 | self.ranges[lr].vreg = vreg; |
201 | 0 | self.vregs[vreg] |
202 | 0 | .ranges |
203 | 0 | .push(LiveRangeListEntry { range, index: lr }); |
204 | 0 | lr |
205 | | } else { |
206 | | // Is contiguous with previously-added range; just extend |
207 | | // its range and return it. |
208 | 0 | let lr = self.vregs[vreg].ranges.last().unwrap().index; |
209 | 0 | debug_assert!(range.to == self.ranges[lr].range.from); |
210 | 0 | self.ranges[lr].range.from = range.from; |
211 | 0 | lr |
212 | | } |
213 | 0 | } |
214 | | |
215 | 0 | pub fn insert_use_into_liverange(&mut self, into: LiveRangeIndex, mut u: Use) { |
216 | 0 | let operand = u.operand; |
217 | 0 | let constraint = operand.constraint(); |
218 | 0 | let block = self.cfginfo.insn_block[u.pos.inst().index()]; |
219 | 0 | let loop_depth = self.cfginfo.approx_loop_depth[block.index()] as usize; |
220 | 0 | let weight = spill_weight_from_constraint( |
221 | 0 | constraint, |
222 | 0 | loop_depth, |
223 | 0 | operand.kind() != OperandKind::Use, |
224 | | ); |
225 | 0 | u.weight = weight.to_bits(); |
226 | | |
227 | 0 | trace!( |
228 | | "insert use {:?} into lr {:?} with weight {:?}", |
229 | | u, |
230 | | into, |
231 | | weight, |
232 | | ); |
233 | | |
234 | | // N.B.: we do *not* update `requirement` on the range, |
235 | | // because those will be computed during the multi-fixed-reg |
236 | | // fixup pass later (after all uses are inserted). |
237 | | |
238 | 0 | self.ranges[into].uses.push(u); |
239 | | |
240 | | // Update stats. |
241 | 0 | let range_weight = self.ranges[into].uses_spill_weight() + weight; |
242 | 0 | self.ranges[into].set_uses_spill_weight(range_weight); |
243 | 0 | trace!( |
244 | | " -> now range has weight {:?}", |
245 | 0 | self.ranges[into].uses_spill_weight(), |
246 | | ); |
247 | 0 | } |
248 | | |
249 | 0 | pub fn find_vreg_liverange_for_pos( |
250 | 0 | &self, |
251 | 0 | vreg: VRegIndex, |
252 | 0 | pos: ProgPoint, |
253 | 0 | ) -> Option<LiveRangeIndex> { |
254 | 0 | for entry in &self.vregs[vreg].ranges { |
255 | 0 | if entry.range.contains_point(pos) { |
256 | 0 | return Some(entry.index); |
257 | 0 | } |
258 | | } |
259 | 0 | None |
260 | 0 | } |
261 | | |
262 | 0 | pub fn add_liverange_to_preg(&mut self, range: CodeRange, reg: PReg) { |
263 | 0 | trace!("adding liverange to preg: {:?} to {}", range, reg); |
264 | 0 | let preg_idx = PRegIndex::new(reg.index()); |
265 | 0 | let res = self.pregs[preg_idx.index()] |
266 | 0 | .allocations |
267 | 0 | .btree |
268 | 0 | .insert(LiveRangeKey::from_range(&range), LiveRangeIndex::invalid()); |
269 | 0 | debug_assert!(res.is_none()); |
270 | 0 | } |
271 | | |
272 | 0 | pub fn is_live_in(&mut self, block: Block, vreg: VRegIndex) -> bool { |
273 | 0 | self.liveins[block.index()].get(vreg.index()) |
274 | 0 | } |
275 | | |
276 | 0 | pub fn compute_liveness(&mut self) -> Result<(), RegAllocError> { |
277 | | // Create initial LiveIn and LiveOut bitsets. |
278 | 0 | for _ in 0..self.func.num_blocks() { |
279 | 0 | self.liveins.push(IndexSet::new()); |
280 | 0 | self.liveouts.push(IndexSet::new()); |
281 | 0 | } |
282 | | |
283 | | // Run a worklist algorithm to precisely compute liveins and |
284 | | // liveouts. |
285 | 0 | let mut workqueue = core::mem::take(&mut self.ctx.scratch_workqueue); |
286 | 0 | let mut workqueue_set = core::mem::take(&mut self.ctx.scratch_workqueue_set); |
287 | 0 | workqueue_set.clear(); |
288 | | // Initialize workqueue with postorder traversal. |
289 | 0 | for &block in &self.cfginfo.postorder[..] { |
290 | 0 | workqueue.push_back(block); |
291 | 0 | workqueue_set.insert(block); |
292 | 0 | } |
293 | | |
294 | 0 | while let Some(block) = workqueue.pop_front() { |
295 | 0 | workqueue_set.remove(&block); |
296 | 0 | let insns = self.func.block_insns(block); |
297 | | |
298 | 0 | trace!("computing liveins for block{}", block.index()); |
299 | | |
300 | 0 | self.output.stats.livein_iterations += 1; |
301 | | |
302 | 0 | let mut live = self.liveouts[block.index()].clone(); |
303 | 0 | trace!(" -> initial liveout set: {:?}", live); |
304 | | |
305 | | // Include outgoing blockparams in the initial live set. |
306 | 0 | if self.func.is_branch(insns.last()) { |
307 | 0 | for i in 0..self.func.block_succs(block).len() { |
308 | 0 | for ¶m in self.func.branch_blockparams(block, insns.last(), i) { |
309 | 0 | live.set(param.vreg(), true); |
310 | 0 | self.observe_vreg_class(param); |
311 | 0 | } |
312 | | } |
313 | 0 | } |
314 | | |
315 | 0 | for inst in insns.iter().rev() { |
316 | 0 | for pos in &[OperandPos::Late, OperandPos::Early] { |
317 | 0 | for op in self.func.inst_operands(inst) { |
318 | 0 | if op.as_fixed_nonallocatable().is_some() { |
319 | 0 | continue; |
320 | 0 | } |
321 | 0 | if op.pos() == *pos { |
322 | 0 | let was_live = live.get(op.vreg().vreg()); |
323 | 0 | trace!("op {:?} was_live = {}", op, was_live); |
324 | 0 | match op.kind() { |
325 | 0 | OperandKind::Use => { |
326 | 0 | live.set(op.vreg().vreg(), true); |
327 | 0 | } |
328 | 0 | OperandKind::Def => { |
329 | 0 | live.set(op.vreg().vreg(), false); |
330 | 0 | } |
331 | | } |
332 | 0 | self.observe_vreg_class(op.vreg()); |
333 | 0 | } |
334 | | } |
335 | | } |
336 | | } |
337 | 0 | for &blockparam in self.func.block_params(block) { |
338 | 0 | live.set(blockparam.vreg(), false); |
339 | 0 | self.observe_vreg_class(blockparam); |
340 | 0 | } |
341 | | |
342 | 0 | for &pred in self.func.block_preds(block) { |
343 | 0 | if self.ctx.liveouts[pred.index()].union_with(&live) { |
344 | 0 | if !workqueue_set.contains(&pred) { |
345 | 0 | workqueue_set.insert(pred); |
346 | 0 | workqueue.push_back(pred); |
347 | 0 | } |
348 | 0 | } |
349 | | } |
350 | | |
351 | 0 | trace!("computed liveins at block{}: {:?}", block.index(), live); |
352 | 0 | self.liveins[block.index()] = live; |
353 | | } |
354 | | |
355 | | // Check that there are no liveins to the entry block. |
356 | 0 | if !self.liveins[self.func.entry_block().index()].is_empty() { |
357 | 0 | trace!( |
358 | | "non-empty liveins to entry block: {:?}", |
359 | 0 | self.liveins[self.func.entry_block().index()] |
360 | | ); |
361 | 0 | return Err(RegAllocError::EntryLivein); |
362 | 0 | } |
363 | | |
364 | 0 | self.ctx.scratch_workqueue = workqueue; |
365 | 0 | self.ctx.scratch_workqueue_set = workqueue_set; |
366 | | |
367 | 0 | Ok(()) |
368 | 0 | } |
369 | | |
370 | 0 | pub fn build_liveranges(&mut self) -> Result<(), RegAllocError> { |
371 | | // Create Uses and Defs referring to VRegs, and place the Uses |
372 | | // in LiveRanges. |
373 | | // |
374 | | // We already computed precise liveouts and liveins for every |
375 | | // block above, so we don't need to run an iterative algorithm |
376 | | // here; instead, every block's computation is purely local, |
377 | | // from end to start. |
378 | | |
379 | | // Track current LiveRange for each vreg. |
380 | | // |
381 | | // Invariant: a stale range may be present here; ranges are |
382 | | // only valid if `live.get(vreg)` is true. |
383 | 0 | let mut vreg_ranges = core::mem::take(&mut self.ctx.scratch_vreg_ranges); |
384 | 0 | vreg_ranges.repopulate(self.func.num_vregs(), LiveRangeIndex::invalid()); |
385 | 0 | let mut operand_rewrites = core::mem::take(&mut self.ctx.scratch_operand_rewrites); |
386 | | |
387 | 0 | for i in (0..self.func.num_blocks()).rev() { |
388 | 0 | let block = Block::new(i); |
389 | 0 | let insns = self.func.block_insns(block); |
390 | | |
391 | 0 | self.output.stats.livein_blocks += 1; |
392 | | |
393 | | // Init our local live-in set. |
394 | 0 | let mut live = self.liveouts[block.index()].clone(); |
395 | | |
396 | | // If the last instruction is a branch (rather than |
397 | | // return), create blockparam_out entries. |
398 | 0 | if self.func.is_branch(insns.last()) { |
399 | 0 | for (i, &succ) in self.func.block_succs(block).iter().enumerate() { |
400 | 0 | let blockparams_in = self.func.block_params(succ); |
401 | 0 | let blockparams_out = self.func.branch_blockparams(block, insns.last(), i); |
402 | 0 | for (&blockparam_in, &blockparam_out) in |
403 | 0 | blockparams_in.iter().zip(blockparams_out) |
404 | 0 | { |
405 | 0 | let blockparam_out = VRegIndex::new(blockparam_out.vreg()); |
406 | 0 | let blockparam_in = VRegIndex::new(blockparam_in.vreg()); |
407 | 0 | self.blockparam_outs.push(BlockparamOut { |
408 | 0 | to_vreg: blockparam_in, |
409 | 0 | to_block: succ, |
410 | 0 | from_block: block, |
411 | 0 | from_vreg: blockparam_out, |
412 | 0 | }); |
413 | 0 |
|
414 | 0 | // Include outgoing blockparams in the initial live set. |
415 | 0 | live.set(blockparam_out.index(), true); |
416 | 0 | } |
417 | | } |
418 | 0 | } |
419 | | |
420 | | // Initially, registers are assumed live for the whole block. |
421 | 0 | for vreg in live.iter() { |
422 | 0 | let range = CodeRange { |
423 | 0 | from: self.cfginfo.block_entry[block.index()], |
424 | 0 | to: self.cfginfo.block_exit[block.index()].next(), |
425 | 0 | }; |
426 | 0 | trace!( |
427 | | "vreg {:?} live at end of block --> create range {:?}", |
428 | 0 | VRegIndex::new(vreg), |
429 | | range |
430 | | ); |
431 | 0 | let lr = self.add_liverange_to_vreg(VRegIndex::new(vreg), range); |
432 | 0 | vreg_ranges[vreg] = lr; |
433 | | } |
434 | | |
435 | | // Create vreg data for blockparams. |
436 | 0 | for ¶m in self.func.block_params(block) { |
437 | 0 | self.vregs[param].blockparam = block; |
438 | 0 | } |
439 | | |
440 | | // For each instruction, in reverse order, process |
441 | | // operands and clobbers. |
442 | 0 | for inst in insns.iter().rev() { |
443 | | // Mark clobbers with CodeRanges on PRegs. |
444 | 0 | for clobber in self.func.inst_clobbers(inst) { |
445 | 0 | // Clobber range is at After point only: an |
446 | 0 | // instruction can still take an input in a reg |
447 | 0 | // that it later clobbers. (In other words, the |
448 | 0 | // clobber is like a normal def that never gets |
449 | 0 | // used.) |
450 | 0 | let range = CodeRange { |
451 | 0 | from: ProgPoint::after(inst), |
452 | 0 | to: ProgPoint::before(inst.next()), |
453 | 0 | }; |
454 | 0 | self.add_liverange_to_preg(range, clobber); |
455 | 0 | } |
456 | | |
457 | | // Does the instruction have any input-reusing |
458 | | // outputs? This is important below to establish |
459 | | // proper interference wrt other inputs. We note the |
460 | | // *vreg* that is reused, not the index. |
461 | 0 | let mut reused_input = None; |
462 | 0 | for op in self.func.inst_operands(inst) { |
463 | 0 | if let OperandConstraint::Reuse(i) = op.constraint() { |
464 | 0 | debug_assert!(self.func.inst_operands(inst)[i] |
465 | 0 | .as_fixed_nonallocatable() |
466 | 0 | .is_none()); |
467 | 0 | reused_input = Some(self.func.inst_operands(inst)[i].vreg()); |
468 | 0 | break; |
469 | 0 | } |
470 | | } |
471 | | |
472 | | // Preprocess defs and uses. Specifically, if there |
473 | | // are any fixed-reg-constrained defs at Late position |
474 | | // and fixed-reg-constrained uses at Early position |
475 | | // with the same preg, we need to (i) add a fixup move |
476 | | // for the use, (ii) rewrite the use to have an Any |
477 | | // constraint, and (ii) move the def to Early position |
478 | | // to reserve the register for the whole instruction. |
479 | | // |
480 | | // We don't touch any fixed-early-def or fixed-late-use |
481 | | // constraints: the only situation where the same physical |
482 | | // register can be used multiple times in the same |
483 | | // instruction is with an early-use and a late-def. Anything |
484 | | // else is a user error. |
485 | 0 | operand_rewrites.clear(); |
486 | 0 | let mut late_def_fixed: SmallVec<[PReg; 8]> = smallvec![]; |
487 | 0 | for &operand in self.func.inst_operands(inst) { |
488 | 0 | if let OperandConstraint::FixedReg(preg) = operand.constraint() { |
489 | 0 | match (operand.pos(), operand.kind()) { |
490 | 0 | (OperandPos::Late, OperandKind::Def) => { |
491 | 0 | late_def_fixed.push(preg); |
492 | 0 | } |
493 | 0 | _ => {} |
494 | | } |
495 | 0 | } |
496 | | } |
497 | 0 | for (i, &operand) in self.func.inst_operands(inst).iter().enumerate() { |
498 | 0 | if operand.as_fixed_nonallocatable().is_some() { |
499 | 0 | continue; |
500 | 0 | } |
501 | 0 | if let OperandConstraint::FixedReg(preg) = operand.constraint() { |
502 | 0 | match (operand.pos(), operand.kind()) { |
503 | | (OperandPos::Early, OperandKind::Use) |
504 | 0 | if live.get(operand.vreg().vreg()) => |
505 | | { |
506 | | // If we have a use constraint at the |
507 | | // Early point for a fixed preg, and |
508 | | // this preg is also constrained with |
509 | | // a *separate* def at Late or is |
510 | | // clobbered, and *if* the vreg is |
511 | | // live downward, we have to use the |
512 | | // multi-fixed-reg mechanism for a |
513 | | // fixup and rewrite here without the |
514 | | // constraint. See #53. |
515 | | // |
516 | | // We adjust the def liverange and Use |
517 | | // to an "early" position to reserve |
518 | | // the register, it still must not be |
519 | | // used by some other vreg at the |
520 | | // use-site. |
521 | | // |
522 | | // Note that we handle multiple |
523 | | // conflicting constraints for the |
524 | | // same vreg in a separate pass (see |
525 | | // `fixup_multi_fixed_vregs` below). |
526 | 0 | if late_def_fixed.contains(&preg) |
527 | 0 | || self.func.inst_clobbers(inst).contains(preg) |
528 | | { |
529 | 0 | trace!( |
530 | | concat!( |
531 | | "-> operand {:?} is fixed to preg {:?}, ", |
532 | | "is downward live, and there is also a ", |
533 | | "def or clobber at this preg" |
534 | | ), |
535 | | operand, |
536 | | preg |
537 | | ); |
538 | 0 | let pos = ProgPoint::before(inst); |
539 | 0 | self.multi_fixed_reg_fixups.push(MultiFixedRegFixup { |
540 | 0 | pos, |
541 | 0 | from_slot: slot_idx(i)?, |
542 | 0 | to_slot: slot_idx(i)?, |
543 | 0 | to_preg: PRegIndex::new(preg.index()), |
544 | 0 | vreg: VRegIndex::new(operand.vreg().vreg()), |
545 | 0 | level: FixedRegFixupLevel::Initial, |
546 | | }); |
547 | | |
548 | | // We need to insert a reservation |
549 | | // at the before-point to reserve |
550 | | // the reg for the use too. |
551 | 0 | let range = CodeRange::singleton(pos); |
552 | 0 | self.add_liverange_to_preg(range, preg); |
553 | | |
554 | | // Remove the fixed-preg |
555 | | // constraint from the Use. |
556 | 0 | operand_rewrites.insert( |
557 | 0 | i, |
558 | 0 | Operand::new( |
559 | 0 | operand.vreg(), |
560 | 0 | OperandConstraint::Any, |
561 | 0 | operand.kind(), |
562 | 0 | operand.pos(), |
563 | | ), |
564 | | ); |
565 | 0 | } |
566 | | } |
567 | 0 | _ => {} |
568 | | } |
569 | 0 | } |
570 | | } |
571 | | |
572 | | // Process defs and uses. |
573 | 0 | for &cur_pos in &[InstPosition::After, InstPosition::Before] { |
574 | 0 | for i in 0..self.func.inst_operands(inst).len() { |
575 | | // don't borrow `self` |
576 | 0 | let operand = operand_rewrites |
577 | 0 | .get(&i) |
578 | 0 | .cloned() |
579 | 0 | .unwrap_or(self.func.inst_operands(inst)[i]); |
580 | 0 | let pos = match (operand.kind(), operand.pos()) { |
581 | 0 | (OperandKind::Def, OperandPos::Early) => ProgPoint::before(inst), |
582 | 0 | (OperandKind::Def, OperandPos::Late) => ProgPoint::after(inst), |
583 | 0 | (OperandKind::Use, OperandPos::Late) => ProgPoint::after(inst), |
584 | | // If there are any reused inputs in this |
585 | | // instruction, and this is *not* the |
586 | | // reused vreg, force `pos` to |
587 | | // `After`. This ensures that we correctly |
588 | | // account for the interference between |
589 | | // the other inputs and the |
590 | | // input-that-is-reused/output. |
591 | | (OperandKind::Use, OperandPos::Early) |
592 | 0 | if reused_input.is_some() |
593 | 0 | && reused_input.unwrap() != operand.vreg() => |
594 | | { |
595 | 0 | ProgPoint::after(inst) |
596 | | } |
597 | 0 | (OperandKind::Use, OperandPos::Early) => ProgPoint::before(inst), |
598 | | }; |
599 | | |
600 | 0 | if pos.pos() != cur_pos { |
601 | 0 | continue; |
602 | 0 | } |
603 | | |
604 | 0 | trace!( |
605 | | "processing inst{} operand at {:?}: {:?}", |
606 | 0 | inst.index(), |
607 | | pos, |
608 | | operand |
609 | | ); |
610 | | |
611 | | // If this is a "fixed non-allocatable |
612 | | // register" operand, set the alloc |
613 | | // immediately and then ignore the operand |
614 | | // hereafter. |
615 | 0 | if let Some(preg) = operand.as_fixed_nonallocatable() { |
616 | 0 | self.set_alloc(inst, i, Allocation::reg(preg)); |
617 | 0 | continue; |
618 | 0 | } |
619 | | |
620 | 0 | match operand.kind() { |
621 | | OperandKind::Def => { |
622 | 0 | trace!("Def of {} at {:?}", operand.vreg(), pos); |
623 | | |
624 | | // Get or create the LiveRange. |
625 | 0 | let mut lr = vreg_ranges[operand.vreg().vreg()]; |
626 | 0 | trace!(" -> has existing LR {:?}", lr); |
627 | | // If there was no liverange (dead def), create a trivial one. |
628 | 0 | if !live.get(operand.vreg().vreg()) { |
629 | 0 | let from = pos; |
630 | | // We want to we want to span |
631 | | // until Before of the next |
632 | | // inst. This ensures that early |
633 | | // defs used for temps on an |
634 | | // instruction are reserved across |
635 | | // the whole instruction. |
636 | 0 | let to = ProgPoint::before(pos.inst().next()); |
637 | 0 | lr = self.add_liverange_to_vreg( |
638 | 0 | VRegIndex::new(operand.vreg().vreg()), |
639 | 0 | CodeRange { from, to }, |
640 | 0 | ); |
641 | 0 | trace!(" -> invalid; created {:?}", lr); |
642 | 0 | vreg_ranges[operand.vreg().vreg()] = lr; |
643 | 0 | live.set(operand.vreg().vreg(), true); |
644 | 0 | } |
645 | | // Create the use in the LiveRange. |
646 | 0 | self.insert_use_into_liverange( |
647 | 0 | lr, |
648 | 0 | Use::new(operand, pos, slot_idx(i)?), |
649 | | ); |
650 | | // If def (not mod), this reg is now dead, |
651 | | // scanning backward; make it so. |
652 | 0 | if operand.kind() == OperandKind::Def { |
653 | | // Trim the range for this vreg to start |
654 | | // at `pos` if it previously ended at the |
655 | | // start of this block (i.e. was not |
656 | | // merged into some larger LiveRange due |
657 | | // to out-of-order blocks). |
658 | 0 | if self.ranges[lr].range.from |
659 | 0 | == self.cfginfo.block_entry[block.index()] |
660 | | { |
661 | 0 | trace!(" -> started at block start; trimming to {:?}", pos); |
662 | 0 | self.ranges[lr].range.from = pos; |
663 | 0 | } |
664 | | |
665 | 0 | self.ranges[lr].set_flag(LiveRangeFlag::StartsAtDef); |
666 | | |
667 | | // Remove from live-set. |
668 | 0 | live.set(operand.vreg().vreg(), false); |
669 | 0 | vreg_ranges[operand.vreg().vreg()] = LiveRangeIndex::invalid(); |
670 | 0 | } |
671 | | } |
672 | | OperandKind::Use => { |
673 | | // Create/extend the LiveRange if it |
674 | | // doesn't already exist, and add the use |
675 | | // to the range. |
676 | 0 | let mut lr = vreg_ranges[operand.vreg().vreg()]; |
677 | 0 | if !live.get(operand.vreg().vreg()) { |
678 | 0 | let range = CodeRange { |
679 | 0 | from: self.cfginfo.block_entry[block.index()], |
680 | 0 | to: pos.next(), |
681 | 0 | }; |
682 | 0 | lr = self.add_liverange_to_vreg( |
683 | 0 | VRegIndex::new(operand.vreg().vreg()), |
684 | 0 | range, |
685 | 0 | ); |
686 | 0 | vreg_ranges[operand.vreg().vreg()] = lr; |
687 | 0 | } |
688 | 0 | debug_assert!(lr.is_valid()); |
689 | | |
690 | 0 | trace!("Use of {:?} at {:?} -> {:?}", operand, pos, lr,); |
691 | | |
692 | 0 | self.insert_use_into_liverange( |
693 | 0 | lr, |
694 | 0 | Use::new(operand, pos, slot_idx(i)?), |
695 | | ); |
696 | | |
697 | | // Add to live-set. |
698 | 0 | live.set(operand.vreg().vreg(), true); |
699 | | } |
700 | | } |
701 | | } |
702 | | } |
703 | | } |
704 | | |
705 | | // Block parameters define vregs at the very beginning of |
706 | | // the block. Remove their live vregs from the live set |
707 | | // here. |
708 | 0 | for vreg in self.func.block_params(block) { |
709 | 0 | if live.get(vreg.vreg()) { |
710 | 0 | live.set(vreg.vreg(), false); |
711 | 0 | } else { |
712 | 0 | // Create trivial liverange if blockparam is dead. |
713 | 0 | let start = self.cfginfo.block_entry[block.index()]; |
714 | 0 | self.add_liverange_to_vreg( |
715 | 0 | VRegIndex::new(vreg.vreg()), |
716 | 0 | CodeRange { |
717 | 0 | from: start, |
718 | 0 | to: start.next(), |
719 | 0 | }, |
720 | 0 | ); |
721 | 0 | } |
722 | | // add `blockparam_ins` entries. |
723 | 0 | let vreg_idx = VRegIndex::new(vreg.vreg()); |
724 | 0 | for &pred in self.func.block_preds(block) { |
725 | 0 | self.blockparam_ins.push(BlockparamIn { |
726 | 0 | to_vreg: vreg_idx, |
727 | 0 | to_block: block, |
728 | 0 | from_block: pred, |
729 | 0 | }); |
730 | 0 | } |
731 | | } |
732 | | } |
733 | | |
734 | | // Make ranges in each vreg and uses in each range appear in |
735 | | // sorted order. We built them in reverse order above, so this |
736 | | // is a simple reversal, *not* a full sort. |
737 | | // |
738 | | // The ordering invariant is always maintained for uses and |
739 | | // always for ranges in bundles (which are initialized later), |
740 | | // but not always for ranges in vregs; those are sorted only |
741 | | // when needed, here and then again at the end of allocation |
742 | | // when resolving moves. |
743 | | |
744 | 0 | for vreg in &mut self.ctx.vregs { |
745 | 0 | vreg.ranges.reverse(); |
746 | 0 | let mut last = None; |
747 | 0 | for entry in &mut vreg.ranges { |
748 | | // Ranges may have been truncated above at defs. We |
749 | | // need to update with the final range here. |
750 | 0 | entry.range = self.ctx.ranges[entry.index].range; |
751 | | // Assert in-order and non-overlapping. |
752 | 0 | debug_assert!(last.is_none() || last.unwrap() <= entry.range.from); |
753 | 0 | last = Some(entry.range.to); |
754 | | } |
755 | | } |
756 | | |
757 | 0 | for range in &mut self.ranges { |
758 | 0 | range.uses.reverse(); |
759 | 0 | debug_assert!(range.uses.windows(2).all(|win| win[0].pos <= win[1].pos)); |
760 | | } |
761 | | |
762 | 0 | self.blockparam_ins.sort_unstable_by_key(|x| x.key()); |
763 | 0 | self.blockparam_outs.sort_unstable_by_key(|x| x.key()); |
764 | | |
765 | 0 | self.output.stats.initial_liverange_count = self.ranges.len(); |
766 | 0 | self.output.stats.blockparam_ins_count = self.blockparam_ins.len(); |
767 | 0 | self.output.stats.blockparam_outs_count = self.blockparam_outs.len(); |
768 | 0 | self.ctx.scratch_vreg_ranges = vreg_ranges; |
769 | 0 | self.ctx.scratch_operand_rewrites = operand_rewrites; |
770 | | |
771 | 0 | Ok(()) |
772 | 0 | } |
773 | | |
774 | 0 | pub fn fixup_multi_fixed_vregs(&mut self) { |
775 | | // Do a fixed-reg cleanup pass: if there are any LiveRanges with |
776 | | // multiple uses at the same ProgPoint and there is |
777 | | // more than one FixedReg constraint at that ProgPoint, we |
778 | | // need to record all but one of them in a special fixup list |
779 | | // and handle them later; otherwise, bundle-splitting to |
780 | | // create minimal bundles becomes much more complex (we would |
781 | | // have to split the multiple uses at the same progpoint into |
782 | | // different bundles, which breaks invariants related to |
783 | | // disjoint ranges and bundles). |
784 | 0 | let mut extra_clobbers: SmallVec<[(PReg, ProgPoint); 8]> = smallvec![]; |
785 | 0 | for vreg in 0..self.vregs.len() { |
786 | 0 | let vreg = VRegIndex::new(vreg); |
787 | 0 | for range_idx in 0..self.vregs[vreg].ranges.len() { |
788 | 0 | let entry = self.vregs[vreg].ranges[range_idx]; |
789 | 0 | let range = entry.index; |
790 | 0 | trace!("multi-fixed-reg cleanup: vreg {:?} range {:?}", vreg, range,); |
791 | | |
792 | | // Find groups of uses that occur in at the same program point. |
793 | 0 | for uses in self.ctx.ranges[range] |
794 | 0 | .uses |
795 | 0 | .chunk_by_mut(|a, b| a.pos == b.pos) |
796 | | { |
797 | 0 | if uses.len() < 2 { |
798 | 0 | continue; |
799 | 0 | } |
800 | | |
801 | | // Search for conflicting constraints in the uses. |
802 | 0 | let mut requires_reg = false; |
803 | 0 | let mut num_fixed_reg = 0; |
804 | 0 | let mut num_fixed_stack = 0; |
805 | 0 | let mut first_reg_slot = None; |
806 | 0 | let mut first_stack_slot = None; |
807 | 0 | let mut min_limit = usize::MAX; |
808 | 0 | let mut max_fixed_reg = usize::MIN; |
809 | 0 | for u in uses.iter() { |
810 | 0 | match u.operand.constraint() { |
811 | 0 | OperandConstraint::Any => { |
812 | 0 | first_reg_slot.get_or_insert(u.slot); |
813 | 0 | first_stack_slot.get_or_insert(u.slot); |
814 | 0 | } |
815 | 0 | OperandConstraint::Reg | OperandConstraint::Reuse(_) => { |
816 | 0 | first_reg_slot.get_or_insert(u.slot); |
817 | 0 | requires_reg = true; |
818 | 0 | } |
819 | 0 | OperandConstraint::Limit(max) => { |
820 | 0 | first_reg_slot.get_or_insert(u.slot); |
821 | 0 | min_limit = min_limit.min(max); |
822 | 0 | requires_reg = true; |
823 | 0 | } |
824 | 0 | OperandConstraint::FixedReg(preg) => { |
825 | 0 | max_fixed_reg = max_fixed_reg.max(preg.hw_enc()); |
826 | 0 | if self.ctx.pregs[preg.index()].is_stack { |
827 | 0 | num_fixed_stack += 1; |
828 | 0 | first_stack_slot.get_or_insert(u.slot); |
829 | 0 | } else { |
830 | 0 | requires_reg = true; |
831 | 0 | num_fixed_reg += 1; |
832 | 0 | first_reg_slot.get_or_insert(u.slot); |
833 | 0 | } |
834 | | } |
835 | | // Maybe this could be supported in this future... |
836 | 0 | OperandConstraint::Stack => panic!( |
837 | | "multiple uses of vreg with a Stack constraint are not supported" |
838 | | ), |
839 | | } |
840 | | } |
841 | | |
842 | | // Fast path if there are no conflicts. |
843 | 0 | if num_fixed_reg + num_fixed_stack <= 1 |
844 | 0 | && !(requires_reg && num_fixed_stack != 0) |
845 | 0 | && max_fixed_reg < min_limit |
846 | | { |
847 | 0 | continue; |
848 | 0 | } |
849 | | |
850 | | // We pick one constraint (in order: FixedReg, Reg, FixedStack) |
851 | | // and then rewrite any incompatible constraints to Any. |
852 | | // This allows register allocation to succeed and we will |
853 | | // later insert moves to satisfy the rewritten constraints. |
854 | 0 | let source_slot = if requires_reg { |
855 | 0 | first_reg_slot.unwrap() |
856 | | } else { |
857 | 0 | first_stack_slot.unwrap() |
858 | | }; |
859 | 0 | let mut first_preg = None; |
860 | 0 | for u in uses.iter_mut() { |
861 | 0 | if let OperandConstraint::FixedReg(preg) = u.operand.constraint() { |
862 | 0 | let vreg_idx = VRegIndex::new(u.operand.vreg().vreg()); |
863 | 0 | let preg_idx = PRegIndex::new(preg.index()); |
864 | 0 | trace!( |
865 | | "at pos {:?}, vreg {:?} has fixed constraint to preg {:?}", |
866 | | u.pos, |
867 | | vreg_idx, |
868 | | preg_idx |
869 | | ); |
870 | | |
871 | | // FixedStack is incompatible if there are any |
872 | | // Reg/FixedReg constraints. FixedReg is |
873 | | // incompatible if there already is a different |
874 | | // FixedReg constraint. If either condition is true, |
875 | | // we edit the constraint below; otherwise, we can |
876 | | // skip this edit. |
877 | 0 | if !(requires_reg && self.ctx.pregs[preg.index()].is_stack) |
878 | 0 | && *first_preg.get_or_insert(preg) == preg |
879 | 0 | && preg.hw_enc() < min_limit |
880 | | { |
881 | 0 | continue; |
882 | 0 | } |
883 | | |
884 | 0 | trace!(" -> duplicate; switching to constraint Any"); |
885 | 0 | self.ctx.multi_fixed_reg_fixups.push(MultiFixedRegFixup { |
886 | 0 | pos: u.pos, |
887 | 0 | from_slot: source_slot, |
888 | 0 | to_slot: u.slot, |
889 | 0 | to_preg: preg_idx, |
890 | 0 | vreg: vreg_idx, |
891 | 0 | level: FixedRegFixupLevel::Secondary, |
892 | 0 | }); |
893 | 0 | u.operand = Operand::new( |
894 | 0 | u.operand.vreg(), |
895 | 0 | OperandConstraint::Any, |
896 | 0 | u.operand.kind(), |
897 | 0 | u.operand.pos(), |
898 | 0 | ); |
899 | 0 | trace!(" -> extra clobber {} at inst{}", preg, u.pos.inst().index()); |
900 | 0 | extra_clobbers.push((preg, u.pos)); |
901 | 0 | } |
902 | | } |
903 | | } |
904 | | |
905 | 0 | for (clobber, pos) in extra_clobbers.drain(..) { |
906 | 0 | let range = CodeRange { |
907 | 0 | from: pos, |
908 | 0 | to: pos.next(), |
909 | 0 | }; |
910 | 0 | self.add_liverange_to_preg(range, clobber); |
911 | 0 | } |
912 | | } |
913 | | } |
914 | 0 | } |
915 | | } |