Skip to main content

lamina_codegen/aarch64/
regalloc.rs

1use std::collections::{HashMap, HashSet, VecDeque};
2
3use crate::regalloc::{Allocation, LocalRegisterAllocator as MirRegisterAllocator};
4use lamina_mir::{Register, RegisterClass, VirtualReg};
5
6// AArch64 register allocator for MIR virtual registers.
7
8/// AArch64 register allocator for MIR virtual registers.
9///
10/// Tracks a fixed pool of available caller-saved GPRs and gives
11/// stable mappings from virtual registers to physical registers.
12/// Returns None when no register is available (caller should spill to stack).
13pub struct A64RegAlloc {
14    free_gprs: VecDeque<&'static str>,
15    used_gprs: HashSet<&'static str>,
16    vreg_to_preg: HashMap<VirtualReg, &'static str>,
17    scratch_free: VecDeque<&'static str>,
18    scratch_used: HashSet<&'static str>,
19}
20
21// Only use caller-saved registers (x9-x15) until we implement callee-saved save/restore
22// x19-x28 are callee-saved and must be saved/restored in prologue/epilogue if used
23const MAP_GPRS: &[&str] = &["x13", "x14", "x15"];
24
25const SCRATCH_GPRS: &[&str] = &["x9", "x10", "x11", "x12"];
26
27impl Default for A64RegAlloc {
28    fn default() -> Self {
29        Self::new()
30    }
31}
32
33impl A64RegAlloc {
34    /// Creates a new register allocator with default register pools.
35    pub fn new() -> Self {
36        let mut free_gprs = VecDeque::new();
37        for &r in MAP_GPRS {
38            free_gprs.push_back(r);
39        }
40
41        let mut scratch_free = VecDeque::new();
42        for &r in SCRATCH_GPRS {
43            scratch_free.push_back(r);
44        }
45
46        Self {
47            free_gprs,
48            used_gprs: HashSet::new(),
49            vreg_to_preg: HashMap::new(),
50            scratch_free,
51            scratch_used: HashSet::new(),
52        }
53    }
54
55    /// Sets conservative mode for complex functions, using fewer registers to reduce pressure.
56    pub fn set_conservative_mode(&mut self) {
57        self.free_gprs.clear();
58        self.free_gprs.push_back("x13");
59        self.free_gprs.push_back("x14");
60        self.free_gprs.push_back("x15");
61        // Removed x19-x23 - these are callee-saved and need prologue/epilogue save/restore
62        self.used_gprs.retain(|r| self.free_gprs.contains(r));
63        let free_set: HashSet<&str> = self.free_gprs.iter().copied().collect();
64        self.vreg_to_preg.retain(|_, preg| free_set.contains(preg));
65    }
66
67    #[inline]
68    pub fn alloc_scratch(&mut self) -> Option<&'static str> {
69        MirRegisterAllocator::alloc_scratch(self)
70    }
71
72    #[inline]
73    pub fn free_scratch(&mut self, phys: &'static str) {
74        MirRegisterAllocator::free_scratch(self, phys);
75    }
76
77    #[inline]
78    pub fn is_occupied(&self, phys: &'static str) -> bool {
79        MirRegisterAllocator::is_occupied(self, phys)
80    }
81
82    #[inline]
83    pub fn occupy(&mut self, phys: &'static str) {
84        MirRegisterAllocator::occupy(self, phys);
85    }
86
87    #[inline]
88    pub fn release(&mut self, phys: &'static str) {
89        MirRegisterAllocator::release(self, phys);
90    }
91
92    #[inline]
93    pub fn get_mapping_for(&self, v: &VirtualReg) -> Option<&'static str> {
94        MirRegisterAllocator::get_mapping(self, v)
95    }
96
97    #[inline]
98    pub fn ensure_mapping(&mut self, v: VirtualReg) -> Option<&'static str> {
99        MirRegisterAllocator::ensure_mapping(self, v)
100    }
101
102    #[inline]
103    pub fn ensure_mapping_for_gpr(&mut self, v: VirtualReg) -> Option<&'static str> {
104        MirRegisterAllocator::ensure_mapping(self, v)
105    }
106
107    #[inline]
108    pub fn mapped_for_register(&self, r: &Register) -> Option<&'static str> {
109        MirRegisterAllocator::mapped_for_register(self, r)
110    }
111
112    pub fn gpr_pool_for_global_allocation() -> Vec<&'static str> {
113        MAP_GPRS.to_vec()
114    }
115
116    pub fn from_global_plan(plan: &HashMap<VirtualReg, Allocation<&'static str>>) -> Self {
117        let mut s = Self::new();
118        for (&vreg, alloc) in plan {
119            if vreg.class != RegisterClass::Gpr {
120                continue;
121            }
122            if let Allocation::Register(phys) = alloc
123                && MAP_GPRS.contains(phys)
124                && s.vreg_to_preg.insert(vreg, *phys).is_none()
125            {
126                s.used_gprs.insert(*phys);
127                if let Some(pos) = s.free_gprs.iter().position(|&p| p == *phys) {
128                    let _ = s.free_gprs.remove(pos);
129                }
130            }
131        }
132        s
133    }
134}
135
136impl MirRegisterAllocator for A64RegAlloc {
137    type PhysReg = &'static str;
138
139    fn alloc_scratch(&mut self) -> Option<Self::PhysReg> {
140        if let Some(phys) = self.scratch_free.pop_front() {
141            self.scratch_used.insert(phys);
142            Some(phys)
143        } else {
144            None
145        }
146    }
147
148    fn free_scratch(&mut self, phys: Self::PhysReg) {
149        if self.scratch_used.remove(&phys) {
150            self.scratch_free.push_back(phys);
151        }
152    }
153
154    fn get_mapping(&self, vreg: &VirtualReg) -> Option<Self::PhysReg> {
155        self.vreg_to_preg.get(vreg).copied()
156    }
157
158    fn ensure_mapping(&mut self, vreg: VirtualReg) -> Option<Self::PhysReg> {
159        if let Some(&phys) = self.vreg_to_preg.get(&vreg) {
160            return Some(phys);
161        }
162
163        if vreg.class != RegisterClass::Gpr {
164            return None;
165        }
166
167        if let Some(phys) = self.free_gprs.pop_front() {
168            self.used_gprs.insert(phys);
169            self.vreg_to_preg.insert(vreg, phys);
170            Some(phys)
171        } else if let Some((vreg_to_replace, &phys)) = self.vreg_to_preg.iter().next() {
172            let vreg_to_replace = *vreg_to_replace;
173            self.vreg_to_preg.remove(&vreg_to_replace);
174            self.vreg_to_preg.insert(vreg, phys);
175            Some(phys)
176        } else {
177            None
178        }
179    }
180
181    fn mapped_for_register(&self, reg: &Register) -> Option<Self::PhysReg> {
182        match reg {
183            Register::Virtual(v) => self.vreg_to_preg.get(v).copied(),
184            Register::Physical(p) => Some(p.name),
185        }
186    }
187
188    fn occupy(&mut self, phys: Self::PhysReg) {
189        if self.used_gprs.insert(phys)
190            && let Some(pos) = self.free_gprs.iter().position(|&p| p == phys)
191        {
192            self.free_gprs.remove(pos);
193        }
194    }
195
196    fn release(&mut self, phys: Self::PhysReg) {
197        if self.used_gprs.remove(&phys) {
198            self.free_gprs.push_back(phys);
199        }
200    }
201
202    fn is_occupied(&self, phys: Self::PhysReg) -> bool {
203        self.used_gprs.contains(&phys)
204    }
205}