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