/src/regalloc2/src/ion/merge.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 | | //! Bundle merging. |
14 | | |
15 | | use crate::ion::data_structures::{ |
16 | | BlockparamOut, CodeRange, Env, LiveBundleIndex, LiveRangeList, SpillSet, SpillSlotIndex, |
17 | | VRegIndex, |
18 | | }; |
19 | | use crate::{Function, Inst, OperandConstraint, OperandKind, PReg, ProgPoint}; |
20 | | use alloc::format; |
21 | | use core::convert::TryFrom; |
22 | | |
23 | | impl<'a, F: Function> Env<'a, F> { |
24 | 0 | fn merge_bundle_properties(&mut self, from: LiveBundleIndex, to: LiveBundleIndex) { |
25 | 0 | if self.bundles[from].cached_fixed() { |
26 | 0 | self.bundles[to].set_cached_fixed(); |
27 | 0 | } |
28 | 0 | if self.bundles[from].cached_fixed_def() { |
29 | 0 | self.bundles[to].set_cached_fixed_def(); |
30 | 0 | } |
31 | 0 | if self.bundles[from].cached_stack() { |
32 | 0 | self.bundles[to].set_cached_stack(); |
33 | 0 | } |
34 | 0 | if let Some(theirs) = self.bundles[from].limit { |
35 | 0 | match self.bundles[to].limit { |
36 | 0 | Some(ours) => self.bundles[to].limit = Some(ours.min(theirs)), |
37 | 0 | None => self.bundles[to].limit = Some(theirs), |
38 | | } |
39 | 0 | } |
40 | 0 | } |
41 | | |
42 | 0 | pub fn merge_bundles(&mut self, from: LiveBundleIndex, to: LiveBundleIndex) -> bool { |
43 | 0 | if from == to { |
44 | | // Merge bundle into self -- trivial merge. |
45 | 0 | return true; |
46 | 0 | } |
47 | 0 | trace!( |
48 | | "merging from bundle{} to bundle{}", |
49 | 0 | from.index(), |
50 | 0 | to.index() |
51 | | ); |
52 | | |
53 | | // Both bundles must deal with the same RegClass. |
54 | 0 | let from_rc = self.spillsets[self.bundles[from].spillset].class; |
55 | 0 | let to_rc = self.spillsets[self.bundles[to].spillset].class; |
56 | 0 | if from_rc != to_rc { |
57 | 0 | trace!(" -> mismatching reg classes"); |
58 | 0 | return false; |
59 | 0 | } |
60 | | |
61 | | // If either bundle is already assigned (due to a pinned vreg), don't merge. |
62 | 0 | if self.bundles[from].allocation.is_some() || self.bundles[to].allocation.is_some() { |
63 | 0 | trace!("one of the bundles is already assigned (pinned)"); |
64 | 0 | return false; |
65 | 0 | } |
66 | | |
67 | | #[cfg(debug_assertions)] |
68 | | { |
69 | | // Sanity check: both bundles should contain only ranges with appropriate VReg classes. |
70 | | for entry in &self.bundles[from].ranges { |
71 | | let vreg = self.ranges[entry.index].vreg; |
72 | | debug_assert_eq!(from_rc, self.vreg(vreg).class()); |
73 | | } |
74 | | for entry in &self.bundles[to].ranges { |
75 | | let vreg = self.ranges[entry.index].vreg; |
76 | | debug_assert_eq!(to_rc, self.vreg(vreg).class()); |
77 | | } |
78 | | } |
79 | | |
80 | | // If a bundle has a fixed-reg def then we need to be careful to not |
81 | | // extend the bundle to include another use in the same instruction. |
82 | | // This could result in a minimal bundle that is impossible to split. |
83 | | // |
84 | | // This can only happen with an early use and a late def, so we round |
85 | | // the start of each range containing a fixed def up to the start of |
86 | | // its instruction to detect overlaps. |
87 | 0 | let adjust_range_start = |bundle_idx, range: CodeRange| { |
88 | 0 | if self.bundles[bundle_idx].cached_fixed_def() { |
89 | 0 | ProgPoint::before(range.from.inst()) |
90 | | } else { |
91 | 0 | range.from |
92 | | } |
93 | 0 | }; |
94 | | |
95 | | // Check for overlap in LiveRanges and for conflicting |
96 | | // requirements. |
97 | 0 | let ranges_from = &self.bundles[from].ranges[..]; |
98 | 0 | let ranges_to = &self.bundles[to].ranges[..]; |
99 | 0 | let mut idx_from = 0; |
100 | 0 | let mut idx_to = 0; |
101 | 0 | let mut range_count = 0; |
102 | 0 | while idx_from < ranges_from.len() && idx_to < ranges_to.len() { |
103 | 0 | range_count += 1; |
104 | 0 | if range_count > 200 { |
105 | 0 | trace!( |
106 | | "reached merge complexity (range_count = {}); exiting", |
107 | | range_count |
108 | | ); |
109 | | // Limit merge complexity. |
110 | 0 | return false; |
111 | 0 | } |
112 | | |
113 | 0 | if adjust_range_start(from, ranges_from[idx_from].range) >= ranges_to[idx_to].range.to { |
114 | 0 | idx_to += 1; |
115 | 0 | } else if adjust_range_start(to, ranges_to[idx_to].range) |
116 | 0 | >= ranges_from[idx_from].range.to |
117 | 0 | { |
118 | 0 | idx_from += 1; |
119 | 0 | } else { |
120 | | // Overlap -- cannot merge. |
121 | 0 | trace!( |
122 | | " -> overlap between {:?} and {:?}, exiting", |
123 | 0 | ranges_from[idx_from].index, |
124 | 0 | ranges_to[idx_to].index |
125 | | ); |
126 | 0 | return false; |
127 | | } |
128 | | } |
129 | | |
130 | | // Check for a requirements conflict. |
131 | 0 | if self.bundles[from].cached_stack() |
132 | 0 | || self.bundles[from].cached_fixed() |
133 | 0 | || self.bundles[from].limit.is_some() |
134 | 0 | || self.bundles[to].cached_stack() |
135 | 0 | || self.bundles[to].cached_fixed() |
136 | 0 | || self.bundles[to].limit.is_some() |
137 | | { |
138 | 0 | if self.merge_bundle_requirements(from, to).is_err() { |
139 | 0 | trace!(" -> conflicting requirements; aborting merge"); |
140 | 0 | return false; |
141 | 0 | } |
142 | 0 | } |
143 | | |
144 | 0 | trace!(" -> committing to merge"); |
145 | | |
146 | | // If we reach here, then the bundles do not overlap -- merge |
147 | | // them! We do this with a merge-sort-like scan over both |
148 | | // lists, building a new range list and replacing the list on |
149 | | // `to` when we're done. |
150 | 0 | if ranges_from.is_empty() { |
151 | | // `from` bundle is empty -- trivial merge. |
152 | 0 | trace!(" -> from bundle{} is empty; trivial merge", from.index()); |
153 | 0 | return true; |
154 | 0 | } |
155 | 0 | if ranges_to.is_empty() { |
156 | | // `to` bundle is empty -- just move the list over from |
157 | | // `from` and set `bundle` up-link on all ranges. |
158 | 0 | trace!(" -> to bundle{} is empty; trivial merge", to.index()); |
159 | 0 | let empty_vec = LiveRangeList::new_in(self.ctx.bump()); |
160 | 0 | let list = core::mem::replace(&mut self.bundles[from].ranges, empty_vec); |
161 | 0 | for entry in &list { |
162 | 0 | self.ranges[entry.index].bundle = to; |
163 | | |
164 | 0 | if self.annotations_enabled { |
165 | 0 | self.annotate( |
166 | 0 | entry.range.from, |
167 | 0 | format!( |
168 | 0 | " MERGE range{} v{} from bundle{} to bundle{}", |
169 | 0 | entry.index.index(), |
170 | 0 | self.ranges[entry.index].vreg.index(), |
171 | 0 | from.index(), |
172 | 0 | to.index(), |
173 | 0 | ), |
174 | 0 | ); |
175 | 0 | } |
176 | | } |
177 | 0 | self.bundles[to].ranges = list; |
178 | 0 | self.merge_bundle_properties(from, to); |
179 | 0 | return true; |
180 | 0 | } |
181 | | |
182 | 0 | trace!( |
183 | | "merging: ranges_from = {:?} ranges_to = {:?}", |
184 | | ranges_from, |
185 | | ranges_to |
186 | | ); |
187 | | |
188 | 0 | let empty_vec = LiveRangeList::new_in(self.ctx.bump()); |
189 | 0 | let mut from_list = core::mem::replace(&mut self.bundles[from].ranges, empty_vec); |
190 | 0 | for entry in &from_list { |
191 | 0 | self.ranges[entry.index].bundle = to; |
192 | 0 | } |
193 | | |
194 | 0 | if from_list.len() == 1 { |
195 | | // Optimize for the common case where `from_list` contains a single |
196 | | // item. Using a binary search to find the insertion point and then |
197 | | // calling `insert` is more efficient than re-sorting the entire |
198 | | // list, specially after the changes in sorting algorithms introduced |
199 | | // in rustc 1.81. |
200 | | // See: https://github.com/bytecodealliance/regalloc2/issues/203 |
201 | 0 | let single_entry = from_list.pop().unwrap(); |
202 | 0 | let pos = self.bundles[to] |
203 | 0 | .ranges |
204 | 0 | .binary_search_by_key(&single_entry.range.from, |entry| entry.range.from) |
205 | 0 | .unwrap_or_else(|pos| pos); |
206 | 0 | self.bundles[to].ranges.insert(pos, single_entry); |
207 | | } else { |
208 | | // Two non-empty lists of LiveRanges: concatenate and sort. This is |
209 | | // faster than a mergesort-like merge into a new list, empirically. |
210 | 0 | self.bundles[to].ranges.extend_from_slice(&from_list[..]); |
211 | 0 | self.bundles[to] |
212 | 0 | .ranges |
213 | 0 | .sort_unstable_by_key(|entry| entry.range.from); |
214 | | } |
215 | | |
216 | 0 | if self.annotations_enabled { |
217 | 0 | trace!("merging: merged = {:?}", self.bundles[to].ranges); |
218 | 0 | let mut last_range = None; |
219 | 0 | for i in 0..self.bundles[to].ranges.len() { |
220 | 0 | let entry = self.bundles[to].ranges[i]; |
221 | 0 | if last_range.is_some() { |
222 | 0 | debug_assert!(last_range.unwrap() < entry.range); |
223 | 0 | } |
224 | 0 | last_range = Some(entry.range); |
225 | | |
226 | 0 | if self.ranges[entry.index].bundle == from { |
227 | 0 | self.annotate( |
228 | 0 | entry.range.from, |
229 | 0 | format!( |
230 | 0 | " MERGE range{} v{} from bundle{} to bundle{}", |
231 | 0 | entry.index.index(), |
232 | 0 | self.ranges[entry.index].vreg.index(), |
233 | 0 | from.index(), |
234 | 0 | to.index(), |
235 | 0 | ), |
236 | 0 | ); |
237 | 0 | } |
238 | | |
239 | 0 | trace!( |
240 | | " -> merged result for bundle{}: range{}", |
241 | 0 | to.index(), |
242 | 0 | entry.index.index(), |
243 | | ); |
244 | | } |
245 | 0 | } |
246 | | |
247 | 0 | if self.bundles[from].spillset != self.bundles[to].spillset { |
248 | 0 | // Widen the range for the target spillset to include the one being merged in. |
249 | 0 | let from_range = self.spillsets[self.bundles[from].spillset].range; |
250 | 0 | let to_range = &mut self.ctx.spillsets[self.ctx.bundles[to].spillset].range; |
251 | 0 | *to_range = to_range.join(from_range); |
252 | 0 | } |
253 | | |
254 | 0 | self.merge_bundle_properties(from, to); |
255 | | |
256 | 0 | true |
257 | 0 | } |
258 | | |
259 | 0 | pub fn merge_vreg_bundles(&mut self) { |
260 | | // Create a bundle for every vreg, initially. |
261 | 0 | trace!("merge_vreg_bundles: creating vreg bundles"); |
262 | 0 | for vreg in 0..self.vregs.len() { |
263 | 0 | let vreg = VRegIndex::new(vreg); |
264 | 0 | if self.vregs[vreg].ranges.is_empty() { |
265 | 0 | continue; |
266 | 0 | } |
267 | | |
268 | 0 | let bundle = self.ctx.bundles.add(self.ctx.scratch_bump.clone()); |
269 | 0 | let mut range = self.vregs[vreg].ranges.first().unwrap().range; |
270 | | |
271 | 0 | self.bundles[bundle].ranges = self.vregs[vreg].ranges.clone(); |
272 | 0 | trace!("vreg v{} gets bundle{}", vreg.index(), bundle.index()); |
273 | 0 | for entry in &self.ctx.bundles[bundle].ranges { |
274 | 0 | trace!( |
275 | | " -> with LR range{}: {:?}", |
276 | 0 | entry.index.index(), |
277 | | entry.range |
278 | | ); |
279 | 0 | range = range.join(entry.range); |
280 | 0 | self.ctx.ranges[entry.index].bundle = bundle; |
281 | | } |
282 | | |
283 | 0 | let mut fixed = false; |
284 | 0 | let mut fixed_def = false; |
285 | 0 | let mut stack = false; |
286 | 0 | let mut limit: Option<u8> = None; |
287 | 0 | for entry in &self.bundles[bundle].ranges { |
288 | 0 | for u in &self.ranges[entry.index].uses { |
289 | | use OperandConstraint::*; |
290 | 0 | match u.operand.constraint() { |
291 | | FixedReg(_) => { |
292 | 0 | fixed = true; |
293 | 0 | if u.operand.kind() == OperandKind::Def { |
294 | 0 | fixed_def = true; |
295 | 0 | } |
296 | | } |
297 | 0 | Stack => stack = true, |
298 | 0 | Limit(current) => { |
299 | 0 | let current = u8::try_from(current) |
300 | 0 | .expect("the current limit is too large to fit in a u8"); |
301 | 0 | match limit { |
302 | 0 | Some(prev) => limit = Some(prev.min(current)), |
303 | 0 | None => limit = Some(current), |
304 | | } |
305 | | } |
306 | | Any | Reg | Reuse(_) => { |
307 | 0 | continue; |
308 | | } |
309 | | } |
310 | 0 | if fixed && stack && fixed_def { |
311 | 0 | break; |
312 | 0 | } |
313 | | } |
314 | | } |
315 | 0 | if fixed { |
316 | 0 | self.bundles[bundle].set_cached_fixed(); |
317 | 0 | } |
318 | 0 | if fixed_def { |
319 | 0 | self.bundles[bundle].set_cached_fixed_def(); |
320 | 0 | } |
321 | 0 | if stack { |
322 | 0 | self.bundles[bundle].set_cached_stack(); |
323 | 0 | } |
324 | 0 | self.bundles[bundle].limit = limit; |
325 | | |
326 | | // Create a spillslot for this bundle. |
327 | 0 | let reg = self.vreg(vreg); |
328 | 0 | let ssidx = self.spillsets.push(SpillSet { |
329 | 0 | slot: SpillSlotIndex::invalid(), |
330 | 0 | required: false, |
331 | 0 | class: reg.class(), |
332 | 0 | hint: PReg::invalid(), |
333 | 0 | spill_bundle: LiveBundleIndex::invalid(), |
334 | 0 | splits: 0, |
335 | 0 | range, |
336 | 0 | }); |
337 | 0 | self.bundles[bundle].spillset = ssidx; |
338 | | } |
339 | | |
340 | 0 | for inst in 0..self.func.num_insts() { |
341 | 0 | let inst = Inst::new(inst); |
342 | | |
343 | | // Attempt to merge Reuse-constraint operand outputs with the |
344 | | // corresponding inputs. |
345 | 0 | for op in self.func.inst_operands(inst) { |
346 | 0 | if let OperandConstraint::Reuse(reuse_idx) = op.constraint() { |
347 | 0 | let src_vreg = op.vreg(); |
348 | 0 | let dst_vreg = self.func.inst_operands(inst)[reuse_idx].vreg(); |
349 | | |
350 | 0 | trace!( |
351 | | "trying to merge reused-input def: src {} to dst {}", |
352 | | src_vreg, |
353 | | dst_vreg |
354 | | ); |
355 | 0 | let src_bundle = self.ranges[self.vregs[src_vreg].ranges[0].index].bundle; |
356 | 0 | debug_assert!(src_bundle.is_valid()); |
357 | 0 | let dest_bundle = self.ranges[self.vregs[dst_vreg].ranges[0].index].bundle; |
358 | 0 | debug_assert!(dest_bundle.is_valid()); |
359 | 0 | self.merge_bundles(/* from */ dest_bundle, /* to */ src_bundle); |
360 | 0 | } |
361 | | } |
362 | | } |
363 | | |
364 | | // Attempt to merge blockparams with their inputs. |
365 | 0 | for i in 0..self.blockparam_outs.len() { |
366 | | let BlockparamOut { |
367 | 0 | from_vreg, to_vreg, .. |
368 | 0 | } = self.blockparam_outs[i]; |
369 | 0 | trace!( |
370 | | "trying to merge blockparam v{} with input v{}", |
371 | 0 | to_vreg.index(), |
372 | 0 | from_vreg.index() |
373 | | ); |
374 | 0 | let to_bundle = self.ranges[self.vregs[to_vreg].ranges[0].index].bundle; |
375 | 0 | debug_assert!(to_bundle.is_valid()); |
376 | 0 | let from_bundle = self.ranges[self.vregs[from_vreg].ranges[0].index].bundle; |
377 | 0 | debug_assert!(from_bundle.is_valid()); |
378 | 0 | trace!( |
379 | | " -> from bundle{} to bundle{}", |
380 | 0 | from_bundle.index(), |
381 | 0 | to_bundle.index() |
382 | | ); |
383 | 0 | self.merge_bundles(from_bundle, to_bundle); |
384 | | } |
385 | | |
386 | 0 | trace!("done merging bundles"); |
387 | 0 | } |
388 | | |
389 | 0 | pub fn compute_bundle_prio(&self, bundle: LiveBundleIndex) -> u32 { |
390 | | // The priority is simply the total "length" -- the number of |
391 | | // instructions covered by all LiveRanges. |
392 | 0 | let mut total = 0; |
393 | 0 | for entry in &self.bundles[bundle].ranges { |
394 | 0 | total += entry.range.len() as u32; |
395 | 0 | } |
396 | 0 | trace!(" -> prio {total}"); |
397 | 0 | total |
398 | 0 | } |
399 | | |
400 | 0 | pub fn compute_bundle_limit(&self, bundle: LiveBundleIndex) -> Option<u8> { |
401 | 0 | let mut limit: Option<u8> = None; |
402 | 0 | for entry in &self.bundles[bundle].ranges { |
403 | 0 | for u in &self.ranges[entry.index].uses { |
404 | | use OperandConstraint::*; |
405 | 0 | match u.operand.constraint() { |
406 | 0 | Limit(current) => { |
407 | 0 | let current = u8::try_from(current) |
408 | 0 | .expect("the current limit is too large to fit in a u8"); |
409 | 0 | match limit { |
410 | 0 | Some(prev) => limit = Some(prev.min(current)), |
411 | 0 | None => limit = Some(current), |
412 | | } |
413 | | } |
414 | | FixedReg(_) | Stack => { |
415 | 0 | break; |
416 | | } |
417 | | Any | Reg | Reuse(_) => { |
418 | 0 | continue; |
419 | | } |
420 | | } |
421 | | } |
422 | | } |
423 | 0 | limit |
424 | 0 | } |
425 | | |
426 | 0 | pub fn queue_bundles(&mut self) { |
427 | 0 | for bundle in 0..self.bundles.len() { |
428 | 0 | trace!("enqueueing bundle{}", bundle); |
429 | 0 | let bundle = LiveBundleIndex::new(bundle); |
430 | 0 | if self.bundles[bundle].ranges.is_empty() { |
431 | 0 | trace!(" -> no ranges; skipping"); |
432 | 0 | continue; |
433 | 0 | } |
434 | 0 | self.recompute_bundle_properties(bundle); |
435 | 0 | let prio = self.bundles[bundle].prio as usize; |
436 | 0 | self.allocation_queue.insert(bundle, prio, PReg::invalid()); |
437 | | } |
438 | 0 | self.output.stats.merged_bundle_count = self.allocation_queue.heap.len(); |
439 | 0 | } |
440 | | } |