Skip to main content

rucc_regalloc/
check.rs

1//! The allocation checker: whether an assignment is one the machine can actually run.
2//!
3//! Design: `spec/10-backend.md` section 10.4, which asks for this in debug and CI builds.
4//!
5//! A register allocator is the pass whose bugs are hardest to find from the outside. It does not
6//! change what a program means, so a wrong allocation compiles, links and runs, and then produces
7//! the wrong number in one function of one program under one register pressure. The stack trace
8//! points at the arithmetic, the arithmetic is right, and the value it read was overwritten four
9//! instructions earlier by something unrelated. A checker turns all of that into an assertion at
10//! the point the mistake was made, naming the two values and the register they were both put in.
11//!
12//! # What it asks
13//!
14//! Five questions, and they are the whole of what an assignment has to get right.
15//!
16//! Every value the function reads is written first, on every path that reaches the read. Every
17//! value the function uses has somewhere to live. Two values that are both wanted at the same
18//! point are not in the same register or the same slot. Nothing is sitting in a register that an
19//! instruction insists on for itself, because that register belongs to the instruction for as long
20//! as it runs. A value an instruction can only read from memory is in memory.
21//!
22//! The first of those is not about the allocation at all, since the value would be read before it
23//! was written whatever register it went to. It is asked here because this is where the answer is
24//! already computed: a value read before it is written is a value live on the way into the entry
25//! block, and the liveness the allocator needs anyway says which those are. A function that gets
26//! this wrong is one the allocator will happily place, and what comes out reads a stack slot
27//! nothing ever stored to.
28//!
29//! # What it does not ask
30//!
31//! Whether the allocation is any good. A function with every value on the stack passes, and so it
32//! should: it is slow and it is correct, and this is the thing that says which of the two a
33//! problem is. Quality is what the numbers in `spec/14-target-ladder.md` are for.
34//!
35//! It also does not read the rewrite. It runs on the assignment, before [`crate::rewrite`] has
36//! touched the function, because the assignment is the decision and the rewrite is a
37//! transcription of it. A rewrite that transcribes a good decision badly is a different bug and
38//! the tests in that file are what catch it.
39//!
40//! # Why it repeats work
41//!
42//! The two address instructions are worked out again here rather than borrowed from
43//! [`crate::assign`], and that is deliberate. A checker that shares its reasoning with the thing
44//! it checks agrees with it about everything, including the mistakes, and the one bug it can never
45//! find is the one in the code they share. Fifteen lines is a cheap price for a second opinion.
46//!
47//! It is also allowed to be slow. Looking for a value in a register an instruction wants is the
48//! plain product of the values and the constrained operands, with no index over either, because a
49//! checker runs in debug builds and in CI and the thing it is checking is the thing that has to be
50//! fast.
51
52use std::fmt;
53
54use rucc_mir::{Constraint, Func, Inst, Reg, Role};
55use rucc_target::{PhysReg, RegClass};
56
57use crate::assign::{Assignment, Place};
58use crate::live::{Live, Range};
59use crate::order::{Order, Point};
60
61/// One thing wrong with an allocation.
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum Problem {
64    /// A value the function reads or writes was given no place at all.
65    Nowhere {
66        /// The value with nowhere to be.
67        reg: Reg,
68    },
69    /// Two values that are both live somewhere were put in the same place, so whichever is written
70    /// second destroys the other.
71    Shared {
72        /// The value that was there first.
73        first: Reg,
74        /// The value that was put on top of it.
75        second: Reg,
76        /// The place they were both given.
77        place: Place,
78    },
79    /// A value was left in a register an instruction claims for itself, over the instruction that
80    /// claims it, so the moves around that instruction overwrite the value.
81    InTheWay {
82        /// The value in the way.
83        reg: Reg,
84        /// The register the instruction insists on.
85        at: PhysReg,
86        /// The instruction that insists on it.
87        inst: Inst,
88    },
89    /// A value an instruction can only read from memory was put in a register.
90    NotOnTheStack {
91        /// The value that has to be in memory.
92        reg: Reg,
93        /// The instruction that says so.
94        inst: Inst,
95    },
96    /// A value is read on some path from the entry block without anything on that path having
97    /// written it, so what the instruction reading it gets is whatever was left there.
98    NeverWritten {
99        /// The value nothing writes.
100        reg: Reg,
101    },
102}
103
104impl fmt::Display for Problem {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        match self {
107            Problem::Nowhere { reg } => write!(f, "{} has nowhere to live", name(*reg)),
108            Problem::Shared { first, second, place } => {
109                let (first, second) = (name(*first), name(*second));
110                write!(f, "{first} and {second} are both live and both in {}", place_name(*place))
111            }
112            Problem::InTheWay { reg, at, inst } => {
113                let reg = name(*reg);
114                let inst = inst.index();
115                write!(f, "{reg} is in register {}, which instruction {inst} wants", at.number())
116            }
117            Problem::NotOnTheStack { reg, inst } => {
118                let reg = name(*reg);
119                write!(f, "{reg} is not on the stack, and instruction {} needs it", inst.index())
120            }
121            Problem::NeverWritten { reg } => {
122                write!(f, "{} is read before anything writes it", name(*reg))
123            }
124        }
125    }
126}
127
128/// Everything wrong with an allocation, in an order a person can read.
129///
130/// An empty answer is the one every allocation is supposed to give. Anything else is a compiler
131/// bug rather than a program the compiler cannot handle, which is why [`crate::run`] asserts on it
132/// instead of reporting it as a diagnostic.
133///
134/// # Panics
135///
136/// Panics on a function with two billion virtual registers in it, which is a function no machine
137/// has the memory to hold.
138#[must_use]
139pub fn check(func: &Func, order: &Order, live: &Live, assignment: &Assignment) -> Vec<Problem> {
140    let mut problems = Vec::new();
141    // What arrives live in the entry block is what the function reads without writing, since
142    // nothing runs in front of the entry block to have written it.
143    if let Some(entry) = func.entry() {
144        for reg in live.live_in(entry) {
145            problems.push(Problem::NeverWritten { reg });
146        }
147    }
148    let reuses = reuses(func, order);
149    let mut values = Vec::new();
150    for (number, reuse) in reuses.iter().enumerate() {
151        let reg = Reg::virtual_reg(u32::try_from(number).expect("a register number"));
152        let (Some(mut range), Some(class)) = (live.range(reg), func.class_of(reg)) else {
153            continue;
154        };
155        let Some(place) = assignment.place(reg) else {
156            problems.push(Problem::Nowhere { reg });
157            continue;
158        };
159        // A two address instruction writes its answer into a register it read, so the answer is
160        // really in that register from the moment the instruction starts and not from the moment
161        // it ends. Reading its range any other way lets it share the register with something the
162        // same instruction is still reading.
163        if let Some(reuse) = reuse {
164            range.start = range.start.min(reuse.at);
165        }
166        values.push(Value { reg, class, range, place });
167    }
168    overlaps(&values, &reuses, live, &mut problems);
169    instructions(func, order, live, assignment, &values, &reuses, &mut problems);
170    problems
171}
172
173/// Everything wrong with an allocation, as an assertion message.
174#[must_use]
175pub fn report(problems: &[Problem]) -> String {
176    let places = if problems.len() == 1 { "place" } else { "places" };
177    let mut report = format!("the allocation is wrong in {} {places}", problems.len());
178    for problem in problems {
179        report.push_str("\n  ");
180        report.push_str(&problem.to_string());
181    }
182    report
183}
184
185/// One value, where it is wanted and where it was put.
186#[derive(Debug, Clone, Copy)]
187struct Value {
188    reg: Reg,
189    class: RegClass,
190    range: Range,
191    place: Place,
192}
193
194/// A value written into the register another operand of the same instruction was read from.
195#[derive(Debug, Clone, Copy)]
196struct Reuse {
197    source: Reg,
198    at: Point,
199}
200
201/// Looks for two values that are both live somewhere and were put in the same place.
202///
203/// A sweep in the order the values start, holding the ones still live, so the pairs it compares
204/// are the pairs that can be wrong rather than all of them.
205fn overlaps(values: &[Value], reuses: &[Option<Reuse>], live: &Live, problems: &mut Vec<Problem>) {
206    let mut sorted = values.to_vec();
207    sorted.sort_by_key(|value| (value.range.start, value.reg));
208    let mut active: Vec<Value> = Vec::new();
209    for value in sorted {
210        active.retain(|held| held.range.end >= value.range.start);
211        for held in &active {
212            if !together(*held, value) || coalesced(*held, value, reuses, live) {
213                continue;
214            }
215            problems.push(Problem::Shared {
216                first: held.reg,
217                second: value.reg,
218                place: value.place,
219            });
220        }
221        active.push(value);
222    }
223}
224
225/// Whether two values were put in the same place.
226///
227/// Two registers of different classes are different registers even when they are the same number,
228/// which is what a class is. Two slots are the same slot whatever is in them, because a frame is
229/// one piece of memory.
230fn together(first: Value, second: Value) -> bool {
231    match (first.place, second.place) {
232        (Place::Reg(first_at), Place::Reg(second_at)) => {
233            first_at == second_at && first.class == second.class
234        }
235        (Place::Slot(first_slot), Place::Slot(second_slot)) => first_slot == second_slot,
236        _ => false,
237    }
238}
239
240/// Whether one of the two is the answer a two address instruction wrote into the register it read
241/// the other from, which is the one overlap that is not a mistake.
242///
243/// It only holds when the value being read is finished with at that instruction. A value read
244/// again afterwards needs its register afterwards, so writing over it is the plain bug this whole
245/// file exists to find.
246///
247/// It also only holds when the value being written begins at that instruction. The start above was
248/// already pulled back to the reuse point, so one still earlier is a value that was already live on
249/// the way in, which is what a loop carrying its own answer round looks like: written at the bottom
250/// and read by the next turn. Such a value is wanted where the instruction reads as well as after
251/// it, so it is genuinely on top of the one it reuses and no excuse at the one instruction they
252/// share makes them fit in a single register.
253fn coalesced(first: Value, second: Value, reuses: &[Option<Reuse>], live: &Live) -> bool {
254    let pair = |source: Value, dest: Value| {
255        let Some(reuse) = reuses[index(dest.reg)] else { return false };
256        reuse.source == source.reg
257            && dest.range.start == reuse.at
258            && live.range(source.reg).is_some_and(|r| r.end == reuse.at)
259    };
260    pair(first, second) || pair(second, first)
261}
262
263/// Looks for a value in a register an instruction wants, and for a value that had to be in memory
264/// and is not.
265fn instructions(
266    func: &Func,
267    order: &Order,
268    live: &Live,
269    assignment: &Assignment,
270    values: &[Value],
271    reuses: &[Option<Reuse>],
272    problems: &mut Vec<Problem>,
273) {
274    for block in func.blocks() {
275        for inst in func.insts(block) {
276            for operand in &func[func[inst].operands] {
277                if operand.constraint == Constraint::Stack
278                    && matches!(assignment.place(operand.reg), Some(Place::Reg(_)))
279                {
280                    problems.push(Problem::NotOnTheStack { reg: operand.reg, inst });
281                }
282                // A physical register an operand names outright is claimed exactly as firmly as
283                // one a constraint asks for, since nothing before allocation writes one except an
284                // instruction that has no choice.
285                let at = match operand.constraint {
286                    Constraint::Fixed(at) => Some(at),
287                    _ => operand.reg.phys(),
288                };
289                let Some(at) = at else { continue };
290                let early = order.early(inst);
291                let point = if operand.role == Role::Def { order.late(inst) } else { early };
292                for value in values {
293                    let mine = value.reg == operand.reg
294                        || reuses[index(value.reg)].is_some_and(|reuse| {
295                            reuse.source == operand.reg
296                                && reuse.at == early
297                                && value.place == Place::Reg(at)
298                        });
299                    if mine || value.class != operand.class {
300                        continue;
301                    }
302                    // The range has no holes in it, so it covers blocks the value never
303                    // reaches. What decides this is whether the value is live in the block the
304                    // instruction is in, which is a question about the function rather than
305                    // about the order it happens to be written in. tamnd/rucc#982.
306                    let live_here = live.anywhere_in(value.reg, block);
307                    if value.place == Place::Reg(at) && value.range.covers(point) && live_here {
308                        problems.push(Problem::InTheWay { reg: value.reg, at, inst });
309                    }
310                }
311            }
312        }
313    }
314}
315
316/// The value each two address instruction reuses, by the virtual register it writes.
317fn reuses(func: &Func, order: &Order) -> Vec<Option<Reuse>> {
318    let mut reuses = vec![None; func.vregs()];
319    for block in func.blocks() {
320        for inst in func.insts(block) {
321            let operands = &func[func[inst].operands];
322            for operand in operands {
323                let Constraint::Reuse(other) = operand.constraint else { continue };
324                let number = operand.reg.number().and_then(|number| usize::try_from(number).ok());
325                let Some(number) = number else { continue };
326                let source = operands[usize::from(other)].reg;
327                reuses[number] = Some(Reuse { source, at: order.early(inst) });
328            }
329        }
330    }
331    reuses
332}
333
334/// A virtual register's number as a table index, and zero for a physical one, which never has an
335/// entry of its own and is never what a reuse writes.
336fn index(reg: Reg) -> usize {
337    reg.number().and_then(|number| usize::try_from(number).ok()).unwrap_or(0)
338}
339
340/// What a value is called in a report.
341fn name(reg: Reg) -> String {
342    match reg.number() {
343        Some(number) => format!("%{number}"),
344        None => format!("register {}", reg.phys().expect("a physical register").number()),
345    }
346}
347
348/// What a place is called in a report, without the target's name for it, since this crate holds
349/// nothing of any target.
350fn place_name(place: Place) -> String {
351    match place {
352        Place::Reg(at) => format!("register {}", at.number()),
353        Place::Slot(slot) => format!("slot {slot}"),
354    }
355}
356
357#[cfg(test)]
358mod tests {
359    use rucc_base::Interner;
360    use rucc_mir::{BlockCall, Opcode, Operand};
361    use rucc_target::x86_64::{GPR, RAX, RCX, SYSV};
362
363    use super::*;
364    use crate::assign::{Env, assign};
365
366    /// The x86-64 environment, with the last three of the allocation order held back as scratch.
367    fn env() -> Env {
368        let (order, scratch) = SYSV.int_order.split_at(SYSV.int_order.len() - 3);
369        Env::new().with(GPR, order, scratch)
370    }
371
372    /// What the checker says about the allocation the single pass allocator works out, which is
373    /// supposed to be nothing at all.
374    fn allocated(func: &Func) -> Vec<String> {
375        let order = Order::of(func);
376        let live = Live::of(func, &order);
377        let assignment = assign(func, &order, &live, &env());
378        said(func, &order, &live, &assignment)
379    }
380
381    /// What the checker says about an allocation somebody wrote by hand.
382    fn said(func: &Func, order: &Order, live: &Live, assignment: &Assignment) -> Vec<String> {
383        check(func, order, live, assignment).iter().map(ToString::to_string).collect()
384    }
385
386    /// The order and the liveness of a function, which every hand written case needs both of.
387    fn read(func: &Func) -> (Order, Live) {
388        let order = Order::of(func);
389        let live = Live::of(func, &order);
390        (order, live)
391    }
392
393    #[test]
394    fn an_allocation_the_allocator_worked_out_has_nothing_wrong_with_it() {
395        let mut names = Interner::new();
396        let mut func = Func::new(names.intern("f"));
397        let opcode = Opcode::new(names.intern("x64.nop"));
398        let block = func.create_block();
399        let first = func.new_vreg(GPR);
400        let second = func.new_vreg(GPR);
401        func.build(block, opcode).def(first, GPR).finish();
402        func.build(block, opcode).def(second, GPR).finish();
403        func.build(block, opcode).uses(first, GPR).uses(second, GPR).finish();
404
405        assert_eq!(allocated(&func), Vec::<String>::new());
406    }
407
408    #[test]
409    fn a_value_with_nowhere_to_live_is_found() {
410        let mut names = Interner::new();
411        let mut func = Func::new(names.intern("f"));
412        let opcode = Opcode::new(names.intern("x64.nop"));
413        let block = func.create_block();
414        let only = func.new_vreg(GPR);
415        func.build(block, opcode).def(only, GPR).finish();
416        func.build(block, opcode).uses(only, GPR).finish();
417
418        let (order, live) = read(&func);
419        let assignment = Assignment::empty(func.vregs());
420
421        assert_eq!(said(&func, &order, &live, &assignment), ["%0 has nowhere to live"]);
422    }
423
424    #[test]
425    fn a_value_read_before_anything_writes_it_is_found() {
426        let mut names = Interner::new();
427        let mut func = Func::new(names.intern("f"));
428        let opcode = Opcode::new(names.intern("x64.nop"));
429        let block = func.create_block();
430        let never = func.new_vreg(GPR);
431        func.build(block, opcode).uses(never, GPR).finish();
432
433        let (order, live) = read(&func);
434        let mut assignment = Assignment::empty(func.vregs());
435        assignment.put(never, Place::Reg(RAX));
436
437        assert_eq!(
438            said(&func, &order, &live, &assignment),
439            ["%0 is read before anything writes it"]
440        );
441    }
442
443    #[test]
444    fn two_values_that_are_both_wanted_and_share_a_register_are_found() {
445        let mut names = Interner::new();
446        let mut func = Func::new(names.intern("f"));
447        let opcode = Opcode::new(names.intern("x64.nop"));
448        let block = func.create_block();
449        let first = func.new_vreg(GPR);
450        let second = func.new_vreg(GPR);
451        func.build(block, opcode).def(first, GPR).finish();
452        func.build(block, opcode).def(second, GPR).finish();
453        func.build(block, opcode).uses(first, GPR).uses(second, GPR).finish();
454
455        let (order, live) = read(&func);
456        let mut assignment = Assignment::empty(func.vregs());
457        assignment.put(first, Place::Reg(RAX));
458        assignment.put(second, Place::Reg(RAX));
459
460        let said = said(&func, &order, &live, &assignment);
461        assert_eq!(said, ["%0 and %1 are both live and both in register 0"]);
462    }
463
464    #[test]
465    fn two_values_that_are_both_wanted_and_share_a_slot_are_found() {
466        let mut names = Interner::new();
467        let mut func = Func::new(names.intern("f"));
468        let opcode = Opcode::new(names.intern("x64.nop"));
469        let block = func.create_block();
470        let first = func.new_vreg(GPR);
471        let second = func.new_vreg(GPR);
472        func.build(block, opcode).def(first, GPR).finish();
473        func.build(block, opcode).def(second, GPR).finish();
474        func.build(block, opcode).uses(first, GPR).uses(second, GPR).finish();
475
476        let (order, live) = read(&func);
477        let mut assignment = Assignment::empty(func.vregs());
478        let slot = assignment.take_slot(GPR);
479        assignment.put(first, Place::Slot(slot));
480        assignment.put(second, Place::Slot(slot));
481
482        let said = said(&func, &order, &live, &assignment);
483        assert_eq!(said, ["%0 and %1 are both live and both in slot 0"]);
484    }
485
486    #[test]
487    fn two_values_that_are_never_both_wanted_may_share_anything() {
488        let mut names = Interner::new();
489        let mut func = Func::new(names.intern("f"));
490        let opcode = Opcode::new(names.intern("x64.nop"));
491        let block = func.create_block();
492        let first = func.new_vreg(GPR);
493        let second = func.new_vreg(GPR);
494        func.build(block, opcode).def(first, GPR).finish();
495        func.build(block, opcode).uses(first, GPR).finish();
496        func.build(block, opcode).def(second, GPR).finish();
497        func.build(block, opcode).uses(second, GPR).finish();
498
499        let (order, live) = read(&func);
500        let mut assignment = Assignment::empty(func.vregs());
501        assignment.put(first, Place::Reg(RAX));
502        assignment.put(second, Place::Reg(RAX));
503
504        assert_eq!(said(&func, &order, &live, &assignment), Vec::<String>::new());
505    }
506
507    #[test]
508    fn a_value_left_in_a_register_an_instruction_wants_is_found() {
509        let mut names = Interner::new();
510        let mut func = Func::new(names.intern("f"));
511        let nop = Opcode::new(names.intern("x64.nop"));
512        let divide = Opcode::new(names.intern("x64.idiv"));
513        let block = func.create_block();
514        let held = func.new_vreg(GPR);
515        let dividend = func.new_vreg(GPR);
516        func.build(block, nop).def(held, GPR).finish();
517        func.build(block, nop).def(dividend, GPR).finish();
518        // The division reads its dividend out of one register and no other, so anything still
519        // wanted afterwards has to be somewhere else while it runs.
520        func.build(block, divide)
521            .operand(Operand::read(dividend, GPR).with(Constraint::Fixed(RAX)))
522            .finish();
523        func.build(block, nop).uses(held, GPR).finish();
524
525        let (order, live) = read(&func);
526        let mut assignment = Assignment::empty(func.vregs());
527        assignment.put(held, Place::Reg(RAX));
528        assignment.put(dividend, Place::Reg(RCX));
529
530        let said = said(&func, &order, &live, &assignment);
531        assert_eq!(said, ["%0 is in register 0, which instruction 2 wants"]);
532    }
533
534    #[test]
535    fn the_value_an_instruction_wants_a_register_for_may_be_in_it_already() {
536        let mut names = Interner::new();
537        let mut func = Func::new(names.intern("f"));
538        let nop = Opcode::new(names.intern("x64.nop"));
539        let divide = Opcode::new(names.intern("x64.idiv"));
540        let block = func.create_block();
541        let dividend = func.new_vreg(GPR);
542        func.build(block, nop).def(dividend, GPR).finish();
543        func.build(block, divide)
544            .operand(Operand::read(dividend, GPR).with(Constraint::Fixed(RAX)))
545            .finish();
546
547        let (order, live) = read(&func);
548        let mut assignment = Assignment::empty(func.vregs());
549        assignment.put(dividend, Place::Reg(RAX));
550
551        // Being in the register the instruction wanted is the best answer, not a problem, and the
552        // rewrite writes no move at all for it.
553        assert_eq!(said(&func, &order, &live, &assignment), Vec::<String>::new());
554    }
555
556    #[test]
557    fn a_value_that_can_only_be_read_from_memory_and_is_in_a_register_is_found() {
558        let mut names = Interner::new();
559        let mut func = Func::new(names.intern("f"));
560        let nop = Opcode::new(names.intern("x64.nop"));
561        let wide = Opcode::new(names.intern("x64.wide"));
562        let block = func.create_block();
563        let only = func.new_vreg(GPR);
564        func.build(block, nop).def(only, GPR).finish();
565        func.build(block, wide).operand(Operand::read(only, GPR).with(Constraint::Stack)).finish();
566
567        let (order, live) = read(&func);
568        let mut assignment = Assignment::empty(func.vregs());
569        assignment.put(only, Place::Reg(RAX));
570
571        let said = said(&func, &order, &live, &assignment);
572        assert_eq!(said, ["%0 is not on the stack, and instruction 1 needs it"]);
573    }
574
575    #[test]
576    fn a_two_address_instruction_may_write_the_register_it_read_a_finished_value_from() {
577        let mut names = Interner::new();
578        let mut func = Func::new(names.intern("f"));
579        let nop = Opcode::new(names.intern("x64.nop"));
580        let add = Opcode::new(names.intern("x64.add"));
581        let block = func.create_block();
582        let left = func.new_vreg(GPR);
583        let right = func.new_vreg(GPR);
584        let sum = func.new_vreg(GPR);
585        func.build(block, nop).def(left, GPR).finish();
586        func.build(block, nop).def(right, GPR).finish();
587        func.build(block, add)
588            .operand(Operand::write(sum, GPR).with(Constraint::Reuse(1)))
589            .uses(left, GPR)
590            .uses(right, GPR)
591            .finish();
592        func.build(block, nop).uses(sum, GPR).finish();
593
594        let (order, live) = read(&func);
595        let mut assignment = Assignment::empty(func.vregs());
596        assignment.put(left, Place::Reg(RAX));
597        assignment.put(right, Place::Reg(RCX));
598        assignment.put(sum, Place::Reg(RAX));
599
600        // The left operand is finished with at the addition, so the sum takes its register and
601        // the addition is the one instruction rather than a move and an instruction.
602        assert_eq!(said(&func, &order, &live, &assignment), Vec::<String>::new());
603    }
604
605    #[test]
606    fn a_two_address_instruction_may_not_write_over_a_value_wanted_afterwards() {
607        let mut names = Interner::new();
608        let mut func = Func::new(names.intern("f"));
609        let nop = Opcode::new(names.intern("x64.nop"));
610        let add = Opcode::new(names.intern("x64.add"));
611        let block = func.create_block();
612        let left = func.new_vreg(GPR);
613        let right = func.new_vreg(GPR);
614        let sum = func.new_vreg(GPR);
615        func.build(block, nop).def(left, GPR).finish();
616        func.build(block, nop).def(right, GPR).finish();
617        func.build(block, add)
618            .operand(Operand::write(sum, GPR).with(Constraint::Reuse(1)))
619            .uses(left, GPR)
620            .uses(right, GPR)
621            .finish();
622        func.build(block, nop).uses(sum, GPR).uses(left, GPR).finish();
623
624        let (order, live) = read(&func);
625        let mut assignment = Assignment::empty(func.vregs());
626        assignment.put(left, Place::Reg(RAX));
627        assignment.put(right, Place::Reg(RCX));
628        assignment.put(sum, Place::Reg(RAX));
629
630        // The left operand is read again after the addition, so the addition may not have its
631        // register even though it is the one the addition reads.
632        let said = said(&func, &order, &live, &assignment);
633        assert_eq!(said, ["%0 and %2 are both live and both in register 0"]);
634    }
635
636    #[test]
637    fn a_two_address_instruction_may_not_write_the_register_it_reads_its_other_operand_from() {
638        let mut names = Interner::new();
639        let mut func = Func::new(names.intern("f"));
640        let nop = Opcode::new(names.intern("x64.nop"));
641        let add = Opcode::new(names.intern("x64.add"));
642        let block = func.create_block();
643        let left = func.new_vreg(GPR);
644        let right = func.new_vreg(GPR);
645        let sum = func.new_vreg(GPR);
646        func.build(block, nop).def(left, GPR).finish();
647        func.build(block, nop).def(right, GPR).finish();
648        func.build(block, add)
649            .operand(Operand::write(sum, GPR).with(Constraint::Reuse(1)))
650            .uses(left, GPR)
651            .uses(right, GPR)
652            .finish();
653        func.build(block, nop).uses(sum, GPR).finish();
654
655        let (order, live) = read(&func);
656        let mut assignment = Assignment::empty(func.vregs());
657        assignment.put(left, Place::Reg(RAX));
658        assignment.put(right, Place::Reg(RCX));
659        assignment.put(sum, Place::Reg(RCX));
660
661        // Copying the left operand into the sum's register would destroy the right operand before
662        // the addition has read it, even though both are finished with at the addition.
663        let said = said(&func, &order, &live, &assignment);
664        assert_eq!(said, ["%1 and %2 are both live and both in register 1"]);
665    }
666
667    #[test]
668    fn a_two_address_instruction_may_not_write_the_register_it_read_over_its_own_last_answer() {
669        let mut names = Interner::new();
670        let mut func = Func::new(names.intern("f"));
671        let nop = Opcode::new(names.intern("x64.nop"));
672        let add = Opcode::new(names.intern("x64.add"));
673        let head = func.create_block();
674        let latch = func.create_block();
675        let out = func.create_block();
676        let source = func.new_vreg(GPR);
677        let carried = func.new_vreg(GPR);
678        func.build(head, nop).def(source, GPR).finish();
679        func.build(head, nop).def(carried, GPR).finish();
680        *func.succs_mut(head) = vec![BlockCall::to(latch)];
681        func.build(latch, add)
682            .operand(Operand::write(carried, GPR).with(Constraint::Reuse(1)))
683            .uses(source, GPR)
684            .uses(carried, GPR)
685            .finish();
686        *func.succs_mut(latch) = vec![BlockCall::to(head), BlockCall::to(out)];
687        func.build(out, nop).uses(carried, GPR).finish();
688
689        let (order, live) = read(&func);
690        let mut assignment = Assignment::empty(func.vregs());
691        assignment.put(source, Place::Reg(RAX));
692        assignment.put(carried, Place::Reg(RAX));
693
694        // The source is finished with at the addition, which is what would normally let the answer
695        // have its register. It does not here, because the answer is the one the last turn round
696        // the loop wrote and the addition reads it too, so both are wanted where it reads.
697        let said = said(&func, &order, &live, &assignment);
698        assert_eq!(said, ["%0 and %1 are both live and both in register 0"]);
699    }
700
701    #[test]
702    fn a_report_names_every_problem() {
703        let mut names = Interner::new();
704        let mut func = Func::new(names.intern("f"));
705        let opcode = Opcode::new(names.intern("x64.nop"));
706        let block = func.create_block();
707        let first = func.new_vreg(GPR);
708        let second = func.new_vreg(GPR);
709        func.build(block, opcode).def(first, GPR).finish();
710        func.build(block, opcode).def(second, GPR).finish();
711        func.build(block, opcode).uses(first, GPR).uses(second, GPR).finish();
712
713        let (order, live) = read(&func);
714        let mut assignment = Assignment::empty(func.vregs());
715        assignment.put(first, Place::Reg(RAX));
716        assignment.put(second, Place::Reg(RAX));
717
718        let problems = check(&func, &order, &live, &assignment);
719        assert_eq!(
720            report(&problems),
721            "the allocation is wrong in 1 place\n  %0 and %1 are both live and both in register 0"
722        );
723        assert_eq!(report(&[]), "the allocation is wrong in 0 places");
724    }
725}