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