regalloc2/ion/
merge.rs

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