/src/regalloc.rs/lib/src/linear_scan/mod.rs
Line | Count | Source (jump to first uncovered line) |
1 | | //! pub(crate) Implementation of the linear scan allocator algorithm. |
2 | | //! |
3 | | //! This tries to follow the implementation as suggested by: |
4 | | //! Optimized Interval Splitting in a Linear Scan Register Allocator, |
5 | | //! by Wimmer et al., 2005 |
6 | | |
7 | | use log::{info, log_enabled, trace, Level}; |
8 | | |
9 | | use std::env; |
10 | | use std::fmt; |
11 | | use std::{cmp::Ordering, default}; |
12 | | |
13 | | use crate::{ |
14 | | checker::CheckerContext, reg_maps::MentionRegUsageMapper, Function, RealRegUniverse, |
15 | | RegAllocError, RegAllocResult, RegClass, Set, SpillSlot, VirtualReg, NUM_REG_CLASSES, |
16 | | }; |
17 | | use crate::{ |
18 | | checker::CheckerStackmapInfo, |
19 | | inst_stream::{add_spills_reloads_and_moves, InstToInsertAndExtPoint}, |
20 | | }; |
21 | | use crate::{ |
22 | | data_structures::{BlockIx, InstIx, InstPoint, Point, RealReg, RegVecsAndBounds}, |
23 | | CheckerErrors, StackmapRequestInfo, |
24 | | }; |
25 | | |
26 | | use analysis::{AnalysisInfo, RangeFrag}; |
27 | | use smallvec::SmallVec; |
28 | | |
29 | | use self::analysis::{BlockBoundary, BlockPos}; |
30 | | |
31 | | mod analysis; |
32 | | mod assign_registers; |
33 | | mod resolve_moves; |
34 | | |
35 | | #[derive(Default)] |
36 | | pub(crate) struct Statistics { |
37 | | only_large: bool, |
38 | | |
39 | | num_fixed: usize, |
40 | | num_vregs: usize, |
41 | | num_virtual_ranges: usize, |
42 | | |
43 | | peak_active: usize, |
44 | | peak_inactive: usize, |
45 | | |
46 | | num_try_allocate_reg: usize, |
47 | | num_try_allocate_reg_success: usize, |
48 | | |
49 | | num_reg_splits: usize, |
50 | | num_reg_splits_success: usize, |
51 | | } |
52 | | |
53 | | impl Drop for Statistics { |
54 | 0 | fn drop(&mut self) { |
55 | 0 | if self.only_large && self.num_vregs < 1000 { |
56 | 0 | return; |
57 | 0 | } |
58 | 0 | println!( |
59 | 0 | "stats: {} fixed; {} vreg; {} vranges; {} peak-active; {} peak-inactive, {} direct-alloc; {} total-alloc; {} partial-splits; {} partial-splits-attempts", |
60 | 0 | self.num_fixed, |
61 | 0 | self.num_vregs, |
62 | 0 | self.num_virtual_ranges, |
63 | 0 | self.peak_active, |
64 | 0 | self.peak_inactive, |
65 | 0 | self.num_try_allocate_reg_success, |
66 | 0 | self.num_try_allocate_reg, |
67 | 0 | self.num_reg_splits_success, |
68 | 0 | self.num_reg_splits, |
69 | 0 | ); |
70 | 0 | } |
71 | | } |
72 | | |
73 | | /// Which strategy should we use when trying to find the best split position? |
74 | | /// TODO Consider loop depth to avoid splitting in the middle of a loop |
75 | | /// whenever possible. |
76 | 0 | #[derive(Copy, Clone, Debug)] |
77 | | enum OptimalSplitStrategy { |
78 | | From, |
79 | | To, |
80 | | NextFrom, |
81 | | NextNextFrom, |
82 | | PrevTo, |
83 | | PrevPrevTo, |
84 | | Mid, |
85 | | } |
86 | | |
87 | 0 | #[derive(Clone)] |
88 | | pub struct LinearScanOptions { |
89 | | split_strategy: OptimalSplitStrategy, |
90 | | partial_split: bool, |
91 | | partial_split_near_end: bool, |
92 | | stats: bool, |
93 | | large_stats: bool, |
94 | | } |
95 | | |
96 | | impl default::Default for LinearScanOptions { |
97 | 0 | fn default() -> Self { |
98 | | // Useful for debugging. |
99 | 0 | let optimal_split_strategy = match env::var("LSRA_SPLIT") { |
100 | 0 | Ok(s) => match s.as_str() { |
101 | 0 | "t" | "to" => OptimalSplitStrategy::To, |
102 | 0 | "n" => OptimalSplitStrategy::NextFrom, |
103 | 0 | "nn" => OptimalSplitStrategy::NextNextFrom, |
104 | 0 | "p" => OptimalSplitStrategy::PrevTo, |
105 | 0 | "pp" => OptimalSplitStrategy::PrevPrevTo, |
106 | 0 | "m" | "mid" => OptimalSplitStrategy::Mid, |
107 | 0 | _ => OptimalSplitStrategy::From, |
108 | | }, |
109 | 0 | Err(_) => OptimalSplitStrategy::From, |
110 | | }; |
111 | | |
112 | 0 | let large_stats = env::var("LSRA_LARGE_STATS").is_ok(); |
113 | 0 | let stats = env::var("LSRA_STATS").is_ok() || large_stats; |
114 | | |
115 | 0 | let partial_split = env::var("LSRA_PARTIAL").is_ok(); |
116 | 0 | let partial_split_near_end = env::var("LSRA_PARTIAL_END").is_ok(); |
117 | 0 |
|
118 | 0 | Self { |
119 | 0 | split_strategy: optimal_split_strategy, |
120 | 0 | partial_split, |
121 | 0 | partial_split_near_end, |
122 | 0 | stats, |
123 | 0 | large_stats, |
124 | 0 | } |
125 | 0 | } |
126 | | } |
127 | | |
128 | | impl fmt::Debug for LinearScanOptions { |
129 | | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { |
130 | 0 | writeln!(fmt, "linear scan")?; |
131 | 0 | write!(fmt, " split: {:?}", self.split_strategy) |
132 | 0 | } |
133 | | } |
134 | | |
135 | | // Local shorthands. |
136 | | type RegUses = RegVecsAndBounds; |
137 | | |
138 | | /// A unique identifier for an interval. |
139 | 0 | #[derive(Clone, Copy, PartialEq, Eq)] |
140 | | struct IntId(pub(crate) usize); |
141 | | |
142 | | impl fmt::Debug for IntId { |
143 | | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { |
144 | | write!(fmt, "int{}", self.0) |
145 | | } |
146 | | } |
147 | | |
148 | 0 | #[derive(Clone)] |
149 | | struct FixedInterval { |
150 | | reg: RealReg, |
151 | | frags: Vec<RangeFrag>, |
152 | | } |
153 | | |
154 | | impl fmt::Display for FixedInterval { |
155 | | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
156 | 0 | write!(f, "fixed {:?} [", self.reg)?; |
157 | 0 | for (i, frag) in self.frags.iter().enumerate() { |
158 | 0 | if i > 0 { |
159 | 0 | write!(f, ", ")?; |
160 | 0 | } |
161 | 0 | if frag.ref_typed { |
162 | 0 | write!(f, "ref ")?; |
163 | 0 | } |
164 | 0 | write!(f, "({:?}, {:?})", frag.first, frag.last)?; |
165 | | } |
166 | 0 | write!(f, "]") |
167 | 0 | } |
168 | | } |
169 | | |
170 | | impl FixedInterval { |
171 | | /// Find the fragment that contains the given instruction point. |
172 | | /// May crash if the point doesn't belong to any fragment. |
173 | 0 | pub(crate) fn find_frag(&self, pt: InstPoint) -> usize { |
174 | 0 | self.frags |
175 | 0 | .binary_search_by(|frag| { |
176 | 0 | if pt < frag.first { |
177 | 0 | Ordering::Greater |
178 | 0 | } else if pt >= frag.first && pt <= frag.last { |
179 | 0 | Ordering::Equal |
180 | | } else { |
181 | 0 | Ordering::Less |
182 | | } |
183 | 0 | }) |
184 | 0 | .unwrap() |
185 | 0 | } |
186 | | } |
187 | | |
188 | | type Safepoints = SmallVec<[(InstIx, usize); 8]>; |
189 | | |
190 | 0 | #[derive(Clone)] |
191 | | pub(crate) struct VirtualInterval { |
192 | | id: IntId, |
193 | | vreg: VirtualReg, |
194 | | |
195 | | /// Is this interval used for a reference type? |
196 | | ref_typed: bool, |
197 | | |
198 | | /// Parent interval in the split tree. |
199 | | parent: Option<IntId>, |
200 | | ancestor: Option<IntId>, |
201 | | /// Child interval, if it has one, in the split tree. |
202 | | child: Option<IntId>, |
203 | | |
204 | | /// Location assigned to this live interval. |
205 | | location: Location, |
206 | | |
207 | | mentions: MentionMap, |
208 | | block_boundaries: Vec<BlockBoundary>, |
209 | | safepoints: Safepoints, |
210 | | start: InstPoint, |
211 | | end: InstPoint, |
212 | | } |
213 | | |
214 | | impl fmt::Display for VirtualInterval { |
215 | | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { |
216 | 0 | write!(fmt, "virtual {:?}", self.id)?; |
217 | 0 | if self.ref_typed { |
218 | 0 | write!(fmt, " ref")?; |
219 | 0 | } |
220 | 0 | if let Some(ref p) = self.parent { |
221 | 0 | write!(fmt, " (parent={:?})", p)?; |
222 | 0 | } |
223 | 0 | write!( |
224 | 0 | fmt, |
225 | 0 | ": {:?} {} [{:?}; {:?}]", |
226 | 0 | self.vreg, self.location, self.start, self.end |
227 | 0 | )?; |
228 | 0 | write!( |
229 | 0 | fmt, |
230 | 0 | " boundaries=[{}]", |
231 | 0 | self.block_boundaries |
232 | 0 | .iter() |
233 | 0 | .map(|boundary| format!( |
234 | | "{:?}{}", |
235 | | boundary.bix, |
236 | | if boundary.pos == BlockPos::Start { |
237 | | "s" |
238 | | } else { |
239 | | "e" |
240 | | } |
241 | 0 | )) |
242 | 0 | .collect::<Vec<_>>() |
243 | 0 | .join(", ") |
244 | 0 | )?; |
245 | 0 | if !self.safepoints.is_empty() { |
246 | 0 | write!(fmt, " safepoints=[")?; |
247 | 0 | for (i, sp) in self.safepoints.iter().enumerate() { |
248 | 0 | if i > 0 { |
249 | 0 | write!(fmt, ", {:?}", sp.0)?; |
250 | | } else { |
251 | 0 | write!(fmt, "{:?}", sp.0)?; |
252 | | } |
253 | | } |
254 | 0 | write!(fmt, "]")?; |
255 | 0 | } |
256 | 0 | Ok(()) |
257 | 0 | } |
258 | | } |
259 | | |
260 | | impl VirtualInterval { |
261 | | fn new( |
262 | | id: IntId, |
263 | | vreg: VirtualReg, |
264 | | start: InstPoint, |
265 | | end: InstPoint, |
266 | | mentions: MentionMap, |
267 | | block_boundaries: Vec<BlockBoundary>, |
268 | | ref_typed: bool, |
269 | | safepoints: Safepoints, |
270 | | ) -> Self { |
271 | | Self { |
272 | | id, |
273 | | vreg, |
274 | | parent: None, |
275 | | ancestor: None, |
276 | | child: None, |
277 | | location: Location::None, |
278 | | mentions, |
279 | | block_boundaries, |
280 | | safepoints, |
281 | | start, |
282 | | end, |
283 | | ref_typed, |
284 | | } |
285 | | } |
286 | | fn safepoints(&self) -> &Safepoints { |
287 | | &self.safepoints |
288 | | } |
289 | | fn safepoints_mut(&mut self) -> &mut Safepoints { |
290 | | &mut self.safepoints |
291 | | } |
292 | | fn mentions(&self) -> &MentionMap { |
293 | | &self.mentions |
294 | | } |
295 | | fn mentions_mut(&mut self) -> &mut MentionMap { |
296 | | &mut self.mentions |
297 | | } |
298 | | fn block_boundaries(&self) -> &[BlockBoundary] { |
299 | | &self.block_boundaries |
300 | | } |
301 | | fn block_boundaries_mut(&mut self) -> &mut Vec<BlockBoundary> { |
302 | | &mut self.block_boundaries |
303 | | } |
304 | | fn covers(&self, pos: InstPoint) -> bool { |
305 | | self.start <= pos && pos <= self.end |
306 | | } |
307 | | } |
308 | | |
309 | | /// This data structure tracks the mentions of a register (virtual or real) at a precise |
310 | | /// instruction point. It's a set encoded as three flags, one for each of use/mod/def. |
311 | 0 | #[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] |
312 | | pub struct Mention(u8); |
313 | | |
314 | | impl fmt::Debug for Mention { |
315 | 0 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { |
316 | 0 | let mut comma = false; |
317 | 0 | if self.0 & 1 == 1 { |
318 | 0 | write!(fmt, "use")?; |
319 | 0 | comma = true; |
320 | 0 | } |
321 | 0 | if (self.0 >> 1) & 1 == 1 { |
322 | 0 | if comma { |
323 | 0 | write!(fmt, ",")?; |
324 | 0 | } |
325 | 0 | write!(fmt, "mod")?; |
326 | 0 | comma = true; |
327 | 0 | } |
328 | 0 | if (self.0 >> 2) & 1 == 1 { |
329 | 0 | if comma { |
330 | 0 | write!(fmt, ",")?; |
331 | 0 | } |
332 | 0 | write!(fmt, "def")?; |
333 | 0 | } |
334 | 0 | Ok(()) |
335 | 0 | } |
336 | | } |
337 | | |
338 | | impl Mention { |
339 | | fn new() -> Self { |
340 | | Self(0) |
341 | | } |
342 | | |
343 | | // Setters. |
344 | | fn add_use(&mut self) { |
345 | | self.0 |= 1 << 0; |
346 | | } |
347 | | fn add_mod(&mut self) { |
348 | | self.0 |= 1 << 1; |
349 | | } |
350 | | fn add_def(&mut self) { |
351 | | self.0 |= 1 << 2; |
352 | | } |
353 | | |
354 | | // Getters. |
355 | | fn is_use(&self) -> bool { |
356 | | (self.0 & 0b001) != 0 |
357 | | } |
358 | | fn is_mod(&self) -> bool { |
359 | | (self.0 & 0b010) != 0 |
360 | | } |
361 | | fn is_def(&self) -> bool { |
362 | | (self.0 & 0b100) != 0 |
363 | | } |
364 | | fn is_use_or_mod(&self) -> bool { |
365 | | (self.0 & 0b011) != 0 |
366 | | } |
367 | | fn is_mod_or_def(&self) -> bool { |
368 | | (self.0 & 0b110) != 0 |
369 | | } |
370 | | } |
371 | | |
372 | | pub type MentionMap = SmallVec<[(InstIx, Mention); 2]>; |
373 | | |
374 | 0 | #[derive(Debug, Clone, Copy)] |
375 | | pub(crate) enum Location { |
376 | | None, |
377 | | Reg(RealReg), |
378 | | Stack(SpillSlot), |
379 | | } |
380 | | |
381 | | impl Location { |
382 | 0 | pub(crate) fn reg(&self) -> Option<RealReg> { |
383 | 0 | match self { |
384 | 0 | Location::Reg(reg) => Some(*reg), |
385 | 0 | _ => None, |
386 | | } |
387 | 0 | } |
388 | 0 | pub(crate) fn spill(&self) -> Option<SpillSlot> { |
389 | 0 | match self { |
390 | 0 | Location::Stack(slot) => Some(*slot), |
391 | 0 | _ => None, |
392 | | } |
393 | 0 | } |
394 | 0 | pub(crate) fn is_none(&self) -> bool { |
395 | 0 | match self { |
396 | 0 | Location::None => true, |
397 | 0 | _ => false, |
398 | | } |
399 | 0 | } |
400 | | } |
401 | | |
402 | | impl fmt::Display for Location { |
403 | 0 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { |
404 | 0 | match self { |
405 | 0 | Location::None => write!(fmt, "none"), |
406 | 0 | Location::Reg(reg) => write!(fmt, "{:?}", reg), |
407 | 0 | Location::Stack(slot) => write!(fmt, "{:?}", slot), |
408 | | } |
409 | 0 | } |
410 | | } |
411 | | |
412 | | /// A group of live intervals. |
413 | | pub struct Intervals { |
414 | | virtuals: Vec<VirtualInterval>, |
415 | | fixeds: Vec<FixedInterval>, |
416 | | } |
417 | | |
418 | | impl Intervals { |
419 | | fn get(&self, int_id: IntId) -> &VirtualInterval { |
420 | | &self.virtuals[int_id.0] |
421 | | } |
422 | | fn get_mut(&mut self, int_id: IntId) -> &mut VirtualInterval { |
423 | | &mut self.virtuals[int_id.0] |
424 | | } |
425 | | fn num_virtual_intervals(&self) -> usize { |
426 | | self.virtuals.len() |
427 | | } |
428 | | |
429 | | // Mutators. |
430 | | fn set_reg(&mut self, int_id: IntId, reg: RealReg) { |
431 | | let int = self.get_mut(int_id); |
432 | | debug_assert!(int.location.is_none()); |
433 | | int.location = Location::Reg(reg); |
434 | | } |
435 | | fn set_spill(&mut self, int_id: IntId, slot: SpillSlot) { |
436 | | let int = self.get_mut(int_id); |
437 | | debug_assert!(int.location.spill().is_none()); |
438 | | int.location = Location::Stack(slot); |
439 | | } |
440 | | fn push_interval(&mut self, int: VirtualInterval) { |
441 | | debug_assert!(int.id.0 == self.virtuals.len()); |
442 | | self.virtuals.push(int); |
443 | | } |
444 | 0 | fn set_child(&mut self, int_id: IntId, child_id: IntId) { |
445 | 0 | if let Some(prev_child) = self.virtuals[int_id.0].child.clone() { |
446 | 0 | self.virtuals[child_id.0].child = Some(prev_child); |
447 | 0 | self.virtuals[prev_child.0].parent = Some(child_id); |
448 | 0 | } |
449 | 0 | self.virtuals[int_id.0].child = Some(child_id); |
450 | 0 | } |
451 | | } |
452 | | |
453 | | /// Finds the first use for the current interval that's located after the given |
454 | | /// `pos` (included), in a broad sense of use (any of use, def or mod). |
455 | | /// |
456 | | /// Extends to the left, that is, "modified" means "used". |
457 | | #[inline(never)] |
458 | | fn next_use(interval: &VirtualInterval, pos: InstPoint, _reg_uses: &RegUses) -> Option<InstPoint> { |
459 | 0 | if log_enabled!(Level::Trace) { |
460 | 0 | trace!("find next use of {} after {:?}", interval, pos); |
461 | 0 | } |
462 | | |
463 | 0 | let mentions = interval.mentions(); |
464 | 0 | let target = InstPoint::max(pos, interval.start); |
465 | | |
466 | 0 | let ret = match mentions.binary_search_by_key(&target.iix(), |mention| mention.0) { |
467 | 0 | Ok(index) => { |
468 | 0 | // Either the selected index is a perfect match, or the next mention is |
469 | 0 | // the correct answer. |
470 | 0 | let mention = &mentions[index]; |
471 | 0 | if target.pt() == Point::Use { |
472 | 0 | if mention.1.is_use_or_mod() { |
473 | 0 | Some(InstPoint::new_use(mention.0)) |
474 | | } else { |
475 | 0 | Some(InstPoint::new_def(mention.0)) |
476 | | } |
477 | 0 | } else if target.pt() == Point::Def && mention.1.is_mod_or_def() { |
478 | 0 | Some(target) |
479 | 0 | } else if index == mentions.len() - 1 { |
480 | 0 | None |
481 | | } else { |
482 | 0 | let mention = &mentions[index + 1]; |
483 | 0 | if mention.1.is_use_or_mod() { |
484 | 0 | Some(InstPoint::new_use(mention.0)) |
485 | | } else { |
486 | 0 | Some(InstPoint::new_def(mention.0)) |
487 | | } |
488 | | } |
489 | | } |
490 | | |
491 | 0 | Err(index) => { |
492 | 0 | if index == mentions.len() { |
493 | 0 | None |
494 | | } else { |
495 | 0 | let mention = &mentions[index]; |
496 | 0 | if mention.1.is_use_or_mod() { |
497 | 0 | Some(InstPoint::new_use(mention.0)) |
498 | | } else { |
499 | 0 | Some(InstPoint::new_def(mention.0)) |
500 | | } |
501 | | } |
502 | | } |
503 | | }; |
504 | | |
505 | | // TODO once the mentions are properly split, this could be removed, in |
506 | | // theory. |
507 | 0 | let ret = match ret { |
508 | 0 | Some(pos) => { |
509 | 0 | if pos <= interval.end { |
510 | 0 | Some(pos) |
511 | | } else { |
512 | 0 | None |
513 | | } |
514 | | } |
515 | 0 | None => None, |
516 | | }; |
517 | | |
518 | 0 | ret |
519 | 0 | } |
520 | | |
521 | | /// Finds the last use of a vreg before a given target, including it in possible |
522 | | /// return values. |
523 | | /// Extends to the right, that is, modified means "def". |
524 | | #[inline(never)] |
525 | | fn last_use(interval: &VirtualInterval, pos: InstPoint, _reg_uses: &RegUses) -> Option<InstPoint> { |
526 | 0 | if log_enabled!(Level::Trace) { |
527 | 0 | trace!("searching last use of {} before {:?}", interval, pos,); |
528 | 0 | } |
529 | | |
530 | 0 | let mentions = interval.mentions(); |
531 | 0 |
|
532 | 0 | let target = InstPoint::min(pos, interval.end); |
533 | | |
534 | 0 | let ret = match mentions.binary_search_by_key(&target.iix(), |mention| mention.0) { |
535 | 0 | Ok(index) => { |
536 | 0 | // Either the selected index is a perfect match, or the previous mention |
537 | 0 | // is the correct answer. |
538 | 0 | let mention = &mentions[index]; |
539 | 0 | if target.pt() == Point::Def { |
540 | 0 | if mention.1.is_mod_or_def() { |
541 | 0 | Some(InstPoint::new_def(mention.0)) |
542 | | } else { |
543 | 0 | Some(InstPoint::new_use(mention.0)) |
544 | | } |
545 | 0 | } else if target.pt() == Point::Use && mention.1.is_use() { |
546 | 0 | Some(target) |
547 | 0 | } else if index == 0 { |
548 | 0 | None |
549 | | } else { |
550 | 0 | let mention = &mentions[index - 1]; |
551 | 0 | if mention.1.is_mod_or_def() { |
552 | 0 | Some(InstPoint::new_def(mention.0)) |
553 | | } else { |
554 | 0 | Some(InstPoint::new_use(mention.0)) |
555 | | } |
556 | | } |
557 | | } |
558 | | |
559 | 0 | Err(index) => { |
560 | 0 | if index == 0 { |
561 | 0 | None |
562 | | } else { |
563 | 0 | let mention = &mentions[index - 1]; |
564 | 0 | if mention.1.is_mod_or_def() { |
565 | 0 | Some(InstPoint::new_def(mention.0)) |
566 | | } else { |
567 | 0 | Some(InstPoint::new_use(mention.0)) |
568 | | } |
569 | | } |
570 | | } |
571 | | }; |
572 | | |
573 | | // TODO once the mentions are properly split, this could be removed, in |
574 | | // theory. |
575 | 0 | let ret = match ret { |
576 | 0 | Some(pos) => { |
577 | 0 | if pos >= interval.start { |
578 | 0 | Some(pos) |
579 | | } else { |
580 | 0 | None |
581 | | } |
582 | | } |
583 | 0 | None => None, |
584 | | }; |
585 | | |
586 | 0 | trace!("mentions: {:?}", mentions); |
587 | 0 | trace!("found: {:?}", ret); |
588 | | |
589 | 0 | ret |
590 | 0 | } |
591 | | |
592 | | /// Checks that each register class has its own scratch register in addition to one available |
593 | | /// register, and creates a mapping of register class -> scratch register. |
594 | 0 | fn compute_scratches( |
595 | 0 | reg_universe: &RealRegUniverse, |
596 | 0 | ) -> Result<Vec<Option<RealReg>>, RegAllocError> { |
597 | 0 | let mut scratches_by_rc = vec![None; NUM_REG_CLASSES]; |
598 | 0 | for i in 0..NUM_REG_CLASSES { |
599 | 0 | if let Some(info) = ®_universe.allocable_by_class[i] { |
600 | 0 | if info.first == info.last { |
601 | 0 | return Err(RegAllocError::Other( |
602 | 0 | "at least 2 registers required for linear scan".into(), |
603 | 0 | )); |
604 | 0 | } |
605 | 0 | let scratch = if let Some(suggested_reg) = info.suggested_scratch { |
606 | 0 | reg_universe.regs[suggested_reg].0 |
607 | | } else { |
608 | 0 | return Err(RegAllocError::MissingSuggestedScratchReg( |
609 | 0 | RegClass::rc_from_u32(i as u32), |
610 | 0 | )); |
611 | | }; |
612 | 0 | scratches_by_rc[i] = Some(scratch); |
613 | 0 | } |
614 | | } |
615 | 0 | Ok(scratches_by_rc) |
616 | 0 | } |
617 | | |
618 | | /// Allocator top level. |
619 | | /// |
620 | | /// `func` is modified so that, when this function returns, it will contain no VirtualReg uses. |
621 | | /// |
622 | | /// Allocation can fail if there are insufficient registers to even generate spill/reload code, or |
623 | | /// if the function appears to have any undefined VirtualReg/RealReg uses. |
624 | | #[inline(never)] |
625 | 0 | pub(crate) fn run<F: Function>( |
626 | 0 | func: &mut F, |
627 | 0 | reg_universe: &RealRegUniverse, |
628 | 0 | stackmap_request: Option<&StackmapRequestInfo>, |
629 | 0 | use_checker: bool, |
630 | 0 | opts: &LinearScanOptions, |
631 | 0 | ) -> Result<RegAllocResult<F>, RegAllocError> { |
632 | | let AnalysisInfo { |
633 | 0 | reg_vecs_and_bounds: reg_uses, |
634 | 0 | intervals, |
635 | 0 | liveins, |
636 | 0 | liveouts, |
637 | 0 | cfg, |
638 | | .. |
639 | 0 | } = analysis::run(func, reg_universe, stackmap_request) |
640 | 0 | .map_err(|err| RegAllocError::Analysis(err))?; |
641 | | |
642 | 0 | let scratches_by_rc = compute_scratches(reg_universe)?; |
643 | | |
644 | 0 | let stats = if opts.stats { |
645 | 0 | let mut stats = Statistics::default(); |
646 | 0 | stats.num_fixed = intervals.fixeds.len(); |
647 | 0 | stats.num_virtual_ranges = intervals.virtuals.len(); |
648 | 0 | stats.num_vregs = intervals |
649 | 0 | .virtuals |
650 | 0 | .iter() |
651 | 0 | .map(|virt| virt.vreg.get_index()) |
652 | 0 | .fold(0, |a, b| usize::max(a, b)); |
653 | 0 | stats.only_large = opts.large_stats; |
654 | 0 | Some(stats) |
655 | | } else { |
656 | 0 | None |
657 | | }; |
658 | | |
659 | 0 | if log_enabled!(Level::Trace) { |
660 | 0 | trace!("fixed intervals:"); |
661 | 0 | for int in &intervals.fixeds { |
662 | 0 | trace!("{}", int); |
663 | | } |
664 | 0 | trace!(""); |
665 | 0 | trace!("unassigned intervals:"); |
666 | 0 | for int in &intervals.virtuals { |
667 | 0 | trace!("{}", int); |
668 | 0 | for mention in &int.mentions { |
669 | 0 | trace!(" mention @ {:?}: {:?}", mention.0, mention.1); |
670 | | } |
671 | | } |
672 | 0 | trace!(""); |
673 | 0 | } |
674 | | |
675 | 0 | let (intervals, mut num_spill_slots) = assign_registers::run( |
676 | 0 | opts, |
677 | 0 | func, |
678 | 0 | ®_uses, |
679 | 0 | reg_universe, |
680 | 0 | &scratches_by_rc, |
681 | 0 | intervals, |
682 | 0 | stats, |
683 | 0 | )?; |
684 | | |
685 | 0 | let virtuals = &intervals.virtuals; |
686 | 0 |
|
687 | 0 | let memory_moves = resolve_moves::run( |
688 | 0 | func, |
689 | 0 | &cfg, |
690 | 0 | ®_uses, |
691 | 0 | virtuals, |
692 | 0 | &liveins, |
693 | 0 | &liveouts, |
694 | 0 | &mut num_spill_slots, |
695 | 0 | &scratches_by_rc, |
696 | 0 | ); |
697 | 0 |
|
698 | 0 | apply_registers( |
699 | 0 | func, |
700 | 0 | virtuals, |
701 | 0 | memory_moves, |
702 | 0 | reg_universe, |
703 | 0 | num_spill_slots, |
704 | 0 | use_checker, |
705 | 0 | stackmap_request, |
706 | 0 | ) |
707 | 0 | } Unexecuted instantiation: regalloc::linear_scan::run::<minira::test_framework::Func> Unexecuted instantiation: regalloc::linear_scan::run::<regalloc::snapshot::IRFunction> |
708 | | |
709 | | #[inline(never)] |
710 | 0 | fn set_registers<F: Function>( |
711 | 0 | func: &mut F, |
712 | 0 | virtual_intervals: &Vec<VirtualInterval>, |
713 | 0 | reg_universe: &RealRegUniverse, |
714 | 0 | use_checker: bool, |
715 | 0 | memory_moves: &Vec<InstToInsertAndExtPoint>, |
716 | 0 | stackmap_request: Option<&StackmapRequestInfo>, |
717 | 0 | stackmaps: &[Vec<SpillSlot>], |
718 | 0 | ) -> Result<Set<RealReg>, CheckerErrors> { |
719 | 0 | info!("set_registers"); |
720 | | |
721 | 0 | let mut clobbered_registers = Set::empty(); |
722 | 0 |
|
723 | 0 | // Collect all the regs per instruction and mention set. |
724 | 0 | let capacity = virtual_intervals |
725 | 0 | .iter() |
726 | 0 | .map(|int| int.mentions.len()) |
727 | 0 | .fold(0, |a, b| a + b); |
728 | 0 |
|
729 | 0 | if capacity == 0 { |
730 | | // No virtual registers have been allocated, exit early. |
731 | 0 | return Ok(clobbered_registers); |
732 | 0 | } |
733 | 0 |
|
734 | 0 | let mut mention_map = Vec::with_capacity(capacity); |
735 | | |
736 | 0 | for int in virtual_intervals { |
737 | 0 | let rreg = match int.location.reg() { |
738 | 0 | Some(rreg) => rreg, |
739 | | _ => continue, |
740 | | }; |
741 | 0 | trace!("int: {}", int); |
742 | 0 | trace!(" {:?}", int.mentions); |
743 | 0 | for &mention in &int.mentions { |
744 | 0 | mention_map.push((mention.0, mention.1, int.vreg, rreg)); |
745 | 0 | } |
746 | | } |
747 | | |
748 | | // Sort by instruction index. |
749 | 0 | mention_map.sort_unstable_by_key(|quad| quad.0); |
750 | 0 |
|
751 | 0 | // Iterate over all the mentions. |
752 | 0 | let mut mapper = MentionRegUsageMapper::new(); |
753 | 0 |
|
754 | 0 | // Set up checker state, if indicated by our configuration. |
755 | 0 | let mut checker: Option<CheckerContext> = None; |
756 | 0 | let mut insn_blocks: Vec<BlockIx> = vec![]; |
757 | 0 | if use_checker { |
758 | 0 | let stackmap_info = |
759 | 0 | stackmap_request.map(|request| CheckerStackmapInfo { request, stackmaps }); |
760 | 0 | checker = Some(CheckerContext::new( |
761 | 0 | func, |
762 | 0 | reg_universe, |
763 | 0 | memory_moves, |
764 | 0 | stackmap_info, |
765 | 0 | )); |
766 | 0 | insn_blocks.resize(func.insns().len(), BlockIx::new(0)); |
767 | 0 | for block_ix in func.blocks() { |
768 | 0 | for insn_ix in func.block_insns(block_ix) { |
769 | 0 | insn_blocks[insn_ix.get() as usize] = block_ix; |
770 | 0 | } |
771 | | } |
772 | 0 | } |
773 | | |
774 | 0 | let mut cur_quad_ix = 0; |
775 | 0 | for func_inst_ix in func.insn_indices() { |
776 | | // Several items in the mention_map array may refer to the same instruction index, so |
777 | | // iterate over all of them that are related to the current instruction index. |
778 | 0 | while let Some((iix, mention_set, vreg, rreg)) = mention_map.get(cur_quad_ix) { |
779 | 0 | if func_inst_ix != *iix { |
780 | 0 | break; |
781 | 0 | } |
782 | | |
783 | | trace!( |
784 | 0 | "{:?}: {:?} is in {:?} at {:?}", |
785 | 0 | iix, |
786 | 0 | vreg, |
787 | 0 | rreg, |
788 | 0 | mention_set |
789 | | ); |
790 | | |
791 | | // Fill in new information at the given index. |
792 | 0 | if mention_set.is_use() { |
793 | 0 | if let Some(prev_rreg) = mapper.lookup_use(*vreg) { |
794 | | debug_assert_eq!(prev_rreg, *rreg, "different use allocs for {:?}", vreg); |
795 | 0 | } |
796 | 0 | mapper.set_use(*vreg, *rreg); |
797 | 0 | } |
798 | | |
799 | 0 | let included_in_clobbers = func.is_included_in_clobbers(func.get_insn(*iix)); |
800 | 0 | if mention_set.is_mod() { |
801 | 0 | if let Some(prev_rreg) = mapper.lookup_use(*vreg) { |
802 | | debug_assert_eq!(prev_rreg, *rreg, "different use allocs for {:?}", vreg); |
803 | 0 | } |
804 | 0 | if let Some(prev_rreg) = mapper.lookup_def(*vreg) { |
805 | | debug_assert_eq!(prev_rreg, *rreg, "different def allocs for {:?}", vreg); |
806 | 0 | } |
807 | | |
808 | 0 | mapper.set_use(*vreg, *rreg); |
809 | 0 | mapper.set_def(*vreg, *rreg); |
810 | 0 | if included_in_clobbers { |
811 | 0 | clobbered_registers.insert(*rreg); |
812 | 0 | } |
813 | 0 | } |
814 | | |
815 | 0 | if mention_set.is_def() { |
816 | 0 | if let Some(prev_rreg) = mapper.lookup_def(*vreg) { |
817 | | debug_assert_eq!(prev_rreg, *rreg, "different def allocs for {:?}", *vreg); |
818 | 0 | } |
819 | | |
820 | 0 | mapper.set_def(*vreg, *rreg); |
821 | 0 | if included_in_clobbers { |
822 | 0 | clobbered_registers.insert(*rreg); |
823 | 0 | } |
824 | 0 | } |
825 | | |
826 | 0 | cur_quad_ix += 1; |
827 | | } |
828 | | |
829 | | // At this point we've correctly filled the mapper; actually map the virtual registers to |
830 | | // the real ones in the Function. |
831 | 0 | trace!("map_regs for {:?}", func_inst_ix); |
832 | | |
833 | | // If available, make sure to update the checker's state *before* actually mapping the |
834 | | // register; the checker must see the function with virtual registers, not real ones. |
835 | 0 | if let Some(ref mut checker) = checker { |
836 | 0 | let block_ix = insn_blocks[func_inst_ix.get() as usize]; |
837 | 0 | checker |
838 | 0 | .handle_insn(reg_universe, func, block_ix, func_inst_ix, &mapper) |
839 | 0 | .unwrap(); |
840 | 0 | } |
841 | | |
842 | 0 | let mut inst = func.get_insn_mut(func_inst_ix); |
843 | 0 | F::map_regs(&mut inst, &mapper); |
844 | 0 |
|
845 | 0 | mapper.clear(); |
846 | | } |
847 | | |
848 | 0 | if let Some(checker) = checker { |
849 | 0 | checker.run()?; |
850 | 0 | } |
851 | | |
852 | 0 | Ok(clobbered_registers) |
853 | 0 | } Unexecuted instantiation: regalloc::linear_scan::set_registers::<minira::test_framework::Func> Unexecuted instantiation: regalloc::linear_scan::set_registers::<regalloc::snapshot::IRFunction> |
854 | | |
855 | | fn compute_stackmaps( |
856 | | intervals: &[VirtualInterval], |
857 | | stackmap_request: Option<&StackmapRequestInfo>, |
858 | | ) -> Vec<Vec<SpillSlot>> { |
859 | 0 | if let Some(request) = stackmap_request { |
860 | 0 | let mut stackmaps = vec![Vec::new(); request.safepoint_insns.len()]; |
861 | 0 | for int in intervals { |
862 | 0 | if !int.ref_typed { |
863 | | continue; |
864 | 0 | } |
865 | 0 | if let Some(slot) = int.location.spill() { |
866 | 0 | for &(_sp_iix, sp_ix) in &int.safepoints { |
867 | 0 | stackmaps[sp_ix].push(slot); |
868 | 0 | } |
869 | 0 | } |
870 | | } |
871 | 0 | stackmaps |
872 | | } else { |
873 | 0 | vec![] |
874 | | } |
875 | 0 | } |
876 | | |
877 | | /// Fills in the register assignments into instructions. |
878 | | #[inline(never)] |
879 | | fn apply_registers<F: Function>( |
880 | | func: &mut F, |
881 | | virtual_intervals: &Vec<VirtualInterval>, |
882 | | memory_moves: Vec<InstToInsertAndExtPoint>, |
883 | | reg_universe: &RealRegUniverse, |
884 | | num_spill_slots: u32, |
885 | | use_checker: bool, |
886 | | stackmap_request: Option<&StackmapRequestInfo>, |
887 | | ) -> Result<RegAllocResult<F>, RegAllocError> { |
888 | 0 | info!("apply_registers"); |
889 | | |
890 | 0 | let stackmaps = compute_stackmaps(virtual_intervals, stackmap_request.clone()); |
891 | | |
892 | 0 | let clobbered_registers = set_registers( |
893 | 0 | func, |
894 | 0 | virtual_intervals, |
895 | 0 | reg_universe, |
896 | 0 | use_checker, |
897 | 0 | &memory_moves, |
898 | 0 | stackmap_request, |
899 | 0 | &stackmaps, |
900 | 0 | ) |
901 | 0 | .map_err(|err| RegAllocError::RegChecker(err))?; |
902 | | |
903 | 0 | let (final_insns, target_map, new_to_old_insn_map, new_safepoint_insns) = |
904 | 0 | add_spills_reloads_and_moves( |
905 | 0 | func, |
906 | 0 | stackmap_request.map(|request| request.safepoint_insns.as_slice()), |
907 | 0 | memory_moves, |
908 | 0 | ) |
909 | 0 | .map_err(|e| RegAllocError::Other(e))?; |
910 | | |
911 | | // And now remove from the clobbered registers set, all those not available to the allocator. |
912 | | // But not removing the reserved regs, since we might have modified those. |
913 | 0 | clobbered_registers.filter_map(|®| { |
914 | 0 | if reg.get_index() >= reg_universe.allocable { |
915 | 0 | None |
916 | | } else { |
917 | 0 | Some(reg) |
918 | | } |
919 | 0 | }); Unexecuted instantiation: regalloc::linear_scan::apply_registers::<minira::test_framework::Func>::{closure#3}Unexecuted instantiation: regalloc::linear_scan::apply_registers::<regalloc::snapshot::IRFunction>::{closure#3} |
920 | 0 |
|
921 | 0 | Ok(RegAllocResult { |
922 | 0 | insns: final_insns, |
923 | 0 | target_map, |
924 | 0 | orig_insn_map: new_to_old_insn_map, |
925 | 0 | clobbered_registers, |
926 | 0 | num_spill_slots, |
927 | 0 | block_annotations: None, |
928 | 0 | stackmaps, |
929 | 0 | new_safepoint_insns, |
930 | 0 | }) |
931 | 0 | } Unexecuted instantiation: regalloc::linear_scan::apply_registers::<minira::test_framework::Func> Unexecuted instantiation: regalloc::linear_scan::apply_registers::<regalloc::snapshot::IRFunction> |