1 #![allow(non_snake_case)]
2 #![allow(non_camel_case_types)]
3 
4 //! Backtracking allocator: the as-yet-unallocated VirtualReg LR prio queue.
5 
6 use std::cmp::Ordering;
7 use std::collections::BinaryHeap;
8 
9 use crate::data_structures::{TypedIxVec, VirtualRange, VirtualRangeIx};
10 
11 //=============================================================================
12 // The as-yet-unallocated VirtualReg LR prio queue, `VirtualRangePrioQ`.
13 //
14 // Relevant methods are parameterised by a VirtualRange env.
15 
16 // What we seek to do with `VirtualRangePrioQ` is to implement a priority
17 // queue of as-yet unallocated virtual live ranges.  For each iteration of the
18 // main allocation loop, we pull out the highest-priority unallocated
19 // VirtualRange, and either allocate it (somehow), or spill it.
20 //
21 // The Rust standard type BinaryHeap gives us an efficient way to implement
22 // the priority queue.  However, it requires that the queue items supply the
23 // necessary cost-comparisons by implementing `Ord` on that type.  Hence we
24 // have to wrap up the items we fundamentally want in the queue, viz,
25 // `VirtualRangeIx`, into a new type `VirtualRangeIxAndSize` that also carries
26 // the relevant cost field, `size`.  Then we implement `Ord` for
27 // `VirtualRangeIxAndSize` so as to only look at the `size` fields.
28 //
29 // There is a small twist, however.  Most virtual ranges are small and so will
30 // have a small `size` field (less than 20, let's say).  For such cases,
31 // `BinaryHeap` will presumably choose between contenders with the same `size`
32 // field in some arbitrary way.  This has two disadvantages:
33 //
34 // * it makes the exact allocation order, and hence allocation results,
35 //   dependent on `BinaryHeap`'s arbitrary-choice scheme.  This seems
36 //   undesirable, and given recent shenanigans resulting from `HashMap` being
37 //   nondeterministic even in a single-threaded scenario, I don't entirely
38 //   trust `BinaryHeap` even to be deterministic.
39 //
40 // * experimentation with the "qsort" test case shows that breaking ties by
41 //   selecting the entry that has been in the queue the longest, rather than
42 //   choosing arbitrarily, gives slightly better allocations (slightly less
43 //   spilling) in spill-heavy situations (where there are few registers).
44 //   When there is not much spilling, it makes no difference.
45 //
46 // For these reasons, `VirtualRangeIxAndSize` also contains a `tiebreaker`
47 // field.  The `VirtualRangePrioQ` logic gives a different value of this for
48 // each `VirtualRangeIxAndSize` it creates.  These numbers start off at 2^32-1
49 // and decrease towards zero.  They are used as a secondary comparison key in
50 // the case where the `size` fields are equal.  The effect is that (1)
51 // tiebreaking is made completely deterministic, and (2) it breaks ties in
52 // favour of the oldest entry (since that will have the highest `tiebreaker`
53 // field).
54 //
55 // The tiebreaker field will wrap around when it hits zero, but that can only
56 // happen after processing 2^32-1 virtual live ranges.  In such cases I would
57 // expect that the allocator would have run out of memory long before, so it's
58 // academic in practice.  Even if it does wrap around there is no danger to
59 // the correctness of the allocations.
60 
61 // Wrap up a VirtualRangeIx and its size, so that we can implement Ord for it
62 // on the basis of the `size` and `tiebreaker` fields.
63 //
64 // NB! Do not derive {,Partial}{Eq,Ord} for this.  It has its own custom
65 // implementations.
66 struct VirtualRangeIxAndSize {
67     vlrix: VirtualRangeIx,
68     size: u16,
69     tiebreaker: u32,
70 }
71 impl VirtualRangeIxAndSize {
new(vlrix: VirtualRangeIx, size: u16, tiebreaker: u32) -> Self72     fn new(vlrix: VirtualRangeIx, size: u16, tiebreaker: u32) -> Self {
73         assert!(size > 0);
74         Self {
75             vlrix,
76             size,
77             tiebreaker,
78         }
79     }
80 }
81 impl PartialEq for VirtualRangeIxAndSize {
eq(&self, other: &Self) -> bool82     fn eq(&self, other: &Self) -> bool {
83         self.size == other.size && self.tiebreaker == other.tiebreaker
84     }
85 }
86 impl Eq for VirtualRangeIxAndSize {}
87 impl PartialOrd for VirtualRangeIxAndSize {
partial_cmp(&self, other: &Self) -> Option<Ordering>88     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
89         Some(self.cmp(other))
90     }
91 }
92 impl Ord for VirtualRangeIxAndSize {
cmp(&self, other: &Self) -> Ordering93     fn cmp(&self, other: &Self) -> Ordering {
94         match self.size.cmp(&other.size) {
95             Ordering::Less => Ordering::Less,
96             Ordering::Greater => Ordering::Greater,
97             Ordering::Equal => self.tiebreaker.cmp(&other.tiebreaker),
98         }
99     }
100 }
101 
102 //=============================================================================
103 // VirtualRangePrioQ: public interface
104 
105 pub struct VirtualRangePrioQ {
106     // The set of as-yet unallocated VirtualRangeIxs.  These are indexes into a
107     // VirtualRange env that is not stored here.  The VirtualRangeIxs are tagged
108     // with the VirtualRange size and a tiebreaker field.
109     heap: BinaryHeap<VirtualRangeIxAndSize>,
110     tiebreaker_ctr: u32,
111 }
112 impl VirtualRangePrioQ {
new(vlr_env: &TypedIxVec<VirtualRangeIx, VirtualRange>) -> Self113     pub fn new(vlr_env: &TypedIxVec<VirtualRangeIx, VirtualRange>) -> Self {
114         let mut res = Self {
115             heap: BinaryHeap::new(),
116             tiebreaker_ctr: 0xFFFF_FFFFu32,
117         };
118         for vlrix in VirtualRangeIx::new(0).dotdot(VirtualRangeIx::new(vlr_env.len())) {
119             let to_add = VirtualRangeIxAndSize::new(vlrix, vlr_env[vlrix].size, res.tiebreaker_ctr);
120             res.heap.push(to_add);
121             res.tiebreaker_ctr -= 1;
122         }
123         res
124     }
125 
126     #[inline(never)]
is_empty(&self) -> bool127     pub fn is_empty(&self) -> bool {
128         self.heap.is_empty()
129     }
130 
131     #[inline(never)]
len(&self) -> usize132     pub fn len(&self) -> usize {
133         self.heap.len()
134     }
135 
136     // Add a VirtualRange index.
137     #[inline(never)]
add_VirtualRange( &mut self, vlr_env: &TypedIxVec<VirtualRangeIx, VirtualRange>, vlrix: VirtualRangeIx, )138     pub fn add_VirtualRange(
139         &mut self,
140         vlr_env: &TypedIxVec<VirtualRangeIx, VirtualRange>,
141         vlrix: VirtualRangeIx,
142     ) {
143         let to_add = VirtualRangeIxAndSize::new(vlrix, vlr_env[vlrix].size, self.tiebreaker_ctr);
144         self.tiebreaker_ctr -= 1;
145         self.heap.push(to_add);
146     }
147 
148     // Look in `unallocated` to locate the entry referencing the VirtualRange
149     // with the largest `size` value.  Remove the ref from `unallocated` and
150     // return the VLRIx for said entry.
151     #[inline(never)]
get_longest_VirtualRange(&mut self) -> Option<VirtualRangeIx>152     pub fn get_longest_VirtualRange(&mut self) -> Option<VirtualRangeIx> {
153         match self.heap.pop() {
154             None => None,
155             Some(VirtualRangeIxAndSize { vlrix, .. }) => Some(vlrix),
156         }
157     }
158 
159     #[inline(never)]
show_with_envs( &self, vlr_env: &TypedIxVec<VirtualRangeIx, VirtualRange>, ) -> Vec<String>160     pub fn show_with_envs(
161         &self,
162         vlr_env: &TypedIxVec<VirtualRangeIx, VirtualRange>,
163     ) -> Vec<String> {
164         let mut resV = vec![];
165         for VirtualRangeIxAndSize { vlrix, .. } in self.heap.iter() {
166             let mut res = "TODO  ".to_string();
167             res += &format!("{:?} = {:?}", vlrix, &vlr_env[*vlrix]);
168             resV.push(res);
169         }
170         resV
171     }
172 }
173