regalloc2/ion/
spill.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//! Spillslot allocation.
14
15use super::{
16    AllocRegResult, Env, LiveRangeKey, LiveRangeSet, PReg, PRegIndex, RegTraversalIter,
17    SpillSetIndex, SpillSlotData, SpillSlotIndex, SpillSlotList,
18};
19use crate::{Allocation, Function, SpillSlot};
20use smallvec::smallvec;
21
22impl<'a, F: Function> Env<'a, F> {
23    pub fn try_allocating_regs_for_spilled_bundles(&mut self) {
24        trace!("allocating regs for spilled bundles");
25        for i in 0..self.spilled_bundles.len() {
26            let bundle = self.spilled_bundles[i]; // don't borrow self
27
28            if self.bundles[bundle.index()].ranges.is_empty() {
29                continue;
30            }
31
32            let class = self.spillsets[self.bundles[bundle.index()].spillset.index()].class;
33            let hint = self.spillsets[self.bundles[bundle.index()].spillset.index()].reg_hint;
34
35            // This may be an empty-range bundle whose ranges are not
36            // sorted; sort all range-lists again here.
37            self.bundles[bundle.index()]
38                .ranges
39                .sort_unstable_by_key(|entry| entry.range.from);
40
41            let mut success = false;
42            self.stats.spill_bundle_reg_probes += 1;
43            for preg in
44                RegTraversalIter::new(self.env, class, hint, PReg::invalid(), bundle.index(), None)
45            {
46                trace!("trying bundle {:?} to preg {:?}", bundle, preg);
47                let preg_idx = PRegIndex::new(preg.index());
48                if let AllocRegResult::Allocated(_) =
49                    self.try_to_allocate_bundle_to_reg(bundle, preg_idx, None)
50                {
51                    self.stats.spill_bundle_reg_success += 1;
52                    success = true;
53                    break;
54                }
55            }
56            if !success {
57                trace!(
58                    "spilling bundle {:?}: marking spillset {:?} as required",
59                    bundle,
60                    self.bundles[bundle.index()].spillset
61                );
62                self.spillsets[self.bundles[bundle.index()].spillset.index()].required = true;
63            }
64        }
65    }
66
67    pub fn spillslot_can_fit_spillset(
68        &mut self,
69        spillslot: SpillSlotIndex,
70        spillset: SpillSetIndex,
71    ) -> bool {
72        for &vreg in &self.spillsets[spillset.index()].vregs {
73            for entry in &self.vregs[vreg.index()].ranges {
74                if self.spillslots[spillslot.index()]
75                    .ranges
76                    .btree
77                    .contains_key(&LiveRangeKey::from_range(&entry.range))
78                {
79                    return false;
80                }
81            }
82        }
83        true
84    }
85
86    pub fn allocate_spillset_to_spillslot(
87        &mut self,
88        spillset: SpillSetIndex,
89        spillslot: SpillSlotIndex,
90    ) {
91        self.spillsets[spillset.index()].slot = spillslot;
92
93        for vreg in &self.spillsets[spillset.index()].vregs {
94            trace!(
95                "spillslot {:?} alloc'ed to spillset {:?}: vreg {:?}",
96                spillslot,
97                spillset,
98                vreg,
99            );
100            for entry in &self.vregs[vreg.index()].ranges {
101                trace!(
102                    "spillslot {:?} getting range {:?} from LR {:?} from vreg {:?}",
103                    spillslot,
104                    entry.range,
105                    entry.index,
106                    vreg,
107                );
108                self.spillslots[spillslot.index()]
109                    .ranges
110                    .btree
111                    .insert(LiveRangeKey::from_range(&entry.range), entry.index);
112            }
113        }
114    }
115
116    pub fn allocate_spillslots(&mut self) {
117        const MAX_ATTEMPTS: usize = 10;
118
119        for spillset in 0..self.spillsets.len() {
120            trace!("allocate spillslot: {}", spillset);
121            let spillset = SpillSetIndex::new(spillset);
122            if !self.spillsets[spillset.index()].required {
123                continue;
124            }
125            // Get or create the spillslot list for this size.
126            let size = self.spillsets[spillset.index()].size as usize;
127            if size >= self.slots_by_size.len() {
128                self.slots_by_size.resize(
129                    size + 1,
130                    SpillSlotList {
131                        slots: smallvec![],
132                        probe_start: 0,
133                    },
134                );
135            }
136            // Try a few existing spillslots.
137            let mut i = self.slots_by_size[size].probe_start;
138            let mut success = false;
139            // Never probe the same element more than once: limit the
140            // attempt count to the number of slots in existence.
141            for _attempt in 0..std::cmp::min(self.slots_by_size[size].slots.len(), MAX_ATTEMPTS) {
142                // Note: this indexing of `slots` is always valid
143                // because either the `slots` list is empty and the
144                // iteration limit above consequently means we don't
145                // run this loop at all, or else `probe_start` is
146                // in-bounds (because it is made so below when we add
147                // a slot, and it always takes on the last index `i`
148                // after this loop).
149                let spillslot = self.slots_by_size[size].slots[i];
150
151                if self.spillslot_can_fit_spillset(spillslot, spillset) {
152                    self.allocate_spillset_to_spillslot(spillset, spillslot);
153                    success = true;
154                    self.slots_by_size[size].probe_start = i;
155                    break;
156                }
157
158                i = self.slots_by_size[size].next_index(i);
159            }
160
161            if !success {
162                // Allocate a new spillslot.
163                let spillslot = SpillSlotIndex::new(self.spillslots.len());
164                self.spillslots.push(SpillSlotData {
165                    ranges: LiveRangeSet::new(),
166                    alloc: Allocation::none(),
167                    slots: size as u32,
168                });
169                self.slots_by_size[size].slots.push(spillslot);
170                self.slots_by_size[size].probe_start = self.slots_by_size[size].slots.len() - 1;
171
172                self.allocate_spillset_to_spillslot(spillset, spillslot);
173            }
174        }
175
176        // Assign actual slot indices to spillslots.
177        for i in 0..self.spillslots.len() {
178            self.spillslots[i].alloc = self.allocate_spillslot(self.spillslots[i].slots);
179        }
180
181        trace!("spillslot allocator done");
182    }
183
184    pub fn allocate_spillslot(&mut self, size: u32) -> Allocation {
185        let mut offset = self.num_spillslots;
186        // Align up to `size`.
187        debug_assert!(size.is_power_of_two());
188        offset = (offset + size - 1) & !(size - 1);
189        let slot = if self.func.multi_spillslot_named_by_last_slot() {
190            offset + size - 1
191        } else {
192            offset
193        };
194        offset += size;
195        self.num_spillslots = offset;
196        Allocation::stack(SpillSlot::new(slot as usize))
197    }
198}