1#![doc(html_root_url = "https://docs.rs/rucc-regalloc/0.24.5")]
30
31pub mod assign;
32pub mod backtrack;
33pub mod check;
34pub mod legalize;
35pub mod live;
36pub mod moves;
37pub mod order;
38pub mod pressure;
39pub mod rewrite;
40pub mod spill;
41pub mod trace;
42
43#[derive(Debug, Clone)]
50pub struct Allocation {
51 pub assignment: assign::Assignment,
53 pub edits: Vec<rewrite::Edit>,
55 pub order: order::Order,
58 pub live: live::Live,
66}
67
68pub fn run(func: &mut rucc_mir::Func, env: &assign::Env, called: &str, verify: bool) -> Allocation {
102 run_with(func, env, called, verify, Allocator::Single)
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
110pub enum Allocator {
111 #[default]
113 Single,
114 Backtracking,
116}
117
118pub fn run_with(
124 func: &mut rucc_mir::Func,
125 env: &assign::Env,
126 called: &str,
127 verify: bool,
128 allocator: Allocator,
129) -> Allocation {
130 let order = order::Order::of(func);
131 let live = live::Live::of(func, &order);
132 let assignment = decide(func, &order, &live, env, allocator);
133 write(func, assignment, env, order, live, called, verify)
134}
135
136pub fn run_either(
155 func: &mut rucc_mir::Func,
156 wide: &assign::Env,
157 env: &assign::Env,
158 called: &str,
159 verify: bool,
160 allocator: Allocator,
161) -> Allocation {
162 let order = order::Order::of(func);
163 let live = live::Live::of(func, &order);
164 let tried = decide(func, &order, &live, wide, allocator);
165 if rewrite::fits(func, &tried, wide) {
166 return write(func, tried, wide, order, live, called, verify);
167 }
168 let assignment = decide(func, &order, &live, env, allocator);
169 write(func, assignment, env, order, live, called, verify)
170}
171
172fn decide(
174 func: &rucc_mir::Func,
175 order: &order::Order,
176 live: &live::Live,
177 env: &assign::Env,
178 allocator: Allocator,
179) -> assign::Assignment {
180 match allocator {
181 Allocator::Single => assign::assign(func, order, live, env),
182 Allocator::Backtracking => backtrack::assign(func, order, live, env),
183 }
184}
185
186fn write(
189 func: &mut rucc_mir::Func,
190 mut assignment: assign::Assignment,
191 env: &assign::Env,
192 order: order::Order,
193 live: live::Live,
194 called: &str,
195 verify: bool,
196) -> Allocation {
197 let checking = verify || cfg!(debug_assertions);
198 for &inst in assignment.commuted() {
202 let list = func[inst].operands;
203 func[list].swap(1, 2);
204 }
205 if checking {
206 let problems = check::check(func, &order, &live, &assignment);
207 assert!(problems.is_empty(), "in '{called}': {}", check::report(&problems));
208 }
209 let shape = checking.then(|| trace::shape(func));
212 let edits = rewrite::rewrite(func, &mut assignment, env);
213 if let Some(shape) = shape {
214 let faults = trace::trace(func, &shape, &assignment, &edits);
215 assert!(faults.is_empty(), "in '{called}': {}", trace::report(&faults));
216 }
217 Allocation { assignment, edits, order, live }
218}
219
220pub const MILESTONE: &str = "M3";
222
223#[cfg(test)]
224mod tests {
225 use rucc_base::Interner;
226 use rucc_mir::{BlockCall, Func, Opcode, Operand, Reg, Weight};
227 use rucc_target::x86_64::{GPR, RAX, RCX, RDX, SYSV};
228
229 use super::*;
230
231 #[test]
232 fn milestone_is_recorded() {
233 assert!(MILESTONE.starts_with('M'));
234 }
235
236 #[test]
237 fn allocating_a_function_places_every_value_and_hands_back_the_moves_it_needs() {
238 let mut names = Interner::new();
239 let mut func = Func::new(names.intern("f"));
240 let opcode = Opcode::new(names.intern("x64.nop"));
241 let block = func.create_block();
242 let first = func.new_vreg(GPR);
243 let second = func.new_vreg(GPR);
244 let third = func.new_vreg(GPR);
245 func.build(block, opcode).def(first, GPR).finish();
246 func.build(block, opcode).def(second, GPR).finish();
247 func.build(block, opcode).def(third, GPR).finish();
248 func.build(block, opcode).uses(first, GPR).uses(second, GPR).uses(third, GPR).finish();
249
250 let env = assign::Env::new().with(GPR, &SYSV.int_order[..2], &SYSV.int_order[2..5]);
254 let allocation = run(&mut func, &env, "test", true);
255
256 assert_eq!(allocation.assignment.spilled(), 1);
257 assert_eq!(allocation.edits.len(), 2);
258 }
259
260 fn three_at_once(names: &mut Interner) -> (Func, [Reg; 3]) {
262 let mut func = Func::new(names.intern("f"));
263 let opcode = Opcode::new(names.intern("x64.nop"));
264 let block = func.create_block();
265 let values = [(); 3].map(|()| func.new_vreg(GPR));
266 for value in values {
267 func.build(block, opcode).def(value, GPR).finish();
268 }
269 func.build(block, opcode)
270 .uses(values[0], GPR)
271 .uses(values[1], GPR)
272 .uses(values[2], GPR)
273 .finish();
274 (func, values)
275 }
276
277 #[test]
278 fn a_function_that_needs_no_scratch_register_is_given_the_scratch_registers() {
279 let mut names = Interner::new();
280 let (mut func, values) = three_at_once(&mut names);
281
282 let wide = assign::Env::new().with(GPR, &SYSV.int_order[..3], &[]);
285 let env = assign::Env::new().with(GPR, &SYSV.int_order[..2], &SYSV.int_order[2..4]);
286 let allocation = run_either(&mut func, &wide, &env, "test", true, Allocator::Single);
287
288 assert_eq!(allocation.assignment.spilled(), 0);
289 assert!(allocation.edits.is_empty());
290 let mut places: Vec<_> =
291 values.iter().filter_map(|&value| allocation.assignment.place(value)).collect();
292 places.sort_by_key(|place| match place {
293 assign::Place::Reg(reg) => reg.number(),
294 assign::Place::Slot(_) => u8::MAX,
295 });
296 let regs = [RAX, RCX, RDX].map(assign::Place::Reg);
297 assert_eq!(places, regs);
298 }
299
300 #[test]
301 fn a_function_that_spills_is_allocated_again_with_the_scratch_registers_held_back() {
302 let mut names = Interner::new();
303 let (mut func, _) = three_at_once(&mut names);
304
305 let wide = assign::Env::new().with(GPR, &SYSV.int_order[..2], &[]);
308 let env = assign::Env::new().with(GPR, &SYSV.int_order[..2], &SYSV.int_order[2..4]);
309 let allocation = run_either(&mut func, &wide, &env, "test", true, Allocator::Single);
310
311 assert_eq!(allocation.assignment.spilled(), 1);
312 assert_eq!(allocation.edits.len(), 2);
313 }
314
315 #[test]
316 fn two_values_swapping_on_an_edge_are_allocated_with_the_scratch_registers_held_back() {
317 let mut names = Interner::new();
318 let mut func = Func::new(names.intern("f"));
319 let opcode = Opcode::new(names.intern("x64.nop"));
320 let head = func.create_block();
321 let body = func.create_block();
322 let first = func.new_vreg(GPR);
323 let second = func.new_vreg(GPR);
324 func.build(head, opcode).def(first, GPR).finish();
325 func.build(head, opcode).def(second, GPR).finish();
326 let left = func.append_param(body, GPR);
327 let right = func.append_param(body, GPR);
328 *func.succs_mut(head) = vec![BlockCall::with(body, vec![first, second])];
329 func.build(body, opcode).uses(left, GPR).uses(right, GPR).finish();
330 *func.succs_mut(body) = vec![BlockCall::with(body, vec![right, left])];
331
332 let wide = assign::Env::new().with(GPR, &SYSV.int_order[..2], &[]);
335 let env = assign::Env::new().with(GPR, &SYSV.int_order[..2], &SYSV.int_order[2..4]);
336 let allocation = run_either(&mut func, &wide, &env, "test", true, Allocator::Single);
337
338 assert_eq!(allocation.assignment.spilled(), 0);
339 let through = assign::Place::Reg(RDX);
340 assert!(allocation.edits.iter().any(|edit| edit.mov.to == through), "{allocation:?}");
341 }
342
343 #[test]
344 fn a_value_carried_round_a_loop_is_allocated_with_the_scratch_registers_given_out() {
345 let mut names = Interner::new();
346 let mut func = Func::new(names.intern("f"));
347 let opcode = Opcode::new(names.intern("x64.nop"));
348 let head = func.create_block();
349 let body = func.create_block();
350 let first = func.new_vreg(GPR);
351 func.build(head, opcode).def(first, GPR).finish();
352 let carried = func.append_param(body, GPR);
353 *func.succs_mut(head) = vec![BlockCall::with(body, vec![first])];
354 let next = func.new_vreg(GPR);
355 func.build(body, opcode).def(next, GPR).uses(carried, GPR).finish();
356 *func.succs_mut(body) = vec![BlockCall::with(body, vec![next])];
357
358 let wide = assign::Env::new().with(GPR, &SYSV.int_order[..2], &[]);
361 let env = assign::Env::new().with(GPR, &SYSV.int_order[..1], &SYSV.int_order[1..3]);
362 let allocation = run_either(&mut func, &wide, &env, "test", true, Allocator::Single);
363
364 assert_eq!(allocation.assignment.spilled(), 0);
365 for value in [first, carried, next] {
366 let place = allocation.assignment.place(value);
367 assert!(matches!(place, Some(assign::Place::Reg(_))), "{allocation:?}");
368 }
369 }
370
371 #[test]
372 fn a_value_a_loop_reads_is_put_away_around_the_call_the_loop_seldom_makes() {
373 let mut names = Interner::new();
374 let mut func = Func::new(names.intern("f"));
375 let opcode = Opcode::new(names.intern("x64.nop"));
376 let [entry, head, cold, skip, latch, back, out] = [(); 7].map(|()| func.create_block());
377 let step = func.new_vreg(GPR);
378 func.build(entry, opcode).def(step, GPR).finish();
379 *func.succs_mut(entry) = vec![BlockCall::to(head)];
380 func.build(head, opcode).uses(step, GPR).finish();
381 *func.succs_mut(head) = vec![BlockCall::to(cold), BlockCall::to(skip)];
382 let call = func
384 .build(cold, opcode)
385 .operand(Operand::write(Reg::physical(RAX), GPR))
386 .operand(Operand::write(Reg::physical(RCX), GPR))
387 .finish();
388 *func.succs_mut(cold) = vec![BlockCall::to(latch)];
389 *func.succs_mut(skip) = vec![BlockCall::to(latch)];
390 func.build(latch, opcode).uses(step, GPR).finish();
391 *func.succs_mut(latch) = vec![BlockCall::to(back), BlockCall::to(out)];
392 *func.succs_mut(back) = vec![BlockCall::to(head)];
393 func.build(out, opcode).uses(step, GPR).finish();
394 for (block, often) in [(head, 100), (skip, 99), (latch, 100), (back, 99)] {
395 func.set_weight(block, Weight::parts(often * Weight::SCALE));
396 }
397
398 let env = assign::Env::new().with(GPR, &SYSV.int_order[..2], &SYSV.int_order[2..5]);
402 let allocation = run_with(&mut func, &env, "test", true, Allocator::Backtracking);
403
404 let assignment = &allocation.assignment;
405 assert_eq!(assignment.spilled(), 0);
406 let Some(assign::Place::Reg(at)) = assignment.place(step) else {
407 panic!("the value went to the stack");
408 };
409 let saves = assignment.saves();
410 assert_eq!(saves.len(), 1);
411 assert_eq!((saves[0].reg, saves[0].inst), (step, call));
412 let slot = assign::Place::Slot(saves[0].slot);
413 let here = |edit: &&rewrite::Edit| match edit.at {
414 rewrite::At::Before(inst) | rewrite::At::After(inst) => inst == call,
415 _ => false,
416 };
417 let around: Vec<_> = allocation
418 .edits
419 .iter()
420 .filter(here)
421 .map(|edit| (edit.at, edit.mov.to, edit.mov.from))
422 .collect();
423 assert_eq!(
424 around,
425 [
426 (rewrite::At::Before(call), slot, assign::Place::Reg(at)),
427 (rewrite::At::After(call), assign::Place::Reg(at), slot),
428 ]
429 );
430 }
431}