Skip to main content

rucc_regalloc/
trace.rs

1//! Following every value from the instruction that wrote it to the instructions that read it.
2//!
3//! Design: `spec/optimizer/39-register-allocation.md` section 39.6, which asks for an independent
4//! verifier that every use reads the value its definition produced, and `spec/10-backend.md`
5//! section 10.4, which says a check like it runs in debug and CI builds.
6//!
7//! # Why there are two checkers
8//!
9//! [`crate::check`] reads the assignment. It asks whether the decision is one the machine can run:
10//! whether two values that are live at once were given the same place, whether anything is sitting
11//! in a register an instruction claims for itself, and so on. Its own module doc says what it
12//! leaves alone, which is the rewrite, on the grounds that the assignment is the decision and the
13//! rewrite is a transcription of it.
14//!
15//! This is the other half. A transcription can lose a value while the decision behind it was
16//! right, and the places it can lose one are exactly the places [`crate::rewrite`] does something:
17//! handing out scratch registers for a value that lives on the stack, moving a value into the
18//! register an instruction insists on, copying one operand into another for a two address form,
19//! and putting an edge's moves into an order that can be performed one at a time. Both #350 and
20//! #726 were bugs in that code and neither is a shape the assignment checker can see.
21//!
22//! # What it asks
23//!
24//! One question. At every instruction, does each register the instruction reads hold the value the
25//! operand said it wanted.
26//!
27//! It is asked only of the values the allocator placed. An operand that named a physical register
28//! before the rewrite named it for its own reasons, which are the calling convention, the frame, or
29//! the text of an `asm` statement the program wrote, and what is in that register is whatever those
30//! reasons put there. There is no value under it for a transcription to lose, so reading one is
31//! nothing this can be wrong about. Writing one is still followed, because a value the allocator
32//! did put in that register is gone once it happens.
33//!
34//! That is asked by walking the function with a note of which value is in each place, starting
35//! from nothing at the entry block. An instruction writing an operand puts that value in the place
36//! the operand ended up naming. A move puts what is in one place into another, or takes the note
37//! away when the place it reads from held nothing known. An edge carries the block's arguments
38//! into the block's parameters, and what arrives is checked and then goes on under the parameter's
39//! name, since from there on that is what the value is called.
40//!
41//! A block with two edges into it keeps only what both of them agree about, because a value that
42//! is in one place on one path and another place on the other is not in either. That is a
43//! fixed point and it is worked out first, before anything is reported, so that a loop is not
44//! complained about on the first time round before the back edge has been seen.
45//!
46//! # Why it is told the function twice
47//!
48//! [`shape`] is taken before the rewrite and holds what each operand's value was called and what
49//! each edge carried. The rewrite is what loses both: afterwards an operand names a physical
50//! register and an edge carries nothing. Checking a transcription means holding on to the thing
51//! that was transcribed, and a snapshot of the operand lists is a cheaper way to do that than a
52//! copy of the function.
53//!
54//! # Why it repeats work
55//!
56//! It works out where a value lives from the assignment again rather than reading it off the
57//! rewritten function, and it sequences nothing. A checker that shares its reasoning with the
58//! thing it checks agrees with it about the mistakes as well, and the bug it can never find is the
59//! one in the code they share.
60//!
61//! It is also allowed to be slow. The state is a map from places to values and it is copied at
62//! every edge, because a checker runs in debug builds and the thing it is checking is the thing
63//! that has to be fast.
64
65use std::collections::HashMap;
66use std::fmt;
67
68use rucc_mir::{Block, Constraint, Func, Inst, Operand, Param, Reg, Role};
69use rucc_target::RegClass;
70
71use crate::assign::{Assignment, Place};
72use crate::rewrite::{At, Edit};
73
74/// One value read out of a place that was not holding it.
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub enum Fault {
77    /// An instruction reads a place holding something other than the value its operand names.
78    Read {
79        /// The instruction doing the reading.
80        inst: Inst,
81        /// The place it reads.
82        place: Place,
83        /// Which file that place is in, since a register is a number inside its class.
84        class: RegClass,
85        /// The value the operand says is there.
86        wanted: Reg,
87        /// What is really there, if anything is known to be.
88        found: Option<Reg>,
89    },
90    /// An edge did not leave a block's parameter holding the argument the edge carried for it.
91    Arrived {
92        /// The block the edge leaves.
93        from: Block,
94        /// The block it goes to.
95        to: Block,
96        /// Where the parameter lives.
97        place: Place,
98        /// Which file that place is in.
99        class: RegClass,
100        /// The argument that was supposed to arrive there.
101        wanted: Reg,
102        /// What is really there, if anything is known to be.
103        found: Option<Reg>,
104    },
105}
106
107impl fmt::Display for Fault {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        match self {
110            Fault::Read { inst, place, class, wanted, found } => write!(
111                f,
112                "instruction {} reads {} out of {}, which {}",
113                inst.index(),
114                name(*wanted),
115                spelled(*class, *place),
116                holding(*found)
117            ),
118            Fault::Arrived { from, to, place, class, wanted, found } => write!(
119                f,
120                "the edge from block {} to block {} was to leave {} in {}, which {}",
121                from.index(),
122                to.index(),
123                name(*wanted),
124                spelled(*class, *place),
125                holding(*found)
126            ),
127        }
128    }
129}
130
131/// What the rewrite is about to lose: what each operand's value was called, and what each edge
132/// carries.
133///
134/// Taken with [`shape`], before [`crate::rewrite`] runs, and read by [`trace`] afterwards.
135#[derive(Debug, Clone, Default)]
136pub struct Shape {
137    /// Every instruction's operands as they were, by the instruction's index.
138    operands: Vec<Vec<Operand>>,
139    /// Every block's parameters, by the block's index.
140    params: Vec<Vec<Param>>,
141    /// Every block's edges out and what each one carries, by the block's index.
142    succs: Vec<Vec<Call>>,
143}
144
145/// One edge out of a block, as it was before the rewrite emptied it.
146#[derive(Debug, Clone)]
147struct Call {
148    block: Block,
149    args: Vec<Reg>,
150}
151
152/// What a function looked like before the rewrite.
153///
154/// # Panics
155///
156/// Panics on a function with more instructions than a `usize` counts, which is one no machine has
157/// the memory to hold.
158#[must_use]
159pub fn shape(func: &Func) -> Shape {
160    let mut shape = Shape {
161        operands: vec![Vec::new(); func.inst_count()],
162        params: vec![Vec::new(); func.block_count()],
163        succs: vec![Vec::new(); func.block_count()],
164    };
165    for block in func.blocks() {
166        shape.params[block.index()] = func[block].params.clone();
167        shape.succs[block.index()] = func[block]
168            .succs
169            .iter()
170            .map(|call| Call { block: call.block, args: call.args.clone() })
171            .collect();
172        for inst in func.insts(block) {
173            shape.operands[inst.index()] = func[func[inst].operands].to_vec();
174        }
175    }
176    shape
177}
178
179/// Everywhere the rewritten function reads a place that is not holding the value it wants.
180///
181/// An empty answer is the one every allocation is supposed to give. Anything else is a compiler
182/// bug rather than a program the compiler cannot handle, which is why [`crate::run`] asserts on it
183/// instead of reporting it as a diagnostic.
184///
185/// The function is the rewritten one, the shape is what [`shape`] took of it beforehand, and the
186/// edits are the ones the rewrite handed back. The three together are the whole of what the
187/// machine will be asked to run.
188#[must_use]
189pub fn trace(func: &Func, shape: &Shape, assignment: &Assignment, edits: &[Edit]) -> Vec<Fault> {
190    let filed = File::of(func, edits);
191    let mut entry: Vec<Option<State>> = vec![None; func.block_count()];
192    let Some(start) = func.entry() else { return Vec::new() };
193
194    // The entry block starts out knowing nothing, because nothing has run to put a value anywhere
195    // yet. A value the allocator placed and the function reads before writing is the assignment
196    // checker's question rather than this one's, and a register the function names itself is not
197    // read as a value at all.
198    entry[start.index()] = Some(State::new());
199    let mut queue = vec![start];
200    let mut ignored = Vec::new();
201    while let Some(block) = queue.pop() {
202        let Some(state) = entry[block.index()].clone() else { continue };
203        ignored.clear();
204        let out = body(func, shape, &filed, edits, block, state, &mut ignored);
205        let single = shape.succs[block.index()].len() == 1;
206        for call in &shape.succs[block.index()] {
207            let over =
208                cross(shape, &filed, edits, assignment, block, call, single, &out, &mut ignored);
209            if narrow(&mut entry[call.block.index()], &over) {
210                queue.push(call.block);
211            }
212        }
213    }
214
215    // Now that every block's state has settled, one pass to say what is wrong with it. Doing this
216    // inside the loop above would complain about the first time round a loop, before the back edge
217    // has had a chance to take anything away.
218    let mut faults = Vec::new();
219    for block in func.blocks() {
220        let Some(state) = entry[block.index()].clone() else { continue };
221        let out = body(func, shape, &filed, edits, block, state, &mut faults);
222        let single = shape.succs[block.index()].len() == 1;
223        for call in &shape.succs[block.index()] {
224            cross(shape, &filed, edits, assignment, block, call, single, &out, &mut faults);
225        }
226    }
227    faults
228}
229
230/// Everything wrong with a rewritten function, as an assertion message.
231#[must_use]
232pub fn report(faults: &[Fault]) -> String {
233    let places = if faults.len() == 1 { "place" } else { "places" };
234    let mut report = format!("the rewrite loses a value in {} {places}", faults.len());
235    for fault in faults {
236        report.push_str("\n  ");
237        report.push_str(&fault.to_string());
238    }
239    report
240}
241
242/// Which value is in which place, as far as anything is known.
243///
244/// A place with no entry is one nothing is known about, which is either a place nothing has
245/// written yet or one the paths into a block disagree about. Reading one is a fault, since the
246/// machine will read whatever is there.
247type State = HashMap<Spot, Reg>;
248
249/// A place and the file it is in.
250///
251/// The class is in here because a physical register is a number inside its class, so the first
252/// general purpose register and the first floating point register are both register zero and are
253/// not the same place at all.
254type Spot = (u8, Place);
255
256/// The place an operand of that class ended up naming.
257fn spot(class: RegClass, place: Place) -> Spot {
258    (class.number(), place)
259}
260
261/// Which edits go where, as indexes into the list the rewrite handed back.
262///
263/// The rewrite hands its edits back in the order it made them, which is every instruction's and
264/// then every edge's, so a walk in program order has to be able to ask for the ones at a point
265/// rather than read them in the order they arrived.
266#[derive(Debug, Default)]
267struct File {
268    before: Vec<Vec<usize>>,
269    after: Vec<Vec<usize>>,
270    start_of: Vec<Vec<usize>>,
271    end_of: Vec<Vec<usize>>,
272}
273
274impl File {
275    /// The edits of a function, filed by where in it they go.
276    fn of(func: &Func, edits: &[Edit]) -> Self {
277        let mut filed = File {
278            before: vec![Vec::new(); func.inst_count()],
279            after: vec![Vec::new(); func.inst_count()],
280            start_of: vec![Vec::new(); func.block_count()],
281            end_of: vec![Vec::new(); func.block_count()],
282        };
283        for (index, edit) in edits.iter().enumerate() {
284            match edit.at {
285                At::Before(inst) => filed.before[inst.index()].push(index),
286                At::After(inst) => filed.after[inst.index()].push(index),
287                At::StartOf(block) => filed.start_of[block.index()].push(index),
288                At::EndOf(block) => filed.end_of[block.index()].push(index),
289            }
290        }
291        filed
292    }
293}
294
295/// Walks one block, saying what is where at the end of it and what went wrong on the way.
296///
297/// The edits an edge into this block turned into are not applied here. They belong to the edge and
298/// [`cross`] has already made them true in the state this is handed.
299fn body(
300    func: &Func,
301    shape: &Shape,
302    filed: &File,
303    edits: &[Edit],
304    block: Block,
305    mut state: State,
306    faults: &mut Vec<Fault>,
307) -> State {
308    for inst in func.insts(block) {
309        for &edit in &filed.before[inst.index()] {
310            moved(&mut state, &edits[edit]);
311        }
312        let was = &shape.operands[inst.index()];
313        let now = &func[func[inst].operands];
314
315        // Every read first and every write afterwards, because an instruction reads its operands
316        // before it writes its answer. A write that lands on something the same instruction still
317        // wants to read is the assignment checker's question and it has already asked it.
318        //
319        // A read of a register the function named itself is skipped, because it is not reading a
320        // value the allocator placed. It is reading whatever the machine has in that register, put
321        // there by the calling convention, by the frame, or by the program's own `asm` text, and
322        // none of that is something a transcription can lose. A write of one is still recorded,
323        // since a value the allocator did place into that register is gone once it happens and a
324        // later read of it should say so.
325        for (operand, place) in was.iter().zip(now.iter()) {
326            let Some(at) = landed(place) else { continue };
327            if operand.role != Role::Use {
328                continue;
329            }
330            if operand.reg.phys().is_some() {
331                continue;
332            }
333            let found = state.get(&spot(operand.class, at)).copied();
334            if found != Some(operand.reg) {
335                let (class, wanted) = (operand.class, operand.reg);
336                faults.push(Fault::Read { inst, place: at, class, wanted, found });
337            }
338        }
339        for (operand, place) in was.iter().zip(now.iter()) {
340            let Some(at) = landed(place) else { continue };
341            if !operand.role.is_def() {
342                continue;
343            }
344            // A write of only the top of the register leaves a value that fits under it where it
345            // was, which is what a call does to `v8` on AArch64 and a `double` in it.
346            let held = state.get(&spot(operand.class, at)).and_then(|&held| func.width(held));
347            let under = |above| held.is_some_and(|width| width <= above);
348            if matches!(operand.constraint, Constraint::Above(above) if under(above)) {
349                continue;
350            }
351            state.insert(spot(operand.class, at), operand.reg);
352        }
353        for &edit in &filed.after[inst.index()] {
354            moved(&mut state, &edits[edit]);
355        }
356    }
357    state
358}
359
360/// Carries one edge's values into the block it goes to, and says what is where when they arrive.
361///
362/// The moves go at the end of the block the edge leaves when that is its only edge out, and at the
363/// start of the block it goes to otherwise, which is safe exactly because a block reached by an
364/// edge from a block with two of them has no other edge into it.
365#[allow(clippy::too_many_arguments, reason = "an edge is the two blocks and everything between")]
366fn cross(
367    shape: &Shape,
368    filed: &File,
369    edits: &[Edit],
370    assignment: &Assignment,
371    from: Block,
372    call: &Call,
373    single: bool,
374    out: &State,
375    faults: &mut Vec<Fault>,
376) -> State {
377    let mut state = out.clone();
378    let params = &shape.params[call.block.index()];
379    let list =
380        if single { &filed.end_of[from.index()] } else { &filed.start_of[call.block.index()] };
381    for &edit in list {
382        moved(&mut state, &edits[edit]);
383    }
384
385    // What each parameter is called from here on. Worked out from the state the moves left and
386    // then written back all at once, because two parameters can be in each other's places and
387    // renaming one before the other has been read would lose the second.
388    let mut arrived = Vec::new();
389    for (param, &arg) in params.iter().zip(&call.args) {
390        let Some(at) = home(assignment, param.reg) else { continue };
391        let found = state.get(&spot(param.class, at)).copied();
392        if found != Some(arg) {
393            let (to, class) = (call.block, param.class);
394            faults.push(Fault::Arrived { from, to, place: at, class, wanted: arg, found });
395        }
396        arrived.push((spot(param.class, at), param.reg));
397    }
398    for (spot, reg) in arrived {
399        state.insert(spot, reg);
400    }
401    state
402}
403
404/// Performs one move on the state, which is putting what is in one place into another.
405///
406/// A move out of a place nothing is known about takes the note away rather than leaving a stale
407/// one, since what it copied is whatever was there.
408fn moved(state: &mut State, edit: &Edit) {
409    let to = spot(edit.class, edit.mov.to);
410    let from = spot(edit.class, edit.mov.from);
411    match state.get(&from).copied() {
412        Some(reg) => state.insert(to, reg),
413        None => state.remove(&to),
414    };
415}
416
417/// Keeps only what a block's state and an edge into it agree about, and says whether that changed
418/// anything.
419///
420/// A block nothing has reached yet takes the edge's state whole. After that every edge can only
421/// take something away, which is what makes the walk finish.
422fn narrow(entry: &mut Option<State>, over: &State) -> bool {
423    match entry {
424        None => {
425            *entry = Some(over.clone());
426            true
427        }
428        Some(state) => {
429            let before = state.len();
430            state.retain(|spot, reg| over.get(spot) == Some(&*reg));
431            state.len() != before
432        }
433    }
434}
435
436/// Where an operand ended up, which after the rewrite is always a physical register.
437fn landed(operand: &Operand) -> Option<Place> {
438    operand.reg.phys().map(Place::Reg)
439}
440
441/// Where a value lives, whether the assignment put it there or it was a physical register already.
442///
443/// Nothing for a value that is neither, which is a value the assignment was never asked about. The
444/// assignment checker reports that as a value with nowhere to live, so this leaves it alone rather
445/// than saying the same thing twice.
446fn home(assignment: &Assignment, reg: Reg) -> Option<Place> {
447    assignment.place(reg).or_else(|| reg.phys().map(Place::Reg))
448}
449
450/// What a value is called in a report.
451fn name(reg: Reg) -> String {
452    match reg.number() {
453        Some(number) => format!("%{number}"),
454        None => match reg.phys() {
455            Some(at) => format!("register {}", at.number()),
456            None => "nothing".to_owned(),
457        },
458    }
459}
460
461/// What a place is called in a report, without the target's name for it, since this crate holds
462/// nothing of any target.
463fn spelled(class: RegClass, place: Place) -> String {
464    match place {
465        Place::Reg(at) => format!("register {} of class {}", at.number(), class.number()),
466        Place::Slot(slot) => format!("slot {slot}"),
467    }
468}
469
470/// What a place is holding, as the end of a sentence.
471fn holding(found: Option<Reg>) -> String {
472    match found {
473        Some(reg) => format!("holds {}", name(reg)),
474        None => "holds nothing anything has put there".to_owned(),
475    }
476}
477
478#[cfg(test)]
479mod tests {
480    use rucc_base::Interner;
481    use rucc_mir::{BlockCall, Constraint, Opcode, Operand};
482    use rucc_target::x86_64::{GPR, RAX, RSP, SYSV};
483
484    use super::*;
485    use crate::assign::{Env, assign};
486    use crate::live::Live;
487    use crate::moves::Move;
488    use crate::order::Order;
489    use crate::rewrite::rewrite;
490
491    /// The x86-64 environment, with the last three of the allocation order held back as scratch.
492    fn env() -> Env {
493        let (order, scratch) = SYSV.int_order.split_at(SYSV.int_order.len() - 3);
494        Env::new().with(GPR, order, scratch)
495    }
496
497    /// An environment with that many general purpose registers and two scratch after them.
498    fn narrow(count: usize) -> Env {
499        Env::new().with(GPR, &SYSV.int_order[..count], &SYSV.int_order[count..count + 2])
500    }
501
502    /// Allocates a function and hands back everything the checker is told about it.
503    fn allocate(func: &mut Func, env: &Env) -> (Shape, Assignment, Vec<Edit>) {
504        let order = Order::of(func);
505        let live = Live::of(func, &order);
506        let mut assignment = assign(func, &order, &live, env);
507        let taken = shape(func);
508        let edits = rewrite(func, &mut assignment, env);
509        (taken, assignment, edits)
510    }
511
512    /// What the checker says about a rewritten function, as lines an assertion can read.
513    fn said(func: &Func, taken: &Shape, assignment: &Assignment, edits: &[Edit]) -> Vec<String> {
514        trace(func, taken, assignment, edits).iter().map(ToString::to_string).collect()
515    }
516
517    #[test]
518    fn every_value_an_instruction_reads_is_the_one_that_was_written_where_it_reads_it() {
519        let mut names = Interner::new();
520        let mut func = Func::new(names.intern("f"));
521        let opcode = Opcode::new(names.intern("x64.nop"));
522        let block = func.create_block();
523        let first = func.new_vreg(GPR);
524        let second = func.new_vreg(GPR);
525        func.build(block, opcode).def(first, GPR).finish();
526        func.build(block, opcode).def(second, GPR).finish();
527        func.build(block, opcode).uses(first, GPR).uses(second, GPR).finish();
528
529        let (taken, assignment, edits) = allocate(&mut func, &env());
530        assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
531    }
532
533    #[test]
534    fn a_value_that_lives_on_the_stack_is_followed_through_the_slot_it_lives_in() {
535        let mut names = Interner::new();
536        let mut func = Func::new(names.intern("f"));
537        let opcode = Opcode::new(names.intern("x64.nop"));
538        let block = func.create_block();
539        let first = func.new_vreg(GPR);
540        let second = func.new_vreg(GPR);
541        let third = func.new_vreg(GPR);
542        func.build(block, opcode).def(first, GPR).finish();
543        func.build(block, opcode).def(second, GPR).finish();
544        func.build(block, opcode).def(third, GPR).finish();
545        func.build(block, opcode).uses(first, GPR).uses(second, GPR).uses(third, GPR).finish();
546
547        // Two registers and three values wanted at once, so one of them is stored to a slot and
548        // read back out of it, and the checker has to follow it both ways to say nothing is wrong.
549        let (taken, assignment, edits) = allocate(&mut func, &narrow(2));
550        assert_eq!(assignment.spilled(), 1);
551        assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
552    }
553
554    #[test]
555    fn an_operand_the_rewrite_pointed_at_the_wrong_register_is_reported() {
556        let mut names = Interner::new();
557        let mut func = Func::new(names.intern("f"));
558        let opcode = Opcode::new(names.intern("x64.nop"));
559        let block = func.create_block();
560        let first = func.new_vreg(GPR);
561        let second = func.new_vreg(GPR);
562        let read = {
563            func.build(block, opcode).def(first, GPR).finish();
564            func.build(block, opcode).def(second, GPR).finish();
565            func.build(block, opcode).uses(first, GPR).uses(second, GPR).finish()
566        };
567
568        let (taken, assignment, edits) = allocate(&mut func, &env());
569        assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
570
571        // The checker has to be checking, so point one operand at the register next door, which is
572        // what a rewrite that handed out the wrong register would leave, and see it named. The
573        // assignment behind it is untouched and is still the right one.
574        let list = func[read].operands;
575        func[list][0].reg = func[list][1].reg;
576
577        assert_eq!(
578            said(&func, &taken, &assignment, &edits),
579            ["instruction 2 reads %0 out of register 1 of class 0, which holds %1"]
580        );
581    }
582
583    #[test]
584    fn a_move_that_writes_the_wrong_register_is_an_instruction_reading_the_wrong_value() {
585        let mut names = Interner::new();
586        let mut func = Func::new(names.intern("f"));
587        let opcode = Opcode::new(names.intern("x64.nop"));
588        let block = func.create_block();
589        let dividend = func.new_vreg(GPR);
590        let quotient = func.new_vreg(GPR);
591        func.build(block, opcode).def(dividend, GPR).finish();
592        func.build(block, opcode)
593            .operand(Operand::write(quotient, GPR).with(Constraint::Fixed(RAX)))
594            .operand(Operand::read(dividend, GPR).with(Constraint::Fixed(RAX)))
595            .finish();
596        func.build(block, opcode).uses(quotient, GPR).finish();
597        func.build(block, opcode).uses(dividend, GPR).finish();
598
599        let (taken, assignment, mut edits) = allocate(&mut func, &env());
600        assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
601
602        // The move that carries the dividend into the register the instruction insists on, sent
603        // to the register next door instead. That is the shape of a rewrite that hands out the
604        // wrong scratch register, and it has to be caught at the instruction that reads it.
605        edits[0].mov.to = Place::Reg(SYSV.int_order[2]);
606        assert_eq!(
607            said(&func, &taken, &assignment, &edits),
608            ["instruction 1 reads %0 out of register 0 of class 0, which holds nothing anything \
609              has put there"]
610        );
611    }
612
613    #[test]
614    fn a_value_carried_over_an_edge_goes_on_under_the_name_the_block_it_arrives_in_gives_it() {
615        let mut names = Interner::new();
616        let mut func = Func::new(names.intern("f"));
617        let opcode = Opcode::new(names.intern("x64.nop"));
618        let head = func.create_block();
619        let tail = func.create_block();
620        let value = func.new_vreg(GPR);
621        func.build(head, opcode).def(value, GPR).finish();
622        let arrived = func.append_param(tail, GPR);
623        *func.succs_mut(head) = vec![BlockCall::with(tail, vec![value])];
624        func.build(tail, opcode).uses(arrived, GPR).finish();
625
626        let (taken, assignment, edits) = allocate(&mut func, &env());
627        assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
628    }
629
630    #[test]
631    fn two_values_that_swap_on_an_edge_arrive_the_right_way_round_only_in_the_order_they_were_put_in()
632     {
633        let mut names = Interner::new();
634        let mut func = Func::new(names.intern("f"));
635        let opcode = Opcode::new(names.intern("x64.nop"));
636        let head = func.create_block();
637        let body = func.create_block();
638        let first = func.new_vreg(GPR);
639        let second = func.new_vreg(GPR);
640        func.build(head, opcode).def(first, GPR).finish();
641        func.build(head, opcode).def(second, GPR).finish();
642        let left = func.append_param(body, GPR);
643        let right = func.append_param(body, GPR);
644        *func.succs_mut(head) = vec![BlockCall::with(body, vec![first, second])];
645        func.build(body, opcode).uses(left, GPR).uses(right, GPR).finish();
646        *func.succs_mut(body) = vec![BlockCall::with(body, vec![right, left])];
647
648        let (taken, assignment, mut edits) = allocate(&mut func, &env());
649        assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
650
651        // The same swap written as the two moves it looks like, which is what a sequencer that
652        // did not notice the cycle would leave. The second move reads a register the first has
653        // already written, so both parameters end up holding the same value.
654        let (to, from) = (Place::Reg(SYSV.int_order[0]), Place::Reg(SYSV.int_order[1]));
655        edits.truncate(edits.len() - 3);
656        edits.push(Edit { at: At::EndOf(body), mov: Move::new(to, from), class: GPR });
657        edits.push(Edit { at: At::EndOf(body), mov: Move::new(from, to), class: GPR });
658
659        assert_eq!(
660            said(&func, &taken, &assignment, &edits),
661            ["the edge from block 1 to block 1 was to leave %2 in register 1 of class 0, which \
662                 holds %3"]
663        );
664    }
665
666    #[test]
667    fn a_loop_is_walked_until_it_settles_rather_than_reported_the_first_time_round() {
668        let mut names = Interner::new();
669        let mut func = Func::new(names.intern("f"));
670        let opcode = Opcode::new(names.intern("x64.nop"));
671        let head = func.create_block();
672        let body = func.create_block();
673        let latch = func.create_block();
674        let out = func.create_block();
675        let start = func.new_vreg(GPR);
676        func.build(head, opcode).def(start, GPR).finish();
677        let counter = func.append_param(body, GPR);
678        *func.succs_mut(head) = vec![BlockCall::with(body, vec![start])];
679        let next = func.new_vreg(GPR);
680        func.build(body, opcode).def(next, GPR).uses(counter, GPR).finish();
681        *func.succs_mut(body) = vec![BlockCall::to(latch), BlockCall::to(out)];
682        func.build(latch, opcode).finish();
683        *func.succs_mut(latch) = vec![BlockCall::with(body, vec![next])];
684        func.build(out, opcode).finish();
685
686        // The back edge is what the counter arrives on the second time round, and the checker only
687        // reports anything once every edge into the block has been taken into account.
688        let (taken, assignment, edits) = allocate(&mut func, &env());
689        assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
690    }
691
692    #[test]
693    fn a_value_the_two_ways_into_a_block_leave_in_different_places_is_not_one_it_may_read() {
694        let mut names = Interner::new();
695        let mut func = Func::new(names.intern("f"));
696        let opcode = Opcode::new(names.intern("x64.nop"));
697        let entry = func.create_block();
698        let arm = func.create_block();
699        let tail = func.create_block();
700        let value = func.new_vreg(GPR);
701        func.build(entry, opcode).def(value, GPR).finish();
702        *func.succs_mut(entry) = vec![BlockCall::to(arm), BlockCall::to(tail)];
703        func.build(arm, opcode).finish();
704        *func.succs_mut(arm) = vec![BlockCall::to(tail)];
705        func.build(tail, opcode).uses(value, GPR).finish();
706
707        let (taken, assignment, mut edits) = allocate(&mut func, &env());
708        assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
709
710        // One arm of the diamond writes over the register the value is in. The block the two arms
711        // meet in reads it, and what it gets depends on which way the program came, which is
712        // exactly the case a walk that only followed one path would miss.
713        let at = Place::Reg(SYSV.int_order[0]);
714        let elsewhere = Place::Reg(SYSV.int_order[1]);
715        edits.push(Edit { at: At::EndOf(arm), mov: Move::new(at, elsewhere), class: GPR });
716
717        assert_eq!(
718            said(&func, &taken, &assignment, &edits),
719            ["instruction 2 reads %0 out of register 0 of class 0, which holds nothing anything \
720              has put there"]
721        );
722    }
723
724    #[test]
725    fn a_register_the_function_names_itself_is_one_it_may_read_without_writing_it_first() {
726        let mut names = Interner::new();
727        let mut func = Func::new(names.intern("f"));
728        let opcode = Opcode::new(names.intern("x64.nop"));
729        let block = func.create_block();
730        func.build(block, opcode).operand(Operand::read(Reg::physical(RSP), GPR)).finish();
731
732        // The stack pointer is not the allocator's to hand out and nothing in the function writes
733        // it, so a checker that asked the same question of it as of a value would read every frame
734        // reference as a read of a register nobody had written.
735        let (taken, assignment, edits) = allocate(&mut func, &env());
736        assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
737    }
738
739    #[test]
740    fn a_register_the_function_names_itself_is_readable_after_a_value_has_been_put_in_it() {
741        let mut names = Interner::new();
742        let mut func = Func::new(names.intern("f"));
743        let opcode = Opcode::new(names.intern("x64.nop"));
744        let block = func.create_block();
745        let argument = func.new_vreg(GPR);
746        func.build(block, opcode)
747            .operand(Operand::write(argument, GPR).with(Constraint::Fixed(SYSV.int_order[0])))
748            .finish();
749        func.build(block, opcode)
750            .operand(Operand::read(Reg::physical(SYSV.int_order[0]), GPR))
751            .finish();
752
753        // This is a function whose argument arrives in a register and whose body is an `asm`
754        // statement naming that same register in its own text. The argument is the allocator's
755        // value and the register is the program's name for the machine, and the two meeting in
756        // one place is the ordinary way an `asm` statement reads what it was passed rather than
757        // anything having gone wrong.
758        let (taken, assignment, edits) = allocate(&mut func, &env());
759        assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
760    }
761
762    #[test]
763    fn what_is_wrong_is_reported_in_a_sentence_that_says_how_many_things_are_wrong() {
764        let fault = Fault::Read {
765            inst: Inst::new(3),
766            place: Place::Slot(1),
767            class: GPR,
768            wanted: Reg::virtual_reg(2),
769            found: None,
770        };
771        assert_eq!(
772            report(&[fault]),
773            "the rewrite loses a value in 1 place\n  instruction 3 reads %2 out of slot 1, which \
774             holds nothing anything has put there"
775        );
776    }
777}