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, PRegIndex, RegTraversalIter, SpillSetIndex, SpillSlotData,
17    SpillSlotIndex,
18};
19use crate::{Allocation, Function, SpillSlot};
20
21impl<'a, F: Function> Env<'a, F> {
22    pub fn try_allocating_regs_for_spilled_bundles(&mut self) {
23        trace!("allocating regs for spilled bundles");
24        let mut scratch = core::mem::take(&mut self.ctx.scratch_conflicts);
25        for i in 0..self.ctx.spilled_bundles.len() {
26            let bundle = self.ctx.spilled_bundles[i]; // don't borrow self
27
28            if self.ctx.bundles[bundle].ranges.is_empty() {
29                continue;
30            }
31
32            let class = self.ctx.spillsets[self.ctx.bundles[bundle].spillset].class;
33            let hint = self.ctx.spillsets[self.ctx.bundles[bundle].spillset]
34                .hint
35                .as_valid();
36
37            // This may be an empty-range bundle whose ranges are not
38            // sorted; sort all range-lists again here.
39            self.ctx.bundles[bundle]
40                .ranges
41                .sort_unstable_by_key(|entry| entry.range.from);
42
43            let mut success = false;
44            self.ctx.output.stats.spill_bundle_reg_probes += 1;
45            for preg in RegTraversalIter::new(self.env, class, None, hint, bundle.index()) {
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, &mut scratch)
50                {
51                    self.ctx.output.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.ctx.bundles[bundle].spillset
61                );
62                self.ctx.spillsets[self.ctx.bundles[bundle].spillset].required = true;
63            }
64        }
65        self.ctx.scratch_conflicts = scratch;
66    }
67
68    pub fn spillslot_can_fit_spillset(
69        &mut self,
70        spillslot: SpillSlotIndex,
71        spillset: SpillSetIndex,
72    ) -> bool {
73        !self.ctx.spillslots[spillslot.index()]
74            .ranges
75            .btree
76            .contains_key(&LiveRangeKey::from_range(
77                &self.ctx.spillsets[spillset].range,
78            ))
79    }
80
81    pub fn allocate_spillset_to_spillslot(
82        &mut self,
83        spillset: SpillSetIndex,
84        spillslot: SpillSlotIndex,
85    ) {
86        self.ctx.spillsets[spillset].slot = spillslot;
87
88        let res = self.ctx.spillslots[spillslot.index()].ranges.btree.insert(
89            LiveRangeKey::from_range(&self.ctx.spillsets[spillset].range),
90            spillset,
91        );
92
93        debug_assert!(res.is_none());
94    }
95
96    pub fn allocate_spillslots(&mut self) {
97        const MAX_ATTEMPTS: usize = 10;
98
99        for spillset in 0..self.ctx.spillsets.len() {
100            trace!("allocate spillslot: {}", spillset);
101            let spillset = SpillSetIndex::new(spillset);
102            if !self.ctx.spillsets[spillset].required {
103                continue;
104            }
105            let class = self.ctx.spillsets[spillset].class as usize;
106            // Try a few existing spillslots.
107            let mut i = self.ctx.slots_by_class[class].probe_start;
108            let mut success = false;
109            // Never probe the same element more than once: limit the
110            // attempt count to the number of slots in existence.
111            for _attempt in
112                0..core::cmp::min(self.ctx.slots_by_class[class].slots.len(), MAX_ATTEMPTS)
113            {
114                // Note: this indexing of `slots` is always valid
115                // because either the `slots` list is empty and the
116                // iteration limit above consequently means we don't
117                // run this loop at all, or else `probe_start` is
118                // in-bounds (because it is made so below when we add
119                // a slot, and it always takes on the last index `i`
120                // after this loop).
121                let spillslot = self.ctx.slots_by_class[class].slots[i];
122
123                if self.spillslot_can_fit_spillset(spillslot, spillset) {
124                    self.allocate_spillset_to_spillslot(spillset, spillslot);
125                    success = true;
126                    self.ctx.slots_by_class[class].probe_start = i;
127                    break;
128                }
129
130                i = self.ctx.slots_by_class[class].next_index(i);
131            }
132
133            if !success {
134                // Allocate a new spillslot.
135                let spillslot = SpillSlotIndex::new(self.ctx.spillslots.len());
136                self.ctx.spillslots.push(SpillSlotData {
137                    ranges: self.ctx.scratch_spillset_pool.pop().unwrap_or_default(),
138                    alloc: Allocation::none(),
139                    slots: self.func.spillslot_size(self.ctx.spillsets[spillset].class) as u32,
140                });
141                self.ctx.slots_by_class[class].slots.push(spillslot);
142                self.ctx.slots_by_class[class].probe_start =
143                    self.ctx.slots_by_class[class].slots.len() - 1;
144
145                self.allocate_spillset_to_spillslot(spillset, spillslot);
146            }
147        }
148
149        // Assign actual slot indices to spillslots.
150        for i in 0..self.ctx.spillslots.len() {
151            self.ctx.spillslots[i].alloc = self.allocate_spillslot(self.ctx.spillslots[i].slots);
152        }
153
154        trace!("spillslot allocator done");
155    }
156
157    pub fn allocate_spillslot(&mut self, size: u32) -> Allocation {
158        let mut offset = self.ctx.output.num_spillslots as u32;
159        // Align up to `size`.
160        debug_assert!(size.is_power_of_two());
161        offset = (offset + size - 1) & !(size - 1);
162        let slot = if self.func.multi_spillslot_named_by_last_slot() {
163            offset + size - 1
164        } else {
165            offset
166        };
167        offset += size;
168        self.ctx.output.num_spillslots = offset as _;
169        Allocation::stack(SpillSlot::new(slot as usize))
170    }
171}