Skip to main content

rucc_regalloc/
rewrite.rs

1//! Making an assignment true in the function it was worked out for.
2//!
3//! Design: `spec/10-backend.md` section 10.4.
4//!
5//! [`crate::assign`] says where every value goes and touches nothing. This is the other half: every
6//! operand is rewritten to the place its value was given, and the moves that the places do not
7//! already say are collected. After it the function names no virtual register and no block asks
8//! for anything, which is the point at which machine IR stops being in SSA form and starts being
9//! something an encoder could read.
10//!
11//! # Why the moves are handed back rather than written
12//!
13//! A move is an instruction, and an instruction has an opcode, and an opcode belongs to a target.
14//! `spec/10-backend.md` section 10.8 says no pipeline crate holds target specific code, so this
15//! crate is not the one that can write `x64.mov`. What it hands back is an [`Edit`]: a move
16//! between two places, the class it is in, and where in the function it goes. `rucc-codegen` turns
17//! each one into whatever its target moves a register with, which for a value on the stack is a
18//! load or a store rather than a move at all.
19//!
20//! The edits at any one place are in the order they have to be made in. That matters in two
21//! places: a spilled operand is read into a scratch register before the instruction that wants it,
22//! and a two address instruction's copy has to come after that read, because what it is copying
23//! may be the thing that was just read in.
24//!
25//! # What a fixed register turns into
26//!
27//! A move each way. The assignment deliberately gave the value some other register, so a division
28//! whose dividend has to be in `rax` gets a move into `rax` in front of it and a move out of `rax`
29//! behind it. That is the cost of the rule the assignment follows, and it is the rule that keeps
30//! the `-O0` allocator one pass.
31//!
32//! # What an edge turns into
33//!
34//! The moves that write the block's parameters, in an order they can be made in one at a time,
35//! which is what [`crate::moves`] is for. Where they go depends on the shape of the edge. A block
36//! with one successor puts them at its own end, in front of the branch it finishes with, and a
37//! block with several puts them at the start of the block the edge goes to, which is safe exactly
38//! because that block has no other predecessor. An edge that is critical has neither place to put
39//! them and has to have been split before allocation ran, which this checks rather than assumes.
40
41use rucc_mir::{Block, Constraint, Func, Inst, Operand, Param, Reg};
42use rucc_target::{PhysReg, RegClass};
43
44use crate::assign::{Assignment, Env, Place};
45use crate::moves::{self, Move};
46
47/// One move the places did not already make true.
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub struct Edit {
50    /// Where in the function it goes.
51    pub at: At,
52    /// What it moves, and where to.
53    pub mov: Move<Place>,
54    /// The class both places are in, which is what says how wide the move is.
55    pub class: RegClass,
56}
57
58/// Where an edit goes.
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum At {
61    /// In front of an instruction, which is where a value it reads is put where it wants it.
62    Before(Inst),
63    /// Behind an instruction, which is where a value it wrote somewhere it insisted on is taken
64    /// away to where it lives.
65    After(Inst),
66    /// At the start of a block, in front of everything in it.
67    StartOf(Block),
68    /// At the end of a block, in front of the branch it finishes with.
69    EndOf(Block),
70}
71
72/// Rewrites a function to the places it was given, and says what moves are still wanted.
73///
74/// # Panics
75///
76/// Panics if the entry block has parameters, since there is no edge into it for their moves to go
77/// on and what arrives in a function is the ABI lowering's to say. Panics on a critical edge, on
78/// an edge carrying the wrong number of arguments, and if a class runs out of scratch registers
79/// for one instruction, all of which are the caller handing it something it was told not to.
80#[must_use]
81pub fn rewrite(func: &mut Func, assignment: &Assignment, env: &Env) -> Vec<Edit> {
82    let blocks: Vec<Block> = func.blocks().collect();
83    assert!(
84        func.entry().is_none_or(|entry| func[entry].params.is_empty()),
85        "what arrives in a function is not a block parameter"
86    );
87
88    let mut edits = Vec::new();
89    for &block in &blocks {
90        let insts: Vec<Inst> = func.insts(block).collect();
91        for inst in insts {
92            instruction(func, assignment, env, inst, &mut edits);
93        }
94    }
95
96    let preds = preds(func, &blocks);
97    for &block in &blocks {
98        edges(func, assignment, env, block, &preds, &mut edits);
99    }
100    for &block in &blocks {
101        func.params_mut(block).clear();
102        for call in func.succs_mut(block) {
103            call.args.clear();
104        }
105    }
106    edits
107}
108
109/// Rewrites one instruction's operands, and says what has to happen either side of it.
110fn instruction(
111    func: &mut Func,
112    assignment: &Assignment,
113    env: &Env,
114    inst: Inst,
115    edits: &mut Vec<Edit>,
116) {
117    let list = func[inst].operands;
118    let mut operands: Vec<Operand> = func[list].to_vec();
119    let mut before: Vec<(Move<Place>, RegClass)> = Vec::new();
120    let mut after: Vec<(Move<Place>, RegClass)> = Vec::new();
121    let mut taken = 0;
122
123    for operand in &mut operands {
124        let fixed = match operand.constraint {
125            Constraint::Fixed(at) => Some(at),
126            _ => None,
127        };
128        let at = match (place(assignment, operand.reg), fixed) {
129            (Place::Reg(at), None) => at,
130            (Place::Reg(at), Some(fixed)) => {
131                if at != fixed {
132                    let (there, here) = (Place::Reg(fixed), Place::Reg(at));
133                    push(&mut before, &mut after, operand, Move::new(there, here));
134                }
135                fixed
136            }
137            (Place::Slot(slot), fixed) => {
138                let at = fixed.unwrap_or_else(|| {
139                    let scratch = *env
140                        .scratch(operand.class)
141                        .get(taken)
142                        .expect("an instruction wanting more scratch registers than the class has");
143                    taken += 1;
144                    scratch
145                });
146                push(
147                    &mut before,
148                    &mut after,
149                    operand,
150                    Move::new(Place::Reg(at), Place::Slot(slot)),
151                );
152                at
153            }
154        };
155        operand.reg = Reg::physical(at);
156    }
157
158    // A two address instruction writes one of the registers it reads, and the copy that makes that
159    // true goes after everything else in front of the instruction, since what it reads may be a
160    // value that was itself only just read in from the stack.
161    for index in 0..operands.len() {
162        let Constraint::Reuse(other) = operands[index].constraint else { continue };
163        let (to, from) = (operands[index], operands[usize::from(other)]);
164        if to.reg != from.reg {
165            let mov = Move::new(Place::Reg(phys(to.reg)), Place::Reg(phys(from.reg)));
166            before.push((mov, to.class));
167        }
168    }
169
170    func[list].copy_from_slice(&operands);
171    edits.extend(before.into_iter().map(|(mov, class)| Edit { at: At::Before(inst), mov, class }));
172    edits.extend(after.into_iter().map(|(mov, class)| Edit { at: At::After(inst), mov, class }));
173}
174
175/// Files a move in front of the instruction or behind it, and turns it round for a value the
176/// instruction writes, since that one travels the other way.
177fn push(
178    before: &mut Vec<(Move<Place>, RegClass)>,
179    after: &mut Vec<(Move<Place>, RegClass)>,
180    operand: &Operand,
181    mov: Move<Place>,
182) {
183    if operand.role.is_def() {
184        after.push((Move::new(mov.from, mov.to), operand.class));
185    } else {
186        before.push((mov, operand.class));
187    }
188}
189
190/// The moves the edges out of a block turn into.
191fn edges(
192    func: &mut Func,
193    assignment: &Assignment,
194    env: &Env,
195    block: Block,
196    preds: &[usize],
197    edits: &mut Vec<Edit>,
198) {
199    let succs = func[block].succs.clone();
200    let single = succs.len() == 1;
201    for call in &succs {
202        let params = func[call.block].params.clone();
203        assert_eq!(
204            params.len(),
205            call.args.len(),
206            "an edge carries what the block it goes to asks for"
207        );
208        if params.is_empty() {
209            continue;
210        }
211        assert!(
212            single || preds[call.block.index()] == 1,
213            "a critical edge has nowhere to put its moves and has to be split before allocation"
214        );
215        let at = if single { At::EndOf(block) } else { At::StartOf(call.block) };
216        edits.extend(edge(assignment, env, &params, &call.args, at));
217    }
218}
219
220/// The moves one edge turns into, in the order they can be made in.
221fn edge(assignment: &Assignment, env: &Env, params: &[Param], args: &[Reg], at: At) -> Vec<Edit> {
222    let mut classes: Vec<RegClass> = params.iter().map(|param| param.class).collect();
223    classes.sort_unstable();
224    classes.dedup();
225
226    let mut edits = Vec::new();
227    for class in classes {
228        // One class at a time, because a scratch register is per class and a value never crosses
229        // from one to another on an edge.
230        let parallel: Vec<Move<Place>> = params
231            .iter()
232            .zip(args)
233            .filter(|(param, _)| param.class == class)
234            .map(|(param, &arg)| Move::new(place(assignment, param.reg), place(assignment, arg)))
235            .collect();
236        let scratch = *env
237            .scratch(class)
238            .first()
239            .expect("a class whose values are passed on an edge and which has no scratch register");
240        edits.extend(moves::sequence(&parallel, Place::Reg(scratch)).into_iter().map(|mov| Edit {
241            at,
242            mov,
243            class,
244        }));
245    }
246    edits
247}
248
249/// How many edges arrive in each block.
250fn preds(func: &Func, blocks: &[Block]) -> Vec<usize> {
251    let mut preds = vec![0; func.block_count()];
252    for &block in blocks {
253        for call in &func[block].succs {
254            preds[call.block.index()] += 1;
255        }
256    }
257    preds
258}
259
260/// Where a register is, whether the allocator put it there or it was already somewhere.
261fn place(assignment: &Assignment, reg: Reg) -> Place {
262    assignment.place(reg).unwrap_or_else(|| Place::Reg(phys(reg)))
263}
264
265/// The physical register a register is, once it has to be one.
266fn phys(reg: Reg) -> PhysReg {
267    reg.phys().expect("a register the assignment says nothing about and that is not a register")
268}
269
270#[cfg(test)]
271mod tests {
272    use rucc_base::Interner;
273    use rucc_mir::{BlockCall, Opcode};
274    use rucc_target::x86_64::{GPR, RAX, RDX, REGS, SYSV};
275
276    use super::*;
277    use crate::assign::assign;
278    use crate::live::Live;
279    use crate::order::Order;
280
281    /// The x86-64 environment, with the last three of the allocation order held back as scratch.
282    fn env() -> Env {
283        let (order, scratch) = SYSV.int_order.split_at(SYSV.int_order.len() - 3);
284        Env::new().with(GPR, order, scratch)
285    }
286
287    /// An environment with that many general purpose registers and one scratch after them.
288    fn narrow(count: usize) -> Env {
289        Env::new().with(GPR, &SYSV.int_order[..count], &SYSV.int_order[count..count + 2])
290    }
291
292    /// What a place is called, which is what an assertion reads.
293    fn named(place: Place) -> String {
294        match place {
295            Place::Reg(reg) => REGS.name(GPR, reg).expect("a register").to_string(),
296            Place::Slot(slot) => format!("slot{slot}"),
297        }
298    }
299
300    /// Runs both halves and reports the edits as lines an assertion can read.
301    fn run(func: &mut Func, env: &Env) -> Vec<String> {
302        let order = Order::of(func);
303        let live = Live::of(func, &order);
304        let assignment = assign(func, &order, &live, env);
305        rewrite(func, &assignment, env)
306            .into_iter()
307            .map(|edit| {
308                let at = match edit.at {
309                    At::Before(inst) => format!("before {}", inst.index()),
310                    At::After(inst) => format!("after {}", inst.index()),
311                    At::StartOf(block) => format!("start of {}", block.index()),
312                    At::EndOf(block) => format!("end of {}", block.index()),
313                };
314                format!("{at}: {} = {}", named(edit.mov.to), named(edit.mov.from))
315            })
316            .collect()
317    }
318
319    /// The registers an instruction's operands ended up naming.
320    fn operands(func: &Func, inst: Inst) -> Vec<String> {
321        func[func[inst].operands]
322            .iter()
323            .map(|operand| named(Place::Reg(phys(operand.reg))))
324            .collect()
325    }
326
327    #[test]
328    fn every_operand_ends_up_naming_the_register_its_value_was_given() {
329        let mut names = Interner::new();
330        let mut func = Func::new(names.intern("f"));
331        let opcode = Opcode::new(names.intern("x64.nop"));
332        let block = func.create_block();
333        let first = func.new_vreg(GPR);
334        let second = func.new_vreg(GPR);
335        func.build(block, opcode).def(first, GPR).finish();
336        func.build(block, opcode).def(second, GPR).finish();
337        let read = func.build(block, opcode).uses(first, GPR).uses(second, GPR).finish();
338
339        assert_eq!(run(&mut func, &env()), Vec::<String>::new());
340        assert_eq!(operands(&func, read), ["rax", "rcx"]);
341    }
342
343    #[test]
344    fn a_register_an_instruction_insists_on_is_moved_into_and_out_of() {
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 block = func.create_block();
349        let dividend = func.new_vreg(GPR);
350        let quotient = func.new_vreg(GPR);
351        func.build(block, opcode).def(dividend, GPR).finish();
352        let divide = func
353            .build(block, opcode)
354            .operand(Operand::write(quotient, GPR).with(Constraint::Fixed(RAX)))
355            .operand(Operand::read(dividend, GPR).with(Constraint::Fixed(RAX)))
356            .finish();
357        func.build(block, opcode).uses(quotient, GPR).finish();
358
359        // The value goes into `rax` in front of the division and the answer comes out of it
360        // behind, which is the move the assignment chose to pay for rather than tie a value to a
361        // register for the whole of its life.
362        assert_eq!(run(&mut func, &env()), ["before 1: rax = rcx", "after 1: rcx = rax"]);
363        assert_eq!(operands(&func, divide), ["rax", "rax"]);
364    }
365
366    #[test]
367    fn a_two_address_instruction_that_did_not_get_its_register_copies_first() {
368        let mut names = Interner::new();
369        let mut func = Func::new(names.intern("f"));
370        let opcode = Opcode::new(names.intern("x64.nop"));
371        let block = func.create_block();
372        let left = func.new_vreg(GPR);
373        let right = func.new_vreg(GPR);
374        let sum = func.new_vreg(GPR);
375        func.build(block, opcode).def(left, GPR).finish();
376        func.build(block, opcode).def(right, GPR).finish();
377        let add = func
378            .build(block, opcode)
379            .operand(Operand::write(sum, GPR).with(Constraint::Reuse(1)))
380            .uses(left, GPR)
381            .uses(right, GPR)
382            .finish();
383        func.build(block, opcode).uses(left, GPR).finish();
384
385        // The left value is wanted afterwards, so the answer could not have its register and the
386        // copy in front of the addition is what makes the instruction two address.
387        assert_eq!(run(&mut func, &env()), ["before 2: rdx = rax"]);
388        assert_eq!(operands(&func, add), ["rdx", "rax", "rcx"]);
389    }
390
391    #[test]
392    fn a_two_address_instruction_that_did_get_its_register_copies_nothing() {
393        let mut names = Interner::new();
394        let mut func = Func::new(names.intern("f"));
395        let opcode = Opcode::new(names.intern("x64.nop"));
396        let block = func.create_block();
397        let left = func.new_vreg(GPR);
398        let right = func.new_vreg(GPR);
399        let sum = func.new_vreg(GPR);
400        func.build(block, opcode).def(left, GPR).finish();
401        func.build(block, opcode).def(right, GPR).finish();
402        let add = func
403            .build(block, opcode)
404            .operand(Operand::write(sum, GPR).with(Constraint::Reuse(1)))
405            .uses(left, GPR)
406            .uses(right, GPR)
407            .finish();
408        func.build(block, opcode).uses(right, GPR).finish();
409
410        assert_eq!(run(&mut func, &env()), Vec::<String>::new());
411        assert_eq!(operands(&func, add), ["rax", "rax", "rcx"]);
412    }
413
414    #[test]
415    fn a_spilled_value_is_read_into_a_scratch_register_at_each_instruction_that_wants_it() {
416        let mut names = Interner::new();
417        let mut func = Func::new(names.intern("f"));
418        let opcode = Opcode::new(names.intern("x64.nop"));
419        let block = func.create_block();
420        let first = func.new_vreg(GPR);
421        let second = func.new_vreg(GPR);
422        func.build(block, opcode).def(first, GPR).finish();
423        func.build(block, opcode).def(second, GPR).finish();
424        let read = func.build(block, opcode).uses(first, GPR).uses(second, GPR).finish();
425
426        // One register between two values, so one of them goes to the stack. It is written there
427        // where it is computed and read back where it is wanted, and both ends of that go through
428        // the scratch register that is held out of the allocation order for exactly this.
429        assert_eq!(run(&mut func, &narrow(1)), ["after 1: slot0 = rcx", "before 2: rcx = slot0"]);
430        assert_eq!(operands(&func, read), ["rax", "rcx"]);
431    }
432
433    #[test]
434    fn an_edge_out_of_a_block_with_one_way_to_go_moves_at_the_end_of_it() {
435        let mut names = Interner::new();
436        let mut func = Func::new(names.intern("f"));
437        let opcode = Opcode::new(names.intern("x64.nop"));
438        let head = func.create_block();
439        let tail = func.create_block();
440        let held = func.new_vreg(GPR);
441        let carried = func.new_vreg(GPR);
442        func.build(head, opcode).def(held, GPR).finish();
443        func.build(head, opcode).def(carried, GPR).finish();
444        func.build(head, opcode).uses(held, GPR).finish();
445        let param = func.append_param(tail, GPR);
446        *func.succs_mut(head) = vec![BlockCall::with(tail, vec![carried])];
447        let read = func.build(tail, opcode).uses(param, GPR).finish();
448
449        // The value the edge carries is in the second register, because the first was busy where
450        // the value was written, and the parameter it arrives as is in the first, because by then
451        // it is not. So the edge is a move, and it goes at the end of the block it leaves.
452        assert_eq!(run(&mut func, &env()), ["end of 0: rax = rcx"]);
453        assert_eq!(operands(&func, read), ["rax"]);
454        // Nothing arrives in a block any more and no edge carries anything, which is where SSA
455        // form stops.
456        assert!(func[tail].params.is_empty());
457        assert!(func[head].succs[0].args.is_empty());
458    }
459
460    #[test]
461    fn an_edge_out_of_a_block_with_a_choice_moves_at_the_start_of_where_it_goes() {
462        let mut names = Interner::new();
463        let mut func = Func::new(names.intern("f"));
464        let opcode = Opcode::new(names.intern("x64.nop"));
465        let head = func.create_block();
466        let left = func.create_block();
467        let right = func.create_block();
468        let held = func.new_vreg(GPR);
469        let carried = func.new_vreg(GPR);
470        func.build(head, opcode).def(held, GPR).finish();
471        func.build(head, opcode).def(carried, GPR).finish();
472        func.build(head, opcode).uses(held, GPR).finish();
473        let taken = func.append_param(left, GPR);
474        *func.succs_mut(head) = vec![BlockCall::with(left, vec![carried]), BlockCall::to(right)];
475        func.build(left, opcode).uses(taken, GPR).finish();
476
477        // The move cannot go at the end of the block it leaves, because the other way out of that
478        // block does not want it. It goes at the start of the block it arrives in, which is safe
479        // because nothing else arrives there.
480        assert_eq!(run(&mut func, &env()), ["start of 1: rax = rcx"]);
481    }
482
483    #[test]
484    fn two_values_that_swap_on_an_edge_get_an_order_and_a_scratch_register() {
485        let mut names = Interner::new();
486        let mut func = Func::new(names.intern("f"));
487        let opcode = Opcode::new(names.intern("x64.nop"));
488        let head = func.create_block();
489        let body = func.create_block();
490        let first = func.new_vreg(GPR);
491        let second = func.new_vreg(GPR);
492        func.build(head, opcode).def(first, GPR).finish();
493        func.build(head, opcode).def(second, GPR).finish();
494        let left = func.append_param(body, GPR);
495        let right = func.append_param(body, GPR);
496        *func.succs_mut(head) = vec![BlockCall::with(body, vec![first, second])];
497        func.build(body, opcode).uses(left, GPR).uses(right, GPR).finish();
498        *func.succs_mut(body) = vec![BlockCall::with(body, vec![right, left])];
499
500        // The loop hands each value back the other way round, which is the case no order of two
501        // moves answers, so one of them goes through the scratch register. The edge into the loop
502        // moves nothing, because each value is already where the parameter it feeds lives.
503        assert_eq!(
504            run(&mut func, &env()),
505            ["end of 1: r13 = rcx", "end of 1: rcx = rax", "end of 1: rax = r13"]
506        );
507    }
508
509    #[test]
510    #[should_panic(expected = "a critical edge has nowhere to put its moves")]
511    fn a_critical_edge_is_refused() {
512        let mut names = Interner::new();
513        let mut func = Func::new(names.intern("f"));
514        let opcode = Opcode::new(names.intern("x64.nop"));
515        let head = func.create_block();
516        let other = func.create_block();
517        let join = func.create_block();
518        let value = func.new_vreg(GPR);
519        func.build(head, opcode).def(value, GPR).finish();
520        let param = func.append_param(join, GPR);
521        *func.succs_mut(head) = vec![BlockCall::with(join, vec![value]), BlockCall::to(other)];
522        *func.succs_mut(other) = vec![BlockCall::with(join, vec![value])];
523        func.build(join, opcode).uses(param, GPR).finish();
524
525        let _ = run(&mut func, &env());
526    }
527
528    #[test]
529    #[should_panic(expected = "what arrives in a function is not a block parameter")]
530    fn a_parameter_on_the_entry_block_is_refused() {
531        let mut names = Interner::new();
532        let mut func = Func::new(names.intern("f"));
533        let block = func.create_block();
534        let param = func.append_param(block, GPR);
535        let opcode = Opcode::new(names.intern("x64.nop"));
536        func.build(block, opcode).uses(param, GPR).finish();
537
538        let _ = run(&mut func, &env());
539    }
540
541    #[test]
542    fn a_value_already_in_a_register_is_left_where_it_is() {
543        let mut names = Interner::new();
544        let mut func = Func::new(names.intern("f"));
545        let opcode = Opcode::new(names.intern("x64.nop"));
546        let block = func.create_block();
547        let inst = func.build(block, opcode).uses(Reg::physical(RDX), GPR).finish();
548
549        assert_eq!(run(&mut func, &env()), Vec::<String>::new());
550        assert_eq!(operands(&func, inst), ["rdx"]);
551    }
552}