1use crate::interval_sched::{peak_concurrency, schedule_or_overflow, Interval, ScheduleOutcome};
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub struct LiveRange {
18 pub var: usize,
20 pub start: i64,
22 pub end: i64,
24}
25
26impl LiveRange {
27 pub fn new(var: usize, start: i64, end: i64) -> Self {
29 LiveRange { var, start, end }
30 }
31}
32
33#[derive(Clone, Debug, PartialEq, Eq)]
35pub enum Allocation {
36 Allocated(Vec<(usize, usize)>),
39 Spill {
43 pressure: usize,
45 must_spill: Vec<usize>,
47 },
48}
49
50pub fn register_pressure(ranges: &[LiveRange]) -> usize {
52 let tasks: Vec<Interval> = ranges.iter().map(|r| Interval::new(r.start, r.end)).collect();
53 peak_concurrency(&tasks)
54}
55
56pub fn allocate(ranges: &[LiveRange], registers: usize) -> Allocation {
59 let tasks: Vec<Interval> = ranges.iter().map(|r| Interval::new(r.start, r.end)).collect();
60 match schedule_or_overflow(&tasks, registers) {
61 ScheduleOutcome::Feasible(reg) => {
62 Allocation::Allocated(ranges.iter().zip(reg).map(|(r, m)| (r.var, m)).collect())
63 }
64 ScheduleOutcome::Infeasible(positions) => Allocation::Spill {
65 pressure: peak_concurrency(&tasks),
66 must_spill: positions.iter().map(|&i| ranges[i].var).collect(),
67 },
68 }
69}
70
71fn live_overlap(a: &LiveRange, b: &LiveRange) -> bool {
72 a.start < b.end && b.start < a.end
73}
74
75pub fn is_valid_allocation(ranges: &[LiveRange], registers: usize, reg_of: &[(usize, usize)]) -> bool {
78 if reg_of.len() != ranges.len() {
79 return false;
80 }
81 let reg: std::collections::HashMap<usize, usize> = reg_of.iter().copied().collect();
82 if reg.len() != ranges.len() || reg.values().any(|&r| r >= registers) {
83 return false;
84 }
85 for i in 0..ranges.len() {
86 for j in (i + 1)..ranges.len() {
87 if live_overlap(&ranges[i], &ranges[j])
88 && reg.get(&ranges[i].var) == reg.get(&ranges[j].var)
89 {
90 return false;
91 }
92 }
93 }
94 true
95}
96
97pub fn is_spill_certificate(ranges: &[LiveRange], registers: usize, must_spill: &[usize]) -> bool {
100 if must_spill.len() <= registers {
101 return false;
102 }
103 let by_var: std::collections::HashMap<usize, &LiveRange> =
104 ranges.iter().map(|r| (r.var, r)).collect();
105 must_spill.iter().enumerate().all(|(a, v)| {
106 by_var.get(v).is_some_and(|rv| {
107 must_spill
108 .iter()
109 .skip(a + 1)
110 .all(|u| by_var.get(u).is_some_and(|ru| live_overlap(rv, ru)))
111 })
112 })
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 fn lr(var: usize, start: i64, end: i64) -> LiveRange {
120 LiveRange::new(var, start, end)
121 }
122
123 #[test]
124 fn low_pressure_block_allocates() {
125 let ranges = vec![lr(0, 0, 2), lr(1, 1, 3), lr(2, 3, 5)];
127 assert_eq!(register_pressure(&ranges), 2);
128 match allocate(&ranges, 2) {
129 Allocation::Allocated(reg_of) => {
130 assert!(is_valid_allocation(&ranges, 2, ®_of), "{reg_of:?}")
131 }
132 o => panic!("expected Allocated, got {o:?}"),
133 }
134 }
135
136 #[test]
137 fn high_pressure_block_must_spill_with_a_clique() {
138 let ranges = vec![lr(0, 0, 5), lr(1, 1, 6), lr(2, 2, 7), lr(3, 2, 8)];
140 assert_eq!(register_pressure(&ranges), 4);
141 match allocate(&ranges, 3) {
142 Allocation::Spill { pressure, must_spill } => {
143 assert_eq!(pressure, 4, "needs 4 registers");
144 assert!(must_spill.len() > 3, "clique must exceed register count");
145 assert!(is_spill_certificate(&ranges, 3, &must_spill), "{must_spill:?}");
146 }
147 o => panic!("expected Spill, got {o:?}"),
148 }
149 }
150
151 #[test]
152 fn exactly_at_pressure_fits() {
153 let ranges = vec![lr(0, 0, 5), lr(1, 1, 6), lr(2, 2, 7)];
155 assert_eq!(register_pressure(&ranges), 3);
156 assert!(matches!(allocate(&ranges, 3), Allocation::Allocated(_)));
157 }
158
159 #[test]
160 fn matches_pressure_oracle_on_random_blocks() {
161 let mut s: u64 = 0xC2B2AE3D27D4EB4F;
162 let mut next = || {
163 s ^= s << 13;
164 s ^= s >> 7;
165 s ^= s << 17;
166 s
167 };
168 for _ in 0..500 {
169 let n = (next() % 10) as usize + 1;
170 let registers = (next() % 5) as usize + 1;
171 let ranges: Vec<LiveRange> = (0..n)
172 .map(|v| {
173 let a = (next() % 12) as i64;
174 let len = (next() % 6) as i64 + 1;
175 lr(v, a, a + len)
176 })
177 .collect();
178 let pressure = register_pressure(&ranges);
179 match allocate(&ranges, registers) {
180 Allocation::Allocated(reg_of) => {
181 assert!(pressure <= registers, "Allocated but pressure {pressure} > {registers}");
182 assert!(is_valid_allocation(&ranges, registers, ®_of), "invalid: {reg_of:?}");
183 }
184 Allocation::Spill { pressure: p, must_spill } => {
185 assert!(pressure > registers, "Spill but pressure {pressure} ≤ {registers}");
186 assert_eq!(p, pressure);
187 assert!(is_spill_certificate(&ranges, registers, &must_spill), "{must_spill:?}");
188 }
189 }
190 }
191 }
192
193 #[test]
194 fn robustness_edge_cases() {
195 assert!(matches!(allocate(&[lr(0, 0, 5)], 1), Allocation::Allocated(_)));
197 let disjoint = vec![lr(0, 0, 2), lr(1, 2, 4), lr(2, 4, 6)];
199 assert_eq!(register_pressure(&disjoint), 1);
200 match allocate(&disjoint, 1) {
201 Allocation::Allocated(a) => assert!(is_valid_allocation(&disjoint, 1, &a), "{a:?}"),
202 o => panic!("disjoint ranges fit 1 register: {o:?}"),
203 }
204 let same: Vec<LiveRange> = (0..6).map(|v| lr(v, 0, 10)).collect();
206 assert_eq!(register_pressure(&same), 6);
207 match allocate(&same, 4) {
208 Allocation::Spill { pressure, must_spill } => {
209 assert_eq!(pressure, 6);
210 assert_eq!(must_spill.len(), 5, "the first overflow is registers+1 wide");
211 assert!(is_spill_certificate(&same, 4, &must_spill));
212 }
213 o => panic!("6 identical ranges over 4 registers must spill: {o:?}"),
214 }
215 let big = vec![lr(0, 0, i64::MAX / 2), lr(1, 1, i64::MAX / 2)];
217 assert_eq!(register_pressure(&big), 2);
218 assert!(matches!(allocate(&big, 2), Allocation::Allocated(_)));
219 assert!(matches!(allocate(&[], 3), Allocation::Allocated(_)));
221 }
222
223 #[test]
224 fn a_bad_spill_certificate_is_rejected() {
225 let ranges = vec![lr(0, 0, 5), lr(1, 1, 6), lr(2, 2, 7), lr(3, 2, 8)];
226 assert!(!is_spill_certificate(&ranges, 3, &[0, 1]), "two vars don't exceed 3 registers");
227 assert!(is_spill_certificate(&ranges, 3, &[0, 1, 2, 3]), "all four are mutually live");
229 }
230}