1use crate::isel::{MInstr, MOpcode, MOperand, MachineFunction, PReg, VReg};
14pub use crate::regalloc_gc::graph_color;
16use std::collections::HashMap;
17
18#[derive(Clone, Debug, PartialEq, Eq)]
22pub struct LiveInterval {
23 pub vreg: VReg,
25 pub start: usize,
27 pub end: usize,
29}
30
31fn block_starts(mf: &MachineFunction) -> Vec<usize> {
33 let mut starts = Vec::with_capacity(mf.blocks.len());
34 let mut pos = 0;
35 for b in &mf.blocks {
36 starts.push(pos);
37 pos += b.instrs.len();
38 }
39 starts
40}
41
42pub fn compute_live_intervals(mf: &MachineFunction) -> Vec<LiveInterval> {
44 let _ = block_starts(mf); let mut map: HashMap<VReg, (usize, usize)> = HashMap::new();
46 let mut pos = 0usize;
47
48 for block in &mf.blocks {
49 for instr in &block.instrs {
50 if let Some(dst) = instr.dst {
52 let e = map.entry(dst).or_insert((pos, pos + 1));
53 if pos < e.0 {
54 e.0 = pos;
55 }
56 if pos + 1 > e.1 {
57 e.1 = pos + 1;
58 }
59 }
60 for op in &instr.operands {
62 if let MOperand::VReg(vr) = op {
63 let e = map.entry(*vr).or_insert((pos, pos + 1));
64 if pos < e.0 {
65 e.0 = pos;
66 }
67 if pos + 1 > e.1 {
68 e.1 = pos + 1;
69 }
70 }
71 }
72 pos += 1;
73 }
74 }
75
76 let mut intervals: Vec<LiveInterval> = map
77 .into_iter()
78 .map(|(vreg, (start, end))| LiveInterval { vreg, start, end })
79 .collect();
80 intervals.sort_by_key(|i| (i.start, i.end, i.vreg.0));
81 intervals
82}
83
84#[derive(Debug, Default)]
88pub struct RegAllocResult {
89 pub vreg_to_preg: HashMap<VReg, PReg>,
91 pub spilled: Vec<VReg>,
93}
94
95#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
97pub enum RegAllocStrategy {
98 #[default]
99 LinearScan,
100 GraphColor,
102}
103
104pub fn allocate_registers(
106 intervals: &[LiveInterval],
107 allocatable: &[PReg],
108 strategy: RegAllocStrategy,
109) -> RegAllocResult {
110 match strategy {
111 RegAllocStrategy::LinearScan => linear_scan(intervals, allocatable),
112 RegAllocStrategy::GraphColor => graph_color(intervals, allocatable),
113 }
114}
115
116pub fn linear_scan(intervals: &[LiveInterval], allocatable: &[PReg]) -> RegAllocResult {
122 if allocatable.is_empty() {
123 return RegAllocResult {
124 vreg_to_preg: HashMap::new(),
125 spilled: intervals.iter().map(|i| i.vreg).collect(),
126 };
127 }
128
129 let mut sorted: Vec<&LiveInterval> = intervals.iter().collect();
132 sorted.sort_by_key(|i| (i.start, i.end, i.vreg.0));
133
134 let mut free: Vec<PReg> = allocatable.to_vec();
135 let mut active: Vec<(usize, VReg, PReg)> = Vec::new();
137 let mut result = RegAllocResult::default();
138
139 for interval in &sorted {
140 let mut returned = Vec::new();
142 active.retain(|&(end, _vr, pr)| {
143 if end <= interval.start {
144 returned.push(pr);
145 false
146 } else {
147 true
148 }
149 });
150 free.extend(returned);
151
152 if free.is_empty() {
153 let spill_idx = if active.is_empty() {
156 None
157 } else {
158 Some(active.len() - 1)
159 };
160
161 if let Some(idx) = spill_idx {
162 let (spill_end, spill_vr, spill_pr) = active[idx];
163 if spill_end > interval.end {
164 active.remove(idx);
166 result.vreg_to_preg.remove(&spill_vr); result.vreg_to_preg.insert(interval.vreg, spill_pr);
168 let pos = active.partition_point(|&(e, vr, _)| {
170 (e, vr.0) <= (interval.end, interval.vreg.0)
171 });
172 active.insert(pos, (interval.end, interval.vreg, spill_pr));
173 result.spilled.push(spill_vr);
174 } else {
175 result.spilled.push(interval.vreg);
177 }
178 } else {
179 result.spilled.push(interval.vreg);
180 }
181 } else {
182 let pr = free.remove(0);
183 result.vreg_to_preg.insert(interval.vreg, pr);
184 let pos = active.partition_point(|&(e, vr, _)| {
187 (e, vr.0) <= (interval.end, interval.vreg.0)
188 });
189 active.insert(pos, (interval.end, interval.vreg, pr));
190 }
191 }
192
193 result
194}
195
196pub fn insert_spill_reloads(
215 mf: &mut MachineFunction,
216 result: &mut RegAllocResult,
217 load_op: MOpcode,
218 store_op: MOpcode,
219) {
220 if result.spilled.is_empty() {
221 return;
222 }
223
224 let spilled: Vec<VReg> = result.spilled.drain(..).collect();
226 for &vr in &spilled {
227 mf.alloc_spill_slot(vr);
228 }
229
230 for bi in 0..mf.blocks.len() {
232 let old_instrs: Vec<MInstr> = std::mem::take(&mut mf.blocks[bi].instrs);
233 let mut new_instrs: Vec<MInstr> = Vec::with_capacity(old_instrs.len() * 2);
234
235 for mut instr in old_instrs {
236 for op in &mut instr.operands {
239 if let MOperand::VReg(vr) = op {
240 if let Some(&slot) = mf.spill_slots.get(vr) {
241 let fresh = mf.fresh_vreg();
242 new_instrs.push(MInstr::new(load_op).with_dst(fresh).with_imm(slot as i64));
244 *op = MOperand::VReg(fresh);
245 }
246 }
247 }
248
249 let store_after = if let Some(dst_vr) = instr.dst {
252 if let Some(&slot) = mf.spill_slots.get(&dst_vr) {
253 let fresh = mf.fresh_vreg();
254 instr.dst = Some(fresh);
255 Some((fresh, slot))
256 } else {
257 None
258 }
259 } else {
260 None
261 };
262
263 new_instrs.push(instr);
264
265 if let Some((fresh, slot)) = store_after {
266 new_instrs.push(MInstr::new(store_op).with_imm(slot as i64).with_vreg(fresh));
267 }
268 }
269
270 mf.blocks[bi].instrs = new_instrs;
271 }
272
273 let fresh_intervals = compute_live_intervals(mf);
275 let fresh_only: Vec<LiveInterval> = fresh_intervals
277 .into_iter()
278 .filter(|iv| !result.vreg_to_preg.contains_key(&iv.vreg))
279 .collect();
280
281 if !fresh_only.is_empty() {
282 let alloc = mf.allocatable_pregs.clone();
283 let second = linear_scan(&fresh_only, &alloc);
284 for (vr, pr) in second.vreg_to_preg {
286 result.vreg_to_preg.insert(vr, pr);
287 }
288 if !second.spilled.is_empty() {
290 let fallback = alloc.first().copied().unwrap_or(PReg(0));
291 for vr in second.spilled {
292 result.vreg_to_preg.entry(vr).or_insert(fallback);
293 }
294 }
295 }
296}
297
298pub fn apply_allocation(mf: &mut MachineFunction, result: &RegAllocResult) {
308 let callee_saved: std::collections::HashSet<PReg> =
310 mf.callee_saved_pregs.iter().copied().collect();
311 let mut used_cs: std::collections::HashSet<PReg> = std::collections::HashSet::new();
312
313 for &pr in result.vreg_to_preg.values() {
314 if callee_saved.contains(&pr) {
315 used_cs.insert(pr);
316 }
317 }
318 mf.used_callee_saved = used_cs.into_iter().collect();
319 let order: HashMap<PReg, usize> = mf
321 .callee_saved_pregs
322 .iter()
323 .enumerate()
324 .map(|(i, &p)| (p, i))
325 .collect();
326 mf.used_callee_saved
327 .sort_by_key(|p| order.get(p).copied().unwrap_or(usize::MAX));
328
329 for block in &mut mf.blocks {
330 for instr in &mut block.instrs {
331 if let Some(ref mut vr) = instr.dst {
333 if let Some(&pr) = result.vreg_to_preg.get(vr) {
334 *vr = VReg(pr.0 as u32);
337 }
338 }
339 for op in &mut instr.operands {
341 if let MOperand::VReg(vr) = op {
342 if let Some(&pr) = result.vreg_to_preg.get(vr) {
343 *op = MOperand::PReg(pr);
344 }
345 }
346 }
347 }
348 }
349}
350
351#[cfg(test)]
354mod tests {
355 use super::*;
356
357 fn iv(vreg: u32, start: usize, end: usize) -> LiveInterval {
358 LiveInterval {
359 vreg: VReg(vreg),
360 start,
361 end,
362 }
363 }
364
365
366 #[test]
367 fn compute_intervals_returns_deterministic_order_for_equal_starts() {
368 use crate::isel::{MInstr, MOpcode, MachineFunction};
369 let mut mf = MachineFunction::new("f".into());
370 let b = mf.add_block("entry");
371 let v0 = mf.fresh_vreg();
372 let v1 = mf.fresh_vreg();
373 let v2 = mf.fresh_vreg();
374
375 mf.push(
376 b,
377 MInstr::new(MOpcode(0))
378 .with_dst(v2)
379 .with_vreg(v1)
380 .with_vreg(v0),
381 );
382
383 let intervals = compute_live_intervals(&mf);
384 let keys: Vec<(usize, usize, u32)> = intervals
385 .iter()
386 .map(|iv| (iv.start, iv.end, iv.vreg.0))
387 .collect();
388 assert_eq!(keys, vec![(0, 1, 0), (0, 1, 1), (0, 1, 2)]);
389 }
390
391 #[test]
392 fn linear_scan_tie_breaks_equal_start_intervals_by_vreg() {
393 let intervals = vec![iv(2, 0, 1), iv(0, 0, 1), iv(1, 0, 1)];
394 let alloc = vec![PReg(0), PReg(1)];
395 let result = linear_scan(&intervals, &alloc);
396
397 assert_eq!(result.vreg_to_preg[&VReg(0)], PReg(0));
398 assert_eq!(result.vreg_to_preg[&VReg(1)], PReg(1));
399 assert_eq!(result.spilled, vec![VReg(2)]);
400 }
401
402 #[test]
403 fn two_non_overlapping_one_reg() {
404 let intervals = vec![iv(0, 0, 2), iv(1, 3, 5)];
406 let alloc = vec![PReg(0)];
407 let result = linear_scan(&intervals, &alloc);
408 assert!(result.spilled.is_empty(), "no spills expected");
409 assert_eq!(result.vreg_to_preg.len(), 2);
410 assert_eq!(result.vreg_to_preg[&VReg(0)], PReg(0));
411 assert_eq!(result.vreg_to_preg[&VReg(1)], PReg(0));
412 }
413
414 #[test]
415 fn two_overlapping_one_register_causes_spill() {
416 let intervals = vec![iv(0, 0, 10), iv(1, 1, 8)];
418 let alloc = vec![PReg(0)];
419 let result = linear_scan(&intervals, &alloc);
420 assert_eq!(result.spilled.len(), 1, "exactly one must spill");
421 assert_eq!(result.vreg_to_preg.len(), 1);
422 }
423
424 #[test]
425 fn three_intervals_two_registers() {
426 let intervals = vec![iv(0, 0, 3), iv(1, 1, 4), iv(2, 5, 8)];
428 let alloc = vec![PReg(0), PReg(1)];
429 let result = linear_scan(&intervals, &alloc);
430 assert!(result.spilled.is_empty());
431 assert_eq!(result.vreg_to_preg.len(), 3);
432 }
433
434 #[test]
435 fn empty_intervals() {
436 let result = linear_scan(&[], &[PReg(0)]);
437 assert!(result.spilled.is_empty());
438 assert!(result.vreg_to_preg.is_empty());
439 }
440
441 #[test]
442 fn no_allocatable_registers_all_spill() {
443 let intervals = vec![iv(0, 0, 5), iv(1, 2, 7)];
444 let result = linear_scan(&intervals, &[]);
445 assert_eq!(result.spilled.len(), 2);
446 assert!(result.vreg_to_preg.is_empty());
447 }
448
449 #[test]
450 fn apply_allocation_rewrites_operands() {
451 use crate::isel::{MInstr, MOpcode, MachineFunction};
452 let mut mf = MachineFunction::new("f".into());
453 let b = mf.add_block("entry");
454 let v0 = mf.fresh_vreg();
455 let v1 = mf.fresh_vreg();
456 mf.push(b, MInstr::new(MOpcode(0)).with_dst(v0).with_vreg(v1));
457 mf.push(b, MInstr::new(MOpcode(1)).with_vreg(v0));
458
459 let mut result = RegAllocResult::default();
460 result.vreg_to_preg.insert(v0, PReg(3));
461 result.vreg_to_preg.insert(v1, PReg(7));
462
463 apply_allocation(&mut mf, &result);
464
465 assert_eq!(mf.blocks[0].instrs[0].dst, Some(VReg(3))); assert_eq!(mf.blocks[0].instrs[0].operands[0], MOperand::PReg(PReg(7)));
468 assert_eq!(mf.blocks[0].instrs[1].operands[0], MOperand::PReg(PReg(3)));
470 }
471
472 #[test]
473 fn apply_allocation_rewrites_dst_register() {
474 use crate::isel::{MInstr, MOpcode, MachineFunction};
475 let mut mf = MachineFunction::new("f".into());
476 let b = mf.add_block("entry");
477 let v5 = VReg(5); mf.next_vreg = 6;
479 mf.push(b, MInstr::new(MOpcode(0)).with_dst(v5));
480
481 let mut result = RegAllocResult::default();
482 result.vreg_to_preg.insert(v5, PReg(2));
483
484 apply_allocation(&mut mf, &result);
485
486 assert_eq!(mf.blocks[0].instrs[0].dst, Some(VReg(2)));
488 }
489
490 #[test]
491 fn many_overlapping_intervals_all_assigned() {
492 let intervals = vec![
496 iv(0, 0, 10),
497 iv(1, 0, 8),
498 iv(2, 0, 6),
499 iv(3, 0, 4),
500 iv(4, 0, 2),
501 ];
502 let alloc: Vec<PReg> = (0u8..5).map(PReg).collect();
503 let result = linear_scan(&intervals, &alloc);
504 assert!(
505 result.spilled.is_empty(),
506 "no spills: 5 regs for 5 simultaneous live ranges"
507 );
508 assert_eq!(result.vreg_to_preg.len(), 5);
509 }
510
511 #[test]
512 fn compute_intervals_single_block() {
513 use crate::isel::{MInstr, MOpcode, MachineFunction};
514 let mut mf = MachineFunction::new("f".into());
515 let b = mf.add_block("entry");
516 let v0 = mf.fresh_vreg();
517 let v1 = mf.fresh_vreg();
518 mf.push(b, MInstr::new(MOpcode(0)).with_dst(v0));
520 mf.push(b, MInstr::new(MOpcode(1)).with_dst(v1).with_vreg(v0));
522
523 let intervals = compute_live_intervals(&mf);
524 let v0_iv = intervals.iter().find(|i| i.vreg == v0).unwrap();
525 let v1_iv = intervals.iter().find(|i| i.vreg == v1).unwrap();
526 assert_eq!(v0_iv.start, 0);
528 assert_eq!(v0_iv.end, 2);
529 assert_eq!(v1_iv.start, 1);
531 }
532
533 #[test]
534 fn insert_spill_reloads_inserts_load_store() {
535 use crate::isel::{MInstr, MOpcode, MachineFunction};
538 const LOAD_OP: MOpcode = MOpcode(0xA0);
539 const STORE_OP: MOpcode = MOpcode(0xA1);
540
541 let mut mf = MachineFunction::new("spill_test".into());
542 mf.allocatable_pregs = vec![PReg(0)];
543 let b = mf.add_block("entry");
544
545 let v0 = mf.fresh_vreg();
546 let v1 = mf.fresh_vreg();
547 mf.push(b, MInstr::new(MOpcode(1)).with_dst(v0));
549 mf.push(b, MInstr::new(MOpcode(2)).with_dst(v1).with_vreg(v0));
551 mf.push(b, MInstr::new(MOpcode(3)).with_vreg(v1));
553
554 let intervals = compute_live_intervals(&mf);
555 let mut result = linear_scan(&intervals, &mf.allocatable_pregs);
556
557 assert_eq!(
559 result.spilled.len(),
560 1,
561 "one VReg must spill with 1 reg, 2 simultaneously live"
562 );
563
564 insert_spill_reloads(&mut mf, &mut result, LOAD_OP, STORE_OP);
565
566 assert!(
568 result.spilled.is_empty(),
569 "spilled list must be empty after insert_spill_reloads"
570 );
571
572 assert_eq!(mf.spill_slots.len(), 1, "one spill slot must be allocated");
574 assert!(mf.frame_size > 0, "frame_size must be non-zero after spill");
575
576 assert!(
578 mf.blocks[0].instrs.len() > 3,
579 "instructions must be inserted for load/store around the spilled VReg"
580 );
581
582 for instr in &mf.blocks[0].instrs {
584 if instr.opcode == LOAD_OP {
585 assert!(instr.dst.is_some(), "LOAD_OP must have a dst VReg");
586 assert!(
587 matches!(instr.operands.first(), Some(MOperand::Imm(_))),
588 "LOAD_OP first operand must be Imm(slot)"
589 );
590 } else if instr.opcode == STORE_OP {
591 assert!(instr.dst.is_none(), "STORE_OP must have no dst");
592 assert!(
593 matches!(instr.operands.first(), Some(MOperand::Imm(_))),
594 "STORE_OP first operand must be Imm(slot)"
595 );
596 }
597 }
598 }
599
600 #[test]
601 fn apply_allocation_populates_used_callee_saved() {
602 use crate::isel::{MInstr, MOpcode, MachineFunction};
603
604 let mut mf = MachineFunction::new("cs_test".into());
605 mf.allocatable_pregs = vec![PReg(0), PReg(3)]; mf.callee_saved_pregs = vec![PReg(3)]; let b = mf.add_block("entry");
608 let v0 = mf.fresh_vreg();
609 let v1 = mf.fresh_vreg();
610 mf.push(b, MInstr::new(MOpcode(0)).with_dst(v0));
611 mf.push(b, MInstr::new(MOpcode(1)).with_dst(v1).with_vreg(v0));
612
613 let intervals = compute_live_intervals(&mf);
614 let result = linear_scan(&intervals, &mf.allocatable_pregs);
615 assert_eq!(result.spilled.len(), 0);
617
618 apply_allocation(&mut mf, &result);
619
620 let used_pregs: std::collections::HashSet<PReg> =
622 result.vreg_to_preg.values().copied().collect();
623 if used_pregs.contains(&PReg(3)) {
624 assert!(
625 mf.used_callee_saved.contains(&PReg(3)),
626 "used callee-saved PReg must appear in mf.used_callee_saved"
627 );
628 } else {
629 assert!(
630 !mf.used_callee_saved.contains(&PReg(3)),
631 "unused callee-saved PReg must not appear in mf.used_callee_saved"
632 );
633 }
634 }
635}