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